19
9

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 1 year has passed since last update.

microCMSとStripe + Next.jsを利用したJamstackなHeadless ECサイトを作る方法(入門編)

Last updated at Posted at 2022-02-22

この記事は、ジャムジャム!!Jamstack_5イベントのフォローアップ記事です。

「microCMSとNext.jsを利用したJamstackなアプリケーションに、Stripeを組み込むことで、簡単に決済機能を実装できる」という内容のセッションで登壇しました。
この記事では、その日のスライドを作成するにあたってテストしたログを元に、実際に組み込む際の作業の進め方や、実案件に採用する際のヒントなどを紹介しています。

イベント当日のスライド

商品データをmicroCMSで管理するメリット

Stripeだけでも商品の説明文や画像・メタ情報などを管理することは可能です。
ですが、以下のような制限があるため、実装したいサイト・アプリの要件には一致しないケースが存在します。

  • 説明文には、HTMLが利用できない
  • 画像は1枚しか登録できない

microCMSなどのCMSを利用することで、「請求に関わるデータはStripe、それ以外はCMS」のような役割分担が可能になります。
また、CMS側にはStripeのIDデータだけを持たせることで、価格改訂などの請求に関わる変更が発生した際のオペレーションを効率化することができます。

今回のケースだけでは「解決できない」問題

今回のサンプルだけでは実現が難しいケースを2つ紹介します。
他にも実案件で利用するには、追加の開発やサービスの利用が必要になる可能性がありますので、ご注意ください。

厳密な在庫管理が必要な商品の販売

本記事を作成した時点(2022/02)では、StripeにもmicroCMSにも在庫管理システムは存在しません。
そのため、大規模なセールなど、厳密な在庫管理が必要になるケースにおいては、別途在庫を管理するためのデータベースが必要です。

メンバー管理を伴うサービス

有料メディアサイトやビジネスアプリケーションのような、「ユーザーがログインして操作をする必要のある要件」についても、別途Auth0やFirebase / AWS Amplifyなどが必要です。

Step1: microCMSアカウントを作成しよう

アカウントを作成するには、メールアドレスとパスワードが必要です。

スクリーンショット 0004-02-21 16.40.05.png

ログインすると、APIのサブドメインやサービスの名称を入力する画面が出ます。
ここで設定したサービスIDがAPIのドメインになりますので、ご注意ください。

スクリーンショット 0004-02-21 16.44.58.png

サービスを作成すると、管理ページへのURLが表示されます。

スクリーンショット 0004-02-21 16.45.08.png

Step2: microCMSで、商品一覧APIを作成しよう

スクリーンショット 0004-02-21 16.53.33.png

今回はmicroCMSで商品情報を管理しますので、まずは以下のAPIを用意します。

  • API名: Product API
  • エンドポイント: /products

このAPIは商品の一覧データを返します。そのため、[APIの型を選択]では、[リスト形式]を選びましょう。

スクリーンショット 0004-02-21 16.56.04.png

[APIスキーマ]を定義します。まずはシンプルに、以下の3項目を設定しましょう。

フィールドID 表示名 種類 必須項目
name 商品名 テキストフィールド 必須
stripe_price_id Stripe料金ID テキストフィールド 必須
description 商品説明 リッチエディタ

スクリーンショット 0004-02-21 16.58.58.png

これでAPIの準備ができました。

Step3: Stripeに商品データを登録する

続いてStripe側に商品データを登録します。

-> 作成ページに直接移動する

今回はシンプルにするため、Stripe側には商品名と料金データのみ登録しましょう。

商品情報

名前 税コード(Optional)
ステッカー 一般 - 有形商品

料金情報

料金体系モデル 価格 価格に税金を含める
標準の料金体系  500 JPY [一括] はい

入力イメージ

スクリーンショット 0004-02-21 17.07.40.png

料金IDを取得しよう

[商品を保存]できれば、作成した商品ページに自動的に移動します。

スクリーンショット 0004-02-21 17.12.17.png

ここでは、作成した料金IDを取得しましょう。

Step4: microCMSに商品データを登録する

登録する商品データが用意できましたので、microCMSに登録しましょう。

作成した[product api]の管理ページに移動し、[追加]ボタンをクリックします。

スクリーンショット 0004-02-21 17.13.31.png

コンテンツ入力画面では、[商品名]に[ステッカー]そして[Stripe料金ID]に[Stripe Dashboardでコピーした料金ID(price-xxx)]を入力します。
[説明]部分には、リッチエディタを利用して段落や画像を挿入してみましょう。

