Stripe Checkoutで「カートの中身」または「実際に注文された商品」の種類と数を取得する方法

Stripe Checkoutを利用することで、手軽に安全なオンライン注文ページを顧客に提供することができます。

また、Checkoutの設定を変更することで、「カートの中の商品の数量を変更」したり、「アップセルの提案」を行うことも可能です。

https://qiita.com/hideokamoto/items/669d8b02195ac148dd3e

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

これにより、顧客は注文の直前にカートの中身を再調整することができ、前のページに戻るなどの余計な手間を省くことができます。

# カートの中身を取得する方法

「ブラウザがエラーでダウンした・移動中で一時的にオフラインになった」などの理由で、Checkoutのセッションが一時的に途切れるケースが存在します。

この場合、顧客はCheckoutページで変更した後の数量で、注文を再開したいと考えます。

その場合、システム側では、Stripe APIを利用し、カートの中身を取得する必要があります。

```js
const items = await stripe.checkout.sessions.listLineItems(sessionId); // cs_xxxx
```

このAPIで取得できるデータの例はこちらです。
料金データや数量などが含まれていることが確認できます。

```js
[{
        id: 'li_xxxx',
        object: 'item',
        amount_subtotal: 1000,
        amount_total: 1000,
        currency: 'jpy',
        description: 'Starter plan',
        price: {
          id: 'price_xxxx',
          object: 'price',
          active: true,
          billing_scheme: 'per_unit',
          created: 1639111716,
          currency: 'jpy',
          livemode: false,
          lookup_key: null,
          metadata: {},
          nickname: null,
          product: 'prod_xxxx',
          recurring: null,
          tax_behavior: 'inclusive',
          tiers_mode: null,
          transform_quantity: null,
          type: 'one_time',
          unit_amount: 1000,
          unit_amount_decimal: '1000'
        },
        quantity: 1
}]
```

# 顧客に同じCheckoutセッションのURLを再提供したい場合

何かしらの理由で、顧客がCheckoutセッションから離脱してしまった場合、同じURLを再度ユーザーに提供することもできます。

```js
  const session = await stripe.checkout.sessions.retrieve(sessionId) // cs_xxxx
  if (session.url || session.status !== 'expired') {
    return {
      url: session.url,
      id: session.id,
    };
  }
  // 有効期限が切れているので、セッション再作成
```

セッションの有効期限が切れていなければ、`retrieve`APIのレスポンスにURLが含まれます。
これを利用して、中断したカートセッションを再開しましょう。

# Webhookなどで、実際に注文された商品データを取得する

`checkout.session.completed`や`checkout.session.async_payment_succeeded`イベントなどを利用し、注文が完了した後の業務を自動化することもできます。

```js

app.post('/webhook', express.raw({type: 'application/json'}), async (request, response) => {
  let event = request.body;
  /**
   * Webhookの署名検証処理がここに入ります
   * @see https://stripe.com/docs/webhooks/signatures#verify-official-libraries
   **/

  const data = event.data.object;
  if (
    event.type !== 'checkout.session.completed' &&
    event.type !== 'checkout.session.async_payment_succeeded'
  ) {
    return response.sendStatus(200);
  }

  /**
   * 支払いが完全に完了している場合のみ処理する
   **/
　　　　if (data.payment_status === 'paid') {
    const { data: items } = await stripe.checkout.sessions.listLineItems(data.id);
    /**
     * カートの中身の情報を利用して、発送業務などのシステムを呼び出す
     **/  
  }
  return response.sendStatus(200);
});
```

合計金額や配送料データなどはイベントで受信するデータに含まれています。
が、カートの中身については別途APIで取得する必要があることにご注意ください。

# 応用アイディア: 期限切れの場合のみ、セッションを新しく作成する

これまでの内容を応用すると、以下のような処理が作れます。
- セッションが有効ならば、そのままURLを返して再度Checkoutページに移動させる
- セッションが期限切れの場合、中断されたカートの中身や設定をできるだけ再現させたセッションを再作成する


```typescript
// TypeScript

const recreateCheckoutSession = async (sessionId: string): Promise<{
  url: string;
  id: string;
}> => {
  const session = await stripe.checkout.sessions.retrieve(sessionId);
  if (session.url && session.status !== 'expired') {
    return {
      url: session.url,
      id: session.id,
    };
  }
  const items = await stripe.checkout.sessions.listLineItems(sessionId);
  const params: Stripe.Checkout.SessionCreateParams = {
    line_items: items.data.map(data => {
      if (!data.price) return null;
      return {
        price: data.price.id,
        quantity: data.quantity || 1,
      };
    }).filter(Boolean) as any,
    cancel_url: session.cancel_url,
    success_url: session.success_url,
    mode: session.mode,
    expires_at: session.expires_at,
  };
  if (session.customer) {
    params.customer = session.customer as string;
  } else if (session.customer_creation) {
    params.customer_creation = session.customer_creation;
  }
  const newSession = await stripe.checkout.sessions.create(params);
  return {
    newSession,
    items,
  };
};
```

**このコードはすべての引数・料金体系をサポートしていません**。
そのため、このままではセッションの再開がうまく動かないケースもあることにご注意ください。


# [PR] Stripe開発者向け情報をQiitaにて配信中！

- [Stripe Updates]：開発者向けStripeアップデート紹介・解説
- ユースケース別のStripe製品や実装サンプルの紹介
- Stripeと外部サービス・OSSとの連携方法やTipsの紹介
- 初心者向けのチュートリアル(予定)

など、Stripeを利用してオンラインビジネスを始める方法について週に２〜３本ペースで更新中です。

-> [Stripe Organizationsをフォローして最新情報をQiitaで受け取る](https://qiita.com/organizations/stripe)