SaaS プロダクト、EC サイト、サブスクリプションサービス — ビジネスが成長するほど、決済機能の実装精度が重要になります。しかし Stripe の API は複雑で、セキュリティ上の落とし穴も多いです。
Antigravity の AI エージェントを活用することで、Stripe の複雑さを大幅に軽減できます。API リファレンスの自動参照、ウェブフック処理の自動生成、エラーハンドリングの自動実装など、AI が面倒な部分をカバーします。
Stripe の基本概念
決済フロー全体の理解
Stripe を使った決済システムは、以下のコンポーネントから構成されます:
- Customer — 顧客情報(メール、カード情報など)
- Payment Method — 支払い方法(クレジットカード、デジタルウォレット)
- Checkout Session — 購入セッション(商品、金額、メタデータ)
- Subscription — 定期課金(請求周期、キャンセル方法)
- Invoice — 請求書(支払い履歴、請求日)
- Webhook — イベント通知(成功、失敗、キャンセルなど)
Stripe のコア概念図
顧客がプロダクトを選択
↓
Checkout Session 作成
↓
Stripe Checkout ページへリダイレクト
↓
支払い方法を入力
↓
Stripe がカード情報を処理(PCI コンプライアンス)
↓
Payment Intent 成功
↓
Webhook: payment_intent.succeeded
↓
サーバー: 購入を確定、メール送信、アクセス付与
Antigravity での実装環境構築
ステップ 1: Stripe API キーの設定
# プロジェクトルートの .env.local に追加
STRIPE_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxxx
STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxAntigravity 上での設定ファイル:
# .antigravity/integrations/stripe.yml
stripe:
enabled: true
environment: "test" # test または production
# API キーは環境変数から自動読み込み
api_keys:
publishable: "${STRIPE_PUBLISHABLE_KEY}"
secret: "${STRIPE_SECRET_KEY}"
webhook: "${STRIPE_WEBHOOK_SECRET}"
# AI エージェントが参照するドキュメント
docs:
api_reference: "https://stripe.com/docs/api"
libraries:
nodejs: "https://github.com/stripe/stripe-node"
python: "https://github.com/stripe/stripe-python"
# テスト用のテストカード情報
test_data:
success_card: "4242 4242 4242 4242"
failure_card: "4000 0000 0000 0002"
expired_card: "4000 0000 0000 0069"ステップ 2: Stripe AI エージェントの初期化
# Stripe 統合用のカスタムエージェントを作成
antigravity agents create stripe-agent \
--template stripe-integration \
--language typescript \
--framework nextjs生成されたエージェント構造:
agents/stripe-agent/
├── skills.sh # Stripe 関連スキル
├── payment-methods.ts # 支払い方法の処理
├── subscriptions.ts # サブスクリプション管理
├── webhooks.ts # ウェブフック処理
├── customer-portal.ts # 顧客ポータル
└── testing.ts # テストユーティリティ
実装パターン
パターン 1: ワンタイム支払い(Checkout Sessions)
最もシンプルな実装パターンです。
// api/checkout.ts
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
export async function createCheckoutSession(
userId: string,
productId: string,
quantity: number
) {
// 商品情報を取得
const product = await getProductFromDatabase(productId);
// Checkout Session を作成
const session = await stripe.checkout.sessions.create({
// 支払い方法
payment_method_types: ['card'],
// 商品情報
line_items: [
{
price_data: {
currency: 'jpy',
product_data: {
name: product.name,
images: [product.image_url],
description: product.description
},
unit_amount: Math.round(product.price * 100) // 円をセント単位に
},
quantity: quantity
}
],
// メタデータ(データベース連携用)
metadata: {
userId: userId,
productId: productId
},
// URLs
success_url: `${process.env.DOMAIN}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.DOMAIN}/cancel`,
// メール
customer_email: (await getUserEmail(userId)),
// 支払い後の動作
mode: 'payment',
payment_intent_data: {
setup_future_usage: 'off_session' // 将来の支払いに備える
}
});
return session;
}Antigravity での使用:
ユーザー: "Checkout Session を作成するコードを生成して"
Antigravity AI:
1. Stripe API リファレンスを参照
2. TypeScript 用 stripe-node ライブラリの最新署名を確認
3. 上記のコードを自動生成
4. エラーハンドリングを追加
5. テストコードを自動生成
パターン 2: サブスクリプション実装
定期課金はより複雑です。Antigravity が大きな価値を発揮します。
// api/subscriptions/create.ts
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
export async function createSubscription(
userId: string,
priceId: string, // 定期課金商品の ID
paymentMethodId: string
) {
// 既存の顧客を確認、なければ作成
let customer = await stripe.customers.list({
email: (await getUserEmail(userId)),
limit: 1
});
let customerId: string;
if (customer.data.length > 0) {
customerId = customer.data[0].id;
} else {
const newCustomer = await stripe.customers.create({
email: (await getUserEmail(userId)),
metadata: { userId: userId }
});
customerId = newCustomer.id;
}
// 支払い方法をアタッチ
await stripe.paymentMethods.attach(paymentMethodId, {
customer: customerId
});
// デフォルト支払い方法を設定
await stripe.customers.update(customerId, {
invoice_settings: {
default_payment_method: paymentMethodId
}
});
// サブスクリプションを作成
const subscription = await stripe.subscriptions.create({
customer: customerId,
items: [{ price: priceId }],
// 設定
payment_settings: {
save_default_payment_method: 'on_subscription'
},
// メタデータ
metadata: {
userId: userId
},
// 試用期間(オプション)
trial_period_days: 14,
// キャンセル時の動作
cancel_at_period_end: false
});
// データベースに保存
await saveSubscriptionToDatabase({
userId,
stripeSubscriptionId: subscription.id,
status: subscription.status
});
return subscription;
}キャンセル処理:
// api/subscriptions/cancel.ts
export async function cancelSubscription(
userId: string,
subscriptionId: string
) {
// 権限確認
const subscription = await stripe.subscriptions.retrieve(subscriptionId);
if (subscription.metadata?.userId !== userId) {
throw new Error('Unauthorized');
}
// キャンセル
const cancelled = await stripe.subscriptions.update(
subscriptionId,
{
cancel_at_period_end: true // 現在の請求期間終了時にキャンセル
}
);
// ユーザーに通知
await sendCancellationEmail(userId, cancelled.current_period_end);
return cancelled;
}パターン 3: ウェブフック処理
ウェブフック処理は Antigravity の AI が特に力を発揮する領域です。
// api/webhooks/stripe.ts
import { NextRequest, NextResponse } from 'next/server';
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
// Stripe イベント型を定義
interface StripeEvent extends Stripe.Event {
type:
| 'payment_intent.succeeded'
| 'payment_intent.payment_failed'
| 'customer.subscription.created'
| 'customer.subscription.deleted'
| 'customer.subscription.updated'
| 'invoice.payment_succeeded'
| 'invoice.payment_failed';
}
// ウェブフックハンドラーマップ
const eventHandlers: Record<
string,
(event: Stripe.Event) => Promise<void>
> = {
'payment_intent.succeeded': handlePaymentIntentSucceeded,
'payment_intent.payment_failed': handlePaymentIntentFailed,
'customer.subscription.created': handleSubscriptionCreated,
'customer.subscription.deleted': handleSubscriptionDeleted,
'customer.subscription.updated': handleSubscriptionUpdated,
'invoice.payment_succeeded': handleInvoicePaymentSucceeded,
'invoice.payment_failed': handleInvoicePaymentFailed
};
export async function POST(request: NextRequest) {
const body = await request.text();
const signature = request.headers.get('stripe-signature');
let event: Stripe.Event;
try {
// Stripe 署名を検証
event = stripe.webhooks.constructEvent(
body,
signature,
webhookSecret
);
} catch (error) {
console.error('⚠️ Webhook signature verification failed', error);
return NextResponse.json(
{ error: 'Invalid signature' },
{ status: 400 }
);
}
// イベントハンドラーを実行
const handler = eventHandlers[event.type];
if (!handler) {
console.warn(`Unhandled event type: ${event.type}`);
return NextResponse.json({ received: true });
}
try {
await handler(event);
} catch (error) {
console.error(`Error handling ${event.type}:`, error);
// エラーログは保存するが、200 OK を返す(Stripe は再試行しない)
// または、デッドレターキューに入れる
}
return NextResponse.json({ received: true });
}
async function handlePaymentIntentSucceeded(event: Stripe.Event) {
const paymentIntent = event.data.object as Stripe.PaymentIntent;
// 購入を確定
const userId = paymentIntent.metadata?.userId;
await confirmPurchase(userId, paymentIntent.id);
// メール送信
const user = await getUserById(userId);
await sendOrderConfirmationEmail(user.email, paymentIntent);
// アクセス権を付与
if (paymentIntent.metadata?.productId) {
await grantProductAccess(userId, paymentIntent.metadata.productId);
}
}
async function handlePaymentIntentFailed(event: Stripe.Event) {
const paymentIntent = event.data.object as Stripe.PaymentIntent;
const userId = paymentIntent.metadata?.userId;
// 支払い失敗をログ
await logPaymentFailure(userId, paymentIntent);
// ユーザーに通知
const user = await getUserById(userId);
await sendPaymentFailureEmail(
user.email,
paymentIntent.last_payment_error?.message
);
}
async function handleSubscriptionCreated(event: Stripe.Event) {
const subscription = event.data.object as Stripe.Subscription;
const userId = subscription.metadata?.userId;
// サブスクリプションをデータベースに保存
await saveSubscriptionToDatabase({
userId,
stripeSubscriptionId: subscription.id,
status: subscription.status,
currentPeriodStart: new Date(subscription.current_period_start * 1000),
currentPeriodEnd: new Date(subscription.current_period_end * 1000)
});
// アクセス権を付与
const priceId = (subscription.items.data[0].price.id);
const planName = await getPlanNameByPriceId(priceId);
await grantSubscriptionAccess(userId, planName);
}
async function handleSubscriptionDeleted(event: Stripe.Event) {
const subscription = event.data.object as Stripe.Subscription;
const userId = subscription.metadata?.userId;
// サブスクリプションをキャンセル
await cancelUserSubscription(userId);
// アクセス権を削除
await revokeSubscriptionAccess(userId);
// メール送信
const user = await getUserById(userId);
await sendSubscriptionCancelledEmail(user.email);
}パターン 4: 顧客ポータル
Stripe Customer Portal は、顧客が自分で支払い方法やサブスクリプションを管理できるホストされたページです。
// api/customer-portal.ts
export async function createCustomerPortalSession(userId: string) {
const user = await getUserById(userId);
// Stripe 顧客 ID を取得
const customers = await stripe.customers.list({
email: user.email,
limit: 1
});
if (customers.data.length === 0) {
throw new Error('Customer not found in Stripe');
}
const customerId = customers.data[0].id;
// ポータルセッションを作成
const portalSession = await stripe.billingPortal.sessions.create({
customer: customerId,
return_url: `${process.env.DOMAIN}/account/billing`,
// 利用可能な機能を制限(オプション)
configuration: process.env.STRIPE_PORTAL_CONFIG_ID
});
return portalSession.url;
}Stripe Dashboard で Porter Configuration を作成し、以下を設定:
- インボイス履歴の表示
- 支払い方法の管理
- サブスクリプション管理(キャンセル、プランの変更など)
Stripe CLI を使ったローカル開発
# Stripe CLI をインストール
brew install stripe/stripe-cli/stripe
# API キーでログイン
stripe login
# ウェブフックをシミュレート
stripe listen --forward-to localhost:3000/api/webhooks/stripe
# テストイベントを送信
stripe trigger payment_intent.succeeded
stripe trigger customer.subscription.createdAntigravity での自動テスト生成:
ユーザー: "Stripe CLI を使ったウェブフックテストを生成して"
Antigravity AI:
1. Stripe CLI のドキュメントを参照
2. 全ウェブフックイベントのテストケースを生成
3. テスト用のモックデータを作成
4. Jest テストスイートを自動生成
生成されるテスト例:
// api/webhooks/__tests__/stripe.test.ts
describe('Stripe Webhooks', () => {
it('handles payment_intent.succeeded', async () => {
const event = {
id: 'evt_test_123',
type: 'payment_intent.succeeded',
data: {
object: {
id: 'pi_test_123',
amount: 5000,
metadata: { userId: '123' }
}
}
};
const response = await POST(
createMockRequest('stripe', event)
);
expect(response.status).toBe(200);
// 購入が確定されたことを確認
const purchase = await getPurchase('123', 'pi_test_123');
expect(purchase.status).toBe('confirmed');
});
});セキュリティベストプラクティス
絶対に避けるべきこと
// ❌ カード情報をサーバーで処理
export async function processPayment(cardDetails) {
// サーバーは絶対にカード情報を見てはいけない
const response = await stripe.charges.create({
amount: 5000,
currency: 'jpy',
card: cardDetails // 危険!PCI 違反
});
}
// ✅ Stripe.js でトークン化
// クライアントサイドで実行
const { token } = await stripe.createToken(cardElement);
// サーバーはトークンのみ処理
export async function processPayment(token) {
const charge = await stripe.charges.create({
amount: 5000,
currency: 'jpy',
source: token // 安全
});
}セキュリティチェックリスト
security_checklist:
- ✅ Stripe API キーを環境変数に(ハードコード禁止)
- ✅ クライアントサイドでは Publishable Key のみ使用
- ✅ サーバーサイドでは Secret Key のみ使用
- ✅ ウェブフック署名を必ず検証
- ✅ HTTPS を必須化
- ✅ PCI コンプライアンスの確認
- ✅ Webhook イベントは冪等に処理(重複実行に対応)
- ✅ エラーメッセージで詳細情報を露出させない
- ✅ Rate Limiting を実装(ブルートフォース対策)
- ✅ ログに機密情報を記録しないトラブルシューティング
よくあるエラーと対応
エラー: "No such customer"
原因: Stripe 顧客が存在しない
対策: stripe.customers.create() で新規作成
エラー: "Webhook signature verification failed"
原因: 環境変数 STRIPE_WEBHOOK_SECRET が間違っている
対策: Dashboard から正しい値をコピー
エラー: "Your card was declined"
原因: テストカード情報が間違っているか、Stripe の要件を満たしていない
対策: テスト用カード(4242...)を使用、または 3D Secure で認証
全体を振り返って
Antigravity を活用することで、Stripe 決済実装は以下のように効率化されます:
- 実装時間 60-70% 削減 — API リファレンス自動参照、コード自動生成
- セキュリティ向上 — ベストプラクティスの自動適用
- テスト完全自動化 — テストケース生成、CI/CD 統合
- トラブルシューティング高速化 — エラーの原因を即座に特定
Stripe + Antigravity の組み合わせで、安全で信頼性の高い決済システムを、スピーディーに構築できます。今すぐプロジェクトに組み込みましょう。