Stripe Connectで構築したマーケットプレイスで、コンビニ決済に対応する３つの方法（デスティネーション支払い編）

Stripeを利用することで、顧客は料金の支払いをコンビニで行えます。また、Stripeユーザーは、**コンビニでの決済処理結果を、Stripe DashboardやWebhookを通して受け取る**ことができます。

コンビニ決済を有効化することで、これまでリーチすることが困難だった「**クレジットカードを持たない顧客**」にリーチすることが可能です。また、顧客も公共交通機関での移動中や、信頼性の低いネットワークを利用している状況など、**「クレジットカードを取り出して入力する」操作がやりにくい場面**でも、安心して注文ができるようになります。

# コンビニ決済は、Connectでも利用可能

この決済はConnectでプラットフォームを構築されているお客様もご利用が可能です。
C2Cのマーケットプレイスでは、クレジットカード情報の取り扱いに不安を感じる顧客も少なからず存在します。プラットフォーム・マーケットプレイスがコンビニ決済をサポートすることで、顧客はより安心して商品・サービスを注文できるようになります。

![nm7W8u4S.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/8d46c218-ce94-855e-0b7e-f0779018da76.png)

# コンビニ決済をConnectで構築したプラットフォームに追加する３つの方法

コンビニ決済に対応する方法は、プラットフォームに組み込まれた決済処理方法によって異なります。支払い方法に応じても異なりますので、今回はダイレクト支払いをご利用中のケースについて紹介します。

