くらげになりたい。

くらげのようにふわふわ生きたい日曜プログラマなブログ。趣味の備忘録です。

Stripeでクーポンを活用してみた

この記事はStripe Advent Calendar 2021の7日目の記事です。

開発しているStripeを使ったWebサービスで、
クーポンをいろいろ使えるようにしたいなと思い、いろいろ調べたときの備忘録。
使い方が特殊なのか、かなりハマった。。

やりたいこと

やりたかったのはこんな感じ。

  • Checkoutでの決済時にクーポンを利用したい
  • 継続中のときに、クーポンを利用したい
  • クーポン適用後に別のクーポンを利用したい

Stripeでの仕組み

出てくるStripeの用語

用語自体、なじみがないので、簡単なまとめ

  • サブスクリプション ... サブスク。継続支払い。
  • インボイス ... 請求書。顧客が払うサブスクの金額など
  • クーポン ... インボイスを減額できるクーポン
  • プロモーションコード ... クーポンから生成する顧客向けのコード

Stripeでのクーポン

公式ドキュメントは以下。
クーポン | Stripe のドキュメント

Stripeでは、減額(ディスカウント)するクーポンを作成できる。
特定期間、対象商品などの指定もできる。

顧客向けのプロモーションコードに関しては以下。
顧客向けのプロモーションコードを使用する | Stripe のドキュメント

クーポンの注意点

いろいろ調べたところ、以下の点に注意する必要がある。

  • クーポンは顧客もしくはサブスクリプションに適用できる
    • ただし、顧客に適用すると、設定回数に関わらず、毎請求ごとに適用される
    • そのため、サブスクリプションに適用することが推奨されている
  • 1つのインボイスに対し、適用できるクーポンは1つのみ
  • クーポンを適用できるインボイスは、支払い前のもののみ
    • 支払い後にクーポンを適用できない
  • Checkoutで入力できるのは、プロモーションコードのみ
  • APIで適用できるのはクーポンIDとプロモーションコードIDのみ
    • 継続中にAPIで適用する場合は、IDが必要
    • 顧客にはプロモーションコードしか提供できない

