33
27

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.

PORTAdvent Calendar 2020

Day 21

Stripeでサブスクリプションを実装するメモ

Last updated at Posted at 2021-01-04

最近個人的に、Stripeでサブスクリプションを実装する機会があったので、メモがてら流れを書いていこうと思います。

  • Laravel 7.0
  • Nuxt.js 2.14.6

Nuxt.jsがSPAで動いており、LaravelがAPIを返す構成になっています。

流れ

契約

  1. stripeのダッシュボード上でプランを作成しておく
  2. [クライアント] プランを選択してサーバーサイドへ送信
  3. [サーバーサイド] 顧客情報を作成して、DBへ保存。セッションを作成してクライアントに渡す
  4. [クライアント] セッションidをstripe checkout(stripeの決済用外部ページ)に渡し、支払い情報を入力させる。成功すれば/checkout/success?session_id=XXXなどのURLにリダイレクトする
  5. [クライアント] successページのqueryからセッションidを取得。サーバーサイドに渡す
  6. [サーバーサイド] セッションidから顧客情報を取得、DBへ必要な情報を登録する。

キャンセル

  1. [クライアント]キャンセル情報をサーバーサイドへ送信
  2. [サーバーサイド]キャンセル処理(期末で解約とする)
  3. 期末で解約。stripeからwebhookでサーバーサイドのAPIを叩くことができる。DBに解約の情報を登録。有料機能の制限などを行う

ざっとこんな感じの流れです。

stripe checkoutとelements

まず、使用を迷ったのが、この2つです。
https://stripe.com/docs/billing/subscriptions/checkout
https://stripe.com/docs/billing/subscriptions/fixed-price

これらは、決済の入力フォームを実装するコンポーネントになります。

npmはこちらを使用しました。
checkoutもelementsもこれ1つで利用できます。
https://github.com/jofftiquez/vue-stripe-checkout

checkoutとelementsの違いは、checkoutが外部のサイトで決済処理が完結するのに対して、
elementsの方がカスタマイズ性が高く、サーバーサイドの処理を必要とします。

checkoutでは、外部サイトからリダイレクトで自サイトに戻ることになるので、ユーザーIDなどを渡すのは難しそうに思っていましたが、session_idから簡単に取得できました。必要があれば、metadataとして定義することもできます。

基本的には、checkoutを選択することになるのかなと思いました。

キャンセルの実装について

即キャンセルではなく、支払済の期末での解約にしました。
ちなみに、オプションを'cancel_at_period_end' => true,とすると、期末のキャンセルにできます。


\Stripe\Subscription::update(
  $subscriptionId,
  [
    'cancel_at_period_end' => true,
  ]
);

(ちなみに即キャンセルの場合は、updateではなく、\Stripe\Subscription::cancelとなります。)

解約時のwebhook

期末になり、実際の解約となるタイミングで、
stripe上でcustomer.subscription.deletedというイベントが走ります。

webhookを設定すると指定のイベントでAPIを叩くことができます。
URLは一律になるので、それをtypeでケースを分けて、処理をする形になります。

stripe cliを使うことで、localでもデバッグすることができます。
https://stripe.com/docs/stripe-cli/webhooks

stripe listen --forward-to localhost:5000/hooks

これで、stripe上でイベントが流れるたびに、localhost:5000/hooksが叩かれることになります。

stripe trigger payment_intent.created

などcli上でイベントを発生させてもいいですし、(イベント一覧)
実際にダッシュボード上や開発中のシステムでstripeのAPIを叩いた場合でも、有効です。

期末解約の実装確認では、キャンセル予定日を確認したのち、stripeのダッシュボード上で即時キャンセルして確認していました。

その他、支払い完了などのタイミングでメールの送信をシステムから行いたい場合などでも使えそうです。

余談: vue-stripe-checkoutのelementsで郵便番号の入力を非表示にする

https://github.com/jofftiquez/vue-stripe-checkout/issues/10
オプションがstylesしか渡せず、困ったのでイシューを探したらありました。

nuxt.js
<template>
  <div>
    <stripe-elements
      ref="elementsRef"
      :publishableKey="publishableKey"
      :amount="amount"
      @token="tokenCreated"
      @loading="loading = $event"
      locale="ja"
    >
    </stripe-elements>
    <v-btn>送信</v-btn>
  </div>
</template>
import { StripeElements } from 'vue-stripe-checkout'
export default {
  data() {
    return {
      loading: false,
      publishableKey: process.env.STRIPE_PUBLIC_KEY,
      amount: 1000,
      token: null,
      stripeConfig: {
        hidePostalCode: true
      }
    }
  },
  mounted() {
    // configをupdateしてhidePostalCodeする
    setTimeout(
      () => this.$refs.elementsRef.card.update(this.stripeConfig),
      1000
    )
  },
}
33
27
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
33
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?