Next.js / React.jsでStripe実装
2022.05.06
Next.js / React.jsでStripe実装の画像

目次

    こんにちわ!
    WebエンジニアのSekineです。

    概要

    • タイトルにある通り、ReactでStripe決済するためのクレジットカード登録の処理実装をしました
    • 処理方式としては、フロントエンドとStripeで完結させるのではなく、Stripeから生成されたトークンを取得し、それをバックエンドのAPIに渡します
    • バックエンド処理にて、そのトークンでStripeへクレジットカード登録という方式にしたかったので、そのように実装してみました
    • UIに関しては、デザイナーがデザインした内容で実装したいため、ライブラリが提供しているCardElementをそのままコンポーネントとして、使うのではなく、分割されている下記のコンポーネントを組み合わせて実装しました
      • CardNumberElement:カード番号
      • CardExpiryElement:有効期限
      • CardCvcElement:セキュリティコード


    開発ドキュメント


    UI


    技術スタック

    • Next.js
    • React.js
    • Tailwind CSS


    ソースコード抜粋

    ライブラリから必要なものをインポート

    import {
      CardCvcElement,
      CardExpiryElement,
      CardNumberElement,
      useElements,
      useStripe,
    } from "@stripe/react-stripe-js";
    import {
      StripeCardCvcElementChangeEvent,
      StripeCardExpiryElementChangeEvent,
      StripeCardNumberElementChangeEvent,
    } from "@stripe/stripe-js";


    チェンジイベントでエラーを捕捉し、エラーメッセージ表示用のローカルstateを定義

      const [state, setErrors] = useState<{
        cardNumberElementErrorMessage: string;
        cardExpiryElementErrorMessage: string;
        cardCvcElementErrorMessage: string;
        formSubmitErrorMessage: string;
      }>({
        cardNumberElementErrorMessage: "",
        cardExpiryElementErrorMessage: "",
        cardCvcElementErrorMessage: "",
        formSubmitErrorMessage: "",
      });
    
    
      const onChangeCardNumberElement = (e: StripeCardNumberElementChangeEvent) => {
        if (e.error) {
          const cardNumberElementErrorMessage = e.error.message;
          setErrors((prev) => ({
            ...prev,
            cardNumberElementErrorMessage,
          }));
        } else {
          setErrors((prev) => ({
            ...prev,
            cardNumberElementErrorMessage: "",
          }));
        }
      };
    
    
      const onChangeCardExpiryElement = (e: StripeCardExpiryElementChangeEvent) => {
        if (e.error) {
          const cardExpiryElementErrorMessage = e.error.message;
          setErrors((prev) => ({
            ...prev,
            cardExpiryElementErrorMessage,
          }));
        } else {
          setErrors((prev) => ({
            ...prev,
            cardExpiryElementErrorMessage: "",
          }));
        }
      };
    
    
      const onChangeCardCvcElement = (e: StripeCardCvcElementChangeEvent) => {
        if (e.error) {
          const cardCvcElementErrorMessage = e.error.message;
          setErrors((prev) => ({
            ...prev,
            cardCvcElementErrorMessage,
          }));
        } else {
          setErrors((prev) => ({
            ...prev,
            cardCvcElementErrorMessage: "",
          }));
        }
      };


    カード情報入力用のパーツ

                  <CardNumberElement
                    onChange={onChangeCardNumberElement}
                    className={
                      "col-span-12 md:col-span-8 xl:col-span-5 xl:col-start-4 mt-1 xl:mt-0 h-8 border-0.5 form-input border-gray-darkest"
                    }
                  />
                  {state.cardNumberElementErrorMessage && (
                    <div
                      className={
                        "col-span-12 md:col-span-8 xl:col-span-3 mt-1 text-xs text-red-vivid"
                      }
                    >
                      {state.cardNumberElementErrorMessage}
                    </div>
                  )}
    
    ...
    
                  <CardExpiryElement
                    onChange={onChangeCardExpiryElement}
                    className={
                      "col-span-3 md:col-span-4 xl:col-span-2 xl:col-start-4 mt-1 xl:mt-0 md:mr-10 h-8 border-0.5 form-input border-gray-darkest"
                    }
                  />
                  {state.cardExpiryElementErrorMessage && (
                    <div
                      className={
                        "col-span-12 md:col-span-8 xl:col-span-3 mt-1 text-xs text-red-vivid"
                      }
                    >
                      {state.cardExpiryElementErrorMessage}
                    </div>
                  )}
    
    ...
    
                  <CardCvcElement
                    onChange={onChangeCardCvcElement}
                    className={
                      "col-span-3 md:col-span-4 xl:col-span-2 xl:col-start-4 mt-1 xl:mt-0 md:mr-10 h-8 border-0.5 form-input border-gray-darkest"
                    }
                  />
                  {state.cardCvcElementErrorMessage && (
                    <div
                      className={
                        "col-span-12 md:col-span-8 xl:col-span-3 mt-1 text-xs text-red-vivid"
                      }
                    >
                      {state.cardCvcElementErrorMessage}
                    </div>
                  )}


    ボタン押下時にstripeが提供している要素を取得して、トークン生成依頼メソッドの引数に渡して、実行する

      const stripe = useStripe();
      const elements = useElements();
    • メソッド呼び出し、要素取得時に必要なため、変数へ代入


      const onSubmit: FormEventHandler = async (event) => {
        event.preventDefault();
    
    
        const cardNumberElement = elements?.getElement("cardNumber");
        if (!stripe || !cardNumberElement) {
          return;
        }
    
    
        const { error, token } = await stripe.createToken(cardNumberElement);
        console.log("error:", error);
        if (error && error.message) {
          const formSubmitErrorMessage = error.message;
          setErrors((prev) => ({
            ...prev,
            formSubmitErrorMessage,
          }));
        } else {
          setErrors((prev) => ({
            ...prev,
            formSubmitErrorMessage: "",
          }));
        }
        if (!token) return;
    
    
        // TODO:トークンをBEへpostする
      };


    • tokenのレスポンスとしては、下記のようなJSONが返却されますので、BEは、idの値をpostすればOKということになります!


    {
      "id": "tok_1Kw2Rxxxxxxxxxxxx",
      "object": "token",
      "card": {
        "id": "card_1Kw2Rkxxxxxxxxxxx",
        "object": "card",
        "address_city": null,
        "address_country": null,
        "address_line1": null,
        "address_line1_check": null,
        "address_line2": null,
        "address_state": null,
        "address_zip": null,
        "address_zip_check": null,
        "brand": "Visa",
        "country": "US",
        "cvc_check": "unchecked",
        "dynamic_last4": null,
        "exp_month": 12,
        "exp_year": 2032,
        "funding": "credit",
        "last4": "4242",
        "name": null,
        "tokenization_method": null
      },
      "client_ip": "xxx.xx.xxx.xx",
      "created": 1651748532,
      "livemode": false,
      "type": "card",
      "used": false
    }


    こんな感じです!
    Stripeの公式サイト、APIリファレンス、提供ライブラリをとっても、とてもわかりやすかったです!!

    最後までお読みいただきまして、ありがとうございました!

    弊社では、エンジニアを募集しています!是非、一緒に良いプロダクトを作りませんか??
    株式会社マチス教育システムまで

    Thank you for reading. writing by

    S. Sekine

    タグ

    matrix_background
    search icon