Amazon Pay を React Native に導入する方法と苦労したこと

この記事は [React Native Advent Calendar 2020](https://qiita.com/advent-calendar/2020/react-native) の14日目の記事です。


最近業務で Amazon Pay 新バージョンである Amazon Pay Checkout V2 （以下 Amazon Pay CV2 ）を、 React Native アプリに導入しました。

[amazonpay-labs](https://github.com/amazonpay-labs) というアカウントが [GitHub](https://github.com/amazonpay-labs/amazonpay-sample-app-v2) でサンプルアプリと実装方法を公開していて、全体像を把握するのに大変助かりました。一方で自分が開発している React Native の環境で Amazon Pay CV2 導入を試みると、マニュアル通りにいかない部分もいくつかありました。

そこで本記事では、そもそも Amazon Pay とは何なのか、導入するメリットを紹介すると共に、実際に導入したときに苦労した部分なども共有したいと思います。

## Amazon Pay とは
そもそも Amazon Pay とは、 Amazon に登録している住所情報とお支払い方法を使用して、 Amazon 以外のサイトでも支払いを可能にするID決済サービスを指します。（恥ずかしながら、初めは Paypay や LINE Pay のような Pay サービスを想像していました🤦）

> Amazon Payは、Amazon.co.jpのアカウントに登録されている住所情報とクレジットカード情報を使用して、Amazon.co.jp以外のサイトで支払いができるサービスです。
>
> 引用：[Amazon Payとは \| Amazon Pay ヘルプ](https://pay.amazon.co.jp/help/5964)

そして、新バージョンである Amazon Pay CV2 が、2020年12月現在プレビュー版として公開されています。
（CV1とCV2の違いに関する説明は割愛させていただきます🙇‍♂️）

> このページは、Amazon Pay CV2 （API Version2対応） に関する開発者向けの情報をまとめたページです。Amazon Pay CV2は現在プレビュー版です。
>
> 引用：[Amazon Pay Integration CV2 Guide](https://amazon-pay-v2.s3-ap-northeast-1.amazonaws.com/V2_Documents.html)

Amazon Pay FAQ(v2)でも以下のように明記されており、今後 CV2 を使用した実装が主流になっていくことが予想されます。

> **CV1（現行バージョン)は使えなくなるのですか。**
> 将来的には使えなくなります。また現時点では終了時期については未定となっています。
ただし、AmazonではCV1（現行バージョン）ではサポートされない機能の追加をCV2（新バージョン）に対して実施予定です。
またブラウザのセキュリティ環境の変化によってCV1が購入者様にとって使いにくくなるということが想定されますので、お早めに移行を検討いただければと思います。
>
> 引用：[Amazon Pay FAQ\(v2\)](http://amazonpay-integration.amazon.co.jp/amazonpay-faq-v2/detail.html?id=QA-131)

## なぜ Amazon Pay を導入するの？
私が開発に携わっているアプリは、食材などを自宅に届けて、オンラインで一緒に料理を作って一緒に食べるといういったサービスを提供しています。食材を届けるために住所登録が必要ですし、予約するためにお支払い方法の設定が必要になります。

個人的な意見ではありますが、様々なサービスを利用するにあたって、サービスごとに住所登録やクレジットカードの情報を入力するのは非常に面倒ですし、管理が難しくなります。そう思っているユーザは私以外にもいらっしゃるのではないでしょうか。

上記のような課題は Amazon Pay を導入することで解決することができます。商品購入などを行う時に Amazon に登録してある住所情報やお支払い方法を選択して決済ができるので、わざわざ入力する手間がなくなるわけですね。

## マニュアルを読めば何をすればよいかを大方理解できる
Amazon Pay が何なのか、導入することでどういった恩恵を得られるのかわかったので、次に導入方法について共有します。

前述したとおり、 [amazonpay-labs](https://github.com/amazonpay-labs) というアカウントが、 [amazonpay-sample-app-v2](https://github.com/amazonpay-labs/amazonpay-sample-app-v2) で Amazon Pay CV2 を使用したサンプルアプリとその実装方法を公開しています。

基本的に提供されている実装方法に従えば、 Amazon Pay CV2 を導入することができるので、以下のページをご確認ください。（本記事では主に実装時に苦労したところを共有したいので、割愛させていただきます）

- iOS
  - [Amazon Payの実装方法 - WebViewアプリ編](https://github.com/amazonpay-labs/amazonpay-sample-app-v2/tree/master/ios#amazon-pay%E3%81%AE%E5%AE%9F%E8%A3%85%E6%96%B9%E6%B3%95---webview%E3%82%A2%E3%83%97%E3%83%AA%E7%B7%A8)
  - [Amazon Payの実装方法 - Nativeアプリ編](https://github.com/amazonpay-labs/amazonpay-sample-app-v2/tree/master/ios#amazon-pay%E3%81%AE%E5%AE%9F%E8%A3%85%E6%96%B9%E6%B3%95---native%E3%82%A2%E3%83%97%E3%83%AA%E7%B7%A8)
- Android
  - [Amazon Payの実装方法 - WebViewアプリ編](https://github.com/amazonpay-labs/amazonpay-sample-app-v2/tree/master/ios#amazon-pay%E3%81%AE%E5%AE%9F%E8%A3%85%E6%96%B9%E6%B3%95---webview%E3%82%A2%E3%83%97%E3%83%AA%E7%B7%A8)
  - [Amazon Payの実装方法 - Nativeアプリ編](https://github.com/amazonpay-labs/amazonpay-sample-app-v2/tree/master/ios#amazon-pay%E3%81%AE%E5%AE%9F%E8%A3%85%E6%96%B9%E6%B3%95---native%E3%82%A2%E3%83%97%E3%83%AA%E7%B7%A8)

[フロー図](https://github.com/amazonpay-labs/amazonpay-sample-app-v2/blob/master/ios/flow-ios.xlsx)と照らし合わせながら読むことで、全体像が把握しやすくなるので、是非ご覧ください。

![flow-ios.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186009/48e0aaf8-c4cd-913d-db8d-ab505b7011fd.png)

## 導入時に苦労したこと
実装方法を一言でまとめると、**「 Native アプリと Secure WebView 間で amazonCheckoutSessionId を受け渡して実装する」** という方法になります。

提供されている実装方法を読み込めば、何をすれば良いのかは大方理解できました。しかしながら、いざ実装してみると別途調査が必要だったり、選択している技術によってマニュアルとは別の方法で実装した部分がありました。

### そもそも Secure WebView って何？どうやって実装するの？
導入マニュアルを見ると Secure WebView という言葉が出てきます。
聞き覚えのない単語だったのですが、実はこのマニュアルで定義されている言葉のようでした。

> 本サンプルアプリでは、Amazon Payの処理についてはモバイルアプリから使えるSecureなブラウザ技術である、
>
> - Android: Chrome Custom Tabs
> - iOS: SFSafariViewController
>
> を起動して実行しており、実行が終わるとまたアプリ側に処理を戻しています。
> ※ 本ドキュメント 及び サンプルアプリのコード内では、「Chrome Custom Tabs」と「SFSafariViewController」の両方を合わせて「Secure WebView」と呼んでおります。
>
> 引用：[amazonpay\-labs/amazonpay\-sample\-app\-v2](https://github.com/amazonpay-labs/amazonpay-sample-app-v2#%E6%9C%AC%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)

つまり、 `Secure WebView` は `Chrome Custom Tabs` と `SFSafariViewController` を指し、 OS によって使い分けなければなりません。

でも安心してください。上記ふたつのブラウザを使用できる [react-native-inappbrowser](https://github.com/proyecto26/react-native-inappbrowser) という React Native のライブラリがあります。

メソッドも共通化されており、 `InAppBrowser.open` を呼び出すことで、 iOS だと `SFSafariViewController` を、 Android だと `Chrome Custom Tabs` を起動してくれます。

> Opens the url with Safari in a modal on iOS using SFSafariViewController, and Chrome in a new custom tab on Android. On iOS, the modal Safari will not share cookies with the system Safari.
>
> 引用：[proyecto26/react\-native\-inappbrowser: 📱InAppBrowser for React Native \(Android & iOS\) 🤘](https://github.com/proyecto26/react-native-inappbrowser#usage)


### Firebase Dynamic Links を使用したアプリへの遷移
導入マニュアルを見ると、 iOS なら `Universal Links` 、 Android なら `Applinks` を使用して `amazonCheckoutSessionId ` をアプリに渡しています。

しかし、私たちのアプリでは `Universal Links` や `Applinks` を使用する代わりに `Firebase Dynamic Links` という技術を採用して、アプリ内遷移を実現しています。

※ いずれもディープリンクを利用した技術ですが、それぞれの関係性などについて知りたい場合はこちらをご覧ください。
記事：[ディープリンクをめぐる歴史とReact NativeにFirebase Dynamic Linksを導入する手順](https://tech.kitchhike.com/entry/2018/04/29/222634)

`Firebase Dynamic Links` を使用して実装するには、若干マニュアル通りにいかなかった部分があったので共有します。

#### 住所・支払い方法選択後のリダイレクト先に Dynamic Links に指定するとプレビュー画面が表示される
導入マニュアルには、住所・支払い方法選択後のリダイレクト先に `Universal Links` や `Applinks` を指定するように書かれています。しかし、同じディープリンクである `Dynamic Links` をリダイレクト先に指定すると、 iOS の場合に限り、アプリ内遷移するときに以下のようなプレビュー画面が表示されてしまいます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186009/b2d9cf71-2e45-353d-40ac-a2aa565ab25f.png" width=200px>
引用：[ソーシャル メタデータでリンク プレビューを生成する  \|  Firebase](https://firebase.google.com/docs/dynamic-links/link-previews?hl=ja)

Dynamic Links のパラメータに `efr=1` を指定すれば、プレビュー画面をスキップできると書かれているのですが、アプリをインストールしていても、常に AppsStore にリダイレクトされてしまいました。

> ダイナミックリンクパラメータ efr=1 を指定すると、アプリのプレビューページをバイパスできます。
>
> 引用：[ソーシャルメタデータでリンクプレビューを生成する  \|  Firebase](https://firebase.google.com/docs/dynamic-links/link-previews?hl=ja)

この問題は GitHub でも Issue が立っていて、解決されないまま、 Close になっているようでした。
参考：[settings efr=1 always redirects to app store · Issue \#836 · firebase/quickstart\-ios](https://github.com/firebase/quickstart-ios/issues/836)

**実施した対応**
住所・支払い選択後のリダイレクト先に `Dynamic Links` を指定せず、必ず救済ページを表示するようにしました。
救済ページに表示するボタンの遷移先に `Dynamic Links` を指定して、アプリ内遷移させるようにしました。
（リンクタップの場合は、アプリ内遷移するときにプレビュー画面は表示されなかったからです）

#### URLパラメタの付与がうまくいかない
導入マニュアルには、 `Universal Links` や `Applinks` の URL パラメタに token を設定し、アプリで token を取得するように書かれています。

```js
// nodejs/app.jsより抜粋 (見やすくするため、一部加工しています。)

//-------------------
// App Login Screen
//-------------------

app.get('/appLogin', async (req, res) => {
    // ※ req.queryには、上記ViewControllerで指定されたURLパラメタが入る
    res.render('appLogin.ejs', calcConfigs(`https://amazon-pay-links-v2.s3-ap-northeast-1.amazonaws.com/redirector_local-${req.query.client}.html?token=${req.query.token}`));
});

function calcConfigs(url) {
    const payload = createPayload(url);
    const signature = apClient.generateButtonSignature(payload);
    return {payload: payload, signature: signature, merchantId: keyinfo.merchantId, publicKeyId: keyinfo.publicKeyId};
}

function createPayload(url) {
    return {
        webCheckoutDetails: {
            checkoutReviewReturnUrl: url
        },
        storeId: keyinfo.storeId
    };
}
```

引用：[amazonpay\-sample\-app\-v2/ios at master · amazonpay\-labs/amazonpay\-sample\-app\-v2](https://github.com/amazonpay-labs/amazonpay-sample-app-v2/tree/master/ios#server%E5%81%B4%E3%81%AEamazon-pay%E3%83%9C%E3%82%BF%E3%83%B3%E5%87%BA%E5%8A%9B%E6%BA%96%E5%82%99)

しかし、 `Dynamic Links` の場合、任意の値を URL パラメタとして設定してもうまくいきませんでした。
link パラメタの部分にディープリンクを仕込むのですが、そこで以下のような書き方をしてしまうと最初の & 以降の文字列がアプリに渡ってこないことがわかりました。

```例
https://${your_subdomain}.page.link/?link=https://example.com?page=1&limit=10&apn=package_name...
↓
アプリで受け取れた文字列
https://example.com?page=1
```

でも安心してください。以下のようにパラメータの部分をエンコードすることで、上記の問題は解決することができます。

```例
https://${your_subdomain}.page.link/?link=https://example.com?page=1&limit=10&apn=package_name...
↓
https://${your_subdomain}.page.link/?link=https://example.com%3Fpage%3D1%26limit%3D10&apn=package_name...
```

## 参考サイト
- [Amazon Payとは \| Amazon Pay ヘルプ](https://pay.amazon.co.jp/help/5964)
- [Amazon Pay Integration CV2 Guide](https://amazon-pay-v2.s3-ap-northeast-1.amazonaws.com/V2_Documents.html)
- [Amazon Pay FAQ\(v2\)](http://amazonpay-integration.amazon.co.jp/amazonpay-faq-v2/detail.html?id=QA-131)
- [Amazon Pay 新バージョン（V2）へ ecbeing 国内初対応｜株式会社ecbeing のプレスリリース](https://prtimes.jp/main/html/rd/p/000000057.000007074.html)
- [先取りでAmazon Pay 新バージョン\(v2\)、 Amazon Pay CV2をサービスに組み込んだ話 \- KitchHike Tech Blog](https://tech.kitchhike.com/entry/amazon-pay-cv2)
- [ディープリンクをめぐる歴史とReact NativeにFirebase Dynamic Linksを導入する手順 \- KitchHike Tech Blog](https://tech.kitchhike.com/entry/2018/04/29/222634)
- [amazonpay\-labs/amazonpay\-sample\-app\-v2](https://github.com/amazonpay-labs/amazonpay-sample-app-v2)
- [proyecto26/react\-native\-inappbrowser](https://github.com/proyecto26/react-native-inappbrowser#usage)

---

最後まで読んでいただいてありがとうございました。

15日目は @grgr-dkrk さんです。お願いします！