Stripeでチャリンチャリン、サービスを開発するエンジニアにとっては夢がありますよね。
例え自分で個人的に売るものが無かったとしても、Web決済システムを構築できるノウハウを持っておけば、公的な仕事で顧客に提案する際に有利に働くはずです。
以前、そんな夢を叶えてくれる「Stripe」を使ってテストWeb決済するまでを確認しました。
後はApple Pay、Google Payに本番申請、Webブラウザが運用ドメイン上でクレジットカード選択出来るようにして、StripeのLive(本番)キーを使えば実運用が出来るWeb決済システムの完成です。
夢の始まり・・・と言いたいところですが、本番で使用するには安全確保の為、もう一手間掛けたいところです。
「商品をユーザに譲渡した後、実際に決済をしようとしたらカードの上限額を超えているカードで決済が出来なかった。または決済はできたけど渡す商品の確保に失敗した。」
こんなケースは避けたいですよね。
こういったケースを防ぐ為に、カード会社に対して予定している決済金額を仮押さえする「オーソリ」 == 「カード会社に対する利用金額の確保(仮押さえ)」を今回は実装してみたいと思います。
オーソリ(与信確保)とは
実際に決済する前に、選択されたクレジットカードが支払い金額を払えるか事前にカード会社に確認、支払い金額を仮押さえすることが出来ます。
オーソリ状態は数日~数十日(カード会社によって異なる)維持することができ、商品を郵送し終わった後に決済することも出来ます。
支払い能力の有るクレジットカードなのか調べられるだけでなく、商品を渡すことが出来なかった時など、もし決済をすることが出来ない場合はオーソリをキャンセルすることで顧客のクレジットカードから無用な支払いを防ぐことができます。
サービス利用者さんとの金銭トラブルは避けたいですから、枕を高くして眠る為に開発の時点から施策を打っておきます。
実装するプログラムの動作イメージ
処理の流れのイメージはこんな感じです。
(順序分かり辛かったらすみません)
オンライン決済ではオーソリ状態から決済する処理を「キャプチャ」と言ったりします。StripeでもAPIレベルでcapture()が出てきます。
図を見るとなんか面倒そうだな、と思うじゃないですか?
ところがStripeを使って実際にプログラムを書いてみると、意外と簡単に実装出来てしまうんです。
実装開始
Stripe公式APIリファレンス(Java版)を見ながら書いていきます。
前回の記事からフロントエンド(HTML、JS)側は変更有りません。
バックエンドのJavaだけ変更します。愚直に書いて動作を確かめてみしょう。
実際の決済処理をオーソリに変更するには”capture”パラメータをfalseにするだけです。
Charge#create()で決済の替わりにオーソリをして、返って来たIDでCharge#capture(ID)することで決済が行われます。
@RequestMapping(path = "/charge", produces = "application/json", method = { RequestMethod.POST })
public ResponseEntity charge(@RequestBody ChargeRequest request) {
ResponseEntity response = null;
// ご自分のStripeプライベートキーを使用してください。
Stripe.apiKey = "sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// 課金ID
String chargeId = null;
try {
// 請求情報の準備
Map<String, Object> chargeMap = new HashMap<String, Object>();
chargeMap.put("amount", 500);
chargeMap.put("description", "Tシャツ");
chargeMap.put("currency", "jpy");
chargeMap.put("source", request.stripeToken);
// オーソリ(与信確保)指定フラグ(キャプチャ:決済はしない)
chargeMap.put("capture", false);
// オーソリ開始(カード会社に支払い金額の仮押さえが行われる)
Charge authori = Charge.create(chargeMap);
chargeId = authori.getId();
System.out.println(authori);
// 例外が発生せず、オーソリが通った状態。
// 商品の確保、ユーザへの返却準備処理を行います。
// オーソリした課金IDで決済(キャプチャ)
Charge charge = Charge.retrieve(chargeId);
charge.capture();
System.out.println(charge);
response = ResponseEntity.ok().build();
} catch (StripeException e) {
// 何か問題が発生した場合はオーソリ状態をキャンセル
Map<String, Object> refundMap = new HashMap<String, Object>();
refundMap.put("charge", chargeId);
Refund refund;
try {
// オーソリに対するRefundは = オーソリキャンセルになる。
refund = Refund.create(refundMap);
System.out.println(refund);
} catch (StripeException e1) {
e1.printStackTrace();
}
e.printStackTrace();
response = ResponseEntity.status(400).build();
}
return response;
}
動確用の実装完了です。
動作確認
前回記事のフロントエンドからPayボタンを押して動作を確認してみます。
以下Eclipseコンソールにダンプした、オーソリ時の戻り値Chargeインスタンス。
capturedがfalseになっています。まだ決済されていません。
クレジットカード会社に対して利用金額を押さえただけの状態です。
<com.stripe.model.Charge@456381243 id=ch_1GxxxxxxxxxxxxxxxxxxxxhpR> JSON: {
(snip)
"captured": false,
(snip)
次に決済(Charge#capture())時の戻り値Chargeインスタンス。capturedがtrueになっています。
このタイミングでカード会社と決済が正常終了したことが分かります。
<com.stripe.model.Charge@992525769 id=ch_1GxxxxxxxxxxxxxxxxxxxxhpR> JSON: {
(snip)
"captured": true,
(snip)
stripe.comにブラウザアクセスしてどのように決済されたかダッシュボードを確認してみます。
オーソリ(承認された支払い)の後に決済(キャプチャされた支払い)をしていることが分かります。(Stripeテストキーなので実際にお金の流れは発生していません)
もしオーソリや決済に失敗した場合は例外が発生してRefund(払い戻し)をし、その時点で処理を止めることが出来ます。
カード会社がオーソリ機能を用意してくれていること、Stripeがそれを使う機能を具備していることで、支払ってくれないカードの為に商品を用意する必要が無いことが分かりました。
まとめ
- カード会社には利用額を仮押さえする「オーソリ」という機能がある。
- オーソリを使えば利用者との無用なトラブルを回避できる。
- Stripeでオーソリするには決済メソッド、Charge#create()のMapパラメータにcapture=falseを設定するだけ。
余談:Stripeサポートのホスピタリティは高い
2020/06初頭、Chrome83がリリースされたタイミングでChromeからStripe経由でGooglePayが使えなくなる障害が発生しました。(6/11頃にChromeマイナーアップデートが出て復旧されています)
世界中のStripeを使ったシステムがこの影響を受け、私の開発、運営するサービスも影響を受けました。
その時Googleサポート、Stripeサポート両方に対応依頼を出しましたが、原因となったGoogle、とばっちりを受けたStripeのユーザ対応の丁寧さの差は歴然でした。
Stripeのサポートについて話題に出されているサイトも多いですが、サポート対応の品質がかなり高いんですよね。
懇切丁寧に、礼節を持って対応をしてくれました。
3.6%の手数料以外払ってないんですけどね(-_-;
日本法人があるのもすばらしいですね。私はStripeの回し者ではありませんが、
「B2Cで個人決済するシステムでは今後もStripeを採用したい」
そう思わせる事案でした。PCI-DSSに準拠するほどの予算が無いプロジェクトでは、Stripe採用は選択肢の一つとして一考する価値が十分にあると思っています。