Software development of explosion! -夢の破片(カケラ)たちの日々 別館-

ソフトウェア開発を中心としたコンピューター関連のネタを扱ったブログです

Software development is passion and explosion!

PayPal のIPNでハマった

仕事でGoogle Apps Marketplace向けアプリを開発していて、PayPalで売り切り(って言うの?)で課金しようとしているのですが、IPN通知の処理でハマった…OTL

そもそも、IPN通知が何かと言うと、即時支払い通知といい、WebHooksというヤツなのでしょうか?ユーザーさんがPayPal経由で商品を購入した際や、何らかの理由でマーチャント(販売者)が返金した際などに、あらかじめ指定されたURLに対して、Paypalから通知が来るというものです。

通知先は、PayPalのマーチャント側の設定画面で設定するか、PayPalで購入するための購入ボタンがクリックされた際に、PayPalへPOSTメソッドでリクエストを発行してユーザーさんに手続きを開始させるのですが、その際にHTML変数(リクエストパラメーター)に「notify_url」という形で、URLを指定します。
前者は、IPNを有効にする際に入力する必要があり、後者は、任意で指定可能で、指定すると、その取引に対してのIPNによる通知は、HTML変数で指定されたURLへと通知されるようになります。
IPNリスナー(IPN通知を受け取る側)のサンプルコードは、PayPalのサイトで公開されているので、細かい話は割愛するとして、ここにも書かれているように、

  1. ポストバックを行って、PayPalに正しい通知か否かを判定してもらう
  2. payment_statusが「Completed」か否か
  3. txn_idが処理済みの過去のデータと比較し、重複していないか
  4. 商品名item_name、価格mc_gross、通貨mc_currencyなどが正しいかどうか

といったことを行う必要があるのですが…
先ず、ポストバック。Javaのサンプルコードをそのまま使っても、上手く動きませんでした。
いや、正確にはサンプルコードは間違ってはいないのですが、何度やってもpayment_statusが「Pending」、payment_reasonが「unilateral」になってしまう。

原因は、メールアドレスが認証されていなかったからなのですが、

手順は

  1. 「マイアカウント」
  2. 「個人設定」
  3. 「メールアドレスの登録または編集」(※)
  4. メールアドレスを選択して「確認」

とすると、PayPalからメールが来るので、そのメールの中に書かれているアドレスで解決出来ました。
※ 名称はプルダウンの場合であり、「個人設定」の画面からは「アカウント情報」の項目の欄の「メールアドレス」となる。

まぁ、PayPalのドキュメントにも書いてありますが、そんな手続きが必要なんて書いてなかったような…

次にハマったのが、文字のエンコードです。
PayPalのサイトで(勿論、SandBoxでテストしているならSandBoxで)、設定を変えないとShift_JISになってしまうのですが、

  1. 「マイアカウント」
  2. 「個人設定」

と進み、「販売の設定」の欄にある「言語のエンコード」を選択して「ウェブサイトの言語」を「日本語」にします。
そして、「詳細オプション」に進みます。
この「詳細オプション」に落とし穴があって、

  • エンコード方式」を「UTF-8
  • PayPalから送信されたデータと同じエンコード方式を使用しますか(IPN、ダウンロード可能なログ、メールなど)?」を「はい」

にすると、前の画面の「ウェブサイトの言語」の設定が何故か「西欧言語(英語を含む)」になってしまいます。
これに気づかずに、IPN通知をポストバックしてみたら、ものの見事にINVALIDとなってしまいました。
(UTF-8て日本語使えるのにね。3バイトとかの文字もあるけど)

そこで、先ほどの「詳細オプション」では、

  • エンコード方式」を「Shift_JIS
  • PayPalから送信されたデータと同じエンコード方式を使用しますか(IPN、ダウンロード可能なログ、メールなど)?」を「いいえ。次のエンコード方式を使用します。」
  • その下のプルダウンで「UTF-8」を選択して「保存」とします。

そうしたら、ものの見事に解決出来ました。

まぁ、その後でハマったのが、payment_statusが「Refunded」(払い戻し)の際に、商品価格が負の値(払い戻す額がそのまんまマイナス)で通知されてきてくれて、「Completed」でない場合にも価格(mc_gross)が購入ボタンの作成時に設定した価格か否かの判定をしていたのでエラーになってくれました。
「Completed」以外の場合はチェックせずに、そのままこちらのサーバー側に保存しちゃっていいのかな?(一応、価格 >= mc_gross >= -価格 という判定にしてみたけど大丈夫かな?)

あと、気になるのが、ポストバックでエラーになった場合はPDF版のドキュメントに「不正な取引の可能性があるため調査してくれ」みたいなことが書いてあったのでいいのですが、ポストバックは正常だけど「商品名item_name、価格mc_gross、通貨mc_currencyなどが正しいかどうか」で正しくなかった場合のエラー処理はどうしたらいいのだろう?

PayPal側に何かするとかしなくてもいいのかな?一応、マーチャントの管理側にメール通知出来るようなアプリの仕様にはしてみたけど。