Nuxt.js+LaravelでStripeのCheckoutの実装をやってみた【決済フォーム埋め込み編】

この記事は個人ブログと同じ内容です

Nuxt.js+LaravelでStripeのCheckoutの実装をやってみた【決済フォーム埋め込み編】


前回の記事の続きです。 https://zenn.dev/ota_rg/articles/5bb03b17198f58

概要

LaravelとNuxt.jsでStripeの決済処理を実装しました。 今回はCheckoutの「自前のページに決済フォームを埋め込む方法」の紹介をしていきたいと思います。

https://stripe.com/docs/payments/checkout/how-checkout-works?payment-ui=embeddable-payment-form

実装したもの

モーダルにStripeの決済フォームを埋め込み、顧客が1つの商品を購入して完了するまでの実装をしました

https://youtu.be/Oz1fp_TcQZE

事前準備

前回説明したので、詳細は割愛しますが、以下の準備をしてください。

  • StripeのAPIキーの取得
  • 商品の追加
  • 顧客アカウントの作成

処理の簡単な流れ

  1. フロントがバックエンドのCheckoutセッション作成APIを叩く(フロント)
  2. バックエンドがstripeのセッション作成APIを叩き、その結果をフロントに返す(バックエンド)
  3. フロントがモーダルを表示する
  4. フロントがレスポンスのclient_secretを使用し、stripeの決済フォームを作成、モーダルにマウントする(フロント)
  5. 決済をし、任意のページにリダイレクト(フロント)

バックエンドの実装(Laravel)

前回同様にstripe-phpという、 Stripe公式のphp用のライブラリを使用して実装しました。 ライブラリを使用し、StripeのCheckoutセッション作成APIを叩き、その結果を返すAPIの作成をしました。

https://stripe.com/docs/api/checkout/sessions/create https://github.com/stripe/stripe-php

重要なところの解説

$stripe->checkout->sessions->create()でStripeのCheckoutセッション作成APIを叩いています。 前回も説明したので、今回は埋め込み式にする上で必要なパラメーターのみ解説します。

ui_mode

このパラメーターをembeddedにすることで、埋め込み式に変わります。 デフォルトはhostedとなっており、Stripeの決済ページに移動する方法になっています。

return_url

埋め込み式の場合のみ使用し、決済成功後のリダイレクト先のURLを指定する。 Stripeの決済ページに移動する方の時は、success_urlとcancel_urlとなっていて、少し違うので注意する

redirect_on_completion

if_requiredに設定することで、決済が成功した後にreturn_urlで指定したURLにリダイレクトしなくなり、 フロントエンドでstripe.initEmbeddedCheckoutの時のonCompleteに自由に処理を書くことができる。

https://stripe.com/docs/payments/checkout/custom-redirect-behavior

ソースコード

public function createCheckoutSession(): JsonResponse
    {
        /** @var string $config */

        $config = config('define.stripe.token');
        $stripe = new StripeClient($config);
        //Checkoutセッション作成
        $checkout = $stripe->checkout->sessions->create([
            // 商品
            'line_items'             => [[
                'price'    => 'price_id',//商品ID
                'quantity' => 1,//個数
            ],
            ],
            'mode'                   => 'payment', // 支払いモード
            'customer'               => 'customer_id', // 顧客ID 
            'ui_mode' => 'embedded',// 埋め込み式にするため
            //支払い成功時のリダイレクト先 ({CHECKOUT_SESSION_ID}とするとセッションIDが取得できる)
            'return_url' => 'http://localhost:8080/success',
            // 税金を自動徴収するかどうか(3万の商品だったら、決済ページで3万3千円になる)
            'automatic_tax'          => [
                'enabled' => true,
            ],
            //フロントのstripe.initEmbeddedCheckoutの時にonCompleteを使用することができる。ただし、return_urlには行かなくなる
            //参考:https://stripe.com/docs/js/embedded_checkout/init#embedded_checkout_init-options-onComplete
            'redirect_on_completion'=> 'if_required',
            // 支払い方法を保存するかどうか
            'payment_method_options' => [
                'card' => [
                    'setup_future_usage' => 'on_session',
                ],
            ],
            // 支払い方法
            'payment_method_types'   => ['card']
        ]);
        return response()->json($checkout);
    }

