11
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

決済代行サービスのStripeでサブスクプランにクーポンを適用する方法【typescript, react, stripe, cloud functions】

Last updated at Posted at 2020-08-20

概要

本記事では決済代行サービスのStripeのAPIを使ってクーポンを適用するための実装方法を紹介したいと思います。決済に必要な機能をほぼ全て提供しているStripeに複雑な決済処理を丸投げすることでECサイトをスピーディーに開発することができるようになります。クーポンの実装に焦点を当てて解説するので、チェックアウトなどの具体的な実装方法は割愛します。以下、どう実装したか解説します。

先ず初めにFree TrialとCouponの違いに触れてからCoupon実装の解説に進みたいと思います。

Free TrialとCouponの違い

Stripeはサブスクに割引を適用させるためにFree TrialCouponの二パターンの割引実装手段を提供しています。

Free Trial は、Apple MusicやSpotify、Netflixなどのサブスクリプションサービスでよくある「○ヶ月無料、その後課金」を実装する際にぴったりな機能です。トライアル有りでサブスクを契約するとユーザーは0円請求され、end_trialの日にちまで無料でサービスを受けることができるようになります。サブスクのトライアル期間終了が近づくと、Stripeが自動的にinvoiceを発行し、請求処理を行います。Free Trialで設定された期間の間は全額割引になるので、例えば請求額から1000円割引だけといった使い方はできません。

Coupon は割引額を自由に設定することができる機能です。例えば、500円のクーポンや1800円のクーポンなど様々なプライスでクーポンを発行することができます。ユーザーが決済時にクーポンコードを入力するとクーポン分の割引が請求額に反映されます。金額をカスタマイズできるところが全額無料にするFree Trialと大きく異なる点です。

サービスの性質に合わせて割引の実装手段を選択すると良いのではないでしょうか。

実装方法

クーポン発行

Stripe APIまたはStripe Dashboardで新規クーポンを発行することが可能です。クーポンを生成すると、以下の形でオブジェクトが返ります。

{
  "id": "f3rf1e",
  "object": "coupon",
  "amount_off": 100,
  "created": 1597722224,
  "currency": "jpy",
  "duration": "repeating",  // 必須
  "duration_in_months": 2,
  "livemode": false, // test環境
  "max_redemptions": null,
  "metadata": {},
  "name": "category_subcategory_couponID",
  "percent_off": null,
  "redeem_by": null,
  "times_redeemed": 0,
  "valid": true
}

Couponを実装する際の注意点

  1. amount_offまたはpercent_offのどちらか一つを選択。
  2. durationは、once,repeating,foreverの中から一つ選択。
  3. durationrepeating(複数月)の場合duration_in_months(クーポンの適用期間)が必須
  4. idがクーポンコード(支払い時に入力するコード)、nameはクーポンの名前。

nameの指定は必須ではありませんが、指定しないと、idがタイトルとしてダッシュボードのクーポン画面に表示されます。管理者画面でクーポンコードを整理する必要がある場合は、「category_subcategory_couponID」という形でnameを指定すると良いと思います。idだけではなくnameも使った方が発行済みクーポンを管理しやすくなると思うのでサービスに合った使い方をすると良さようです。

クーポンには利用回数の情報が含まれているので、times_redeemed(引換された回数)とmax_redemptions(引換回数の上限)を使って残りの引換可能回数を知ることができます。 ちなみに、

この制限は顧客全体に適用されるため、これにより 1 人の顧客による複数回の引き換えができないことはありません。

と記載があったので一人の顧客が同じクーポンIDを複数回使うことができるらしいです。また、times_redeemdedmax_redemptionsに達すると以下のHTTPエラーが発生します。

{
  "error": {
    "code": "coupon_expired",
    "doc_url": "https://stripe.com/docs/error-codes/coupon-expired",
    "message": "Coupon expired: f3rf1e",
    "param": "coupon",
    "type": "invalid_request_error"
  }
}

Unique Idを作成

ランダムに文字列生成する関数を作成してクーポンコードを生成すると良いと思います。

export function generateRandomCode(len: number): string {
	const patterns = "abcdefghijklmnopqrstuvwxyz0123456789";
	let code = shuffle(patterns.split("")).slice(0, len);
	return code.join("");
}
const shuffle = (array: any[]) => {
	for (var i = array.length - 1; i > 0; i--) {
		var r = Math.floor(Math.random() * (i + 1));
		var tmp = array[i];
		array[i] = array[r];
		array[r] = tmp;
	}
	return array;
};

クーポン適用

サブスクにクーポンを適用する際はランダムに生成したクーポンのIDをSubscriptionCreateParamsオブジェクトに含めます。

let params: Stripe.SubscriptionCreateParams = {
      customer: customer,
      tax_percent: 8,
      items: items,
       metadata: { ... }
      },
      coupon:f3rf1e
}
await stripe.subscriptions.create(params)

クーポン適用後のinvoiceamountはプランの請求額なので、請求額を調整する必要があります。チェックアウトが完了すると、invoicediscountオブジェクトが含まれるので、discount.coupon.amount_off を取得して、invoice.amount から discount.coupon.amount_offを引くと良いと思います。

所感

以上、クーポンAPIの実装で必要だったプロセスを書いてみました。実装完了後に、Promotion CodesというAPI(が登場?)を発見。Promotion Codes APIの今後のアップデートが気になりました。もっとスマートにクーポン管理できるようになってほしい。

リファレンス

https://stripe.com/docs/testing
https://stripe.com/docs/billing/subscriptions/trials
https://stripe.com/docs/api/coupons

11
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?