Stripeのオンライン決済を実装する時はChargesよりPayment Intentsを使おう #Stripe
まずStripeのオンライン決済を実装するには3通りの方法があります。
- Stripe Checkout: Stripeが提供する支払いページにリダイレクトして決済する
- Charges API: Charges APIを呼んで自前の支払いフォームで決済する
- Payment Intents API: Payment Intents APIを呼んで自前の支払いフォームで決済する
Charges
と Payment Intents
、どちらを使っても自前の支払いフォームでの支払いフローを構築できます。
しかし、Chargesの方は今後は拡張せずPayment Intentsの製品開発に集中することがこちらのページで説明されています。
なのでできればPayment Intentsの方を使いましょう。
Charges自体の廃止が決まっているわけではないので要件に応じてChargesを使うことも検討してください。
例えば、Alipay・WeChat PayはPayment Intentsではサポートされていませんが、Chargesではサポートされています。
ChargesからPayment Intentsへの移行
Payment Intentsへ移行するには、クライアント側とサーバー側両方変更する必要があります。
ざっくり移行手順
- APIバージョンとライブラリのバージョンを更新する
- アプリ内で使っているChargesのプロパティを置き換える
- APIを変更する前に読み取るプロパティを置き換えることで読み取り後の処理が保証されます。
- (クライアント側)ソースをPayment Intentsへ移行
- (サーバー側)カード登録フローを統合
- (サーバー側)決済フローを統合
- 移行後のフローでテスト
アプリ内で使っているChargesのプロパティを置き換える
段階的に移行するためにChargeオブジェクトにpayment_method_detailsとbilling_detailsが追加されています。
charge.source
→charge.payment_method_details
, charge.billing_details
に置き換えましょう。
よく使われているプロパティはこちらです。
プロパティ | Charges | AFTER |
---|---|---|
請求に使用した支払い方法の詳細 | charge.source | charge.payment_method_details |
請求に使用された支払い方法のID | charge.source.id | charge.payment_method |
使用された支払い方法のタイプ | charge.source.object | charge.payment_method_details.type |
請求の請求情報(例:請求郵便番号) | charge.source.address_zip | charge.billing_details.address.postal_code |
カード所有者の名前 | charge.source.name | charge.billing_details.name |
使用したカードの下4桁 | charge.source.last4 | charge.payment_method_details.card.last4 |
カードの指紋 | charge.source.fingerprint | charge.payment_method_details.card.fingerprint |
請求のCVC検証ステータス | charge.source.cvc_check | charge.payment_method_details.card.checks.cvc_check |
カードブランド | charge.source.brand | charge.payment_method_details.card.brand |
Google PayとAndroid Pay | charge.source.tokenization_method is android_pay | card.wallet.type within charge.payment_method_details is google_pay |
カードを登録して支払うフロー
カード情報の送信(クライアント側)
旧APIのcreateToken とcreateSourceをconfirmCardSetupに置き換えます。
stripe.confirmCardSetup( clientSecret, { payment_method: { card: cardElement, billing_details: {name: cardholderName.value} } } ).then(function(result) { if (result.error) { // Display error.message in your UI. } else { // The SetupIntent was successful! } });
成功するとカード情報をID化したPaymentMethodが返却されるので、それをサーバーに送信します。
カードを登録(サーバー側)
クライアント側で作成したPaymentMethodをカスタマーに追加します。
既存カスタマーの場合
const customer = await stripe.paymentMethods.attach( 'pm_1FWS6ZClCIKljWvsVCvkdyWg', {customer: 'cus_HgIgWLxUAsvn0A'}, );
新規カスタマーの場合
カスタマー登録はCharges APIの時と変わりありません。
const customer = await stripe.customers.create({ email: '[email protected]', payment_method: 'pm_1FWS6ZClCIKljWvsVCvkdyWg', invoice_settings: { default_payment_method: 'pm_1FWS6ZClCIKljWvsVCvkdyWg', // デフォルトの支払いに設定 }, });
確認
const paymentMethods = await stripe.paymentMethods.list( { customer: 'cus_HgIgWLxUAsvn0A', type: 'card' } );
決済処理
customerを指定してpaymentIntentを作成します。confirmはデフォルトで分離されているので省略したい場合はfalseを指定してください。
const paymentIntent = await stripe.paymentIntents.create({ amount: 550, currency: 'JPY', customer: 'cus_HgIgWLxUAsvn0A', confirm: true })
上で作成されたpaymentIntentを確定
const paymentIntent = await stripe.paymentIntents.capture('pi_1EUnBFF5IfL0eXz9XzeK6PqQ');
1回限りの決済フロー
アプリケーションに会員登録しないまま決済をする場合、もちろんStripe側にもカスタマー登録はされていないのでフローが変わってきます。
決済情報を作成(サーバー側)
サーバー側で決済情報を作成します。
const paymentIntent = await stripe.paymentIntents.create({ amount: 1099, currency: 'jpy', });
カード情報(クライアント側)
confirmCardPaymentを使ってstripeに直接カード情報を送ります。
stripe.confirmCardPayment( 'pi_1EUnBFF5IfL0eXz9XzeK6PqQ', { payment_method: {card: cardElement} } ).then(function(result) { if (result.error) { // Display error.message in your UI. } else { // The payment has succeeded // Display a success message } });
webhooksで決済の結果を取得(サーバー側)
confirmCardPaymentで直接Stripeでの決済を作成するので加盟店サーバーの方でwebhookを受け取って決済結果を取得します。
app.use(require('body-parser').text({type: '*/*'})); const endpointSecret = 'whsec_...'; app.post('/webhook', function(request, response) { const sig = request.headers['stripe-signature']; const body = request.body; let event = null; try { event = stripe.webhooks.constructEvent(request.body, sig, endpointSecret); } catch (err) { // invalid signature response.status(400).end(); return; } let intent = null; switch (event['type']) { case 'payment_intent.succeeded': intent = event.data.object; console.log("Succeeded:", intent.id); break; case 'payment_intent.payment_failed': intent = event.data.object; const message = intent.last_payment_error && intent.last_payment_error.message; console.log('Failed:', intent.id, message); break; } response.sendStatus(200); });