スクリーンショット 0004-02-21 17.16.41.png

ページ上部にある[公開]ボタンをクリックして公開すれば完了です。

公開後、[公開]ボタンしたに表示される[APIプレビュー]をクリックすることで、APIレスポンスのプレビューができます。

スクリーンショット 0004-02-21 17.18.39.png

Step5: Next.jsでmicroCMS / Stripeのデータを表示する

ここまでで商品の登録が完了しました。
Next.jsを利用して、登録した商品データを実際に表示させましょう。

% npx create-next-app microcms-next-jamstack-blog
% cd microcms-next-jamstack-blog/

# microCMS SDKのインストール
% npm i microcms-js-sdk

#  Stripe SDKのインストール
% npm i stripe

(まとめてインストールしたい方用: npm i microcms-js-sdk stripe

環境変数を設定する

続いてmicroCMS / Stripeで利用するAPIキーを、環境変数に設定します。

% touch .env.local

.env.local

# Stripe:シークレットキー
STRIPE_SECRET_KEY=sk_test_xxxxxxx
# microCMS:APIキー
MICROCMS_API_KEY=xxxxx

StripeのAPIキーは、Dashboardの[開発者>APIキー]
から取得できます。

スクリーンショット 0004-02-21 17.46.20.png

microCMSは、管理画面の[権限管理>APIキー管理]から取得しましょう。

スクリーンショット 0004-02-21 17.46.29.png

トップページに、microCMSのデータを表示する

まずは商品データをmicroCMSから取得しましょう。

pages/index.jsを以下のように変更します。

import Head from 'next/head';
import styles from '../styles/Home.module.css';
import { createClient } from 'microcms-js-sdk';

export const getStaticProps = async () => {
  /**
   * microCMS SDKを利用して、商品データを取得する
   **/
  const client = createClient({
    serviceDomain: '作成したmicroCMS APIのドメイン名',
    apiKey: process.env.MICROCMS_API_KEY,
  });
  const { contents } = await client.get({ endpoint: 'products' });
  return {
    props: {
      products: contents,
    },
  };
};

export default function Home(props) {
  return (
    <div className={styles.container}>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main>
        <h1 className={styles.title}>
          Welcome to My Store
        </h1>
        <div>
          {props.products.map(product => {
            return (
              <section key={product.id}>
                <h2>{product.name}</h2>
                <div dangerouslySetInnerHTML={{
                  __html: product.description
                }} />
              </section>
            )
          })}
        </div>
      </main>
    </div>
  );
};

変更を保存し、npm run devでNext.jsアプリを起動してみましょう。
以下のようにmicroCMSに登録したデータが表示されていればOKです。

スクリーンショット 0004-02-21 17.59.40.png

microCMSのデータから、Stripeの料金データを取得する

商品データが取得できたので、続いて料金データを表示させましょう。

pages/index.jsを以下のように変更します。

import Head from 'next/head';
import styles from '../styles/Home.module.css';
import { createClient } from 'microcms-js-sdk';
import Stripe from 'stripe';

export const getStaticProps = async () => {
  /**
   * microCMS SDKを利用して、商品データを取得する
   **/
  const client = createClient({
    serviceDomain: 'demo-stripe-ec',
    apiKey: process.env.MICROCMS_API_KEY,
  });
  const { contents } = await client.get({ endpoint: 'products' });
+  const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
+  /**
+   * Stripe SDKを利用して、料金データを取得し、マージする
+   **/
+  const products = await Promise.all(contents.map(async (content) => {
+    try {
+      const price = await stripe.prices.retrieve(content.stripe_price_id);
+      return {
+        ...content,
+        price: {
+          unit_amount: price.unit_amount,
+          currency: price.currency,
+          id: price.id,
+        },
+      };
+    } catch (e) {
+      return content;
+    }
+  }));
  return {
    props: {
-      products: contents,
+      products,
    },
  };
};

..

        <div>
          {props.products.map(product => {
            return (
              <section key={product.id}>
                <h2>{product.name}</h2>
+                {
+                  product.price ? (
+                    <dl>
+                      <dt>価格</dt>
+                      <dd>{product.price.unit_amount.toLocaleString()} {product.price.currency.toLocaleUpperCase()}</dd>
+                    </dl>
+                  ): null
+                }
                <div dangerouslySetInnerHTML={{
                  __html: product.description
                }} />
              </section>
            )
          })}
        </div>

この変更により、Stripe側で管理している料金データもサイト上に表示できるようになりました。

スクリーンショット 0004-02-21 18.10.28.png

Step6: Stripe Checkoutを利用して、表示している商品を注文できるようにする

最後に注文画面を作りましょう。今回はStripeが用意する決済画面にリンクさせる方法(Stripe Checkout)を利用します。

まずはCheckoutセッションを作成するためのAPIを用意します。

pages/api/checkout_session.jsを作成し、以下のように実装しましょう。

import Stripe from 'stripe';

export default async function handler(req, res) {
    const priceId = req.body.price_id
    if (!priceId) {
        return res.status(400).json({message: "商品が選択されていません"})
    }
    const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
    const session = await stripe.checkout.sessions.create({
        customer_creation: 'if_required',
        line_items: [{
            price: priceId,
            quantity: 1,
        }],
        success_url: 'http://localhost:3000',
        cancel_url: 'http://localhost:3000',
        mode: 'payment',
    });
    res.status(200).json({ url: session.url })
  }

続いて、pages/index.jsを以下のように変更します。

                {
                  product.price ? (
                    <dl>
                      <dt>価格</dt>
                      <dd>{product.price.unit_amount.toLocaleString()} {product.price.currency.toLocaleUpperCase()}</dd>
+                      <dt>注文</dt>
+                      <dd>
+                        <button onClick={async () => {
+                          const session = await fetch('/api/checkout_session', {
+                            method: "POST",
+                            headers: {
+                              'content-type': 'application/json',
+                            },
+                            body: JSON.stringify({
+                              price_id: product.price.id
+                            })
+                          }).then(data => data.json())
+                          .catch(e => {
+                            console.log(e)
+                            window.alert(e.message)
+                          })
+                          window.open(session.url, 'checkout-session')
+                        }}>注文する</button>
+                      </dd>
                    </dl>
                  ): null
                }

この実装により、「ボタンをクリックすると、API経由で注文のためのセッションが開始される」→「作成されたURLで、新しいウィンドをを開き、決済ページにアクセスできるようにする」フローが実装できます。

これでとてもシンプルな形ですが、microCMSのコンテンツ管理機能と、決済管理のためのStripeを組み合わせたアプリケーションが実装できました。

[Appendix] より実践的なアーキテクチャにするためのポイント

実際の運用で利用するには、まだまだ考慮しないといけない部分が存在します。
チャレンジのアイディアとして、アイディアをいくつか紹介しますので、ぜひお試しください。

  • Stripe Webhookを利用し、Stripeで登録した商品・画像をmicroCMSのデータに自動で追加する
  • Stripe Webhookを利用し、Stripe側に登録できる説明文を「商品の簡易説明文」としてmicroCMSに登録する
  • microCMSのWebhookを利用し、簡易説明文や商品名の変更をStripe側に反映させる
    -> microCMSブログで紹介いただけました!: https://blog.microcms.io/sync-microcms-to-stripe/
  • microCMSの繰り返しフィールドを利用し、商品画像を複数登録する
  • microCMSの繰り返しフィールドを利用し、商品バリエーションごとにStripeの料金IDや画像を登録できるようにする
  • firebaseやAWS Amplifyを利用し、会員管理や在庫DBなどを実装する

[Appendix] もっと簡単な方法

Checkoutのセッションを作成するのではなく、「microCMSのフィールドに、Stripeの支払いリンク(Payment Links)URLを登録するだけ」でも実装可能です。

下の例では、microCMSのスキーマとしてpayment_linksを追加し、そこに支払いリンクのURLを設定しています。


                      <dd>
                        <button onClick={async () => {
-                          const session = await fetch('/api/checkout_session', {
-                            method: "POST",
-                            headers: {
-                              'content-type': 'application/json',
-                            },
-                            body: JSON.stringify({
-                              price_id: product.price.id
-                            })
-                          }).then(data => data.json())
-                          .catch(e => {
-                            console.log(e)
-                            window.alert(e.message)
-                          })
+                          window.open(product.payment_links, 'checkout-session')
                        }}>注文する</button>
                      </dd>

この方法であれば、APIを用意する必要がなくなります。
そのため、NetlifyやAWS AmplifyでFunctionsを利用せずにサイトをデプロイできるようになります。

おわりに

とてもシンプルな例のため、これだけでは2サービスを連携させるメリットは見えにくいかもしれません。
ですが、「コンテンツやデータを管理することに長けたサービス」をStripeと組み合わせることで、サイトの表現力を高めることができます。

また、microCMSでサイト・アプリケーションを開発されている方にとって、この記事がマネタイズのヒントになれば幸いです。

参考記事など

19
9
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
19
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?