この記事では、Node.jsとReactを例に実装方法を紹介します。phpやRubyなど、その他の言語やフレームワークでの実装については、APIドキュメント(https://stripe.com/docs/api) をご確認ください。記事内で紹介しているメソッド・関数を、それぞれの言語で実行する方法を見ることができます。

https://stripe.com/docs/api

## 1: Checkoutを利用して、決済処理を行なっている場合
Stripe Checkoutを利用して決済画面を提供している場合、組み込みはとても簡単です。
以下の例のように、`payment_method_types`に`konbini`を追加するか、`payment_method_types`を削除して、ダッシュボードから決済方法( https://stripe.com/docs/payments/dashboard-payment-methods )を設定します。

```js
         await stripe.checkout.sessions.create({
            line_items: [{
              price: 'price_xxxxxx',
              quantity: 1
            }],
            success_url: 'http://localhost:3000/success',
            cancel_url:'http://localhost:3000/cancel',
            mode: 'payment',
            payment_method_types: ['card', 'konbini'],
            payment_intent_data: {
              application_fee_amount: 100,
              transfer_data: {
                destination: "acct_xxxx",
              },
            },
            customer_creation: 'if_required'
          })
```

変更が完了すると、Checkoutの支払い方法に「コンビニ決済」が表示されます。

![YTzCxnsQ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/77ed0fb3-fdec-c9dc-9ece-ba56c387272a.png)


## 2: ElementsのPaymentElementを利用している場合
PaymentElementを利用して決済フォームを実装している場合についても、ほとんど設定の変更が必要ありません。
PaymentIntentを作成する際に、`payment_method_types`を指定している場合のみ、`konbini`を追加しましょう。

```js
        const paymentIntent = await stripe.paymentIntents.create({
            amount: 2000,
            currency: 'jpy',
            payment_method_types: ['konbini', 'card'],
            application_fee_amount: 200,
            transfer_data: {
              destination: "acct_xxxx",
            },
        })
```

以下の例のように、`payment_method_options['konbini']`に追加情報を登録することもできます。

```js
        const paymentIntent = await stripe.paymentIntents.create({
            amount: 2000,
            currency: 'jpy',
            payment_method_types: ['konbini', 'card'],
            application_fee_amount: 200,
            transfer_data: {
              destination: "acct_xxxx",
            },
            payment_method_options: {
                konbini: {
                    product_description: "Tシャツ",
                    expires_after_days: 3,
                    tax_amount: 200
                }
            }
        })
```

あとはPaymentIntentのClient SecretをStripe.jsに渡すだけで、PaymentElementがコンビニ決済を選択できるように表示を変更します。

![BHOfUmLN.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/2b0c7f3c-1edb-17e0-43e5-1f5448a7c810.png)

カードなどの処理と同様、`confirmPayment`を実行すると、PaymentIntentがconfirmされます。
![C-NRGZpc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/86477536-3f2d-5210-fb4a-2450015fec63.png)


## 3: ElementsでPayment Elementを利用して「いない」場合
Elementsを利用する場合、基本的にはPaymentElementを利用することをお勧めします。
しかしサイトデザインの要件を満たすために、PaymentElement以外のElementを利用する必要があるケースでは、上記のPaymentElementを利用した実装が行えません。

Payment Elementを利用せずに実装する場合、フロントエンドにも追加の実装が必要です。
最も簡単な方法は、**「コンビニ決済を処理するためのformを、新しく用意すること」**です。

まず、「氏名・メールアドレス・電話番号」を入力するフォームを用意します。

```jsx
    const KonbiniForm: FC = () => {
        const [name, setName] = useState("")
        const [email, setEmail] = useState('')
        const [phone, setPhone] = useState('')
        return (
            <form onSubmit={async (e)=>{
                e.preventDefault()
             }>
                <div>
                    <label>電話番号</label>
                    <input type='tel' value={phone} onChange={e=>setPhone(e.target.value)} /> 
                </div>
                <div>
                    <label>名前</label>
                    <input type='text' value={name} onChange={e=>setName(e.target.value)} /> 
                </div>
                <div>
                    <label>メールアドレス</label>
                    <input type='email' value={email} onChange={e=>setEmail(e.target.value)} /> 
                </div>
                <button type='submit' disabled={status === 'inprogress'}>注文する</button>
            </form>
        )
    }
```

その後、PaymentIntentを作成するAPI呼び出しと、コンビニ決済を処理するメソッドを呼び出す実装をsubmitイベントに設定します。


```jsx
    const KonbiniForm: FC = () => {
        const stripe = useStripe()
        const [name, setName] = useState("")
        const [email, setEmail] = useState('')
        const [phone, setPhone] = useState('')
        return (
            <form onSubmit={async (e)=>{
                e.preventDefault()
                try {
                    if (!stripe) return;            
                    const intent = await fetch('/api/payment_intent', {
                        method: 'post'
                    }).then(data=>data.json())
                    const result = await stripe.confirmKonbiniPayment(intent.client_secret, 
                        {
                          payment_method: {
                            billing_details: {
                              name,
                              email,
                            },
                          },
                          payment_method_options: {
                            konbini: {
                              confirmation_number: phone,
                            },
                          },
                        }
                    )
                    console.log(result)
                } catch(e) {
                    console.log(e)
                }
            }}>
    ...
```

`confirmKonbiniPayment`が成功すると、こちらもコンビニでの支払い情報がモーダル表示されます。
もし独自のUIで情報を表示させたい場合は、以下のように引数を追加します。

```js
                    const result = await stripe.confirmKonbiniPayment(intent.client_secret, 
                        {
                          payment_method: {
                            billing_details: {
                              name,
                              email,
                            },
                          },
                          payment_method_options: {
                            konbini: {
                              confirmation_number: phone,
                            },
                          },
                        },
                        {
                            handleActions: false
                        }
                    )
```
`handleActions: false`を設定すると、モーダルが表示されなくなります。
コンビニでの支払いのために必要な情報は、`result.paymentIntent.next_action`から取得することができます。

![7RRPyAKx.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/9f96c29c-3ef7-ab3d-9a7b-9de1840bcba4.png)


この中には、以下の情報が含まれています。

- 支払い期日（`konbini_display_details.expires_at`）
- Stripeが用意する支払い方法ページのURL（`konbini_display_details.hosted_voucher_url`）
- コンビニチェーンごとの入力情報(確認番号と支払い番号)
    （`konbini_display_details[familymart|lawson|ministop|seicomart]`）

コンビニのロゴ・アイコン画像などは含まれません。実装時に自分で用意する必要がありますので、ご注意ください。


# コンビニ決済をサポートする際の注意点

## 注文完了時点では、決済が完了していません
コンビニ決済での注文の場合、実際に顧客が代金を支払うまでに時間差があります。

そのため、カード決済のみ対応していたプラットフォームなどでは、「決済が完了したことを確認できるまで、発送業務等の後続処理を行わない」実装になっているかを確認しましょう。
もし決済完了を確認せずに後続の業務を行なった場合、実際には決済が完了できていない（コンビニでの支払いが実行されなかった）場合にも商品を発送してしまうなどの問題が発生します。

### Checkoutを利用している場合

Checkoutを利用した決済では、まず`checkout.session.completed`イベントを受け付けます。Webhookイベント内の`"payment_status"`をチェックし、カード決済などで支払い済みの場合(`payment_status=paid`)ならば後続業務を実行します。

コンビニ決済など、支払いが未完了の場合は、`payment_status=unpaid`ですので、在庫の確保を続けるだけにするなど、決済の完了を待つシステムにする必要があります。

顧客の決済が完了した場合には、追加で`checkout.session.async_payment_succeeded`イベントが発生します。こちらのイベントを受け付けた場合にはじめて発送等の業務を開始させましょう。

### Elements(PaymentIntent)を利用している場合

Elements(PaymentIntent)を利用している場合、カード決済でもコンビニ決済でも`payment_intent.succeeded`イベントの発火で後続業務を開始できます。

## Webhookを利用した期限切れの処理が必要になるケースがあります
コンビニ決済では、設定した期限内に支払いが行われないケースも発生します。その場合、確保している在庫を開放するなどの操作が必要になります。
この操作を実装するには、Webhookで以下のイベントを受け付ける必要があります。

- Checkoutを利用したケース：`checkout.session.async_payment_failed`
- Elements(PaymentIntent)を利用したケース: `payment_intent.payment_failed`

また、支払いが完了していない顧客に、メールを送信する仕組みも用意されています。
[設定 > メール]から設定することができますので、こちらも合わせてご利用ください。

![Uvs2KO3-.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/c3ca92d9-8242-9234-bb4b-6dc1e648cd3f.png)

## 手数料の最低金額が設定されています
コンビニ決済を利用する場合、決済手数料は最低でも120円が発生します。
この手数料は、デスティネーション支払いの場合、プラットフォーム側で負担します。
そのため、手数料（application_fee）が120円を下回る場合には、決済手数料の方が高くなりますのでご注意ください。

# おわりに

コードを見ていただくと、Connectを利用したプラットフォームでも、コンビニ決済に対応するための追加作業は、通常の決済サービスでの対応とほぼ同じです。
コンビニ決済をサポートすることで、クレジットカードを持たない顧客でもサービスを利用できるようになりますので、ぜひご自身のプラットフォームにも導入してみてください。


# [関連記事]コンビニ決済入門シリーズ

https://qiita.com/hideokamoto/items/c59c4c4ee24b98ac997b

https://qiita.com/hideokamoto/items/f0304cb005f276e35d43

https://qiita.com/hideokamoto/items/605096153bdf473895f0



# [関連記事]コンビニ決済入門シリーズ

## Checkout / Elementsへの導入方法

https://qiita.com/hideokamoto/items/c59c4c4ee24b98ac997b

## Billing（サブスクリプション）への導入方法

https://qiita.com/hideokamoto/items/605096153bdf473895f0

## Webhookでの処理方法

https://qiita.com/hideokamoto/items/f0304cb005f276e35d43