Stripe APIは、決済・顧客・返金などのオブジェクトをHTTPSのリクエストで操作するREST形式のAPIです。カード決済を自社の画面に組み込むときの中心はPayment Intents APIで、サーバーで決済の意図(PaymentIntent)を作り、画面側で確定させる流れになります。この記事では、APIキーの選び方と認証、stripe-node 23系でのAPIバージョン固定、PaymentIntentの作成とステータス遷移、冪等キーによる二重決済の防止、エラー処理とレート制限、一覧取得とexpandまでを、curlとNode.jsのコードで解説します。仕様の確認元は2026年10月時点の公式ドキュメントです。
まとめ|Stripe API実装で外せないAPIキー・PaymentIntent・冪等キーの要点
サーバーには制限付きキー(rk_)かシークレットキー(sk_)を環境変数や保管庫から渡し、画面には公開可能キー(pk_)とPaymentIntentのclient_secretだけを渡します。金額は必ずサーバー側の注文データから決めます。
決済を作るPOSTには冪等キーを付けます。通信が切れても同じキーで再送すれば、決済が2件できることはありません。SDKを初期化するときはapiVersionを明示して、ライブラリ更新でAPIの挙動が変わらないようにします。
決済の成否はAPIの戻り値だけで確定させず、Webhookで受け取って注文を確定します。自社画面で体験を作り込む要件がなければ、PaymentIntentsを直接書かずにCheckoutで止めるのが手堅い選択です。
Stripe APIを呼ぶ前の準備:APIキーの種類と認証方式・SDKの導入
最初に決めるのは、どのキーをどこに置くかです。ここを誤るとキーの漏えいがそのまま不正請求につながります。
pk_・sk_・rk_の3種類のAPIキーと制限付きキーを選ぶ基準
StripeのAPIキーの公式解説によると、キーは公開可能キー(pk_)、シークレットキー(sk_)、制限付きキー(rk_)に分かれ、サンドボックスではpk_test_のように_test_、本番では_live_が付きます。ブラウザやアプリに含めてよいのは公開可能キーだけです。
見落としやすいのは、同ページがシークレットキーを新しい用途に使うことを推奨しておらず、既存の実装も制限付きキーへ移すよう勧めている点です。決済作成だけを行うサーバーなら、PaymentIntentsの書き込み権限だけを持つ制限付きキーを発行します。キーの値はキー管理のベストプラクティスのとおり、AWS Secrets Managerなどの保管庫か環境変数から読み、ソースコードやCIの設定ファイルに書きません。AIエージェントへ渡すキーの種類と2026年11月30日までの移行はStripe MCPの使い方:接続手順とエージェントAPIキーの権限設計にまとめています。
HTTP Basic認証で最初のリクエストを送るcurlコマンドの書き方
認証に関するAPIリファレンスによると、Stripe APIはHTTP Basic認証を使い、APIキーをユーザー名として渡します。パスワードは不要です。HTTPではなくHTTPSで送らないとリクエストは失敗します。疎通確認には、残高を取得する読み取り専用のリクエストが向いています。
# キーは環境変数から渡す(末尾のコロンはパスワードが空という意味)
curl https://api.stripe.com/v1/balance \
-u "$STRIPE_SECRET_KEY:"
# Bearer形式で送る場合
curl https://api.stripe.com/v1/balance \
-H "Authorization: Bearer $STRIPE_SECRET_KEY"
キーが無効なら401、キーに権限が足りなければ403が返ります。制限付きキーで403が出たら、ダッシュボードでそのキーの権限を確かめてください。
stripe-node 23系のインストールとAPIバージョン2026-09-30の固定
Node.jsではStripe公式のNode.jsライブラリを使います。READMEでは対応環境をNode.js 20以上のLTSとしており、npmの最新版は2026年10月時点で23.0.0系です。CHANGELOGによると23.0.0(2026-09-30)で、ライブラリが固定するAPIバージョンが2026-09-30.endiveに変わりました。
バージョニングのAPIリファレンスでは、curlのリクエストはアカウントの既定バージョンで処理され、Stripe-Versionヘッダーで上書きできるとしています。SDKでは初期化時にapiVersionを書いておくと、どのバージョンを前提としたコードかがソース上で明確です。Webhookのイベントはエンドポイント作成時に指定したバージョンで届くため、API側とそろえておきます。日付ベースのバージョン管理の考え方は親記事のStripeとは?仕組み・手数料・実装パターンで解説しています。
PaymentIntents APIで決済を作る:作成・確定・ステータス遷移の実装手順
Payment Intents APIの公式ガイドは、組み込みに必要な操作をPaymentIntentの作成と確定の2つとしています。作成はサーバー、確定は多くの場合ブラウザ側のStripe.jsが担います。
PaymentIntent作成時にclient_secretだけを画面へ返す実装
次の例はExpressを使い、サーバー側で取得した注文データに基づいて注文ごとのPaymentIntentを作り、画面側に返す情報を絞ってclient_secretだけを返します。findOrderは自社の注文取得処理を表す仮の関数です。
import express from 'express';
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
apiVersion: '2026-09-30.endive', // stripe-node 23.0.0 の固定版と同じ値を明示する
maxNetworkRetries: 2, // 通信失敗時の自動再試行回数(既定は1)
});
const app = express();
app.post('/api/orders/:orderId/payment-intent', async (req, res) => {
// 金額は画面から受け取らず、サーバー側の注文データから決める
const order = await findOrder(req.params.orderId);
const paymentIntent = await stripe.paymentIntents.create(
{
amount: order.totalYen, // 日本円は小数点なし。1,980円なら1980
currency: 'jpy',
automatic_payment_methods: { enabled: true },
metadata: { order_id: order.id },
},
// 注文作成時に crypto.randomUUID() で発行して保存しておいた値
{ idempotencyKey: order.paymentIdempotencyKey }
);
res.json({ clientSecret: paymentIntent.client_secret });
});
PaymentIntentオブジェクトをそのまま画面へ返さないのは、公式ガイドがclient_secretだけを渡す方式を案内しているためです。同ガイドは、client_secretをログに記録したりURLに埋め込んだりしないよう注意しています。automatic_payment_methodsを有効にすると、ダッシュボードで有効にした決済手段が自動で候補になります。
決済作成から成功・拒否までの7つのステータスと自社システムの分岐
PaymentIntentのライフサイクル解説によると、ステータスはrequires_payment_method、requires_confirmation、requires_action、processing、requires_capture、succeeded、canceledの7つです。実装で分岐を書くべきは次の4つに絞れます。
| ステータス | 起きている状況 | 自社システムの処理 |
|---|---|---|
| requires_action | 3Dセキュア認証など利用者の操作待ち | Stripe.jsに認証画面を出させる |
| processing | 口座振替など非同期の決済手段で処理中 | 注文を「入金待ち」にして待つ |
| succeeded | 決済完了・資金はアカウントにある | 注文を確定し発送へ進める |
| requires_payment_method | 作成直後、または決済が拒否された | 別の決済手段の入力を促す |
決済が拒否されるとPaymentIntentは失敗にならず、requires_payment_methodへ戻って再試行を待ちます。「失敗」というステータスを探す実装は動きません。succeededの検知はブラウザからの通知に頼らず、payment_intent.succeededのWebhookで行います。署名検証と二重処理を防ぐ受け側は決済Webhookの署名検証と冪等な注文確定で扱っています。3Dセキュアでrequires_actionになる流れはEMV 3-Dセキュアの仕組みと実装が詳しいです。
日本円のamount指定とmetadataに注文IDを入れて照合する方法
金額の単位は通貨の補助単位です。対応通貨の公式ページでは日本円を小数点のない通貨としており、500円はamount=500と書きます。米ドルの感覚で100倍すると、100倍の金額を請求してしまいます。
注文との照合に使う項目はmetadataです。メタデータのAPIリファレンスによると、1オブジェクトに最大50キー、キー名40字・値500字まで保存できます。カード情報や口座番号などの機密情報は入れないよう明記されています。
冪等キーで二重決済を防ぐ:Idempotency-Keyの付け方と24時間の保存期間
決済APIで一番避けたい事故は、タイムアウト後の再送で同じ注文に2回請求することです。Stripeはこれを冪等キーで防ぎます。
注文ごとのUUIDを冪等キーにして再送時に同じ応答を受け取る実装
冪等リクエストのAPIリファレンスによると、API v1ではすべてのPOSTが冪等キーを受け付け、同じキーでの再送には1回目の応答(500エラーを含む)がそのまま返ります。キーの長さは最大255文字で、推奨されるのはV4 UUIDなど衝突しにくい値です。メールアドレスなどの個人情報はキーに使わないよう求めています。
curl https://api.stripe.com/v1/payment_intents \
-u "$STRIPE_SECRET_KEY:" \
-H "Idempotency-Key: 6f1c2a9e-3b7d-4e21-9a55-0c8d7e4b1f30" \
-d amount=1980 \
-d currency=jpy \
-d "automatic_payment_methods[enabled]=true" \
-d "metadata[order_id]=A-10293"
キーはリクエストのたびに作り直さず、注文を作った時点で発行して注文と一緒に保存します。再送のたびに新しいUUIDを作ると、冪等キーを付けていない場合と同じく決済が増えます。GETとDELETEはもともと冪等なので、キーを付けても効果はありません。
同じキーで金額を変えるとidempotency_errorになる条件と24時間の期限
同リファレンスによると、Stripeは再送のパラメータを1回目と比べ、異なればエラーを返します。カートの中身が変わって金額が変わったのに同じキーで作り直そうとすると、その結果はidempotency_errorです。金額を変えるなら、既存のPaymentIntentを更新するか、新しいキーで作り直します。
キーは24時間以上たつと削除される場合があり、その後に同じキーを送ると新しいリクエストとして処理されます。エラー処理の公式ガイドも、接続エラー後の再試行は元のリクエストから24時間以内に同じキーとパラメータで行うよう案内しています。パラメータの検証で失敗したリクエストや、同時実行と衝突したリクエストは結果が保存されないため、同じキーでの再試行が可能です。
エラー処理とリトライの実装:4種類のエラー型とHTTPステータスの扱い
Stripe APIのエラーは、型を見れば「利用者に伝えるもの」「再試行するもの」「コードを直すもの」に分けられます。
決済APIの4種類のエラー型とHTTPステータスに応じた画面の文言
エラーのAPIリファレンスでは、エラー型をcard_error、invalid_request_error、idempotency_error、api_errorの4つとしています。HTTPステータスは、パラメータは正しいがリクエストが失敗した402、別のリクエストと衝突した409、送信が多すぎる429などの区分です。Node.jsのライブラリでは、これらがStripeCardErrorなどの例外クラスとして投げられます。
async function chargeSavedCard(params, idempotencyKey) {
try {
return await stripe.paymentIntents.create(params, { idempotencyKey });
} catch (err) {
switch (err.type) {
case 'StripeCardError':
// カード発行会社の拒否。理由は decline_code で分岐し、別カードを促す
return { ok: false, reason: err.decline_code ?? err.code };
case 'StripeRateLimitError':
case 'StripeConnectionError':
case 'StripeAPIError':
// 結果が不明。同じ冪等キーで再試行するか、PaymentIntentを取得して状態を確かめる
throw err;
case 'StripeIdempotencyError':
case 'StripeInvalidRequestError':
// 実装側の誤り。再試行しても直らないので requestId を付けて記録する
console.error('Stripe API error', err.requestId, err.code);
throw err;
default:
throw err;
}
}
}
画面に出す文言は、残高不足や有効期限切れなど利用者が対処できる理由だけに絞ります。紛失・盗難を示す拒否理由をそのまま表示しない実装が一般的です。拒否理由ごとの動作はStripeテストカード一覧と使い方の番号でサンドボックスから再現できます。
429のレート制限:本番100件/秒・サンドボックス25件/秒への対処
レート制限の公式ページによると、全体の上限は本番が毎秒100リクエスト、サンドボックスが毎秒25リクエストです。個々のエンドポイントにも既定で毎秒25リクエストの上限があり、PaymentIntentは1件につき1時間あたり1,000回までしか更新できません。
制限に当たると429が返り、Stripe-Rate-Limited-Reasonヘッダーに全体超過(global-rate)かエンドポイント単位の超過(endpoint-rate)かが入ります。同ページは同時実行数を減らし、再試行にはジッター付きの指数バックオフを使うという推奨です。夜間バッチで数万件の顧客を更新するような処理は、並列数を上限から逆算して組みます。
SDKの自動リトライmaxNetworkRetriesと接続エラー後の結果照合
stripe-nodeのREADMEによると、v13以降は安全に再試行できる失敗を自動で1回再送し、その際に冪等キーを自動で付けます。回数はmaxNetworkRetriesで変えられ、1リクエストの待ち時間の既定は80,000ミリ秒です。前述のコードで2回にしているのは、決済作成の失敗を利用者に見せる頻度を下げるためです。
再試行しても接続エラーが続くときは、決済ができたかどうかを判断できない状態です。エラー処理ガイドは、この場合に対象オブジェクトを取得して状態を確かめるか、Webhookで結果を受け取って照合するよう案内しています。metadataに注文IDを入れておけば、ダッシュボードからも照合できます。
一覧取得と関連オブジェクトの展開:ページ分割とexpandで呼び出しを減らす
売上の突き合わせや管理画面の作成では、一覧取得と関連データの取得が増えます。呼び出し回数を抑える書き方を押さえておくと、前述のレート制限にも当たりにくくなります。
一覧APIのカーソル型ページ分割とfor awaitによる全件取得
ページ分割のAPIリファレンスによると、一覧APIはlimit(1〜100・既定10)と、オブジェクトIDを渡すstarting_after・ending_beforeのカーソル方式です。ページ番号での指定はできません。Node.jsのライブラリには、次のページを自動で取りに行く仕組みがあります。
// 直近の決済を100件ずつ自動でページ送りしながら取得する
for await (const pi of stripe.paymentIntents.list({
limit: 100,
expand: ['data.latest_charge'],
})) {
if (pi.status !== 'succeeded') continue;
await reconcile(pi.metadata.order_id, pi.amount, pi.latest_charge?.receipt_url);
}
reconcileは自社の照合処理を表す仮の関数です。limitを100にすると、既定の10件ずつに比べて呼び出し回数が10分の1になります。
expandで最大4階層の関連オブジェクトを1回の呼び出しで取得する
PaymentIntentのlatest_chargeやcustomerは、既定ではIDの文字列しか返りません。レスポンス展開のAPIリファレンスによると、expandに項目名を渡すと中身のオブジェクトに置き換わり、深さは4階層まで指定できます。一覧取得では前の例のようにdata.を先頭に付けます。
決済ごとにChargeとCustomerを別々に取得すると、100件の照合で200回の追加呼び出しが発生します。展開すれば一覧の呼び出しだけで済みます。
Stripe APIを直接実装するか、Checkoutや外部の開発会社に任せるかの判断基準
ここまでのコードを書けば決済は動きます。ただし、書いたコードはAPIバージョンの更新やWebhookの順序違いまで含めて保守し続けることになります。
PaymentIntentsの直接実装が合う条件と、Checkoutで止めるべき場面
PaymentIntentsを直接書く価値があるのは、決済画面を自社の購入フローに埋め込みたい場合と、金額が注文処理の途中で決まる場合です。予約の確定時に与信だけ取り後から売上を確定する(requires_captureを使う)フローも、直接実装でないと組めません。
単発販売で、決済画面がStripeのページでも困らないなら、Checkoutで止めるという判断です。冪等キー・ステータス分岐・3Dセキュアの処理の大半をStripe側が持つため、本稿で書いたコードの多くが不要になります。Checkoutのセッション作成とWebhookでの注文確定の書き方はStripe Checkoutの実装方法:ui_mode別のセッション作成とWebhookでの注文確定で解説しています。国内の決済代行と比べたい場合は、同じ観点でAPIを整理したPAY.JPのAPI実装とStripeとの選び分けが参考になります。
基幹連携・Connect・サブスクが重なる案件で外部に任せる目安
決済の結果を在庫・会計・基幹システムへ反映する、保存カードで継続課金する、出店者へ売上を分配する、の3つのうち2つ以上が重なると、冪等キーとWebhookの設計を誤ったときの影響が大きくなります。二重請求や入金済み未反映は、問い合わせ対応と返金処理のコストに直結するためです。継続課金はサブスクリプション課金の実装、分配はStripe Connectの資金フローで論点を整理しています。
社内に決済実装の経験者がいない場合は、設計段階だけでも外部に入ってもらうほうが、リリース後の障害対応より安く済みます。一創では決済・サブスクリプションシステムの開発として、Stripe APIを使った決済と既存システムの連携を設計から支援しています。カード情報をどこまで自社で扱うかの整理には、PCI DSSの要件と準拠レベルの記事が役立つはずです。
Stripe APIのよくある質問|キーの扱い・バージョン・決済の確定
Stripe APIを実装するときによく出る質問をまとめました。
Stripe APIはNode.js以外のどの言語から使えますか?
Stripe SDKの公式一覧には、サーバー側の公式ライブラリとしてRuby・Python・Go・Java・Node.js・PHP・.NETの7言語が載っています。2026年10月時点の版はNode.jsが23.0.0系、Pythonが16.0.0系です。互換性に関わるAPI変更があるとメジャー版が上がるため、更新時は変更履歴を確かめます。公式ライブラリが無い言語でも、本稿のcurlと同じHTTPリクエストを送れば呼び出せます。
シークレットキーをフロントエンドに置いてはいけないのはなぜですか?
シークレットキーはすべてのAPIを無制限に呼べるため、ブラウザから読み取られると返金や顧客データの取得まで第三者に許してしまいます。フロントエンドに置けるのは公開可能キー(pk_)だけです。Stripeは新しい用途にはシークレットキーより権限を絞った制限付きキー(rk_)を使うよう勧めています。
APIバージョンを上げるときは何に注意すればよいですか?
ライブラリのメジャー更新はAPIバージョンの変更を伴うことがあり、stripe-node 23.0.0では固定版が2026-09-30.endiveに変わりました。変更履歴で自社が使うオブジェクトの変更点を確かめ、サンドボックスで新しいバージョンを指定して動かしてから本番を切り替えます。Webhookのエンドポイントもバージョンを持つため、API側と合わせて更新します。
stripe paymentとPaymentIntentの違いは何ですか?
「stripe payment」はStripeでの決済全般を指す言葉として検索されることが多く、APIの実体としてはPaymentIntentが1回の決済の意図と状態を表すオブジェクトです。確定すると決済の記録であるChargeが作られ、PaymentIntentのlatest_chargeから参照できます。古い実装で使われていたCharges APIでの直接請求は、3Dセキュアなどの追加認証に対応しにくいため、新規実装ではPaymentIntentsを使います。
決済が成功したかどうかはAPIの戻り値で判断してよいですか?
戻り値だけで注文を確定するのは避けます。3Dセキュア認証や非同期の決済手段では、作成時点ではまだ成功していないためです。決済の確定はpayment_intent.succeededのWebhookを署名検証したうえで受け取り、同じイベントが2回届いても二重に処理しない作りにします。
関連記事
- Stripeとは?決済プラットフォームの仕組み・手数料・実装パターンを開発視点で解説:手数料と、Payment Links・Checkout・Elementsの実装方式の選び方。
- 決済Webhookとは?仕組み・主要イベントと署名検証・冪等な注文確定の実装を開発視点で解説:PaymentIntentの成功を受け取る側の実装。
- Stripeテストカード一覧と使い方:3Dセキュア・決済拒否・不審請求をテストする手順:本稿のコードを拒否理由別に試すテストカード。
- 3Dセキュアとは?EMV 3-Dセキュア2.0の仕組み・認証フローとEC決済への実装を開発視点で解説:requires_actionになる認証フローの仕組み。
- PAY.JPとは?3プランの手数料とAPI実装コード・Stripeとの選び分けを開発視点で解説:国内決済代行のAPIとの比較。