オンライン決済における高いリスク
お金を扱う処理は非常に神経を使うものです。ロジックの些細なミスがUIのクラッシュだけでなく、収益の損失や重大なセキュリティ侵害につながる可能性があります。多くの開発者は、厳格なPCI DSS準拠ルールやクレジットカード情報の漏洩リスクから、決済システムの構築を敬遠しがちです。
Stripeは、セキュアな仲介役としてこの問題を解決します。自社のデータベースに16桁のカード番号を保存するという大きなリスクを負う代わりに、そのリスクをStripeのインフラにオフロードできます。Stripe CheckoutとWebhookを組み合わせることで、メンテナンスが容易で、正しく設定すれば偽装がほぼ不可能なシステムを構築できます。
Stripeのフローの仕組み
このプロセスは、顧客向けの「ハンドオフ」とバックエンドの「確認」という2つの明確なステージに分けて考えます。データの整合性を保つためには、この分離を理解することが不可欠です。
1. Checkoutセッション
独自のクレジットカード入力フォームを作成するのは、不慣れな人にとっては罠となります。Stripe Checkoutは、Apple PayやGoogle Pay、iDEALなどのローカルな決済手段をサポートし、コンバージョンが最適化された既製のページを提供します。ユーザーをStripeがホストするURLにリダイレクトするだけで済むため、サーバーを機密データの処理範囲から完全に外すことができます。
2. Webhookという安全網
データベースの更新をブラウザのリダイレクトのみに頼るのは危険です。顧客のPCのバッテリーが切れたり、決済ボタンを押した瞬間にインターネットが切断されたりすると、成功ページが読み込まれず、データベースも更新されません。Webhookは、Stripeからサーバーへ直接非同期のHTTP POSTリクエストを送信することで、この問題を解決します。これはユーザーの不安定なブラウザ接続を介さない、サーバー間での確実な確認手段です。
環境構築
Stripeの開発者ダッシュボードからシークレットキーとWebhookシークレットを取得します。これらをハードコードしてはいけません。.envファイルを使用して、認証情報をバージョン管理に含めないようにしてください。
STRIPE_SECRET_KEY=sk_test_51Mz...YourKey
STRIPE_WEBHOOK_SECRET=whsec_...YourSecret
DOMAIN=http://localhost:3000
プロジェクトを初期化し、コアライブラリをインストールします。stripeパッケージが、複雑なAPI通信を肩代わりしてくれます。
bash
npm install stripe express dotenv
ステップ1:Checkoutセッションの作成
バックエンドには、セキュアな決済URLを生成するためのエンドポイントが必要です。Stripeでは、金額を最小通貨単位で指定する必要があります。例えば、JavaScriptでよくある浮動小数点数の計算エラーを避けるため、$20.00は2000セントとして渡す必要があります。
javascript
const express = require('express');
const app = express();
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
app.post('/create-checkout-session', async (req, res) => {
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: [
{
price_data: {
currency: 'usd',
product_data: { name: 'プレミアムサブスクリプション' },
unit_amount: 2000, // セント単位での$20.00
},
quantity: 1,
},
],
mode: 'payment',
success_url: `${process.env.DOMAIN}/success.html`,
cancel_url: `${process.env.DOMAIN}/cancel.html`,
});
res.json({ url: session.url });
});
フロントエンドはこのURLを取得してユーザーをリダイレクトするだけです。このアプローチにより、バックエンドをクリーンに保ち、セキュリティ上のリスクを最小限に抑えることができます。
ステップ2:Webhookのセキュリティ保護
ここが多くの連携で失敗するポイントです。Stripeが/webhookエンドポイントにアクセスしてきた際、リクエストの署名を検証する必要があります。これを行わないと、誰でも偽の「決済完了」JSONをサーバーに送信して、商品を無料で手に入れることができてしまいます。
重要なのは、署名の検証にはStripeがリクエストの生のボディ(raw body)を必要とすることです。express.json()をグローバルに使用していると検証に失敗するため、このルートには専用にexpress.rawを使用します。
javascript
app.post('/webhook', express.raw({ type: 'application/json' }), (request, response) => {
const sig = request.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(
request.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
console.error(`検証に失敗しました: ${err.message}`);
return response.status(400).send(`Webhookエラー: ${err.message}`);
}
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
// ここでデータベースを更新
console.log(`注文 ${session.id} の処理が完了しました!`);
}
response.json({ received: true });
});
ステップ3:Stripe CLIによるローカルテスト
かつてlocalhostでWebhookをテストするには、Ngrokのような複雑なツールが必要でした。今ではStripe CLIを使って簡単にテストできます。CLIがセキュアなトンネルを作成し、イベントをローカルマシンに直接転送してくれます。
- Stripe CLIをインストールし、
stripe loginを実行します。 - イベントの転送を開始します:
stripe listen --forward-to localhost:3000/webhook
CLIからローカル用のWebhookシークレットが提供されるので、それを.envファイルに設定します。これで、テスト決済を実行し、Node.jsのコンソールがリアルタイムで反応するのを確認できるようになります。
本番環境への準備
本番環境に移行する前に、以下の3つの実務的なポイントを確認してください。
- べき等性(Idempotency): Stripeは稀に同じWebhookを2回送信することがあります。ロジックでは、処理を行う前にその注文がすでに「支払い済み」とマークされていないか確認する必要があります。
- ステータスコード: 常に素早く
200 OKを返してください。内部処理(重いメール送信など)に時間がかかりすぎると、Stripeはタイムアウトと判断してイベントを再試行する可能性があります。 - ロギング: Stripeの
session.idをデータベースに保存してください。顧客から問い合わせがあった際、このIDが自社のデータとStripeダッシュボードを紐付ける唯一の手段になります。
最後に
堅牢な決済システムは、UIとビジネスロジックを分離します。インターフェースには Checkoutセッションを、データ処理にはWebhookを使用することで、ユーザーとビジネスの両方を守ることができます。まずはCLIを通じて1件のテスト決済を通すことから始めてみてください。署名検証が確立されれば、数千件のトランザクションにも耐えうる基盤が整ったことになります。

