決済まわりはお金が直接絡んでいて、けっこう面倒な印象が強いですが、Stripeを利用したらあっという間に決済処理を実装することができます。
諸々自前でカスタマイズする方法もありますが、今回はStripe側で用意されている決済ページなどを利用して、簡単に実装する方法を備忘録ついでにまとめます(カスタマイズする話が多いですからね)
1) まずはStraipeに登録&ログインして、「テスト連携」を有効化しましょう
2) Stripeのファーストビューに公開可能キーとシークレットキーがあるので、その2つをNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=xxxxxxxとSTRIPE_SECRET_KEY=xxxxxxxという感じで、環境変数に設定しましょう
3) 次は「商品カタログ」から月額課金する商品情報を新規作成します
4) 月額課金用の商品を登録したら、作成した商品のAPI IDという項目をコピーしましょう
5) Next.jsのpages/api/checkout.tsを作成します
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
export default async function handler(req: any, res: any) {
if (req.method === 'POST') {
const email = req.body?.email;
const userId = req.body?.userId;
if (!email || !userId) {
res.status(403).end('email or userId is required');
return;
}
try {
// 顧客を作成または既存の顧客を検索
const customer = await stripe.customers.list({
email,
}).then((customers: any) => {
if (customers.data.length) {
return customers.data[0];
}
return stripe.customers.create({
email,
metadata: {
userId: userId,
},
});
});
const session = await stripe.checkout.sessions.create({
customer: customer.id, // 顧客IDをセッションに紐付け
payment_method_types: ['card'],
line_items: [
{
price: "[コピーした月額商品のAPI ID]",
quantity: 1,
},
],
billing_address_collection: 'required',
mode: 'subscription',
success_url: `${req.headers.origin}/?success=true`,
cancel_url: `${req.headers.origin}/?canceled=true`,
});
res.redirect(303, session.url);
} catch (err: any) {
res.status(500).json(err.message);
}
} else {
res.setHeader('Allow', 'POST');
res.status(405).end('Method Not Allowed');
}
}
このcheckout.tsは、
フロントエンドのformから/api/checkoutへアクセス
↓
POSTでメールアドレスをユーザーIDを受け取る
↓
Stripeにすでに顧客情報があるかメールアドレスで検索し、
顧客情報が存在しない場合は、顧客情報を新規作成
(ちなみに顧客情報の識別子はemailでないといけない仕様らしいです)
↓
作成した顧客情報の顧客IDを紐付けてStripe側のセッションを作成し、
Stripe側の決済ページ(月額課金の登録ページ)へ飛ぶ
というフローをしています!
後は、フロントエンド側で、
import React, { useEffect } from "react";
import { loadStripe } from "@stripe/stripe-js";
const key = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;
const stripePromise = loadStripe(key);
const App = () => {
return (
<form action="/api/checkout" method="POST">
<section>
<input type="hidden" name="email" value="user-example@gmail.com" />
<input type="hidden" name="userId" value="123" />
<button type="submit" role="link">
月額プランの登録ページへ移動する
</button>
</section>
</form>
);
};
export default App;
という感じでformを用意してあげたら、
という感じで、
Stripe側の月額登録ページへ移動することができます!
(ちなみに4242 4242 4242 4242というテストのカード番号を使うことができます。有効期限とセキュリティコードは適当でOKです)
6) 次は、Next.jsのpages/api/portal.tsというファイルを作成して、同じような手順でStripe側の「カード情報変更や決済履歴表示ページ」へ移動します
下記のコードをpages/api/portal.tsに書きましょう
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
export default async function handler(req: any, res: any) {
if (req.method === 'POST') {
const email = req.body?.email;
if (!email) {
res.status(403).end('email is required');
return;
}
// 顧客を検索
const customers = await stripe.customers.list({
email,
});
let customer;
if (customers.data.length) {
customer = customers.data[0];
} else {
res.status(403).end('customer not found');
return;
}
const session = await stripe.billingPortal.sessions.create({
customer: customer.id,
return_url: `${req.headers.origin}/?return=true`,
});
res.redirect(303, session.url);
}
}
このportal.tsは、
emailをもとにStripe似登録している顧客情報を検索
↓
取得した顧客情報の顧客IDをもとに、Stripe側のページへ移動しています
後は、フロントエンド側で、
import React, { useEffect } from "react";
import { loadStripe } from "@stripe/stripe-js";
const key = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;
const stripePromise = loadStripe(key);
const App = () => {
return (
<form action="/api/portal" method="POST">
<section>
<input type="hidden" name="email" value="user-example@gmail.com" />
<input type="hidden" name="userId" value="123" />
<button type="submit" role="link">
Stripeのユーザー画面へ
</button>
</section>
</form>
);
};
export default App;
という感じで書いて、
「Stripeのユーザー画面へ」というボタンを押したら、
↑のようなStripe側のユーザーページへ移動することができます!
たったこれだけで、
月額課金の登録、プラン変更、カード変更、決済の履歴などほぼすべての管理画面の出来上がりです!
後はユーザーさんのカードで決済処理に失敗した時などの対応ですが、
決済に失敗した時は↑のようなwebhookで通知を受けることができるので、
こちらでwebhook用のAPIを作ってあげて、決済に失敗したよという通知を受け取るだけで、
後は自由に処理を実装する感じでOKです!
たったこれだけで、月額課金の基本処理がすべて揃ってしまいます!
カスタマイズする記事は多いですが、シンプルな用途ならこれだけで十分に実用的だと思いますし、ユーザーページなどはOpenAIもそのまま使っているので、全然いけると思います!
Strapiで月額課金の実装を楽にしましょう!