PayPalでのアプリ内購入の実装（覚書）

##はじめに
　私は趣味で同人ゲームを作っており、昨年2018年にも3年ほどかけて作ったゲームをコミックマーケットで頒布しました。
　このゲームには試験的にサーバを使っての、セーブデータの複数端末での共有、シリアルコードでの購入処理、アプリ内購入、などの機能を実装しましたが、この内アプリ内購入には（Windows版では）PayPalを利用しました。
　ここではPayPalを使ったアプリ内購入について忘れかかった記憶を呼び起こしながら、その実装などについて書き記しておきたいと思います。

##PayPalアカウントの準備
　まずPayPalアカウントを用意します。
　アプリ内購入に使用するアカウントを作成したらビジネスアカウントに変更します。PayPalのビジネスアカウントは法人でなくても作成可能です。

##少額決済手数料の申請
　PayPalでの決済では1件当たり3.6%+40円（30万円以下の場合）の手数料がかかります。
　アプリ内購入のアイテムは高額ではないためこれでは手数料が高すぎます。
　ですのでまず少額決済手数料の申請を行いました。
　この申請をすることによって1件当たりの手数料は5%+7円となります。仮に100円のアイテムでも手数料は12円で済むため非常に現実的です。

##利用するPayPal決済の仕組み
　PayPalには即時支払い通知(IPN)と、購入後の自動復帰ページからの支払いデータ転送(PDT)機能があり、これらを利用することによってアプリケーションと購入情報をやり取りすることができます。

####支払いデータ転送(PDT)
　PayPalで購入した後、自動的に遷移するURLを設定することができます。
　その遷移先のページからPayPalサーバに対して、購入情報などを取得するための仕組みです。

####即時支払い通知(IPN)
　PayPalで購入した後、支払いの通知を受けるためのURLを設定することができます。これはブラウザでユーザーが閲覧するものではなく、PayPal側とアプリケーションサーバ（自分とこ）が情報のやり取りを行うものです。
　正常に通信が行えた場合には、購入後10秒ほどで通知を受け取ることができます。PayPal側からの通信が失敗した場合、数分、数時間後などに何度かリトライがなされるようです。
　尚、この通信には2018年6月28日以降、HTTP/1.1、TLS1.2が必須となりました。

##購入処理のフローを考える
　PDTやIPNを利用したアプリ内購入のフローを考えていきます。
　図はおおよそのイメージです。
![購入フロー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/285025/2d2cfee3-402a-0e52-059c-b052333f01b5.png)
　ユーザーインターフェースとしては、まずゲーム中の購入メニューから購入したいアイテムをクリックするとブラウザが開き、PayPalの購入ページへ遷移する仕組みを作ります。
　次にIPNやPDTに対応する処理をサーバ上に実装します。
　購入後の復帰ページは、PayPalで購入後、PayPalサイト内での購入完了ページが表示されたのち、数秒後に自動遷移するページになりますので、必ず表示されるわけではありません。
　したがってPDTのみで購入処理を実装すると。購入後すぐにブラウザを閉じたユーザーにはゲーム内に購入が反映されません。
　ですので購入処理は少なくともIPNで実装しなければなりません。

　ただIPNは必ずすぐに通知されるとは限らないため、IPNとPDTの両方で購入処理を行い、重複購入にならないような処理を入れておきました。

　購入メニューからブラウザを開いた後、ゲーム画面はモーダルな状態にしておき、ユーザーは購入が完了した後にゲーム画面上のOKボタンを押すとサーバに購入情報を取得しに行きます。サーバ上で購入が確認されれば反映し、そうでない場合にはリトライするかキャンセルするか選択します。（即時反映されるとは限らないので、ゲーム起動時などにサーバに接続し購入アイテムを確認するようにしておきます）
　私が試した範囲においては、IPNのみでもおおよそ10秒～20秒程度で購入が反映されました。

####購入アイテムの設定
　より詳しく各要素について説明します。
　まずは購入アイテムです。
　これにはPayPalの管理画面のツールから「今すぐ購入ボタン」をアイテムごとに作成しておきます。
　PayPalにログインし、「アカウント設定＞販売ツール＞販売の管理＞新しいボタン作成」から、「今すぐ購入」タイプのボタンを作成します。
　ここで商品名や価格を設定できます。
　ボタンを作成したらボタンの（ウェブに埋め込むための）コードを表示します。form 要素の hosted_button_id の値をメモしておきます。

　私はサーバ上のDBにこの hosted_button_id とゲーム内で使用するアイテムID、名称を保存する管理用アプリを作成しました。

