Apple Payを実装する上での注意点や罠

## 実装
実装に関しては下記を参考でできると思われる。
https://qiita.com/shindooo/items/ebe69238368a2d6a7ebb

Apple Payの実装を行なっていて、意外とハマるまではいかないが、罠みたいな要素が多かったので記載しておく。

## 注意点1. CapabilitiesとentitlementsとmerchantIdentifierの設定忘れ
SimulatorではApple Payのシートが表示されるのに、実機では何故か表示されない。とか、`PKPaymentAuthorizationViewController.canMakePayments(usingNetworks:)`が常に`false`になる。
この時、Capabilitiesとentitlements、merchantIdentifierの設定忘れの可能性がある。

特に開発環境では大丈夫だったが、TestFlightやDeployGate、FabricのBetaで本番環境に近い状態でテストすると上手くいかなかい場合、`entitlements`がDevelopmentとProductionで別になっており、Productionの方に`Merchant IDs`を記載し忘れているのはよくあるパターンだと思う。

忘れずに設定を行おう


## 注意点2. 決済に使えるカードブランドを明示した方が良い
`PKPaymentAuthorizationViewController.canMakePayments(usingNetworks:)` を使うことで端末に決済に使用可能なカードが登録されているか判定できる。

しかし、仮にアプリはMasterCardのみApple Payでの決済を許可していて、ユーザがJCBしかApple Payに登録していなかった場合、`false`が返り決済に進めずユーザにはバグに見える可能性がある。
それを回避するために、事前にApple Payに使用可能なカードブランドを明示するかエラー等で知らせた方が親切である。


## 注意点3. Apple Payのシートは一部見た目と内部のデータで異なる
Apple Payで必要な情報は事前に連絡先Appで自分として設定されている情報から読み込まれる。必須情報が不足している場合や別の値を使用したい時は、Apple Payのシート上で追加や編集可能だが、ここで入力した値は連絡先Appに反映されないようだ。

このApple Payで扱う情報だが、郵便番号と電話番号は数字以外も問題なく入力できてしまう。実機のみでの操作であれば多少キーボードの制限が入って入力できる文字も限られるが、物理キーボードを使うとその制限はない。
人によっては`-`や` (space)`を含んだ入力を行う場合もあるだろう。

この時、電話番号は`-`や` (space)`を数字間に入れても省略されてspaceで統一されて表示される。
しかし、ApplePayのAuthorizationDelegateで返ってくる`PKPayment`の郵便番号や電話番号は、ユーザが入力した文字列がそのまま渡され、Apple Payのシートの見た目とは異なる値で取得できてしまう。

下記のような処理等を行なって不要な文字排除する考慮が必要

```swift
let characterSet = CharacterSet.decimalDigits.inverted
let decimalOnlyPhoneNumber = payment.shippingContact?.phoneNumber?.stringValue.components(separatedBy: characterSet).joined()
```


