stripe-payments について
stripe-paymentsは、WebアプリにStripe決済を追加するための開発スキルです。単発決済、サブスクリプション、Webhook、カスタマーポータルなどの実装例を示し、Cloudflare Workersでの注意点も説明します。
主な機能
- 01Checkout Sessionsによる単発決済とサブスクリプション
- 02Payment ElementとPayment Intents
- 03Webhook処理と署名検証
- 04カスタマーポータルと請求管理
- 05Stripe CLIによるローカルWebhookテスト
活用シーン
- 01Webアプリへの単発決済の追加
- 02サブスクリプションと状態確認の実装
- 03決済成功やキャンセルイベントの処理
- 04顧客による契約と支払い方法の管理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jezweb/ claude-skills --skill stripe-paymentsインストール後、Claude Code で/stripe-paymentsを入力して読み込まれたか確認します。
詳細
Stripe 決済
ウェブアプリに Stripe 決済を追加します。一般的なパターンである単発決済、サブスクリプション、ウェブフック、カスタマーポータルを、動作するコードとともに網羅しています。MCP サーバーは不要です。
どの Stripe API を使えばよいですか?
| 実現したいこと | 使用するもの | 複雑さ |
|---|---|---|
| 単発決済を受け付ける | Checkout Sessions | 低 — Stripe が決済ページをホストします |
| UI に決済フォームを埋め込む | Payment Element + Payment Intents | 中 — フォームは自分で作成し、カード情報の処理は Stripe に任せます |
| 継続課金/サブスクリプション | Checkout Sessions(サブスクリプションモード) | 低〜中 |
| カード情報を保存して後で使う | Setup Intents | 低 |
| マーケットプレイス/プラットフォーム決済 | Stripe Connect | 高 |
| 顧客が請求を管理できるようにする | Customer Portal | 低 — Stripe がホストします |
デフォルトの推奨:まずは Checkout Sessions から始めてください。最も手早く決済を受け付けられます。埋め込みフォームは後からいつでも追加できます。
セットアップ
インストール
npm install stripe @stripe/stripe-js
API キー
# Get keys from: https://dashboard.stripe.com/apikeys
# Test keys start with sk_test_ and pk_test_
# Live keys start with sk_live_ and pk_live_
# For Cloudflare Workers — store as secrets:
npx wrangler secret put STRIPE_SECRET_KEY
npx wrangler secret put STRIPE_WEBHOOK_SECRET
# For local dev — .dev.vars:
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_PUBLISHABLE_KEY=pk_test_...
サーバーサイドクライアント
import Stripe from 'stripe';
// Cloudflare Workers
const stripe = new Stripe(c.env.STRIPE_SECRET_KEY);
// Node.js
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
単発決済(Checkout Sessions)
決済を受け付ける最も手早い方法です。Stripe がチェックアウトページ全体をホストします。
Checkout Session を作成する(サーバー)
app.post('/api/checkout', async (c) => {
const { priceId, successUrl, cancelUrl } = await c.req.json();
const session = await stripe.checkout.sessions.create({
mode: 'payment',
line_items: [{ price: priceId, quantity: 1 }],
success_url: successUrl || `${new URL(c.req.url).origin}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: cancelUrl || `${new URL(c.req.url).origin}/pricing`,
});
return c.json({ url: session.url });
});
チェックアウトにリダイレクトする(クライアント)
async function handleCheckout(priceId: string) {
const res = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ priceId }),
});
const { url } = await res.json();
window.location.href = url;
}
商品と価格を作成する
# Via Stripe CLI (recommended for setup)
stripe products create --name="Pro Plan" --description="Full access"
stripe prices create --product=prod_XXX --unit-amount=2900 --currency=aud --recurring[interval]=month
# Or via Dashboard: https://dashboard.stripe.com/products
コード内では価格 ID をハードコードしてください(変更されません)。
const PRICES = {
pro_monthly: 'price_1234567890',
pro_yearly: 'price_0987654321',
} as const;
サブスクリプション
単発決済と同じですが、mode: 'subscription' を指定します。
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
line_items: [{ price: PRICES.pro_monthly, quantity: 1 }],
success_url: `${origin}/dashboard?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${origin}/pricing`,
// Link to existing customer if known:
customer: customerId, // or customer_email: '[email protected]'
});
サブスクリプションのステータスを確認する
async function hasActiveSubscription(customerId: string): Promise<boolean> {
const subs = await stripe.subscriptions.list({
customer: customerId,
status: 'active',
limit: 1,
});
return subs.data.length > 0;
}
ウェブフック
決済の成功やサブスクリプションのキャンセルなどが発生すると、Stripe からサーバーにイベントが送信されます。ウェブフックの署名を必ず検証してください。
ウェブフックハンドラー(Cloudflare Workers / Hono)
app.post('/api/webhooks/stripe', async (c) => {
const body = await c.req.text();
const sig = c.req.header('stripe-signature')!;
let event: Stripe.Event;
try {
// Use constructEventAsync for Workers (no Node crypto)
event = await stripe.webhooks.constructEventAsync(
body,
sig,
c.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
console.error('Webhook signature verification failed:', err);
return c.json({ error: 'Invalid signature' }, 400);
}
switch (event.type) {
case 'checkout.session.completed': {
const session = event.data.object as Stripe.Checkout.Session;
// Fulfill the order — update database, send email, grant access
await handleCheckoutComplete(session);
break;
}
case 'customer.subscription.updated': {
const sub = event.data.object as Stripe.Subscription;
await handleSubscriptionChange(sub);
break;
}
case 'customer.subscription.deleted': {
const sub = event.data.object as Stripe.Subscription;
await handleSubscriptionCancelled(sub);
break;
}
case 'invoice.payment_failed': {
const invoice = event.data.object as Stripe.Invoice;
await handlePaymentFailed(invoice);
break;
}
}
return c.json({ received: true });
});
ウェブフックを登録する
# Local testing with Stripe CLI:
stripe listen --forward-to http://localhost:8787/api/webhooks/stripe
# Production — register via Dashboard:
# https://dashboard.stripe.com/webhooks
# URL: https://yourapp.com/api/webhooks/stripe
# Events: checkout.session.completed, customer.subscription.updated,
# customer.subscription.deleted, invoice.payment_failed
Cloudflare Workers での注意点
constructEvent(同期処理)は、Workers には存在しない Node.js の crypto を使用します。代わりに constructEventAsync を使用してください。Web Crypto API を使います。
Customer Portal
顧客が自分のサブスクリプションを管理(アップグレード、ダウングレード、キャンセル、支払い方法の更新)できるようにします。
app.post('/api/billing/portal', async (c) => {
const { customerId } = await c.req.json();
const session = await stripe.billingPortal.sessions.create({
customer: customerId,
return_url: `${new URL(c.req.url).origin}/dashboard`,
});
return c.json({ url: session.url });
});
Dashboard でポータルを設定してください:https://dashboard.stripe.com/settings/billing/portal
料金ページのパターン
Stripe の商品情報を読み込んで料金ページを生成します。
// Server: fetch products and prices
app.get('/api/pricing', async (c) => {
const prices = await stripe.prices.list({
active: true,
expand: ['data.product'],
type: 'recurring',
});
return c.json(prices.data.map(price => ({
id: price.id,
name: (price.product as Stripe.Product).name,
description: (price.product as Stripe.Product).description,
amount: price.unit_amount,
currency: price.currency,
interval: price.recurring?.interval,
})));
});
プランが 2〜3 個だけの場合は、ハードコードするほうがシンプルで、ページ読み込みごとの API 呼び出しも不要です。
Stripe CLI(ローカル開発)
# Install
brew install stripe/stripe-cli/stripe
# Login
stripe login
# Listen for webhooks locally
stripe listen --forward-to http://localhost:8787/api/webhooks/stripe
# Trigger test events
stripe trigger checkout.session.completed
stripe trigger customer.subscription.created
stripe trigger invoice.payment_failed
よくあるパターン
Stripe の顧客を自分のユーザーに紐付ける
// On first checkout, create or find customer:
const session = await stripe.checkout.sessions.create({
customer_email: user.email, // Creates new customer if none exists
// OR
customer: user.stripeCustomerId, // Use existing
metadata: { userId: user.id }, // Link back to your user
// ...
});
// In webhook, save the customer ID:
case 'checkout.session.completed': {
const session = event.data.object;
await db.update(users)
.set({ stripeCustomerId: session.customer as string })
.where(eq(users.id, session.metadata.userId));
}
無料トライアル
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
line_items: [{ price: PRICES.pro_monthly, quantity: 1 }],
subscription_data: {
trial_period_days: 14,
},
// ...
});
オーストラリアドル
// Set currency when creating prices
const price = await stripe.prices.create({
product: 'prod_XXX',
unit_amount: 2900, // $29.00 in cents
currency: 'aud',
recurring: { interval: 'month' },
});
注意点
| 注意点 | 解決方法 |
|---|---|
Workers で constructEvent が失敗する | constructEventAsync を使用します(Web Crypto API) |
| ウェブフックは発火するが、ハンドラーが呼び出されない | エンドポイントの URL が完全に一致しているか確認します(末尾のスラッシュも重要です) |
| テストモードの決済が表示されない | sk_live_ ではなく sk_test_ キーを使っていることを確認します |
| 価格の金額はセント単位 | 2900 は $29.00 です。表示する際は必ず 100 で割ってください |
| 顧客のメールアドレスがユーザーと一致しない | 既存ユーザーには customer_email ではなく customer(既存の ID)を使用します |
| サブスクリプションのステータスが古い | キャッシュしないでください。API で確認するか、ウェブフックのイベントを信頼してください |
| ウェブフックの再試行 | Stripe は失敗したウェブフックを最大 3 日間再試行します。速やかに 200 を返してください |
| チェックアウトへのリダイレクトでの CORS | チェックアウト URL は stripe.com 上にあります。fetch ではなく window.location.href を使用してください |