####購入ページへの遷移
　PayPalの購入ページへは、ブラウザの form から post することによって遷移します。
　これを自動的に行うページを作成しておきます。
　私は非表示の form に対して JavaScript で submit() を呼び出すようにしました。また遷移できなかったときの為、一定時間後に href="javascript:document.form1.submit()" となるリンクを表示させています。
（この例の form1 は form の ID。リンクではなくフォーム自体を表示してもいいと思います。）

　また、form に追加で custom パラメータを設定しています。
　単に PayPal で購入しただけでは、誰のゲームアカウントに購入処理をすればいいのか分かりません。
　そこでゲーム側にあらかじめユニークなIDを生成し保存しておき、custom にはこのIDを渡します。（実際にはセッションIDを生成し、これを更にゲームアカウントに紐づけしました）

####IPN と PDT の実装
　IPN や PDT はサンプルコードが公開されているためこれを参考にすれば比較的容易に記述できます。

PDT: https://github.com/paypal/pdt-code-samples
IPN: https://github.com/paypal/ipn-code-samples

　PDT も IPN も PayPal サーバにリクエストを送ることによって各種パラメータを取得できます。
　PDT には専用のトークンが必要になりますが、「アカウント設定＞販売ツール＞ウェブサイトの設定＞支払いデータ転送」の「IDトークン」で確認できます。（支払いデータ転送をONに設定しておく必要があります）

　IPN の URL は「アカウント設定＞販売ツール＞即時支払い通知」で、PDT の URL は、「アカウント設定＞販売ツール＞ウェブサイトの設定＞ウェブペイメントの自動復帰」で設定できます。

####PDT で取得できるパラメータ（使ったもの）
| パラメータ名      | 内容               |
|:-----------------|:------------------|
| txn_id           | 取引ID             |
| item_name        | アイテム名          |
| mc_gross         | 価格               |
| mc_currency      | 通貨               |
| custom           | custom に渡した文字列 |

####IPN で取得できるパラメータ（使ったもの）
| パラメータ名      | 内容               |
|:-----------------|:------------------|
| txn_id           | 取引ID            |
| item_name        | アイテム名         |
| mc_gross         | 価格              |
| mc_currency      | 通貨              |
| payment_status   | 取引完了判定       |
| custom           | custom に渡した文字列 |

　PDT と IPN で取得できるパラメータには多少の違いがあるため、これら以外のパラメータを参照する場合には、それが渡されるかどうか等を確認したほうが良いと思います。

https://developer.paypal.com/docs/classic/ipn/integration-guide/IPNandPDTVariables/

　また、IPN のデバッグには開発者ページの IPN シミュレータを利用すると便利かもしれません。

https://developer.paypal.com/docs/classic/ipn/integration-guide/IPNSimulator/


##Sandboxでのデバッグ
　PayPal の購入処理は Sandbox でデバッグすることができます。
　Sandbox を使えば実際に支払いを行うことなく、支払処理のテストができます。
　まず、開発者ページの DASHBOARD＞SANDBOX＞Accounts から「Create Account」でアカウントを作成します。

https://developer.paypal.com/developer/accounts/

　アカウントは購入者用の「PERSONAL」タイプを少なくとも１つと、「BUSINESS」タイプを１つ作っておきます。

　次に、[PayPal の Sandbox](https://www.sandbox.paypal.com/) から先ほど作った「BUSINESS」でログインし、これまで説明してきたのと同様に「今すぐ購入ボタン」の作成、PDT、IPN の設定を行います。

　PDT では PayPal のホストアドレスを www.paypal.com → www.sandbox.paypal.com に変更し、またトークンIDも Sandbox 側で取得したものに書き換えます。
　IPN も同様にホストアドレスを ipnpb.paypal.com → ipnpb.sandbox.paypal.com に変更します。

　また、ゲーム画面の購入メニューから開くブラウザの購入ページも、Sandbox に変更します。
　購入の際には Sandbox の「PERSONAL」アカウントでログインします。

##終わりに
　そんなこんなで自作の同人ゲームに Android 版だけでなく Windows 版にもアプリ内購入を実装しました。
　実際には PayPal で購入いただいた件数は非常に少ないのですが、次回作にも実装しようと思っています。

　PayPal でのアプリ内購入の他に、シリアルコードによる購入処理も実装したわけですが、運用上、シリアルコードの方が色々と便利だなと思いました。と言うのも、何か購入処理で問題が生じた場合、シリアルコードを発行してそれを入力してもらうという回避方法が………いえ、本来はコミケで頒布する際の為のものなんですけどね…（＾＾；