フロントエンドの実装

ボタンを押したら、バックエンドのセッション作成のAPIを叩き、モーダルを表示し、レスポンスのclientSecretを使用し、Stripeの決済フォームを作成し、モーダルにマウントするようになっています。

前回同様に、stripe-jsという、Stripeの公式のJavaScript用のライブラリを使用しました。 モーダルは、vue-js-modalというライブラリを使用しました。 https://www.npmjs.com/package/vue-js-modal

重要なところの解説

決済フォームの作成

バックエンドからもらったclientSecretを使用し、ライブラリのinitEmbeddedCheckout()を呼べば、 Stripeの決済フォームを作成します。

const checkout = await stripe?.initEmbeddedCheckout({
            clientSecret: res.data.client_secret,
          })
// Mount Checkout
checkout?.mount('#checkout')

マウントしたフォームの削除

Stripeの決済フォームを一度マウントした後に、そのままもう一度フォームを作成し、マウントしようとするとエラーになる。 なので、以下のようにモーダルを閉じるたびにマウントしたフォームを削除する必要があります。 vue-js-modalを使用しているので、hide()に処理を書くだけでモーダルが閉じた時の処理を書くことができる。

async hide() {
      // 一度destroyしないとcheckoutが残ってしまうので削除
      await this.checkout?.destroy()
      await this.$modal.hide('modal-content')
    },

ソースコード

<template>
  <div>
    <div>
      <button @click="submit">支払いをする</button>
    </div>

    <modal
      name="modal-content"
      height="auto"
      :scrollable="true"
      :click-to-close="false"
    >
      <button @click="hide">閉じる</button>
      <div id="checkout">
        <!-- Checkout will insert the payment form here -->
      </div>
    </modal>
  </div>
</template>

<script lang="ts">
import Vue from 'vue'
import { StripeEmbeddedCheckout, loadStripe } from '@stripe/stripe-js'
import axios from 'axios'

export default Vue.extend({
  name: 'StripeTest',
  head: () => ({
    title: 'StripeTest | back check',
  }),
  components: {},
  data() {
    return {
      publishableKey: process.env.STRIPE_PUBLISHABLE_KEY,
      loading: false,
      clientSecret: '',
      checkout: undefined as StripeEmbeddedCheckout | undefined,
    }
  },

  methods: {
    async submit() {
      const stripe = await loadStripe(
        process.env.STRIPE_PUBLISHABLE_KEY
          ? process.env.STRIPE_PUBLISHABLE_KEY
          : '',
      )

      await axios
        .post('http://localhost:8080/api/create_checkout_session')
        .then(async (res) => {
          this.$modal.show('modal-content')
          this.clientSecret = res.data.client_secret
          // StripeのCheckoutの作成
          const checkout = await stripe?.initEmbeddedCheckout({
            clientSecret: res.data.client_secret,
          })
          this.checkout = checkout

          // Mount Checkout
          checkout?.mount('#checkout')
        })
    },


    async hide() {
      // 一度destroyしないとcheckoutが残ってしまうので削除
      await this.checkout?.destroy()
      await this.$modal.hide('modal-content')
    },
  },
})
</script>

終わりに

今回は、StripeのCheckoutの実装で決済フォームを自前のページに埋め込む方法で行いました。 前回の決済フォームに遷移しての決済と比べると、モーダルなどを使用した分少し工数はかかりましたが、そこまで工数がかかるわけではないので、UIを重視したい方はこちらをお勧めします。 Stripeには、決済フォーム自体を自前で作る方法もあるので、さらにUIにこだわりたい方はそちらを使用してください。ただし工数がかなりかかると思います、、、


現在 back check 開発チームでは一緒に働く仲間を募集中です。 herp.careers