Stripe Checkoutで商品の発送を伴う決済ページ・フローを構築する方法とカスタマイズ

Stripe Checkoutを利用して、リダイレクト型の決済ページを簡単に構築することができます。

この記事では、Stripe Checkoutを利用して、通販サイトのような「商品の発送を伴う決済」を提供する方法を紹介します。

## 配送を伴う決済（ECサイトなど）で必要な設定

通販サイトのような「商品の発送を伴う決済」を提供する場合、注文フローの中で決済情報に加えて「配送に関する情報」も入力・選択してもらう必要があります。

### 商品の発送に必要な情報の例

- お客様の氏名
- お届け先の住所
- お届け先の電話番号（配送業者によっては省略も可能）
- 配送方法（速達便・通常便など）

## 配送に関する情報を、Stripe Checkoutで収集する方法

Stripe Checkoutを利用して決済フローを提供している場合、次のようなフローでこれらの配送情報を集めることができます。

### お客様の氏名と、配送先住所を入力できるようにする

まずは配送先住所を入力できるようにしましょう。

[`Create a Checkout Session API`](https://stripe.com/docs/api/checkout/sessions/create)の、`shipping_address_collection`パラメータを利用します。

```diff_javascript
    await stripe.checkout.sessions.create({
      mode: 'payment',
      line_items: [{
        price: 'price_xxxx',
        quantity: 1
      }],
+      shipping_address_collection: {
+        allowed_countries: ['JP']
+      },
      success_url: 'https://example.com',
      cancel_url: 'https://example.com'
    })
```

ここでは、「配送可能な国」を２文字のコードで指定します。

日本国外でも配送を行う場合には、[該当する国コード](https://stripe.com/docs/api/checkout/sessions/create#create_checkout_session-shipping_address_collection-allowed_countries)を追加してください。

この３行を追加することで、配送先住所を指定できるようになります。

![スクリーンショット 2023-01-31 15.58.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/75f13e72-7e98-1c4e-a3cc-862747a3a692.png)

### 電話番号を入力できるようにする

配送のための送り状に電話番号を設定する場合、Checkoutのパラメータを１つ追加します。

```diff_javascript
    await stripe.checkout.sessions.create({
      mode: 'payment',
      line_items: [{
        price: 'price_xxxx',
        quantity: 1
      }],
+      phone_number_collection: {
+        enabled: true,
+      },
      shipping_address_collection: {
        allowed_countries: ['JP']
      },
      success_url: 'https://example.com',
      cancel_url: 'https://example.com'
    })
```

`phone_number_collection.enabled`を`true`に設定することで、住所入力フォームに電話番号の入力欄が追加されます。

![スクリーンショット 2023-01-31 16.03.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/03aa5b5c-e1a2-f05f-00f7-470b96650451.png)

なお、配送業者や伝票の作成方法によっては、お届け先の電話番号の入力が必須ではありません。

https://faq.kuronekoyamato.co.jp/app/answers/detail/a_id/1525/~/%E3%81%8A%E5%B1%8A%E3%81%91%E5%85%88%E3%81%AE%E9%9B%BB%E8%A9%B1%E7%95%AA%E5%8F%B7%E3%81%8C%E5%88%86%E3%81%8B%E3%82%89%E3%81%AA%E3%81%84%E3%81%A8%E3%81%8D%E3%80%81%E3%81%A9%E3%81%AE%E3%82%88%E3%81%86%E3%81%AB%E9%80%81%E3%82%8A%E7%8A%B6%EF%BC%88%E4%BC%9D%E7%A5%A8%EF%BC%89%E3%82%92%E6%9B%B8%E3%81%91%E3%81%B0%E8%89%AF%E3%81%84%E3%81%A7%E3%81%99%E3%81%8B%EF%BC%9F

### 配送方法・配送料金を設定する

配送方法や「いつ頃に届くか」などの情報も追加しましょう。

まず、ダッシュボードの[配送料金](https://dashboard.stripe.com/test/shipping-rates)から、新規追加で登録しましょう。

![スクリーンショット 0003-12-18 3.15.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/fb7f3f32-f730-77ea-183d-fd4086111c59.png)

送料無料の場合は、0円で作成します。

作成された配送料金のIDを、[`Create a Checkout Session API`](https://stripe.com/docs/api/checkout/sessions/create)の引数に追加します。

```diff_javascript

    await stripe.checkout.sessions.create({
      mode: 'payment',
      line_items: [{
        price: 'price_xxxx',
        quantity: 1
      }],
+      shipping_options: [{
+        shipping_rate: 'shr_xxxxx'
+      }],
      phone_number_collection: {
        enabled: true,
      },
      shipping_address_collection: {
        allowed_countries: ['JP']
      },
      success_url: 'https://example.com',
      cancel_url: 'https://example.com'
    })
```

配送方法が１種類の場合は、商品の**小計**と**合計**の間に、**配送料金**と**予定配送日**が表示されます。

![スクリーンショット 2023-01-31 16.08.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/5bf28aaa-38cc-9357-4eea-6671066b8c20.png)

配送方法を複数登録した場合は、顧客が料金や予定配送日を見て選択できる画面が表示されます。

![スクリーンショット 2023-01-31 16.11.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/ed94437c-cf11-70bc-962e-d294e642b7cf.png)


**配送料金の設定やAPIについての詳細**

https://qiita.com/hideokamoto/items/1a56ede321e1650a6302

### 「配送に関する注意事項・追加情報」を表示する

「離島や私書箱宛への発送の場合、お届けに追加の日数がかかることがあります」など、配送に関する補足情報を表示したいケースもあります。

その場合は、`custom_text.shipping_address.message`パラメータを追加しましょう。

 
```diff_javascript

    await stripe.checkout.sessions.create({
      mode: 'payment',
      line_items: [{
        price: 'price_xxxx',
        quantity: 1
      }],
      shipping_options: [{
        shipping_rate: 'shr_123456789',
      }, {
        shipping_rate: 'shr_xxxxxxxxx',
      }],
+      custom_text: {
+        shipping_address: {
+          message: '離島や私書箱宛への発送の場合、お届けに追加の日数がかかることがあります',
+        },
+      },
      phone_number_collection: {
        enabled: true,
      },
      shipping_address_collection: {
        allowed_countries: ['JP']
      },
      success_url: 'https://example.com',
      cancel_url: 'https://example.com'
    })
```

この設定で、住所入力フォーム下部にテキストメッセージを追加表示できます。

![スクリーンショット 2023-01-31 16.16.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/5f8cd8f3-2a38-4342-a687-5dc46cf91ed9.png)

:::note warn
Checkout で Stripe によって生成されたテキスト、Stripe との契約、Stripe のポリシー、適用される法律に基づく義務に違反したり、あいまいさを生むカスタムテキストの作成に、この機能を使用することは禁止されています。
:::

## 追加の提案やカゴ落ちリスクを減らすための追加設定

この他にも売上を増やすためのクロスセルや、カートの中身を編集できるようにすることでカゴ落ちのリスクを減らすことなどがCheckoutでは可能です。

### カートの中身を変更できるようにする

消耗品など、複数個購入することが多い商材の場合、数量を変更するためにカートページに戻るステップが発生しやすくなります。

このステップを無くすために、Checkoutの用意する決済ページ上で数量の変更を可能にできます。

```diff_javascript
    await stripe.checkout.sessions.create({
      mode: 'payment',
      line_items: [{
        price: 'price_xxxx',
        quantity: 1,
+        adjustable_quantity: {
+          enabled: true,
+          minimum: 1,
+          maximum: 5,
+        }
      }],
...
```

これでCheckoutの決済ページから数量の変更ができるようになります。

![スクリーンショット 2023-01-31 16.23.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/c73ef3de-e622-00d3-051b-1b67c1401e8f.png)


:::note warn
**在庫に関する注意**
決済が完了するまで、顧客がどの数量で注文したかをシステムが知ることができません。
「`maximum`の数量が在庫より多くならないようにすること」に注意しましょう。

場合によっては、`maximum`の数量分を在庫DBで仮押さえする必要があります。
そのケースでは、決済完了後のWebhookイベントにて、注文されなかった数量の在庫を解放しましょう。
:::

https://stripe.com/docs/payments/checkout/adjustable-quantity

### ダッシュボードからクロスセルの設定を行う

Stripeダッシュボードの商品設定画面から、商品の追加購入の提案（クロスセル）が行えます。

![スクリーンショット 2023-01-31 16.28.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/50b1985e-753d-ad4b-2b5d-e89f7c1598b6.png)

ここで設定した商品は、Checkoutの決済ページにも表示されます。

![スクリーンショット 2023-01-31 16.29.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/243347ae-7760-3654-3a06-8ab22c42c21b.png)

詳細な設定方法や、Payment Linksでの始め方については、以下の記事をご覧ください。

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

### 割引・クーポンコードの設定を行う

セールや顧客優待など、様々なケースで商品の割引オファーを行うことがあります。

Checkoutの場合、以下のパラメータを追加することでクーポンコードの入力を受け付けます。


```diff_javascript
    await stripe.checkout.sessions.create({
      mode: 'payment',
      line_items: [{
        price: 'price_xxxx',
        quantity: 1,
      }],
+      allow_promotion_codes: true,
...
```

![スクリーンショット 2023-01-31 16.31.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/c5de941a-2c97-eeaa-f827-7eaae9638cbf.png)

#### 限定オファーメールなどで、「クーポンコードの事前設定」を行う

限定オファーのメールやSMS / LINEメッセージなど向けに、クーポンコードを既に設定した状態で決済フローを始めることもできます。

```diff_javascript
    await stripe.checkout.sessions.create({
      mode: 'payment',
      line_items: [{
        price: 'price_xxxx',
        quantity: 1,
      }],
+      discounts: [{
+        promotion_code: 'promo_xxxx' // プロモーションコードの「ID」を入れましょう
+      }],
...
```

こちらの設定の場合、クーポンによる割引が設定された状態で決済フローが始まります。

![スクリーンショット 2023-01-31 16.34.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/f13b3ff1-a476-2b8d-4458-da28abaf1bc8.png)

:::note warn
**パラメータの併用はNG**
`allow_promotion_codes`と`discounts`は併用できませんのでご注意ください。

併用した場合、以下のエラーがAPIから返ってきます。

```js
{
    message: 'You may only specify one of these parameters: allow_promotion_codes, discounts.',
    param: 'allow_promotion_codes',
}
```
:::

https://stripe.com/docs/payments/checkout/discounts

## 有料のオプションを活用する

この他にも有料の機能を利用して、決済フローや注文後のやり取りを便利にすることもできます。

### Stripe Taxを使って、消費税の徴収を自動化する

軽減税率の対象商品が商材に混ざっている場合、商品ごとに税率の設定・計算が必要です。

Stripe Taxを利用すると、事前に商品ごとに税率を設定しておくことで、複数の税率の商品注文があった場合にも、徴収額の計算や決済ページの表示をStripe側に任せることができます。

```diff_javascript
    await stripe.checkout.sessions.create({
      mode: 'payment',
      line_items: [{
        price: 'price_xxxx',
        quantity: 1,
      }],
+      customer_update: {
+        shipping: 'auto'
+      },
+      automatic_tax: {
+        enabled: true,
+      },
```

![スクリーンショット 2023-01-31 16.41.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/b2ffcac8-d020-4671-1ee4-b466320f5c62.png)


https://stripe.com/jp/tax

### カスタムドメインを設定する

`checkout.example.com`のように、所有しているドメインを利用して決済用のサブドメインを設定できます。

![スクリーンショット 2022-04-02 16.44.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/5dec79f4-a5c7-0c96-96d1-3e365ecb617c.png)

https://stripe.com/docs/payments/checkout/custom-domains

AWSのRoute 53で設定する方法を紹介していますので、こちらもお試しください。

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

### カスタマーポータルで領収書のPDFをDLできるようにする

注文履歴や領収書の発行をStripe側に任せることも、有料オプションを利用することで可能です。

```diff_javascript
    const session = await stripe.checkout.sessions.create({
      mode: 'payment',
      line_items: [{
        price: 'price_xxxx',
        quantity: 1
      }],
+      invoice_creation: {
+        enabled: true,
+      },
      success_url: 'https://example.com',
      cancel_url: 'https://example.com'
    })
```

このパラメータを追加することで、カスタマーポータルの請求履歴から履歴や領収書PDFのダウンロードが可能になります。


![スクリーンショット 2023-01-23 10.38.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/e3387ac4-1482-a1fd-2c4f-f66dbbe1e1ed.png)

https://stripe.com/docs/payments/checkout/post-payment-invoices

https://qiita.com/hideokamoto/items/2dddc4898772f2cc19dc

## 注文後、配送情報や商品の個数をシステムで受け取る方法

Stripeの場合、さまざまな決済手段（クレジットカードや銀行振込、Apple Payなど）をサポートするため、決済完了後のフローはWebhookベースで行います。

### Webhook APIの作り方

Stripeのドキュメントに、Webhook　APIの作り方やCheckoutでハンドルするイベントの例を掲載していますので、こちらをぜひご確認ください。

https://stripe.com/docs/payments/checkout/fulfill-orders

### `checkout.session.completed`イベントから、注文内容や配送情報を取得する方法

`checkout.session.completed`イベントからは、セッションのIDや注文者情報などが取得できます。

次の画像にある注文を例にしましょう。

![スクリーンショット 2023-01-31 16.58.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/437371fa-186b-9134-5a4b-57b2a6a924d4.png)

この場合、次のようなJSONが取得できます。

```json
{
  "api_version": "2020-08-27",
  "created": 1675151950,
  "data": {
    "object": {
      "amount_subtotal": 5000,
      "amount_total": 3300,
      "automatic_tax": {
        "enabled": true,
        "status": "complete"
      },
      "billing_address_collection": "required",
      "cancel_url": "https://example.com",
      "created": 1675151891,
      "currency": "jpy",
      "customer": "cus_NCGybxwanHUtCc",
      "customer_creation": null,
      "customer_details": {
        "address": {
          "city": null,
          "country": "JP",
          "line1": null,
          "line2": null,
          "postal_code": null,
          "state": null
        },
        "email": "test@example.com",
        "name": "test user",
        "phone": "+8109000000000",
        "tax_exempt": "none",
        "tax_ids": []
      },
      "customer_email": null,
      "expires_at": 1675238291,
      "id": "cs_test_a1gve9F9W91peCYDDN9xObfmljhTyS82SgEurD971gyLLjg7JyzaI2PkgS",
      "metadata": {},
      "mode": "payment",
      "object": "checkout.session",
      "payment_intent": "pi_3MWEkAL6R1kGwUF41Ol6SPaQ",
      "payment_status": "paid",
      "shipping": {
        "address": {
          "city": "",
          "country": "JP",
          "line1": "千代田区千代田",
          "line2": "1-1",
          "postal_code": "100-0001",
          "state": "東京都"
        },
        "name": "John due"
      },
      "shipping_rate": "shr_1K7kp5L6R1kGwUF40CJjxc7t",
      "status": "complete",
      "success_url": "https://example.com",
      "total_details": {
        "amount_discount": 2500,
        "amount_shipping": 800,
        "amount_tax": 275
      },
      "url": null
    }
  },
  "id": "evt_1MWEkEL6R1kGwUF4zIFc3h12",
  "type": "checkout.session.completed"
}
```

※項目数が多いため、関係性の低い値は割愛しています。

- `data.object.shipping`: 配送先住所
- `data.object.amount_total`: 決済金額
- `data.object.phone`: 電話番号
- `data.object.total_details`: 送料・割引額・消費税などの内訳
- `data.object.payment_status`: 決済が完了しているか否か（銀行振込やコンビニ決済などでは要確認）

など、`data.object`内から必要な情報を取得しましょう。

### CheckoutのセッションIDからカートの中身を取得する

実際に注文された商品や数量については、CheckoutのセッションID（`data.object.id`）を利用してAPIから取得します。

```javascript
if (data.type === "checkout.session.completed") {
  const lineItems = await stripe.checkout.sessions.listLineItems(data.object.id)
}
```

このAPIからは次のようなデータが取得できます。

```json
{
  "object": "list",
  "data": [
    {
      "id": "li_1MWEjHL6R1kGwUF4V5Xvsdaj",
      "object": "item",
      "amount_discount": 1500,
      "amount_subtotal": 3000,
      "amount_tax": 111,
      "amount_total": 1500,
      "currency": "jpy",
      "description": "コーヒー豆",
      "price": {...},
      "quantity": 2
    },
    {
      "id": "li_1MWEjSL6R1kGwUF4DACIr7Ge",
      "object": "item",
      "amount_discount": 1000,
      "amount_subtotal": 2000,
      "amount_tax": 91,
      "amount_total": 1000,
      "currency": "jpy",
      "description": "コーヒーフィルター（布）",
      "price": {...},
      "quantity": 1
    }
  ],
}
```

※項目数が多いため、関係性の低い値は割愛しています。

`data`配列内の`id`や`quantity`を利用して、在庫DBや配送系システムとの連携を行いましょう。