結構ややこしい。。(´・ω・`)

やってみたこと

やりたいことに対して、それぞれ試してみた。

Checkoutでの決済時にクーポンを利用したい

まだ購入していない顧客(ユーザ)が初めて購入するときに、
プロモーションコードを入力して割引を受ける場合。

これは特に問題なくできる。

  1. クーポンを作成する
  2. クーポンからプロモーションコードを作成する
  3. カスタマーポータルの設定でユーザがプロモーションコードを入力できるようにする
    • 「設定 > カスタマーポータル」で設定画面に移動
    • 「顧客によるプロモーションコードの適用を許可します。」をONにする

Checkoutで支払いをするときに、プロモーションコードを追加できるようになる。

f:id:wannabe-jellyfish:20211207001255p:plain

継続中のときにクーポンを利用したい

ここからはちょっとめんどくさい。。
以下の前提がネック。。

  • 1つのインボイスに1つのクーポンしか適用できない
  • 未決済のインボイスにしかクーポンを適用できない
  • 顧客にはプロモーションコードしか提供できない

また、Checkoutやカスタマーポータルでは対応できないので、
APIを使ってがんばる感じ。

流れはこんな感じ。

  1. 未決済のインボイスを取得
  2. 入力されたプロモーションコードからプロモーションコードIDを取得
  3. 取得したプロモーションコードIDをサブスクリプションに適用

それぞれのAPIをみていく。

1. 未決済のインボイスを取得

未決済のインボイスは以下のAPIで取得できる。

未決済インボイスのプレビューについてはガイドの以下にかかれている。

取得するのはこんな感じ。

import Stripe from "stripe";
const stripe = new Stripe('your_api_secret_key');

async function main() {
  const customerId = "cus_XXXXXXXXX";
  const res = await stripe.invoices.retrieveUpcoming({
    customer: customerId
  });
  console.info(`res=${JSON.stringify(res, null, 2)}`);
}

main().then();

必要なところだけに絞っているけど、以下のレスポンスが返ってくる。

{
  "object": "invoice",
  "billing_reason": "upcoming",
  "created": 1637300842,
  "customer": "cus_XXXXXXXXX",
  "discount": null,
  "discounts": [],
  "lines": { ... },
  "next_payment_attempt": 1637304442,
  "paid": false,
  "status": "draft",
  "subscription": "sub_1JusbeGjCKgy1AJ9ZucGSMHO",
  "subtotal": 100,
  "tax": null,
  "total": 100,
  "total_discount_amounts": [],
  "total_tax_amounts": [],
}
  • total: 請求金額
  • next_payment_attempt: 次回の支払い予定日時
  • discount: 適用されている割引情報

クーポンが適用されているとこんな感じ。

  • totalが割引後の金額になる
  • discount: 適用されている割引情報
  • total_discount_amounts: 割引情報と割引金額
{
  "object": "invoice",
  "billing_reason": "upcoming",
  "created": 1637300842,
  "customer": "cus_XXXXXXXXX",
  "discount": {
    "id": "di_1JxPXIGjCKgy1AJ9UXJqykPS",
    "object": "discount",
    "coupon": {
      "id": "X9gdG0V8",
    },
    "customer": "cus_XXXXXXXXX",
    "end": null,
    "invoice": null,
    "invoice_item": null,
    "promotion_code": null,
    "start": 1637299279,
    "subscription": "sub_1JusbeGjCKgy1AJ9ZucGSMHO"
  },
  "discounts": ["di_1JxPXIGjCKgy1AJ9UXJqykPS"],
  "lines": { ... },
  "next_payment_attempt": 1637304442,
  "paid": false,
  "status": "draft",
  "subscription": "sub_1JusbeGjCKgy1AJ9ZucGSMHO",
  "subtotal": 100,
  "tax": null,
  "total": 0,
  "total_discount_amounts": [
    { "amount": 100, "discount": "di_1JxPXIGjCKgy1AJ9UXJqykPS" }
  ],
}

2. 入力されたプロモーションコードからプロモーションコードIDを取得

クーポンから発行したプロモーションコードは、そのままAPI使えないので、
顧客に提供したプロモーションコードからプロモーションコードIDを取得する。

const promoCodes = await stripe.promotionCodes.list({
  code: code,   // プロモーションコードの指定
  active: true, // 有効なもののみ
});
console.info(`res=${JSON.stringify(promoCodes, null, 2)}`);
const promoCodeId = promoCodes.data.length > 0 ? promoCodes.data[0].id : null;

プロモーションコードに該当するものがあれば、以下のようなレスポンスが返ってくる。

{
  "object": "list",
  "data": [
    {
      "id": "promo_1JxSFqGjCKgy1AJ91YuTZNBh",
      "object": "promotion_code",
      "active": true,
      "code": "CMSC3HBE",
      "coupon": { ... },
      "created": 1637309730,
      "customer": null,
      "expires_at": null,
      "livemode": false,
      "max_redemptions": null,
      "metadata": {},
      "restrictions": { ... },
      "times_redeemed": 0
    }
  ],
  "has_more": false,
  "url": "/v1/promotion_codes"
}

該当するものがないと、以下のようなレスポンス。

{
  "object": "list",
  "data": [],
  "has_more": false,
  "url": "/v1/promotion_codes"
}

3. 取得したプロモーションコードIDをサブスクリプションに適用

プロモーションコードIDが取得できたので、サブスクリプションに適用する。

const subscriptionId = "...サブスクリプションのID"
const promoCodeId = "...プロモーションコードのID"
await stripe.subscriptions.update(subscriptionId, {
  promotion_code: promoCodeId
});

もちろん、プロモーションコードじゃなくてクーポンも適用できる。

const subscriptionId = "...サブスクリプションのID"
const couponId = "...クーポンのID"
await stripe.subscriptions.update(subscriptionId, {
  coupon: couponId
});

クーポン適用後に別のクーポンを利用したい

最後にクーポンの変更。

基本的には、上記のサブスクリプションの更新APIでOK。
更新なので、支払いが完了するまでは自由に変更できる。

ただ、クーポンを削除する場合は、別のAPIを利用する必要がある。

const subscriptionId = "...サブスクリプションのID"
await stripe.subscriptions.deleteDiscount(subscriptionId);

おまけ: Firebase Extensionsでの動き

Firebase ExtensionsにもStripeを便利に使うものが用意されている。
Run Payments with Stripe

これを使うと、
・Stripe Checkoutを使った支払いを簡単にするFunctionsを追加してくれたり
・Webhookを使って、サブスクの情報をFirestoreと同期してくれたり
する。

インボイス情報も同期してくれるので、それを見る方法もある。
Webhookのイベントは、invoice.upcoming
Stripe API reference – Types of events

ただ、このイベントが発火するのは、
「設定>サブスクリプションおよびメール
の「次回の更新イベント」で設定した日付になる。

ここで何日前に新しいインボイスを作成するかを指定でき、
・指定日になったら、インボイスが作成されて、
・イベントが発火したあとに、Firestoreに保存される
感じ。。

なので、好きなのタイミングで取得するには、 やっぱり、以下のAPIを呼ぶ必要がある。
Retrieve an upcoming invoice | Stripe API Reference

Firebase Extensionsに関しては、こちらの記事をどうぞ!

www.memory-lovers.blog

まとめ

これで、やりたかったことは全部できるように(´ω`)!!
クーポンに関しては情報が少ないので参考になれば

Stripeを使ったSSSAPIも、ぜひよろしくおねがいします!!

GoogleスプレッドシートAPI化サービス 『SSSAPI

https://sssapi.app