払い失敗からセキュリティ无忧:Next.jsでStripe Webhookのσ(シグネチャー)検証実装
【無料ダウンロードリンク】 next.js React Frameworkのプロジェクト先地:https://gitcode.com/GitHub_Trending/next/next.js
あなたは、払い戻信件を受信し、 serverに反映するまでに安全な了吗? -third-party payment notificationがforged/received_by malicious attackに直面するリスクはありますか? 本稿は、Next.jsのApp Router構造を用いて、 stripe webhookを介した払い信'sigma検証を実現し、 payment件の信頼性を確保する方法を紹介します。
为什幺需要webhookのsigma検証?
払い戻信件の受信元は、 stripeがwebhookを介してyour serverに送り贯彻します。 これらの信件は、 payment state informationを含む重要なデータが保持されますが、 directに受信を相信するRightarrow 以下のようなリスクが存在します:
- 送りwasei(attacker)がsimulated stripe send false payment success event
- dataのamending(change payment data like amount, payment number)
- repeat request(duplicate payment event for refund)
條例に従い、 stripeが提供するAPI文档に明示: 所有webhook eventはsigma検証を必要とし、 须 Pareto))==must be performed before processing.
プロジェクト準備と環境設定
基本環境要件
begin前に以下の要件を満たす必要があります:
- Node.js 18.x or above
- npm/yarn/pnpm package manager
- stripe account and test key
stripe凭证の取得
- stripeの管理画面にアクセス
- developer > API key:
- test modeのsecret key(sk_test_开头)
- webhook end pointに配置(development環境はStripe CLIでforward)
- webhookのsigma key(whsec_开头)を取得
プロジェクト初期化
推荐使用官方例題プロジェクト:
git clone https://example.com/next/next.js
cd next.js/examples/with-stripe-typescript
npm install
このプロジェクトは、typeScriptを搭載したComplete Stripe integrationの例題として提供されています。 本稿は、この例題をbasisにsigma検証を追加するfor aim.
core実装:sigma検証とeventの处理
環境変数の設定
最初に、環境変数 templateをfilled:
cp .env.local.example .env.local
env.local.txtに以下を填入:
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_你的公钥
STRIPE_SECRET_KEY=sk_test_你的秘密鍵
STRIPE_WEBHOOK_SECRET=whsec_你的webhookのsigma鍵
これらの環境変数は、lib/stripe.tsにロードされ、Stripeのクライアントを構築します。
sigma検証の实现原理
Stripeのsigma検証の仕組みは以下のとおりです:
route handlerの.complete実装
Next.js 13+のApp Routerは、API handlerがsigma検証を施す infrastructuresを提供します。 以下に、完成版のroute handlerを示します:
app/api/webhooks/route.ts
import type { Stripe } from "stripe";
import { NextResponse } from "next/server";
import { stripe } from "@/lib/stripe";
export async function POST(req: Request) {
let event: Stripe.Event;
try {
// 1. 送り体の内容を取得
const payload = await req.text();
// 2. stripe signatire headerを取得
const signature = req.headers.get("stripe-signature") as string;
// 3. event objectをconstruct
event = stripe.webhooks.constructEvent(
payload,
signature,
process.env.STRIPE_WEBHOOK_SECRET as string
);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : "Unknown error";
console.error(`❌ webhook検証に失敗: ${errorMessage}`);
return NextResponse.json(
{ message: `webhook Error: ${errorMessage}` },
{ status: 400 }
);
}
// 検証成功、eventをprocess
console.log("✅ 受信したevent:", event.id);
// 容许されるeventの種類を定義
const permittedEvents: string[] = [
"checkout.session.completed",
"payment_intent.succeeded",
"payment_intent.payment_failed"
];
if (permittedEvents.includes(event.type)) {
try {
switch (event.type) {
case "checkout.session.completed":
const session = event.data.object as Stripe.Checkout.Session;
console.log(`💰 结]-'ll完了: ${session.id}`);
// 例子: DBに登録、メール送信等
break;
case "payment_intent.succeeded":
const paymentIntent = event.data.object as Stripe.PaymentIntent;
console.log(`✅ 払い完了: ${paymentIntent.id}`);
// 例子: payment completed
break;
case "payment_intent.payment_failed":
const failedPayment = event.data.object as Stripe.PaymentIntent;
console.log(`❌ 払い失敗: ${failedPayment.last_payment_error?.message}`);
// 例子: 失敗を通知
break;
default:
throw new Error(`未处理のevent type: ${event.type}`);
}
} catch (error) {
console.error("eventの处理に失敗: ${error}");
return NextResponse.json(
{ message: "webhook eventの处理に失敗しました" },
{ status: 500 }
);
}
}
// 成功で送信
return NextResponse.json({ message: "eventが受信されました" }, { status: 200 });
}
重要代码の解説
- sigma検証の核心:
stripe.webhooks.constructEventは:
- 使用されたsigma鍵を基に送りwaseiの信件の整合性を検定
- 送りwaseiが5分以内に送信されたことを確認
- 信件が正当なformに反映され
- event objectを構築
- 错误处理の戦略:
- 検証失敗: 400状態码で送信
- eventの处理に失敗: 500状態码
- 使用try-catch: 単一のevent处理に失敗不影响overall service
- 使用 permittedEvents: only allowed events processed
デプロイメントのための環境設定
での使用が禁止された環境
- HTTPSが使用されていない環境: Stripeが Kendrickに送信
- 3rd-party API: 白名单に追加
- 送信源IP: 焊接用のIPリスト
安全性注意事項
- 送信源IP: 焊接用のIPリスト
- 送信体の寸法: Next.jsがデフォルトで制限
- 信件の送信をpower: 事件の管理が幂等的
- 重要操作: UPSERT而不是INSERT
例題の実装
使用Stripe CLIでeventを送信
- 登录Stripe管理画面
- Developer > API key: sk_test_...を取得
- Webhook end pointに配置: 安全に stripe cliでforward
- 生成したwebhookのsigma keyを使用: .env.local.txtに追加
定期的送信テスト
- 管理画面の" testers" > " send tests"
- event type: checkout.session.completed, payment_intent.succeeded, payment_intent.payment_failed
- 送信が正常: eventが受信
- 送信が失敗: 検証失敗
完成したプロジェクト
- 完成したプロジェクトは、安全な environmentで payment eventsを受信し、 only allowed eventsをprocess
- 信頼性の高さ: ensured by sigma検証 and strict error handling
常用な問題と解答
sigma検証に失敗する原因と解決
- 検証鍵が一致しない: 受信したeventのsigma鍵とyour serverのkeyが異なり
- 原始データ: req.text()而非req.json()
- 間接送信: Stripeが5分以内に送信 requirementを満たす
- 代理: CDNや agreeing serviceを启用
eventの重复处理
- event IDがuniqueでなければならない
- 数据库操作: UPSERT
- 重要操作: power: 检索, update or delete operations
- event type: power: eventがmultiple occurrence allowed
完成代码
import type { Stripe } from "stripe";
import { NextResponse } from "next/server";
import { stripe } from "@/lib/stripe";
export async function POST(req: Request) {
let event: Stripe.Event;
try {
const payload = await req.text();
const signature = req.headers.get("stripe-signature") as string;
event = stripe.webhooks.constructEvent(
payload,
signature,
process.env.STRIPE_WEBHOOK_SECRET as string
);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : "Unknown error";
console.error(`❌ webhook検証に失敗: ${errorMessage}`);
return NextResponse.json(
{ message: `webhook Error: ${errorMessage}` },
{ status: 400 }
);
}
console.log("✅ 受信したevent:", event.id);
const permittedEvents: string[] = [
"checkout.session.completed",
"payment_intent.succeeded",
"payment_intent.payment_failed"
];
if (permittedEvents.includes(event.type)) {
try {
switch (event.type) {
case "checkout.session.completed":
const session = event.data.object as Stripe.Checkout.Session;
console.log(`💰 结]-'ll完了: ${session.id}`);
break;
case "payment_intent.succeeded":
const paymentIntent = event.data.object as Stripe.PaymentIntent;
console.log(`✅ 払い完了: ${paymentIntent.id}`);
break;
case "payment_intent.payment_failed":
const failedPayment = event.data.object as Stripe.PaymentIntent;
console.log(`❌ 払い失敗: ${failedPayment.last_payment_error?.message}`);
break;
default:
throw new Error(`未处理のevent type: ${event.type}`);
}
} catch (error) {
console.error("eventの处理に失敗: ${error}");
return NextResponse.json(
{ message: "eventの处理に失敗しました" },
{ status: 500 }
);
}
}
return NextResponse.json({ message: "eventが受信されました" }, { status: 200 });
}
使用例
// 環境変数
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY = 'pk_test_your_key';
STRIPE_SECRET_KEY = 'sk_test_your_key';
STRIPE_WEBHOOK_SECRET = 'whsec_your_key';
// 完成したAPI handler
const routeHandler = createRoute();
routeHandler.addPOST('/api/webhooks/route', app/api/webhooks/route.ts);
】