この記事は、ジャムジャム!!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アカウントを作成しよう
アカウントを作成するには、メールアドレスとパスワードが必要です。
ログインすると、APIのサブドメインやサービスの名称を入力する画面が出ます。
ここで設定したサービスIDがAPIのドメインになりますので、ご注意ください。
サービスを作成すると、管理ページへのURLが表示されます。
Step2: microCMSで、商品一覧APIを作成しよう
今回はmicroCMSで商品情報を管理しますので、まずは以下のAPIを用意します。
- API名: Product API
- エンドポイント: /products
このAPIは商品の一覧データを返します。そのため、[APIの型を選択]では、[リスト形式]を選びましょう。
[APIスキーマ]を定義します。まずはシンプルに、以下の3項目を設定しましょう。
フィールドID | 表示名 | 種類 | 必須項目 |
---|---|---|---|
name | 商品名 | テキストフィールド | 必須 |
stripe_price_id | Stripe料金ID | テキストフィールド | 必須 |
description | 商品説明 | リッチエディタ |
これでAPIの準備ができました。
Step3: Stripeに商品データを登録する
続いてStripe側に商品データを登録します。
-> 作成ページに直接移動する
今回はシンプルにするため、Stripe側には商品名と料金データのみ登録しましょう。
商品情報
名前 | 税コード(Optional) |
---|---|
ステッカー | 一般 - 有形商品 |
料金情報
料金体系モデル | 価格 | 価格に税金を含める |
---|---|---|
標準の料金体系 | 500 JPY [一括] | はい |
入力イメージ
料金IDを取得しよう
[商品を保存]できれば、作成した商品ページに自動的に移動します。
ここでは、作成した料金IDを取得しましょう。
Step4: microCMSに商品データを登録する
登録する商品データが用意できましたので、microCMSに登録しましょう。
作成した[product api]の管理ページに移動し、[追加]ボタンをクリックします。
コンテンツ入力画面では、[商品名]に[ステッカー]そして[Stripe料金ID]に[Stripe Dashboardでコピーした料金ID(price-xxx
)]を入力します。
[説明]部分には、リッチエディタを利用して段落や画像を挿入してみましょう。
ページ上部にある[公開]ボタンをクリックして公開すれば完了です。
公開後、[公開]ボタンしたに表示される[APIプレビュー]をクリックすることで、APIレスポンスのプレビューができます。
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キー]
から取得できます。
microCMSは、管理画面の[権限管理>APIキー管理]から取得しましょう。
トップページに、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です。
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側で管理している料金データもサイト上に表示できるようになりました。
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でサイト・アプリケーションを開発されている方にとって、この記事がマネタイズのヒントになれば幸いです。
参考記事など