mcpField
S
stripe-payments

stripe-payments

github.com/jezweb開発1.1k1.1k 回インストール3 週間前に更新
リポジトリ

stripe-payments について

stripe-paymentsは、WebアプリにStripe決済を追加するための開発スキルです。単発決済、サブスクリプション、Webhook、カスタマーポータルなどの実装例を示し、Cloudflare Workersでの注意点も説明します。

主な機能

  1. 01Checkout Sessionsによる単発決済とサブスクリプション
  2. 02Payment ElementとPayment Intents
  3. 03Webhook処理と署名検証
  4. 04カスタマーポータルと請求管理
  5. 05Stripe CLIによるローカルWebhookテスト

活用シーン

  1. 01Webアプリへの単発決済の追加
  2. 02サブスクリプションと状態確認の実装
  3. 03決済成功やキャンセルイベントの処理
  4. 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 を使用してください

コメント