勉強の為に転載しました。
転載元はこちら
ちなみに彼はよくLGTM画像にされていますので、ご自由にお使いくだ
さい←
さい←
まえがき
はじめまして、コンバンハ。森川です。
2016年も終わりに近づき、日に日に寒くなってきましたね。
2016年も終わりに近づき、日に日に寒くなってきましたね。
あまりにも寒すぎて、オフィスのある外苑前から帰宅するたびに
「これはアカンやろ…アカンやろ…」状態だったので、半年間クリーニ
ング店に放置してあったコートを取りに行くという、2016年でも一二
を争う決断をしました。(英断)
「これはアカンやろ…アカンやろ…」状態だったので、半年間クリーニ
ング店に放置してあったコートを取りに行くという、2016年でも一二
を争う決断をしました。(英断)
平日に行ったため閉店が近い時間だったのですが、そのクリーニング店
はガラ空きでした。
店員さんは2名いて、「2人も必要なのかな…」と思いながらコートの
伝票を渡した所、中々見つからなかったらしく2名がかりで探しはじめ、3分経っても見つからず、
そうこうしている内に僕の後ろに4,5人くらい並び始めました。
はガラ空きでした。
店員さんは2名いて、「2人も必要なのかな…」と思いながらコートの
伝票を渡した所、中々見つからなかったらしく2名がかりで探しはじめ、3分経っても見つからず、
そうこうしている内に僕の後ろに4,5人くらい並び始めました。
これをちょっと
ワーカーが並行処理をし、タスクが詰まっているところに、待ち行列に
さらに5件エンキューされはじめました。
そうすると、僕の後ろの人々が閉店時間というガベージコレクターに
よって回収・破棄されてしまうのではないかとビクビク怯える気持ちと、
「今、この場を支配しているのはワタシ」という相反するキモチが
フツフツと湧いてきて、ニヤニヤしそうでした。(フィクションです)
これは
インテリ・アンニュイ・メガネ男子
っぽく表現すると、2つのワーカーが並行処理をし、タスクが詰まっているところに、待ち行列に
さらに5件エンキューされはじめました。
そうすると、僕の後ろの人々が閉店時間というガベージコレクターに
よって回収・破棄されてしまうのではないかとビクビク怯える気持ちと、
「今、この場を支配しているのはワタシ」という相反するキモチが
フツフツと湧いてきて、ニヤニヤしそうでした。(フィクションです)
これは
インテリ
でもアンニュイ
でもなく、ただのメガネおっさんWebエンジニア
ですね。
というわけで、今日は決済周りの ボディをえぐられるような非常に苦し
い経験 あれこれについてお話したいと思います。
合言葉は、「これでみんな決済マスターだ!」です。
い経験 あれこれについてお話したいと思います。
合言葉は、「これでみんな決済マスターだ!」です。
はじめに
この記事の対象者は↓のような人々です。
- 決済処理は怖いと思っている人
- 決済処理は恐ろしいと思っている人
- 決済処理に関わってしまうと、もはや永久に安眠することができない
と思っている人
この記事で学べることは以下の通りです。
- 決済処理はそこまで怖くない
- 上司の首は簡単には飛ばない
この記事の注意事項は以下の通りです。
- 記載されている内容・仕様が最新の情報ではない可能性があります
- 各決済プラットフォームのアップデートにより変更される可能性が高
いです - 経験からの帰納や推測を多分に含んでおり、正しくない可能性があり
ます - (圧倒的力不足で申し訳ないです。間違っていたら教えてください…!)
目次
- A. システム構成
- 全体のシステム構成
- 決済システムの構成
- 決済システム マイクロサービス化の背景
- B. 決済の種類
- C. 決済プラットフォーム
- クレジットカード
- PayPal
- iTunes Connect In-App Purchase (iOSアプリ)
- Google Play In-app Billing (Androidアプリ)
- アップグレード商品
- D. Goでの決済システムの実装
- 主要ライブラリ
- ディレクトリ・パッケージ構成
- E. その他
- 定期課金の更新
- 決済システムとプロダクト側とのデータ同期
- ベンダリング
- CI
- デプロイ
- システム監視
- バッチ処理をHTTP経由にする弊害の例
- データベース
- F. まとめ
うーん、長いですね…^^;
A. システム構成
最初にマクロな視点でPairsを含めた全体の構成、そして少し詳細な決済
システムの構成について説明いたします。
システムの構成について説明いたします。
全体のシステム構成
以下の図はPairsと決済システムの大まかな構成図です。
- 左側の水色っぽい
(1)
の箇所がPairs
- 右下のピンク色の
(2)
の箇所が決済システム
- 右上のピンク色の
(3)
の箇所が外部の決済プラットフォーム
となっています。
システムはほぼ全てAWS上で動かしており、アプリケーションはEC2
で動作しています。
本番ではDockerはあまり使っておらず、一部の機械学習系システムだけPythonコンテナを 雑に 使ってるくらいです。
で動作しています。
本番ではDockerはあまり使っておらず、一部の機械学習系システムだけPythonコンテナを 雑に 使ってるくらいです。
Go言語という以外は、オーソドックスなAWSを利用したWebアプリケーションになっていると思います。
最近加えた変更としては、アプリケーションが出力するログをGoogle Stackdriver Loggingで一元化するようにしたことです。
各ログに含まれるのパラメータを自由にセットしたかったので、
fluentd経由ではなく、Goのログライブラリsirupsen/logrusのhook経由
で送っています。
各ログに含まれるのパラメータを自由にセットしたかったので、
fluentd経由ではなく、Goのログライブラリsirupsen/logrusのhook経由
で送っています。
なお工事中のKinesisは、Google BigQueryへ送っているログの一時置き
として検討・構築中です。
として検討・構築中です。
「SaaS」では似たようなものを色々と使っていますが、ここについては
またの機会にお話しします。
(一生話さないフラグ)
またの機会にお話しします。
(一生話さないフラグ)
決済システムの構成
決済システムはPairs側に比べると非常にシンプルな作りになっています。
PairsとはRESTのインターフェースで通信を行うようになっています。
DBにはAWS Auroraを使っています。
弊社だけの決済系なので、DBに負荷がかかることはないのですが、
Pairs側への導入にあたって検証利用という形でずっと使い続けています。
弊社だけの決済系なので、DBに負荷がかかることはないのですが、
Pairs側への導入にあたって検証利用という形でずっと使い続けています。
SQSは課金の自動更新に使用しています。
負荷も安定しておりキャッシュが必要な箇所はほぼないため、
memcachedやRedisは使用していません。
memcachedやRedisは使用していません。
決済システム マイクロサービス化の背景
2015年、2016年にかけてPairsのサーバーサイドアプリケーションを
PHPからGoへと置き換えましたが、その時はモノリスなアプリ
ケーションで、決済もその中に組み込まれていました。
私が入社した当初は、原始地球ともいうべきカオスな様相を呈しており、カスタマーによる購入処理と、バッチによる自動更新処理で違う処理
関数を使っていて、そこら中に星くずのように煌めく様々なマーケ
ティングキャンペーンのロジックが散りばめられており、恵比寿に
いながらにして、さながら九龍城に迷いこんだ気分を味わうことが
出来ました。
ドキドキ・ロマンティックですね。
PHPからGoへと置き換えましたが、その時はモノリスなアプリ
ケーションで、決済もその中に組み込まれていました。
私が入社した当初は、原始地球ともいうべきカオスな様相を呈しており、カスタマーによる購入処理と、バッチによる自動更新処理で違う処理
関数を使っていて、そこら中に星くずのように煌めく様々なマーケ
ティングキャンペーンのロジックが散りばめられており、恵比寿に
いながらにして、さながら九龍城に迷いこんだ気分を味わうことが
出来ました。
ドキドキ・ロマンティックですね。
“新商品の追加を行うと、PC版でカスタマーの新規登録ができなくなる。”
『Pairsの開発中の話』より (共著: 小島ヒロキ・森川タクマ)
エブリデイがサマー・オブ・ラブ、そんな厳しい冬の虚無僧の時代を
経たために、
「もう我々の子孫にこんな辛い思いはさせたくないでござる!」と思う
のは当然のことで、私たちはGo版リプレイスにあたって、マイクロサー
ビスとして切り出すことにしました。
人類(エウレカ)の悲願ですね。
経たために、
「もう我々の子孫にこんな辛い思いはさせたくないでござる!」と思う
のは当然のことで、私たちはGo版リプレイスにあたって、マイクロサー
ビスとして切り出すことにしました。
人類(エウレカ)の悲願ですね。
B. 決済の種類
多くの決済プラットフォームで使われる決済には2種類あります。
- (a) 購入すると1回だけ支払いがあるもの
- 例: ZOZOTOWNでの商品の購入、LINEでのスタンプの購入、Amazon Kindle向けの電子書籍の購入
- (b) 購入すると定期的に支払いがあるもの
- 例: 定額制サイトやサービスへの登録(着メロ取り放題、Yahoo!
プレミアム、evernote、Netflix、インターネット契約)
ここではStripeさんやWebPayさんに倣って、
(a)
のような単発の決済は「課金」(charge)
(b)
のような自動更新のある決済は「定期課金」(subscription)
と呼んでいきます。
弊社のサービスのビジネスモデルはサブスクリプションモデルであり、
特に(b)の
サブスクリプションモデル、特にtoC向けは収益がいきなり落ちること
はめったに無いため、比較的安定しやすくなります。 そこまでたどり
着くのは簡単ではないですが...
特に(b)の
定期課金
が重要になってきます。サブスクリプションモデル、特にtoC向けは収益がいきなり落ちること
はめったに無いため、比較的安定しやすくなります。 そこまでたどり
着くのは簡単ではないですが...
そして自動的に引き落とされるということは、その分システムの安定性
が求められることになります。
が求められることになります。
決済システムの修正を担当したことがある人であれば、
「もし明日の朝出社して、数千件、数万件が自動で引き落としされてた
らどうしよう…」
とか寝る前に考えたことがあるのではないでしょうか。
そして「逃げちゃダメだ…逃げちゃダメだ…いや..逃げよう…」と何度も
つぶやいたことがあるはずです。
「もし明日の朝出社して、数千件、数万件が自動で引き落としされてた
らどうしよう…」
とか寝る前に考えたことがあるのではないでしょうか。
そして「逃げちゃダメだ…逃げちゃダメだ…いや..逃げよう…」と何度も
つぶやいたことがあるはずです。
C. 決済プラットフォーム
一旦視点を変えまして、外部の決済プラットフォームの話をします。
先程の図の「3」の箇所となります。
先程の図の「3」の箇所となります。
現在、Pairsでサポートしている決済手段は以下の4つです。
- クレジットカード
- iOSのIAP (iTunes Connect In-App Purchase)
- AndroidのIAB(Google Play In-app Billing)
- PayPal(台湾版のみ)
iOS, Android等のネイティブアプリケーションでは、扱っている商品に
よっては必ずIAPやIABを使って決済をしなくてはなりません。
よっては必ずIAPやIABを使って決済をしなくてはなりません。
カスタマーにしてみればAppleやGoogleに対して支払うことになり、
安心感がありますし、購入手続きや月々の明細の管理も楽です。
安心感がありますし、購入手続きや月々の明細の管理も楽です。
そんないいことづくめなネイティブアプリのプラットフォーム決済ですが、手数料が高いです。
購入価格の30%が手数料としてプラットフォームに取られるため、
100万円売上があったとしても30万円は手数料として消えてしまい、
実質手元に入るのは70万円です。
美味しすぎる手数料ビジネスですネ
購入価格の30%が手数料としてプラットフォームに取られるため、
100万円売上があったとしても30万円は手数料として消えてしまい、
実質手元に入るのは70万円です。
美味しすぎる手数料ビジネスですネ
それに対してクレジットカードやPayPalは通常の手数料が約3%〜5%、
さらに一回当たりのシステム手数料(
〜50円ほどかかります。
さらに一回当たりのシステム手数料(
トランザクション手数料
)として10円〜50円ほどかかります。
この場合に、1個1000円の商品を千個売って月に100万円の売上があった
とすると、
とすると、
- 通常の手数料で3.4%で 3.4万円 (*100万1円からは3.2%, 1000万1円
以上は2.9%) - 40円*千件 = 4万円
7.4万円が手数料として引かれ、92.6万円が手元に入ることになります。
サブスクリプションモデルの場合は、購入済みのシステムを勝手に切り
替えることはできないので、手数料、安全性、安定性、カスタマーに
とってのUX、実装・運用工数等々、様々な要因を考えた上で、対応
する決済プラットフォームを考える必要があります。
替えることはできないので、手数料、安全性、安定性、カスタマーに
とってのUX、実装・運用工数等々、様々な要因を考えた上で、対応
する決済プラットフォームを考える必要があります。
以下の比較表に簡単な違いをまとめています。
プラットフォーム 手数料 1ヶ月のサイクル 課金作成API 定期課金の
解約API クレジットカード 2〜5% + 数十円 自由 or 1ヶ月(独自) ○
(代行会社次第) PayPal 2.9〜3.6% + 40円 1ヶ月(独自) △ ○ iOS IAP 30% 1ヶ月(独自) × × Android IAB 30% 1ヶ月(独自) × ○
解約API クレジットカード 2〜5% + 数十円 自由 or 1ヶ月(独自) ○
(代行会社次第) PayPal 2.9〜3.6% + 40円 1ヶ月(独自) △ ○ iOS IAP 30% 1ヶ月(独自) × × Android IAB 30% 1ヶ月(独自) × ○
手数料を考えるときは、単純に
チャージバック時の手数料とカスタマーの返金率を考慮して、トータル
の手数料で考えると良いと思います。
国によっては返金率が高くなることもあり、ベンダーの返金手数料が
高い場合は想定以上の手数料となる可能性があります。
トランザクション手数料
の他にも、返金・チャージバック時の手数料とカスタマーの返金率を考慮して、トータル
の手数料で考えると良いと思います。
国によっては返金率が高くなることもあり、ベンダーの返金手数料が
高い場合は想定以上の手数料となる可能性があります。
1ヶ月のサイクル
というのは、引き落としが行われるサイクルになります。例えばPairsでは、クレジットカード決済の場合、1ヶ月プランを30日と
いうサイクルで販売しています。1ヶ月は30日だったり31日だったり
するため、31日の場合は同月に2回引き落としがされる可能性も
あります。
決済プラットフォーム間では、1ヶ月の期間判定が異なる場合があります。
通常の場合は月だけを+1することが多く、例えば
は
場合は、各社で異なる期間判定をされる場合があります。
例えばiOS IAPの場合は1月29日〜31日のどこで決済しても、次の更新日
は3月1日になり、その次の更新日は3月29日になる一方で、Android IABの場合は1月29日〜31日で決済すると、次の更新日は2月28日になるが、
その次の更新日は元に戻り、3月は1月の購入日と一致する、といった
具合です。
通常の場合は月だけを+1することが多く、例えば
7月1日
に購入した場合は
8月1日
に更新されます。ただし、2月というイレギュラー月をまたぐ場合は、各社で異なる期間判定をされる場合があります。
例えばiOS IAPの場合は1月29日〜31日のどこで決済しても、次の更新日
は3月1日になり、その次の更新日は3月29日になる一方で、Android IABの場合は1月29日〜31日で決済すると、次の更新日は2月28日になるが、
その次の更新日は元に戻り、3月は1月の購入日と一致する、といった
具合です。
この辺りの挙動は、仕様を正確に把握して事前に定義しようと思っても
難しいので、素直にプラットフォームから返却される有効期限を使う
ようにしましょう。 Yes as is.
難しいので、素直にプラットフォームから返却される有効期限を使う
ようにしましょう。 Yes as is.
課金作成API
というのは、こちらで自由に決済ができるかどうかということになります。
「自由」といっても一度決済を行った経験があり、プラットフォーム
側にクレジットカード情報があることが前提条件となります。
多くのクレジットカード決済代行会社の場合は、前回の決済時のユーザ
ーIDやメールアドレス、電話番号といった情報を使うことで、同じ
クレジットカードでの引き落としが可能です。
代行会社側で
エウレカ側で毎回決済リクエストを送っています。
こちらがリクエストを送信しない限り決済されることはなく、Pairs退会
済みのカスタマーから引き落とされることはないため、この点は安心で
すが、エラーハンドリングを適切に行わなかったり、ステージングと
本番を間違えるような致命的なミスを犯すと、多重に引き落としが発生
する可能性があるため注意が必要です。
自動引き落し機能
が付いている場合もありますが、Pairsではエウレカ側で毎回決済リクエストを送っています。
こちらがリクエストを送信しない限り決済されることはなく、Pairs退会
済みのカスタマーから引き落とされることはないため、この点は安心で
すが、エラーハンドリングを適切に行わなかったり、ステージングと
本番を間違えるような致命的なミスを犯すと、多重に引き落としが発生
する可能性があるため注意が必要です。
多重引き落としの場合にも、すぐに気づけば返金処理を行うことでカス
タマーに迷惑をかけず、上司の首が飛ぶのを防ぐことができますが、
誤決済の件数が多い場合は返金処理が一気にできない場合があります。
アクワイアラは国際ブランドに対して「量が多く質の良い決済」の獲得
を代行している立場上、返金率・回数が想定を大きく上回ると
「こんなに返金しやがってお前んとこは一体どうなってんだ」となって
しまい、1日や1ヶ月での上限が決まってるんじゃないかと、ワタシは
推測しています。
タマーに迷惑をかけず、上司の首が飛ぶのを防ぐことができますが、
誤決済の件数が多い場合は返金処理が一気にできない場合があります。
アクワイアラは国際ブランドに対して「量が多く質の良い決済」の獲得
を代行している立場上、返金率・回数が想定を大きく上回ると
「こんなに返金しやがってお前んとこは一体どうなってんだ」となって
しまい、1日や1ヶ月での上限が決まってるんじゃないかと、ワタシは
推測しています。
クレジットカード
各プラットフォームについてですが、まずはクレジットカードについて
説明していきます。
説明していきます。
日本ではクレジットカードが普及しており、オンライン決済手段として
一般的なものとなっています。
一般的なものとなっています。
入力フォームにカード番号や有効期限を入力するのが一般的かと思います。
システムやカード会社によっては、CVC(カード番号とは別にカード上
に記載されている3,4桁の暗証番号のようなもの)やパスワードを入力させるものがあるかもしれません。
システムやカード会社によっては、CVC(カード番号とは別にカード上
に記載されている3,4桁の暗証番号のようなもの)やパスワードを入力させるものがあるかもしれません。
クレジットカードにはみなさんご存知の通り、
VISA
, MasterCard
, JCB
,アメックス
, ダイナース
といった国際ブランドがあります。
これとは別にカード発行会社があり、この辺りを話すとキリがないため
省略しますが、アルファノートさんの「決済システムの仕組み」という
図が分かりやすかったのでリンク先を参照してもらえると、少しイメー
ジがつきやすいと思います。
省略しますが、アルファノートさんの「決済システムの仕組み」という
図が分かりやすかったのでリンク先を参照してもらえると、少しイメー
ジがつきやすいと思います。
図の中の「加盟店」が私たちのようなサービス提供会社にあたります。
サービス提供会社やエンジニアにとって直接関係があるのは、
を代行してくれる事業者と契約し、彼らのシステムと接続して決済を行
います。
サービス提供会社やエンジニアにとって直接関係があるのは、
決済代行会社
(アクワイアラ・プロセッサ)と呼ばれるその名の通り決済を代行してくれる事業者と契約し、彼らのシステムと接続して決済を行
います。
決済代行会社によって、以下の事項が変わってきます。
- (a) 利用できるカードブランド
- (b) 手数料
- © 決済システムのAPI
(a)は飲食店や海外のお店などであると思いますが、「VISAとMasterCardは使えるけど、JCBとアメックスは使えない」のような
ケースです。
ケースです。
(b)も決済代行会社によって変わってきます。
決済の取引金額が大きくなると、手数料が安くなることが多いです。
また利用するカードの国際ブランドによって変わることもあります。
例えば、「VISAとMasterCardは3.5%で、その他は3.8%」 のように
なることがあります。
決済の取引金額が大きくなると、手数料が安くなることが多いです。
また利用するカードの国際ブランドによって変わることもあります。
例えば、「VISAとMasterCardは3.5%で、その他は3.8%」 のように
なることがあります。
©はエンジニアとしては一番気になる箇所ですね。
APIやライブラリがいかに使いやすく、どのくらい機能が豊富かによって、実装工数やアーキテクチャ、稼働後の運用の負荷、実現できる機能
が変わってきます。
APIやライブラリがいかに使いやすく、どのくらい機能が豊富かによって、実装工数やアーキテクチャ、稼働後の運用の負荷、実現できる機能
が変わってきます。
例: 自動引落し機能、カード情報の一部表示機能(下4桁表示)、サン
ドボックス環境、etc…
ドボックス環境、etc…
多くの決済代行会社を利用するには審査や導入費用が必要ですが、最近
はStripeやSPIKEのように、導入が簡単で使いやすいAPIを持ったクレジ
ットカード向け決済プラットフォームがあるので、そちらを検討するの
も良いかもしれません。
はStripeやSPIKEのように、導入が簡単で使いやすいAPIを持ったクレジ
ットカード向け決済プラットフォームがあるので、そちらを検討するの
も良いかもしれません。
通常クレジットカード情報を取り扱うには、
カード向けの国際的なセキュリティ基準の認定資格を取得する必要があ
ります。(
PCI DSS
というクレジットカード向けの国際的なセキュリティ基準の認定資格を取得する必要があ
ります。(
ISMS(ISO27001)
やPマーク
のようなもの)
クレジットカード情報入力フォームをカスタマーへ直接提供する場合は、クレジットカード情報を取得することになり、代行会社の審査時に
PCI DSS
の準拠・認定を求められます。PCI DSS
取得も簡単ではないですし、システム・ネットワーク構成も厳重にする必要があります。
そしてクレジットカード情報なんて怖くて取得・保管したくありません。
そういう用途のために、
のシステムへ接続し、間接的に決済を行う方法があります。
Pairsでもそれを利用しています。
iframe
やURLリダイレクト
を使って決済代行会社のシステムへ接続し、間接的に決済を行う方法があります。
Pairsでもそれを利用しています。
一度決済を行ってしまえば、該当カスタマーのクレジットカード情報は
決済代行会社に保存されているため、
次からはフォームに入力したメールアドレスや電話番号等の情報を使って、Pairs側から簡単に決済リクエストを送信することが出来ます。
決済代行会社に保存されているため、
次からはフォームに入力したメールアドレスや電話番号等の情報を使って、Pairs側から簡単に決済リクエストを送信することが出来ます。
PayPal
PayPalは元々、個人間の送金サービスとして発展してきた決済プラット
フォームです。
PayPalの追い立ちは英語のwikipediaに譲るとして、現在では欧米系サイ
トや日本でも対応しているサービスが多いと思います。
2015年のAnnual Reportを見ると売上高は92億ドルで19%成長(!)、
1.79億人の有効なアカウントがありこちらも11%成長となっており、今も
伸び続けているようです。
フォームです。
PayPalの追い立ちは英語のwikipediaに譲るとして、現在では欧米系サイ
トや日本でも対応しているサービスが多いと思います。
2015年のAnnual Reportを見ると売上高は92億ドルで19%成長(!)、
1.79億人の有効なアカウントがありこちらも11%成長となっており、今も
伸び続けているようです。
そんなPayPalで使える決済のパターンはいくつかありますが、それだけ
で1記事になる、というか既に誰か説明しているでしょ!
ってことで調べてみたところakiyoko blogさんの【PayPal 決済まとめ】PayPal の決済システムが分かりにくいので 画面遷移パターンごとに使える決済システム・API を整理してみたに詳しく記載されています。
で1記事になる、というか既に誰か説明しているでしょ!
ってことで調べてみたところakiyoko blogさんの【PayPal 決済まとめ】PayPal の決済システムが分かりにくいので 画面遷移パターンごとに使える決済システム・API を整理してみたに詳しく記載されています。
かいつまんで説明すると、
- 元サイト上で、iframeかURLリダイレクトでPayPalのログイン画面を
出す - PayPal上で決済(または決済許可)を行う
- PayPal側から元サイト上へ決済完了通知(または決済許可トークン
付きで再リダイレクト) - (決済許可トークンを使って決済を実行)
という流れで、直接クレジットカードの番号を扱わない仕組みになって
います。
ええ、あなたの言いたいことは分かります。「文字だけじゃ分かりづらい」ですね。
まあ落ち着きましょう。
います。
ええ、あなたの言いたいことは分かります。「文字だけじゃ分かりづらい」ですね。
まあ落ち着きましょう。
- Pairs 購入画面
オレンジ色の購入ボタンを押すとPayPalへ遷移します。
- PayPal ログイン画面
ログインすると、PayPalに登録されているクレジットカードで支払い
確認画面が出ます。
確認画面が出ます。
- PayPal 支払い確認画面
社長のカード(色付き)で好きな買い物をする、最高の瞬間ですね!
「同意して続行」を押下すると決済トークン付きで元サイトへ遷移します。
この時点ではまだ決済はされていません。
この時点ではまだ決済はされていません。
- Pairs 最終購入確認画面
購入完了ボタンを押すと、PayPalから受け取った決済トークンを使用
して決済処理を行うようになっています。ここで初めて実際の決済処理
が行われます。(決済トークンには有効期限があるため、
この状態で一定時間が経過すると無効になります。)
して決済処理を行うようになっています。ここで初めて実際の決済処理
が行われます。(決済トークンには有効期限があるため、
この状態で一定時間が経過すると無効になります。)
使用できるAPIにも古くからあり
Classic API
と呼ばれているNVP / SOAP API
か、REST API
の2種類があります。
Pairsでは台湾版でPayPal決済を使っています。
実装当時は
(現在ではREST APIも使えるようです。もっと早く欲しかった...(;O;))
実装当時は
REST API
が台湾・台湾ドルをサポートしていなかったため、Classic API
のExpress Checkout機能を使っています。(現在ではREST APIも使えるようです。もっと早く欲しかった...(;O;))
参考までに、Pairsでの決済の流れは以下の通りです。
- (1) カスタマーがPairsの決済ページを開く
- (2) カスタマーが決済手段としてPayPalを選択し、購入ボタンを
押す - (3) Pairs側でSetExpressCheckout APIを使い、カスタマーをPayPal
へリダイレクト - (4) カスタマーはPayPalサイト上でログインし、購入確認ボタンを
押す - (5) PayPal側でカスタマーをPairsへ再リダイレクト。リダイレクト時
に決済トークンが付いてくる - (6) カスタマーはPairs上で最終確認ボタンを押す。
- (7) Pairs側(実際はeureka決済システム)で決済トークンを用いて、DoExpressCheckout APIを使い決済を行う
- (8) 定期課金の場合は、CreateRecurringPaymentsProfile APIを使い、定期的に引き落としがされるようにする。(GetRecurringPaymentsProfileDetails APIを使い、正常に定期課金
プロファイルが作成されているか確認もする)
ええ、あなたの言いたいことは分かります。「文字だけじゃ分かりづらい」ですね。(再掲)
(1)〜(6)までは上掲のスクリーンショットがカバーしています。
(7)〜(8)は↓のようなイメージです。
(7)〜(8)は↓のようなイメージです。
(安定のパワーポイント作図)
たまに(7)の決済が成功し、(8)の定期引き落とし処理に失敗することが
あります。
こういう場合はエラーとして購入失敗にし、決済については返金処理を
しています。
(たまにしか発生しないこと、そして(8)でエラーがでるということは、返金処理が失敗する可能性も高いことから今のところは手動運用にして
います。運用チームに多謝。)
あります。
こういう場合はエラーとして購入失敗にし、決済については返金処理を
しています。
(たまにしか発生しないこと、そして(8)でエラーがでるということは、返金処理が失敗する可能性も高いことから今のところは手動運用にして
います。運用チームに多謝。)
PayPal管理画面
PayPalの販売者向けの管理画面では、課金データの確認や、返金処理、CSVデータのダウンロード等が可能です。
マスターアカウントから個別ユーザー用のアカウントが発行できます。
特定の権限を絞ってアカウントを作成できるため、担当者ごとにきちん
と作った方が良いですね。
特定の権限を絞ってアカウントを作成できるため、担当者ごとにきちん
と作った方が良いですね。
元々はマスターアカウントだけで1Password Teamに入れてましたが、
危うさの限界線を超えたために個別アカウントを発行するようになり
ました。折角1Passwordで乱数発行しても、アカウント作成時のパスワードは手入力を求められるため、「最重要サービスのパスワードは最低限
30桁以上を要求するぜ!」みたいな社内ポリシーがあると、何回も打ち
間違えて詰むので気をつけてください。
危うさの限界線を超えたために個別アカウントを発行するようになり
ました。折角1Passwordで乱数発行しても、アカウント作成時のパスワードは手入力を求められるため、「最重要サービスのパスワードは最低限
30桁以上を要求するぜ!」みたいな社内ポリシーがあると、何回も打ち
間違えて詰むので気をつけてください。
サンドボックス環境
大抵の決済プラットフォームにはサンドボックス環境と呼ばれる、
開発向けの環境が用意されています。
開発向けの環境が用意されています。
ここでは本番さながらに決済を行えますが、実際にお金が引き落とされ
ないという素晴らしい環境です。
ないという素晴らしい環境です。
PayPalもサンドボックス環境があり、接続先のURLが異なっています。
サンドボックス独自ルールが少ないため使いやすいですが、PayPal社
のステージング環境として使われているのか、たまに本番と違う
新UIになっていたりします。
サンドボックス独自ルールが少ないため使いやすいですが、PayPal社
のステージング環境として使われているのか、たまに本番と違う
新UIになっていたりします。
そういった関係なのか分かりませんが、2ヶ月に1回くらいは数時間くら
い使えなくなります。
その間はPayPalでの決済テストが一切できなくなり、当日リリースと
かだったりすると、
「PayPalで決済テストができません」となぜか私に相談がきます。
い使えなくなります。
その間はPayPalでの決済テストが一切できなくなり、当日リリースと
かだったりすると、
「PayPalで決済テストができません」となぜか私に相談がきます。
そんなときは早く帰宅して寝てもらっています。
iTunes Connect In-App Purchase (iOSアプリ)
続いて我らがキング、Apple社がiOS向けに提供する決済プラットフォー
ムになります。
iOSアプリケーションでは、アプリ内課金のためにIn-App Purchase(以下
、IAPと表記)を使うことが出来ます。
ムになります。
iOSアプリケーションでは、アプリ内課金のためにIn-App Purchase(以下
、IAPと表記)を使うことが出来ます。
大体のことは公式のIn-App Purchase プログラミングガイドに書いてあります。
詳しくはそちらを参照してください。
詳しくはそちらを参照してください。
PDFのP9,P10辺りの「プロダクトのタイプ」という項目で、商品の種類
が5種類記載されています。このうち、Pairsでは、消耗型(Consumable)
と自動更新購読(Auto-renewable subscriptions)を使用しています。
が5種類記載されています。このうち、Pairsでは、消耗型(Consumable)
と自動更新購読(Auto-renewable subscriptions)を使用しています。
消耗型は「Pairsポイント」のような、消費するコンテンツに対して
使っています。そのままですね。自動更新購読は「有料プラン」
「プレミアムオプション」のような、定期課金に使用しています。これ
もそのままですね。
使っています。そのままですね。自動更新購読は「有料プラン」
「プレミアムオプション」のような、定期課金に使用しています。これ
もそのままですね。
エウレカではCouplesというカップル向けリア充コミュニケーション
アプリがありますが、メッセンジャー機能の中でテキスト以外にも
スタンプを送信することができます。
(FacebookやLINEさんのようなやつです)
アプリがありますが、メッセンジャー機能の中でテキスト以外にも
スタンプを送信することができます。
(FacebookやLINEさんのようなやつです)
その有料スタンプでは非消耗型(Non-consumable)を使っています。
(月額会員向けのCouples Plusという商品では自動更新購読を使っています)
(月額会員向けのCouples Plusという商品では自動更新購読を使っています)
決済とは全然関係ないですが、Couples Qという恋愛相談サービスも最近開始したので、恋人がいる方は使ってみてください。誰にも言えない
相談、修羅場をくぐってきた兵のアドバイスお待ちしております。
(ナチュラルな宣伝)
相談、修羅場をくぐってきた兵のアドバイスお待ちしております。
(ナチュラルな宣伝)
決済のフローについては、クライアントサイドだけでやる方法と
サーバーサイドを含める方法があります。APIサーバーがなかったり、mBaaSとかを使っているアプリであれば、iOSアプリだけで決済処理を
完結できますが、多くのサービスのようにサーバーサイドAPIが存在する場合は、サーバーサイドで決済の検証を行うのが確実です。
サーバーサイドを含める方法があります。APIサーバーがなかったり、mBaaSとかを使っているアプリであれば、iOSアプリだけで決済処理を
完結できますが、多くのサービスのようにサーバーサイドAPIが存在する場合は、サーバーサイドで決済の検証を行うのが確実です。
Pairsの場合は以下のフローとなっています。
- (1) カスタマーがアプリ上でPairsの決済ページを開く
- (2) ページ内の購入ボタンを押すとiOSの購入確認モーダルが表示
される - (3) モーダルのOKボタンを押すと、Appleの購入レシートをiOS
アプリ側からPairs側へ送信する - (4) Pairs側(実際はeureka決済システム)は受け取ったレシートを
そのままAppleの決済APIへ渡す - (5) レスポンスとして決済情報が含まれているJSONデータが返却され、その情報を元に以下のような購入バリデーションを行う
status
が0かどうかbundle_id
が正しいかどうかproduct_id
が正しいかどうか- 未使用の
transaction_id
かどうか expires_date
が過ぎていないかどうか- (6) 全てOKであれば購入成功、NGな項目があれば購入エラーを返却
する
なんだか分かりづらいですね。
ここでレシートという単語が出てきましたが、MD5ハッシュ化された
文字列です。
こいつをAppleのAPIへ送信すると、そのレシートに紐づく決済情報がJSON形式で返却されてきます。(5)のバリデーションのところで、
その中身を確認し、ちゃんと決済が行われたかどうか確認します。
ここでレシートという単語が出てきましたが、MD5ハッシュ化された
文字列です。
こいつをAppleのAPIへ送信すると、そのレシートに紐づく決済情報がJSON形式で返却されてきます。(5)のバリデーションのところで、
その中身を確認し、ちゃんと決済が行われたかどうか確認します。
レシートの形式には iOS6型とiOS7型があります。
古いiOS6型は単体の購入情報のみがあり、定期課金の有効ステータスを
確認することができます。新しいiOS7型は定期課金に紐づく課金履歴
情報が全て含まれており、個別の定期課金の有効ステータスは確認でき
ません。
確認することができます。新しいiOS7型は定期課金に紐づく課金履歴
情報が全て含まれており、個別の定期課金の有効ステータスは確認でき
ません。
イメージしづらいと思うので、実データを見てみましょう。
どうでしょうか。iOS7型はクレイジーですね。
といってもこれは実環境ではなく、サンドボックス環境のレシートを
元に整形したものなので、実際のレシートがここまで肥大化することは
ないと思います。
ただ、全ての課金履歴が含まれているようなので、1ヶ月サイクルで
定期課金が更新される度に、15行くらい増えます。
増えます。
これが3年間継続すると (30行 * 3年 * 12ヶ月) = 1080行 くらいに肥大化
することになります。
アプリ側からサーバーサイド側へPOST送信される、ハッシュ化された
レシート文字列自体もボリューミーになるため、nginxの
あります。
元に整形したものなので、実際のレシートがここまで肥大化することは
ないと思います。
ただ、全ての課金履歴が含まれているようなので、1ヶ月サイクルで
定期課金が更新される度に、15行くらい増えます。
in_app
と latest_receipt_info
の両方に含まれるため、実質30行ほど増えます。
これが3年間継続すると (30行 * 3年 * 12ヶ月) = 1080行 くらいに肥大化
することになります。
アプリ側からサーバーサイド側へPOST送信される、ハッシュ化された
レシート文字列自体もボリューミーになるため、nginxの
client_max_body_size
を低めに制限している場合は気をつける必要があります。
検証時には
一致する
iOS7型にすることで検証のロジックが少し複雑になりましたね。
latest_receipt_info
から末尾順に辿り、購入した商品と一致する
product_id
とその有効期限を見ることになると思います。iOS7型にすることで検証のロジックが少し複雑になりましたね。
そして定期課金の有効ステータスですが、iOS6型の場合は定期課金の
有効期限が切れると21006というstatusを返却してくれます
有効期限が切れると21006というstatusを返却してくれます
定期課金の更新については、
だけなので、iOS6型は非常に楽ですね。
status
が 0
か 21006
かどちらかを調べるだけなので、iOS6型は非常に楽ですね。
あれ、なぜ私たちはiOS7型に切り替えてしまったのでしょうか/(^o^)\
ここまで説明していて、パワポで作図したくないため良い感じの図が
無いかどうか探していたところ、サイバーエージェントさん公式エンジ
ニアブログに自動購読課金について【iOS編】という記事を見つけ、とても整理されて分かりやすくまとまっていました。「冒頭で紹介しろよ!」
って感じですね。ハハハ。
無いかどうか探していたところ、サイバーエージェントさん公式エンジ
ニアブログに自動購読課金について【iOS編】という記事を見つけ、とても整理されて分かりやすくまとまっていました。「冒頭で紹介しろよ!」
って感じですね。ハハハ。
こちらの記事の著者の辻さんが、Go言語でのIAP, IAB向けに作られたdogenzaka/go-iapを利用させていただいています。本当にありがとう
ございます。
ございます。
iTunes Connect 管理画面
Appleの管理画面から決済関連でできることは、決済データのCSVのダウンロードです。
私から言えることはそれだけです。よろしくお願いします。
私から言えることはそれだけです。よろしくお願いします。
マンパワーを使って毎回CSVをダウンロードしてもいいんですが、API経由でデータを取得することもできます。Node向けのライブラリもあるので、整形してサマリ表示するようなこともSlack経由でできます
(仕掛中)
(仕掛中)
なおCSVデータには誰が買ったか特定できる情報はないため、サービス
側で保存している決済情報と完全に突合するのは難しいです。
側で保存している決済情報と完全に突合するのは難しいです。
iTunes Connectの該当画面のスクリーンショットを上げようかと思いま
したが、なぜか私の権限が剥奪されているため、遷移できませんでした。
よろしくお願いします。
したが、なぜか私の権限が剥奪されているため、遷移できませんでした。
よろしくお願いします。
サンドボックス環境
iTunes Connect上でテスターのアカウントを追加し、Testflightでアプリ
を配信することでテストをすることが多いんじゃないかと思います。
を配信することでテストをすることが多いんじゃないかと思います。
PayPal同様に、サーバーサイドで検証する際は、サンドボックス環境と本番とで接続先URLが違います。
また自動更新の期間が短くなっており、購入後に6回更新されます。
途中解約はできません。
途中解約はできません。
そして購読画面からも変更ができないため、途中解約やクロスグレード
変更、後述のアップグレードといった細かい挙動の確認はできません。
これは「計測するな、推測せよ!」 ということでしょうか?!
変更、後述のアップグレードといった細かい挙動の確認はできません。
これは「計測するな、推測せよ!」 ということでしょうか?!
Google Play In-app Billing (Androidアプリ)
次はみんなのヒーロー、Google社がAndroid向けに提供する決済プラットフォームになります。
iOS同様、Androidアプリケーションでもアプリ内課金のためにIn-app Billing(以下、IABと表記)という仕組みを使うことができます。
iOS同様、Androidアプリケーションでもアプリ内課金のためにIn-app Billing(以下、IABと表記)という仕組みを使うことができます。
フローについてはiOSと同様なので省略します。
そして大体のことは公式のドキュメントに載っています。
というか、こちらもサイバーエージェントさんの自動購読課金について【Android編】にまとまっています。
「この記事のおかげで、私から言えることはもうありません (`・ω・´)
キリッ」
で済ませたいところですが、上長に怒られそうなので一応説明いたします。
「この記事のおかげで、私から言えることはもうありません (`・ω・´)
キリッ」
で済ませたいところですが、上長に怒られそうなので一応説明いたします。
iOS IAPのレシートに当たる箇所で最低限必要なパラメータは、
productId
と purchaseToken
, orderId
になります。productId
はGoogle Play上で登録した商品のIDが入ります。purchaseToken
には購入トークン(乱数)が入り、iOSでいうところのレシートの検証時に使います。
orderId
は API操作では一切使いませんが、Googleペイメントセンターの決済と紐づくオーダー番号が入ります。
オーダー番号を使ってWebUI上から手動で返金操作を行ったり、GooglePlayの収益レポートCSVと紐付けて会計チームに引き渡したり
する際に使用します。
purchaseToken
とorderId
は一つの決済につき1つになります。purchaseToken
はシステム側で使用し、orderId
は人間が使うと思ってください。
orderId
は元々 XXXXXXXXXXXXXXXX.YYYYYYYYYYYYYYYYY
のような形でしたが、2015年の夏頃から
GPA.0000-0000-0000-0000
のような形に変わりました。
旧形式の
バリデーションをかけていました。
よく訓練されたAndroider(特に海外)の方々はカジュアルに不正な
レシートを送信してくる傾向があり、こちらもカジュアルに400エラー対応をしていました。
XXXXXXXXXXXXXXXX.
の部分はアプリによって固定だったため、PairsではGoogleのAPIへリクエストを行う前にフォーマットによる事前バリデーションをかけていました。
よく訓練されたAndroider(特に海外)の方々はカジュアルに不正な
レシートを送信してくる傾向があり、こちらもカジュアルに400エラー対応をしていました。
そんな中、
トが上がったことがありました(寒気)
しかも両方の形式が混在しており、全て失敗するわけではなくたまに
失敗するという状況でした(吐き気)
orderId
がカジュアルに新形式に移行し、決済のエラーレートが上がったことがありました(寒気)
しかも両方の形式が混在しており、全て失敗するわけではなくたまに
失敗するという状況でした(吐き気)
その日はむせび泣きました。
この教訓としてそれ以降、ワタシタチは勝手に変なバリデーションをか
けるのはやめました。
ベンダーのAPIへ素直にそのまま渡すことを熱烈におすすめします。
けるのはやめました。
ベンダーのAPIへ素直にそのまま渡すことを熱烈におすすめします。
また、GoogleのAPIは非常に安定しています。
主観ですが、
主観ですが、
Google >> PayPal >>>> Apple
というくらい安定しています。
キングApple様は元々安定していましたが、今年の5月くらいから不安定(エラー率が多い)な気がします。
キングApple様は元々安定していましたが、今年の5月くらいから不安定(エラー率が多い)な気がします。
GoogleもAppleもたまに特定のレシートで不明なエラーを返却してくる
ことがあります。
前回の更新時には正常に使えたのに、現在は40Xエラーを返却してくる
ケースです。
問い合わせたところ、「Googleをアカウント削除すると、既存のレシートで400エラーが返却される」ということで、400番が返ってきた場合
は定期課金を解約状態にしてしまって大丈夫なようです。
(Apple様の
ことがあります。
前回の更新時には正常に使えたのに、現在は40Xエラーを返却してくる
ケースです。
問い合わせたところ、「Googleをアカウント削除すると、既存のレシートで400エラーが返却される」ということで、400番が返ってきた場合
は定期課金を解約状態にしてしまって大丈夫なようです。
(Apple様の
404 newNullResponse エラー
は 「問い合わせ」 -> 「調査中」 -> 「優先度低」 という奥義を喰らっており不明なままです。推測ですが、おそらくGoogle Playと同じような状態じゃないかと思います)Google Playの管理画面
アカウントごとに固有のCloud Storageへのリンクがあるようなので、
そこからCSVファイルをダウンロードできます。このCSVファイルには
側で保存している決済情報と一対一の突合が可能です。
そこからCSVファイルをダウンロードできます。このCSVファイルには
Description
というカラムにorderId
が記載されているため、サービス側で保存している決済情報と一対一の突合が可能です。
(↑この画面の下部にCloud Storageへのリンクがあります)
11月までは
今はGoogle ペイメントセンターというシステムに切り替わっています。
前はURL指定で検索クエリが使えたため、Pairs, Couplesの管理画面から個別の課金へ直接遷移するリンクを生成することができましたが、
個別の課金へ飛ぶことが出来なくなってしまっため、
ヒューマンが入力しないとあかん状態になっています。
誰かこっそり解決策を教えてください。タノミマス。
Google Wallet Merchant Center
というUIだったのですが、今はGoogle ペイメントセンターというシステムに切り替わっています。
前はURL指定で検索クエリが使えたため、Pairs, Couplesの管理画面から個別の課金へ直接遷移するリンクを生成することができましたが、
Google ペイメントセンター
になってからは、よりSPAな動きになり、URLから直接個別の課金へ飛ぶことが出来なくなってしまっため、
ヒューマンが入力しないとあかん状態になっています。
誰かこっそり解決策を教えてください。タノミマス。
サンドボックス環境
AndroidはiOSと違って、そこまで詰まった経験がないのでようわからんのが正直なとこです。
詳しくは公式のドキュメントを見てみてください。
詳しくは公式のドキュメントを見てみてください。
Googleアカウントをテスター登録するとテスト課金できますが、1ヶ月の有効期限は1日になり、さらに
そのためテスターアカウントを使うこともありますが、
デーションエラーを避けるために本物のアカウントを使ってカジュアルに課金をすることもあります。
Google ペイメントセンター上で返金処理をしましょう。)
orderId
が取得できないようです。そのためテスターアカウントを使うこともありますが、
orderId
のバリデーションエラーを避けるために本物のアカウントを使ってカジュアルに課金をすることもあります。
Google ペイメントセンター上で返金処理をしましょう。)
アップグレード商品
元々Android IABには商品に
例えば、
Tier
という概念がありまして、例えば、
ブロンズプラン
月額100円 メールサポートのみ 5営業日以内のレスポンスシルバープラン
月額300円 月3回の電話サポートあり 3営業日以内のレスポンス SLAゴールドプラン
月額1000円 月100回の電話サポート 1営業日以内のレスポンス SLAエンタープライズプラン
要お問い合わせ 無制限のサポート
このような、プラン間での上位・下位があった場合に、購入済み商品
から切り替え(アップグレード・ダウングレード)が可能な仕組みがありました。
これを行うと切り替え後プランの期間延長が行われるようです。
参考: アプリ内定期購入 — 定期購入のアップグレードとダウングレード
から切り替え(アップグレード・ダウングレード)が可能な仕組みがありました。
これを行うと切り替え後プランの期間延長が行われるようです。
参考: アプリ内定期購入 — 定期購入のアップグレードとダウングレード
そしてApple様にはこの仕組みはありませんでしたが、
今夏WWDC2016にて決済関連のアップデートがあり、国別価格や価格変更時の据え置きに加え、アップグレードプランも対応しました。
今夏WWDC2016にて決済関連のアップデートがあり、国別価格や価格変更時の据え置きに加え、アップグレードプランも対応しました。
上位・下位の関係がある商品の場合はこちらを適用しないといけません。例えば、Netflixには1画面プラン・2画面プラン・4画面プランがあり、
既にアップグレード・ダウングレードが出来るようです。
既にアップグレード・ダウングレードが出来るようです。
詳しい仕様、特に引き落とし開始日の変化や決済金額が不明なため
ヒアリング中ですが、まだ良くわかっていないため、
私から伝えられることは少ないです。ごめんなさいm(_ _)m
ヒアリング中ですが、まだ良くわかっていないため、
私から伝えられることは少ないです。ごめんなさいm(_ _)m
D. Goでの決済システムの実装
ようやく実装の話に入ってきます。
ここからはGo言語に興味がない人は全くつまらない話なので、
心苦しいです。
ここからはGo言語に興味がない人は全くつまらない話なので、
心苦しいです。
ちなみにPairs・Couples本体とはかなり構成が違うので、この記事を
見て「Pairsって○○で××なんでしょ?」とか弊社社員と話しても話が
通じないことがあります。心苦しいです。
見て「Pairsって○○で××なんでしょ?」とか弊社社員と話しても話が
通じないことがあります。心苦しいです。
主要ライブラリ
外部ライブラリはこの辺りを使っています。
よく聞かれるので頑張って列挙してみました。
あんま珍しいものは使ってないと思います。
よく聞かれるので頑張って列挙してみました。
あんま珍しいものは使ってないと思います。
種別 ライブラリ 備考 WAF・ルーティング
zenazn/goji DB・ORM go-xorm/xorm DB・ORM evalphobia/wizard xormをシャーディング対応したもの キャッシュ evalphobia/eurekache
メモリキャッシュのみ使用
HTTPクライアント h2non/gentleman franela/goreqから移行中… ログ sirupsen/logrus (彼の名前が小文字になりましたね…) ログフック logrus_appneta TraceViewへのエラーログフック ログフック logrus_fluentfluentdへのフック ログフック logrus_sentry Sentryへのフック ログフック Stackdriver Stackdriver loggingへのフック
モニタリング fukata/golang-stats-api-handler モニタリング
tracelytics/go-traceview TraceViewへの
トレースログ送信 テスト stretchr/testify
決済系 evalphobia/go-iapdogenzaka/go-iapにiOS6型レシート対応を
加えたものです 決済系 evalphobia/go-paypal-classic
zenazn/goji DB・ORM go-xorm/xorm DB・ORM evalphobia/wizard xormをシャーディング対応したもの キャッシュ evalphobia/eurekache
メモリキャッシュのみ使用
HTTPクライアント h2non/gentleman franela/goreqから移行中… ログ sirupsen/logrus (彼の名前が小文字になりましたね…) ログフック logrus_appneta TraceViewへのエラーログフック ログフック logrus_fluentfluentdへのフック ログフック logrus_sentry Sentryへのフック ログフック Stackdriver Stackdriver loggingへのフック
モニタリング fukata/golang-stats-api-handler モニタリング
tracelytics/go-traceview TraceViewへの
トレースログ送信 テスト stretchr/testify
決済系 evalphobia/go-iapdogenzaka/go-iapにiOS6型レシート対応を
加えたものです 決済系 evalphobia/go-paypal-classic
ディレクトリ・パッケージ構成
大体以下のような形になってます。
├ main.go ├ config/ // 設定ファイル ├ routing/ // ルーティング定義 ├ middleware/ // リクエスト処理時のミドルウェア ├ controller/ // コントローラー ├ service/ // ビジネスロジック置き場 │ └─ platform/ // 各決済プラットフォーム固有のロジック ├ model/ // DDDでいうところのEntityとRepository ├ library/ // 各種ライブラリ置き場 ├ client/ // 外部向けのSDK ├ test/ // テストヘルパー・fixture置き場 └ misc/ // RakeタスクとかドキュメントとかSQLとか
それぞれ詳しくみていきます。
main.go
main.goでしていることは、
- フラグのパース
- 設定の読み込みと初期化
- ルーティングとミドルウェア設定
- HTTPサーバー起動
くらいです。至って普通ですね。
config
例えば以下のようなログ用の設定があったとすると、
$ cat ./config/log.toml
[log]
[log.fluent] host = "127.0.0.1" port = 24224 enable = false
[log.sentry] url = "" enable = false
# ----ここまで----
$ cat ./config/dev/log.toml [log]
[log.sentry] url = "https://XXX:YYY@app.getsentry.com/999999" enable = true
簡単な使い方は↓のようになります。
import( config "github.com/evalphobia/go-config-loader" )
// (中略)
const confType = "toml"
conf := config.NewConfig() conf.LoadConfigs("config/dev", confType) // ./config/dev から .toml ファイルを全て読み込む conf.LoadConfigs("config", confType) // ./config から .toml ファイルを全て読み込む
// ./config/log.tml のデータ useFluentd := conf.ValueBool("log.fluent.enable") // => false
// ./config/dev/log.toml のデータ useSentry := conf.ValueBool("log.sentry.enable") // => true sentryURL := conf.ValueString("log.sentry.url") // => https://XXX:YYY@app.getsentry.com/999999
LoadConfigs
では先に読み込んだ項目が優先される仕様になっているため、固有の設定
から読み込むようにし、全体のデフォルト設定は最後に読み込むようにします。
routing
goji.SubRouter
を使ってAPIの種類ごとにルーティング設定を保存しています。 ルーティングは全てcontroller
の関数を設定しています。
middleware
WAFのルーティングの文脈でいうミドルウェアを定義しています。(一番最初に何かの WAFでミドルウェアって見た時はよく理解できませんでした...(/_;)) HTTPリクエストがコントローラ層で処理される前(と後)にロジックを仕込むことができます。
認証やHTTPヘッダー処理、パラメーター処理なんかを行ってます。
特に変わったことはしていませんが、http.Request
からリクエストパラメータを取得
して、内部ロジックで利用する生データと、個人情報っぽいものを削除したログ用途のもの
に分けて、Contextに入れてます。こうすることで、不用意にログにヤバイデータが残され
ないようにしています。
controller
service層の関数を一つ呼び出し、その結果をJSONとして返却しています。
呼び出しの結果は全て map[string]interface{}
で受け取っています。
ただしモニタリングやテスト用のコントローラはそのままロジックが書かれていることも あります。
service
様々なロジックが書かれる場所です。 地道です。
platform
各決済プラットフォームごとの固有のロジックを置いています。
いわゆるファクトリパターンで決済の interface
を作成し、service層ではplatformを意識せずに新規購入や更新処理を行っています。
たまにプラットフォーム独自のフローがあったりすると、全てにダミーのメソッドを追加しないといけないのが辛いです
テストは一部本番のデータを使っています。 本番のデータを使っているがために、1ヶ月ごとにリアルデータの有効期限が延長されます。 そのためアサーション部分を適宜変更しなければいけませんが、本番のレスポンス以外は 信じたくない病的思考のためこうしてます。 CIが通ったとき、有効期限が変わってCIが落ちたとき、そして修正後にまたCIが通ったときの安心感・満ち足りた高揚感はひとことでは言い表せません。
model
DDDの概念でいうエンティティとレポジトリが置かれています。 レポジトリで更新処理や特定の処理をしたときに、エンティティ側の非公開フィールドのフラグやデータをいじりたかったので、同じパッケージに格納しています。 (必須ではないので分けてもいいですが、そこまで不便さを感じていないためそのままです。)
library
内部ライブラリが置かれています。 ディレクトリ構成は標準ライブラリと似た命名になっています。 (import元で標準ライブラリと名前がかぶった時はこちらのパッケージ名を変えています。)
外部ライブラリを使う場合はここにアダプタを置き、サービス層や他の層では直接外部ライブラリを扱わないようにしています。
client
clientにはPairs側でimportして使うためのSDKを置いています。エンドポイントやパラメータ情報が書かれています。 新たにエンドポイントやパラメータを追加する場合はサーバー側だけでなく、こちら側にも更新を反映します。決済システム本体のコードとは分離していますが、唯一エラーコード のファイルだけ共有しています。
ここにもテストコードを置いており、本体サーバーを起動して、fixutureを流した際の レスポンスを検証しています。代わりにPairs側での決済リクエスト周りの単体テストは 簡素になっており、固定のダミーレスポンスを使うようにしています。
test
テスト用のヘルパーとfixtureしか置いてないです。
misc
バッチ用のRakeタスクが置いてあります。
「あれ、バッチ処理はRubyで書いてるの?」と思うかもしれませんが、実体はHTTP
リクエストを送信しているだけです。
主なバッチ処理は定期課金の更新処理とデータ不整合時のアラートです。 それぞれに順番的な依存関係を持たせないようにしており、各リクエストは数秒以内に 終わるようにしています。(と言ってもプラットフォーム側の応答速度にも依りますが...)
わざわざワーカーを作ったりCLIツールを作り、それぞれ監視を行ったり使い方を教えるのも面倒ですしシンプルさが失われると感じていて、全ての処理はcurlコマンドさえあれば出来るようになっています。
E. その他
定期課金の更新
上で説明したように、処理のインタフェースはHTTPサーバー&クライアント&cronで 構築しています。大きくエンキューとデキューのプロセスに分かれています。
有効期限が切れたり、確認状態になった定期課金をエンキューのプロセスでSQSへ突っ込 みます。一回あたりの処理件数は上限を設けています。通常は問題ありませんが、多く処理 しなければならない時、例えば日付が変わった瞬間は通常より多くのエンキュー処理が実行 されます。 この辺りはRakeタスク実行時のパラメータで、総リクエスト実行数が変更できるように なっています。
各プラットフォームごとにSQSキューがあり、そのキューごとに個別のHTTPエンドポイン トが存在します。HTTPリクエスト1回につき定期課金が1件処理されるため、こちらもRake タスクのパラメータで増減が調整可能になっています。
またSQSキューのメッセージ数を監視することで、どこかのプラットフォームで異常が起きていないかを把握することができます。
ちなみにGo言語でのAWS SQSの使い方については先週末に記事にしたので、興味ある方は 見てみてください。(未だにFacebookシェア数が0なので愛しさや心強さは一切なく、 ただただセツナさを感じています。)
決済システムとプロダクト側とのデータ同期
決済システムでは決済データをデータベースへ保存しています。 またプロダクト(Pairs)側でも決済データをデータベースを保存しています。
基本的には決済システム側のデータがマスターとなり、プロダクト側ではその一部を保存 します。
プロダクト側から決済システムを通して決済プラットフォームへ決済処理を行う際のフロ ーは大きく以下のようになります。
- (1) プロダクトから決済システムへ購入リクエストを送信する
- (2) 決済システムから決済プラットフォームへ購入(or 確認)リクエ
ストを送信する - (3) 決済システムからプロダクトへレスポンスを返却する
ここで(2)
が成功し(3)
が失敗すると、決済システムだけにデータが保存される可能性が あります。マイクロサービス化を進めるとデータの不整合を防ぐ仕組みが必要になります。
- (4) プロダクトから決済システムへ確認リクエストを送信する
決済システムではデータベースに確認用のカラムを用意しており、(3)
が成功すると確認 リクエスト(4)
を送信し、同期完了とします。TCPのACK
に近いです。
データが不整合になってしまった場合はバッチで自動同期をするか、アラートが発生する ようになっています。
ベンダリング
kovetskiy/manulでしてます。
たまにおかしくなるのでgit submodule
を生でいじることもあります。
CI
Travisを使っています。カバレッジはCodecovを使っています。 masterでCIをパスした場合はgitbookのビルドプロセスが走り、マニュアルが更新され るようになっています。
デプロイ
ステージングのデプロイはRundeckとconsulを使っています。 元々はAWS CodeDeployを使っていましたが、Web UI上でブランチを指定してデプロイ できるようにRundeckに変えました。本番へのデプロイは独自の人肌温いスクリプトを使っ ています。
システム監視
インフラ・ミドルウェアレイヤーは、はてなさんのMackerelを使用しています。ありがとうございます。
ログ周りはSentry・SolarWinds TraceView・Google Stackdriver loggingを 使っています。
アプリケーションレイヤーでは、基本的にcronバッチ&アラート通知(Email & Slack)が多いです。アラート通知がアプリケーションプロセス・HTTPサーバーに依存している ため、ここで不具合が発生するとどうしようもない状況になります。
バッチ処理をHTTP経由にする弊害の例
決済システムの前段にはELBが入っており、オンライン向けの処理系統とバッチ処理向け の処理系統を分けておらず、どちらも一つのアプリケーションとして動作しています。
先日、メモリリーク調査のために数日間バッチ処理系統を分けていました。 そんな中、Fatalエラー(logrusの各フック内でmapの並行アクセス起きてしまった...)が発生し、アプリケーションが落ちてしまいました。 本来はsupervisorで再起動されるはずですが、なぜかうまくされずに変な状態で生き残ってしまっていたようです。(プロセスKILL等では問題なく再起動される) このため全てELBから外されしまい、バッチ処理が行われないようになってしまいました。 (仮で作ってもらったのでオートヒーリングが設定されていなかった...)
HTTPクライアント側にはHTTPステータスでのアラートを設定しておらず、アラートの ほぼ全てをHTTPサーバー経由の処理が担っていたためにアラートが機能しなくなってしまっていました。
休日でしかもMacherelアラートも他の通知に紛れてしまっていたため、対応が遅れてし まいました。(吐き気)
本当に気をつけてください。
データベース
最初の方で説明した通り、DBにはAmazon Auroraを使っています。 そこまでトラフィックがないため、アプリケーションサイドでは特に辛さやありがたみ、 畏敬の念を感じたことはないですが、インフラ運用面では何かあるかもしれないので、 恐らく来年中には弊社のオラオラ系テラフォーマー恩田氏が何か語ってくれるはずです。
テーブル数は10ちょいです。 直接決済に関わるテーブル以外だと、
- マルチテナンシー用のアカウントテーブル
- 監査ログテーブル
- エラーログテーブル
が存在しています。
エラーログテーブルはステージングでの調査に使用することが多く、本番ではあまり活用 できていないので、もう少しチューニングをしていきたいと思っています。
WebUI
ありません。
上記のエラーログやBaremetricsのようなUIを提供したいのですが、 「ドヤァァァァ!」と作っても自己満になりそうなので、今のところはやっていません。
F. まとめ
ここまで見てきたように、決済のシステムを作るには各プラットフォームの知識が必要に なってきます。ある意味では、実装を行うよりも詳しい仕様を把握する方が大変かもしれ ません。 プラットフォーム間の差異を吸収するために、eurekaでは決済システムをマイクロサー ビスとして切り出すことにしました。 サポートする決済手段が1,2種類ほどでは分ける必要性が薄いかもしれませんが、決済手 段が増えるに従い、別のシステムとして分けるメリットは増してくると思います。
決済の苦しみスバラシサを1%でもお届けできたか不明ですが、少しでもお役に立てたらと 思います。
はてぶ数が100以上、FBシェア数が200以上つくと、ブログ工数が確保され、この続き
を図付きで詳しく書かせてくれるということなので、コメントを添えてシェアリングパ
ーティーをしてもらえればと思います。
明日の23日は、まつけんはんによる「Pairsのインフラコストを最適化しました」となり ます。 乞うご期待!
0 コメント:
コメントを投稿