## 注意点4. Apple Payボタンが自動翻訳されない
[Apple PayのHIG](https://developer.apple.com/design/human-interface-guidelines/apple-pay/overview/buttons-and-marks/)には、Apple Payのボタンはデバイスの設定されている言語で自動翻訳されると記述されている。
しかし、いざ実装して日本語のデバイスでテストするも英語で表示され、自動翻訳されなかった。

結果として、自動翻訳される条件は下記だった。

* アプリがLocalize対応している
* デバイスの設定言語が、アプリのLocalize対応した言語を設定されている

それ故、HIGの言葉をより正しくするなら、
**デバイスで設定されている言語で、アプリがその言語のLocalizeに対応している時に自動翻訳される。**

日本のアプリでよくありそうなのが、Xcode Projectを作って日本でしかリリースしないからとProject TargetのLocalizations LanguageをデフォルトのEnglishのままにして開発しているパターンだ。
手っ取り早くApple Payのボタンを自動翻訳させるのであれば、アプリの`Info.plist`を開き、keyに`Localizations`、Valueに自動翻訳されて欲しい言語(ex. `Japanese`)を追加することで自動翻訳される。


## 注意点5. PKPaymentRequestのpaymentSummaryItemsはindexの若い順に表示される
Apple Payの決済で複数商品や割引、合計額を表示したい場合、`PKPaymentRequest`の`paymentSummaryItems`に`PKPaymentSummaryItem`の配列を渡すことになる。

この時、シートに表示される順番はindexの若い順に上から表示される。


## 注意点6. Apple Payのシート上では、アルファベットは全て大文字で表示される
`PKPaymentSummaryItem`のlabelにアルファベットを入れると、全て大文字で表示される。
バグのように見えるが、そういう仕様である。
なお、メールアドレス等も同様に大文字で表示される


## 注意点7. Apple Payのシートの上にUIViewControllerを表示出来ない
Apple Payのシートが表示中は、重ねて上にUIViewControllerを表示することが出来なかった。UIWindowを使用して最前面に持ってくるとかすることで回避は出来るかもしれないが試してはいない。

この事象で困るのは、delegateの`paymentAuthorizationViewController(_:didAuthorizePayment:handler :)`内でAPI等叩きエラーになった際に、UIAlertControllerでエラー内容を示唆できないことだ。

iOS 11以降かつ住所周りのチェックであれば、Documentにもあるように、エラー内容をApple Payのシートに表示可能である。

```swift
// Error in postal code field
let shippingInvalidZip =
PKPaymentRequest.paymentShippingAddressInvalidError(withKey:CNPostalAddressPostalCodeKey,
                                                    localizedDescription: "Invalid ZIP code")
// Error in street address field
let shippingInvalidStreet = PKPaymentRequest.paymentShippingAddressInvalidError(withKey:CNPostalAddressStreetKey,
                                                    localizedDescription: "Missing street name")
// Result with failure status and errors
let result = PKPaymentAuthorizationResult(status: .failure, 
                                          errors: [shippingInvalidZip, shippingInvalidStreet])

completion(result)
```

私は、delegateの`paymentAuthorizationViewControllerDidFinish(_:)`で、Apple Payのシートを閉じる際の`controller.dismiss(animated:completion:)`のcompletion内でUIAlertControllerを表示するようにした。


## 注意点8. Simulatorだとtokenが空文字になる
Apple Payに関しては実機がなくとも、Simulatorで動作確認できる。

しかし、Authorization後のdelegate methodの`paymentAuthorizationViewController(_:didAuthorizePayment:handler :)`で返ってくる決済情報の`payment.token.paymentData`は0 byteとなっている。
テストを行う際に考慮の必要がある。


## 注意点9. Simulatorだと2度決済を行うと画面がフリーズしたり、PassbookUIServiceが予期しない理由で終了したエラーWindowが表示される
SimulatorでApple Payの決済の動作確認を行う際、`Apple Payのシートを表示` > `パスコードで支払う` の動作を2回行うと画面がフリーズし、操作が一切効かなくなることがある。

仮に操作が効く場合でも、`PassbookUIServiceが予期しない理由で終了しました`というエラーWindowが表示される

決済後、画面遷移を挟むと大丈夫な場合もあるが、Simulatorだと不便な部分も多いので、Simulatorでの動作確認は最小限にし、実機での確認を勧める。


## 注意点10. Sandbosユーザでテストカードの登録が上手くいかない
Apple PayのテストのためにApp Store Connectの`ユーザとアクセス`からSandboxユーザを追加できる。

Sandbox用テストカードは[Apple Developer]([Apple Pay - Sandbox Testing - Apple Developer](https://developer.apple.com/apple-pay/sandbox-testing/))にあるのだが、SandboxユーザでiCloudにログインして、Apple Payにテストカードを登録しようとすると上手くいかないことがある。カードのブランドの規約が表示されるところにいくまでにエラーが出たり、規約に同意した後にエラーが出たりと…。
また、Sandboxのテストカードを登録と解除を繰り返していると一切登録が出来なくなることもあったが、次の動作を行なって解決できた。

試行錯誤の結果、下記を試すと登録できることがあるので試して欲しい。

* 登録が成功する番号と失敗する番号があるので、同じブランドでもいくつか登録を試す
* iCloudからサインアウトし、サインインし直した後に再度カードの登録を試す


## 注意点ex. アプリ内でApple Payの登録設定(In-App setup Apple Pay)
Apple Payでの決済が出来るように、`PKPaymentAuthorizationViewController.canMakePayments(usingNetworks:)`が`false`の時は、`Apple Payを設定する`ボタンを表示し、登録画面へ遷移させるため、`PKPassLibrary().openPaymentSetup()`を叩くフローで処理している人もいるでしょう。

ただ、`PKPassLibrary().openPaymentSetup()`を叩いた場合、Walletアプリが起動し、一度アプリの外に飛ぶため、あまりスマートなフローとは言い難い。一昔前にあった、ボタンを押すとSafariで認証画面が開いて、認証後にアプリ戻るような流れを思い出す。そのダサかった流れもAppleによりアプリ内でIn-App WebViewでやるように改められた。

このApple Payの登録フローもIn-Appで行うことが可能で、やり方は`PKPassLibrary().openPaymentSetup()`を呼ばず、決済シートを表示するのと同じ処理を行うだけで良い。

つまり、

1. `PKPaymentRequest`を作る
2. `PKPaymentAuthorizationViewController(paymentRequest:)`にセットしてviewContreollerを作る
3. 「2.」のviewControllerを表示する

これでApple Payが設定されていない端末では、アプリ内でApple Payの設定画面が自動的に呼ばれる。
また、Apple Pay設定後はそのまま決済シートが表示された状態になる。
この挙動はiOS 11以降で可能らしく、iOS 10.3では`PKPaymentAuthorizationViewController `を作る際にnilになってしまう

<br>
<追記 2019/09/05: kenmazさんによる指摘反映>
~~昔のやり方は下記だったが、もう許可とか不要になったようだ。~~
>~~1. In-Appで設定するには、Appleから特別な許可を貰う必要があり、連絡手段はメール(メールアドレスは下記資料の中に載っているが、現在も有効かは不明)~~
>~~2. 特別な許可が付与されると、entitlementsにKey`com.apple.developer.payment-pass-provisioning`、Value`true(Bool)`を設定してbuildが出来るようになる~~
>~~3. `PKAddPaymentPassViewController`を生成でき、表示させるとアプリ内でApple Pay登録が可能になる…らしい~~


上記の特別な許可がAppleから必要なプロセスのものは`In-App Provisioning`と呼ばれるもので、アプリから直接Walletにクレジットカードを登録にするためのものでした。

アプリ内でクレジットカード登録画面が立ち上がり、カメラで読み取ったり手動入力するものとは異なります。
混同して扱っていました。

## 参考資料
* https://stackoverflow.com/questions/51060832/how-to-call-the-apple-wallet-from-ios-app-using-swift
* https://stackoverflow.com/questions/50436058/pkaddpasspaymentrequest-not-able-to-send-a-request/50475819#50475819
* https://developer.apple.com/library/archive/documentation/Miscellaneous/Reference/EntitlementKeyReference/ApplePayandPassKitEntitlements/ApplePayandPassKitEntitlements.html