SB Payment リンク型決済(実装・運用ガイド)¶
このドキュメントの位置づけ¶
本ドキュメントは、Ritsubi の Vendure に実装している SB Payment 連携の 現在の実装仕様(リンク型・ブラウザPOST遷移)をまとめた運用ガイドです。
- 対象実装:
packages/plugins/src/payment-integration/sb-payment-link/ - 対象方式: リンク型(Hosted Payment Page)
- 更新日: 2026-05-22
結論(先に要点)¶
- 現在の実装は、
xmlapi.doを直接叩く方式ではなく、FepBuyInfoReceive.doへ フォームPOST するリンク型です。 pay_methodはcredit3d2を使用します。- SBPS の接続先は secret に
SB_PAYMENT_FEP_BASE_URLのドメイン部分だけを持たせ、 購入要求 / XML API / 顧客決済情報登録 / 定期申込の path は@ritsubi/config/specの定数から組み立てます。 - 顧客決済情報登録(
FepPayInfoReceive.do)を実装済みで、保存済みカードはCustomer.customFields.sbpsCustNo/sbpsPaymentNoを使って次回購入時に再利用します。 - Vendure Dashboard の Admin API には
buildSbpsCardRegistrationUrl(customerId)mutation を追加済みで、顧客ごとのカード登録URLを発行できます。 request_dateは SBPS の有効期限判定に使われるため、Vendure サーバーの timezone に依存させず JST 固定のyyyyMMddHHmmssで生成します。amountは SBPS 仕様に合わせて 円単位の整数 で送信します。Vendure のPaymentMethodHandler.createPaymentに渡る金額は JPY でも 100 倍整数の Vendure Money 値なので、SBPS 境界でtoYenFromVendureMoney()により円へ戻します。 変換はsb-payment-link.amount.tsに集約し、購入要求・結果 callback 検証・返金/取消 XML API のすべてで同じ変換を使います。- 2026-05-21 時点で、staging runtime は
SB_PAYMENT_SIMULATOR_MODE=falseとし、 外部 SBPS 試験 FEP(https://stbfep.sps-system.com)を叩く live-mode 構成です。 prod と同じ SBPS 認証情報を staging の Secrets Manager 正本へコピーしたうえで Fly secrets へ反映しています。 - URL責務は次の通りです。
SB_PAYMENT_CALLBACK_URL: Vendure(commerce系ドメイン)- Cloudflare 経由にする場合は、SBPS 試験環境通知元 IP
61.215.213.47を WAF / Bot / Access で許可する。許可できるまでは Fly origin 直指定で切り分ける。
- Cloudflare 経由にする場合は、SBPS 試験環境通知元 IP
SB_PAYMENT_CANCEL_URL: Storefront(order系ドメイン)- browser の最終戻り先(成功/エラー/キャンセル)は payment metadata の
returnUrl/errorUrl/cancelUrlに保持し、SBPS へは Vendure の redirect route を渡してから Storefront へ戻します。 - 環境設定は
stg/prdではなく、原則staging/prod命名で運用します。
対象決済手段(スコープ)¶
本プロジェクトのSB Payment連携は、当面 クレジットカード決済のみ を対象とします。
- 対象: クレジットカード(
pay_method=credit3d2) - 非対象(現時点): コンビニ、PayPay、WebMoney、NET CASH、BitCash など
- 現コード上の責務外: 銀行振込、代引き、売掛(これらは別ハンドラーとして登録)
- 運用判断: PayPay / コンビニ払いは本SB Payment実装のスコープ外として扱う
将来拡張を行う場合は、別途要件合意のうえでハンドラー拡張を実施します。
アーキテクチャ¶
採用方式¶
- 採用: 純粋リンク型(ブラウザPOST遷移)
- 非採用: XML API 直接連携(
/api/xmlapi.doを加盟店側で直接呼ぶ方式)
xmlapi.do が 405 になる理由¶
https://stbfep.sps-system.com/api/xmlapi.do
はブラウザで開くページではなく、APIエンドポイントです。GET でアクセスすると
405 Method Not Allowed になります。これは異常ではありません。
SBPS FEP endpoint の扱い¶
- secret / 環境変数で保持する正本:
SB_PAYMENT_FEP_BASE_URL - 試験環境:
https://stbfep.sps-system.com - 本番環境:
https://fep.sps-system.com - コード側で固定する path
- 購入要求:
/f01/FepBuyInfoReceive.do - 顧客決済情報登録(登録・更新):
/f04/FepPayInfoReceive.do - 顧客決済情報登録(削除):
/f04/FepPayInfoResign.do - 定期申込:
/f01/FepMrcInfoReceive.do - XML API:
/api/xmlapi.do SB_PAYMENT_FEP_BASE_URLを唯一の正本とし、SB_PAYMENT_LINK_URL/SB_PAYMENT_API_ENDPOINTは 廃止済みです。- ただし
staging/prodの live mode では endpoint secret の明示設定が必須で、 非 dev 環境で test endpoint 既定値へ暗黙 fallback してはいけません。
ドメインと責務¶
環境別ドメイン¶
- Storefront
- production:
https://order.ritsubi-platform.com - staging:
https://order-staging.ritsubi-platform.com - Vendure
- production:
https://commerce.ritsubi-platform.com - staging:
https://commerce-staging.ritsubi-platform.com - Assets
- production:
https://ec-assets.ritsubi-platform.com - staging:
https://ec-assets-staging.ritsubi-platform.com
SBPS URLパラメータの責務¶
pagecon_url: Vendure(commerce系ドメイン)- サーバー間通知の受け口は Vendure が担当
- Cloudflare 経由にする場合は SBPS 通知元 IP を許可する。接続支援サイトの
結果通知テストで
SERVER STATUS OK(200)になることを確認する。 success_url/error_url: Vendure(commerce系ドメイン)- Hosted page の戻り先は Vendure の
/sb-payment/success/:trackingId//sb-payment/error/:trackingIdを使い、そこから Storefront へ安全に redirect する cancel_url: Vendure(commerce系ドメイン)- Hosted page のキャンセル戻り先は
/sb-payment/cancel/:trackingIdに固定し、 そこから Storefront の最終戻り先へ redirect する
実装ファイル¶
- サービス本体
packages/plugins/src/payment-integration/sb-payment-link/sb-payment-link.service.ts- ハンドラー
packages/plugins/src/payment-integration/sb-payment-link/sb-payment-link.handler.ts- コールバックコントローラー
packages/plugins/src/payment-integration/sb-payment-link/sb-payment-callback.controller.ts- Dashboard 注文取消し override
packages/plugins/src/payment-integration/sb-payment-link/sb-payment-link.admin.resolver.ts- Customer customFields(保存カード参照)
apps/vendure-server/src/config/custom-fields/customer-custom-fields.ts- Customer customFields migration
apps/vendure-server/src/migrations/20260515000000_add_sbps_customer_card_fields.ts- プラグイン初期化
packages/plugins/src/payment-integration/index.ts- Dashboard 参照ブロック
packages/plugins/src/standard-extensions/admin-extensions/dashboard/components/OrderSbPaymentPageBlock.tsx
決済フロー(現実装)¶
- Storefront から Vendure に決済作成を要求
- Vendure が SBPS 向けのリクエストパラメータを組み立てる
sps_hashcodeを計算して付与- Vendure は
payment.metadata.publicに遷移情報を返す - ブラウザは
GET /sb-payment/redirect/:trackingIdを開く - サーバーが auto-submit HTML を返し、SBPS へ
POST遷移 - ユーザーが SBPS 画面で決済
- SBPS が
POST /sb-payment/callbackへ通知 - Hosted page の browser return は
/sb-payment/success/:trackingIdまたは/sb-payment/error/:trackingIdに戻る - Vendure が
PaymentをSettledへ遷移し、最終的に Storefront の/checkout/completeへ redirect する
補足:
- SBPS の購入結果 CGI レスポンス body は Shift-JIS の CSV 形式で返す。成功時は
OK、 失敗時はNG,理由を返す。 - SBPS 戻り電文の
res_tracking_idは SBPS 払出 ID であり、Vendure のPayment.transactionIdではない。Payment 検索には購入要求時にfree1/order_idへ入れた加盟店側 trackingId を使い、res_tracking_idは後続の 返金・売上確定 API 参照用に metadata へ保持する。 - 戻り電文の
sps_hashcodeはキー名ソートではなく、SBPS の項目定義順で 検証する。 - live mode の購入結果 callback では、SBPS から返る
amountを円単位として読み、 Vendure のPayment.amountを同じ helper で円へ戻した値と一致するか検証する。 不一致の場合はPaymentをSettledに進めず、Amount verification failedとして fail-closed する。これにより、Vendure Money 値を誤って100倍のまま送った取引や、 SBPS側の決済金額が注文金額とずれた取引を確定しない。
カード保存フロー(顧客決済情報登録)¶
- Vendure Dashboard / 運用API から
buildSbpsCardRegistrationUrl(customerId)を呼ぶ - 返却URL(
/sb-payment/payinfo/register/:token)を顧客に案内する。:tokenは admin (Permission.UpdateCustomer) のみが発行できる HMAC 署名付き token で、customerIdは URL に出さない。 - Vendure が
FepPayInfoReceive.do向け auto-submit form を返す - 顧客が SBPS Hosted Page でカード情報を登録する
- SBPS が
POST /sb-payment/payinfo/callbackへサーバー通知する - Vendure が
res_sps_cust_no/res_sps_payment_noをCustomer.customFieldsに保存する - 次回の通常決済(
FepBuyInfoReceive.do)では保存値をsps_cust_no/sps_payment_noとして自動送信する
[!IMPORTANT] カード番号等の機微情報は SBPS 側保持で、アプリ側には保存しません。 アプリ側で保持するのは
sps_cust_no/sps_payment_noの参照情報のみです。
simulatorMode(local / staging 明示切替)の補足¶
simulatorMode=trueを 明示設定した場合のみ、外部 SBPS へ直送せずGET /sb-payment/simulator/:trackingIdの local simulator を返す。- 現在のアプリ実装では
apps/vendure-server/src/vendure-config.shared.tsでSB_PAYMENT_SIMULATOR_MODEが未指定ならsimulatorMode: IS_DEV && !env.SB_PAYMENT_MERCHANT_IDを使い、local 開発だけ simulator を許可する。SB_PAYMENT_SIMULATOR_MODE=trueを明示した場合のみ、 staging など non-local 環境でも simulator を有効化できる。production での 常用は想定しない。 - staging / production で merchant 情報が欠けても simulator へ暗黙
フォールバックしない。切替は必ず
SB_PAYMENT_SIMULATOR_MODEの opt-in で行う。 - endpoint についても同じで、
staging/prodの live mode ではSB_PAYMENT_FEP_BASE_URLもしくは legacy endpoint secret を明示しない限り起動時に fail する。 import / startup resilience のための fallback を許容するのは local/dev だけです。 - 2026-05-21 時点の staging 環境は
SB_PAYMENT_SIMULATOR_MODE=falseで、 外部 SBPS 試験 FEP へ接続する構成です。staging dogfooding を simulator で安定化したい期間だけSB_PAYMENT_SIMULATOR_MODE=trueを明示 opt-in します。 - local simulator は Playwright のテストカード入力と同じ DOM id(
#ccNumber,#ccExpirationMonth,#ccExpirationYear,#securityCode,#exec-button)を持ち、POST /sb-payment/callbackを local で完了させる。 - テストカード欄は callback payload に送られないため、カード番号を
Payment.metadataに保存しない。 - simulator モードでは
addPaymentToOrderの際、注文のorderLaneTypeが"RESERVATION"の場合に metadata のsbPaymentPayType: "1"が含まれます。 この値はsettlePaymenthandler の三段分岐(NOP / 冪等スキップ / capture API 呼び出し) に使われるため、simulator を使った staging 上でのオーソリ+キャプチャーのフル動作確認が可能です。 2026-05-15 時点で staging 注文NetB2B_00100093を使い、sbCapturePaymentmutation →PaymentSettled遷移を確認済みです。
pay_method の方針¶
- 使用値:
credit3d2 - 背景:
- 試験環境で
creditだとres_err_code=2211(システムエラー)に該当 credit3d2だと「支払い情報の入力」画面まで遷移可能
設定¶
必須環境変数(Vendure)¶
SB_PAYMENT_MERCHANT_ID=...
SB_PAYMENT_SERVICE_ID=...
SB_PAYMENT_HASH_KEY=...
SB_PAYMENT_FEP_BASE_URL=https://stbfep.sps-system.com
SB_PAYMENT_XML_API_BASIC_USERNAME=...
SB_PAYMENT_XML_API_BASIC_PASSWORD=...
SB_PAYMENT_CALLBACK_URL=https://commerce-staging.ritsubi-platform.com/sb-payment/callback
SB_PAYMENT_CANCEL_URL=https://order-staging.ritsubi-platform.com/sbps/cancel
SB_PAYMENT_TDS2INFO_FREE_CSV_ENCODING=3des-base64
# 明示 opt-in 時のみ。staging dogfooding 用 simulator 切替
SB_PAYMENT_SIMULATOR_MODE=false
補足:
SB_PAYMENT_FEP_BASE_URLが唯一の正本staging/prodでSB_PAYMENT_SIMULATOR_MODE=trueを明示しない live mode では、SB_PAYMENT_FEP_BASE_URLが必須- live mode で 返金 API を使う場合は
SB_PAYMENT_XML_API_BASIC_USERNAME/SB_PAYMENT_XML_API_BASIC_PASSWORDが必須
production では以下を使用します。
SB_PAYMENT_FEP_BASE_URL=https://fep.sps-system.com
SB_PAYMENT_XML_API_BASIC_USERNAME=...
SB_PAYMENT_XML_API_BASIC_PASSWORD=...
SB_PAYMENT_CALLBACK_URL=https://commerce.ritsubi-platform.com/sb-payment/callback
SB_PAYMENT_CANCEL_URL=https://order.ritsubi-platform.com/sbps/cancel
Secrets config 命名¶
just レシピで使う設定名は以下を使用します。
- Storefront:
staging_storefront/prod_storefront - Vendure:
staging_vendure/prod_vendure
注意: 旧命名(stg_* / prd_*)との混在は設定漏れの原因になります。互換のため
production_* も受け付けるが、新規運用では prod_* に統一します。
追加で控えておくべき情報(再発防止)¶
- 本番向けの XML API 403 調査結果で特に重要なのは「egress IP と allowlist」の分離。
- Fly outbound(本番)は
209.71.88.227/2a09:8280:e625:1:0:a1:2053:0を固定。 - staging の outbound は
216.246.19.72(検証時点)で、staging では 403 での拒否は確認されていない。 - allowlist は SBPS 側で個別申請・反映必要。申請完了前は
cancel/refund/captureすべて失敗。 - 通知結果の最優先ルートは
SB_PAYMENT_CALLBACK_URL。 - Cloudflare 経由なら SBPS 側の通知元 IP 許可設定を確認(staging の検証観点として
61.215.213.47が想定)。 - callback が保存されない場合は、アプリ側より先に通知経路を切り分ける。
- staging/prod で最も事故が起きやすい差分
SB_PAYMENT_SIMULATOR_MODE(明示のみ有効)SB_PAYMENT_FEP_BASE_URL- TDS2INFO 設定の公開/秘密情報の揃い具合
- Cloudflare/WAF ルール(
/sbps/*の login リダイレクト) - 運用連絡テンプレ(SBPS 問い合わせ時)
- merchant:
59224 - service:
002 - pay_method:
credit3d2 - 直近再現注文コード
- 事象:
FepBuyInfoReceive→ 3D 認証完了 →FepBridgeAuthorityResult.doerror - callback 到達有無、
pagecon_url到達ログ - 固定 IP の運用追跡は issue #698 で継続管理。
Vendure 初期化例¶
apps/vendure-server/src/vendure-config.ts で以下のように有効化します。
実際の env var 読み込みと SbPaymentIntegrationPlugin.init() への引き渡しは
apps/vendure-server/src/config/plugins.ts が担当しています。
SbPaymentIntegrationPlugin.init({
simulatorMode:
process.env.SB_PAYMENT_SIMULATOR_MODE === undefined
? IS_DEV && !process.env.SB_PAYMENT_MERCHANT_ID
: ["true", "1", "yes"].includes(process.env.SB_PAYMENT_SIMULATOR_MODE.toLowerCase()),
fepBaseUrl: process.env.SB_PAYMENT_FEP_BASE_URL,
merchantId: process.env.SB_PAYMENT_MERCHANT_ID,
serviceId: process.env.SB_PAYMENT_SERVICE_ID,
hashKey: process.env.SB_PAYMENT_HASH_KEY,
apiBasicUsername: process.env.SB_PAYMENT_XML_API_BASIC_USERNAME,
apiBasicPassword: process.env.SB_PAYMENT_XML_API_BASIC_PASSWORD,
callbackUrl: process.env.SB_PAYMENT_CALLBACK_URL,
cancelUrl: process.env.SB_PAYMENT_CANCEL_URL,
});
コールバック仕様¶
エンドポイント¶
POST /sb-payment/callback- 成功:
200 OK+OK - 失敗:
400+NG GET /sb-payment/payinfo/register/:token- 顧客決済情報登録フォーム(auto-submit HTML)を返す。
:tokenはbuildSbpsCardRegistrationUrl(adminPermission.UpdateCustomer) が発行する HMAC 署名付き token。第三者が任意 customerId を指定して登録フォームを生成できないよう、customerId は URL に出さず token から server-side で解決する。既存sps_cust_no/sps_payment_noはブラウザに返さない。 - SBPS callback で送り返される
free1値もこの token であり、再度署名検証して customerId を解決する(callback は時間差が出るため expiry は無視し署名のみ検証)。 POST /sb-payment/payinfo/callback- 成功:
200 OK+OK - 失敗:
400+NG GET|POST /sb-payment/payinfo/success- カード登録成功メッセージを返す
GET|POST /sb-payment/payinfo/cancel- カード登録キャンセルメッセージを返す
GET|POST /sb-payment/payinfo/error- カード登録失敗メッセージを返す
GET|POST /sb-payment/success/:trackingId- Storefront の
returnUrlへ redirect GET|POST /sb-payment/error/:trackingId- Storefront の
errorUrlへ redirect POST /sb-payment/cancel200 OK+OK(ログ記録のみ)
動作上のポイント¶
callbackはフォームURLエンコード payload を受け取るtracking_id(またはfree1)で対象決済を特定res_result=OK以外は失敗扱い- ハッシュ検証失敗時は fail-closed で決済確定しない
- 顧客決済情報登録 callback では
res_sps_cust_no/res_sps_payment_noを受信し、free1(customerId)で特定した Customer に保存する - unsigned callback を許可するのは
simulatorMode=trueの local simulator のみ - browser return で使う redirect URL は、
SB_PAYMENT_CANCEL_URL/SB_PAYMENT_PUBLIC_BASE_URLと同一 origin のものだけを許可し、任意 origin への open redirect を許容しない
環境別運用ルール¶
- local
- merchant 情報未設定の通常開発では、明示設定された built-in local
simulator を使えるため、callback 検証に
Cloudflare Tunnelは不要 SB_PAYMENT_FEP_BASE_URL未設定でも plugin import / startup は継続するが、これは live 決済を許可する意味ではなく local 起動耐性のためだけの fallback- 実際の SBPS 試験環境まで疎通確認したい場合のみ、
Cloudflare Tunnelなどで callback URL を一時公開する - staging
- 恒久 URL(
*.ritsubi-platform.com)を使用 SB_PAYMENT_SIMULATOR_MODE=trueを明示していない live mode では、SB_PAYMENT_FEP_BASE_URLもしくは legacy endpoint secret の明示設定が必須- production
- 本番ドメインのみ使用
- staging と同様に live mode での endpoint fallback は禁止
Secrets 監査入口¶
Secrets Manager 側の配置と endpoint 要件は次で監査します。
just vendure-sb-payment-audit staging
just vendure-sb-payment-audit prod
この監査は次を確認します。
shared/vendure間に duplicateSB_PAYMENT_*key が無いことstaging/prodの live mode で endpoint secret が明示されていることstaging/prodの live mode で refund 用 XML API Basic auth secret が明示されていること- endpoint secret を省略する場合は
SB_PAYMENT_SIMULATOR_MODE=trueが明示されていること
staging 確認チェックリスト¶
https://commerce-staging.ritsubi-platform.com/shop-apiが200POST https://commerce-staging.ritsubi-platform.com/sb-payment/callbackが400(空POST時)SB_PAYMENT_CALLBACK_URLがcommerce-stagingを向いているSB_PAYMENT_CANCEL_URLがorder-stagingを向いている- リンク型開始時に SBPS 入力画面へ遷移できる
- 最新
payment.metadata.sbPaymentRequest.cancel_urlがhttps://commerce-staging.ritsubi-platform.com/sb-payment/cancel/:trackingIdを向いている checkout.sbps.real.spec.tsで/checkout/completeまで到達できるcheckout.sbps.real.spec.tsで/checkout/complete?status=cancelledまで到達できる
オーソリ+キャプチャー(pay_type=1)追加チェック¶
- 予約注文(
orderLaneType: "RESERVATION")でaddPaymentToOrderを実行し、payment.metadata.sbPaymentPayType === "1"が返ること - 通常注文(
orderLaneType: "NORMAL"または未設定)でaddPaymentToOrderを実行し、payment.metadata.sbPaymentPayType === "0"が返ること - 上記の仮売上
paymentIdを使いsbCapturePaymentmutation を実行し、SbCapturePaymentSuccessが返ること payment.state === "Settled"かつorder.state === "PaymentSettled"に遷移することpayment.metadata.sbPaymentCaptureRequest.operationIdが記録されること
staging smoke / deploy gate¶
.github/workflows/staging-smoke-storefront.ymlは、staging deploy 成功後に Storefront smoke とcheckout.staging-gate.real.spec.tsによる staging checkout(銀行振込の注文完了)を実行します。- SBPS live checkout は #556 の vendor-side 残件があるため deploy gate にしません。
tests/e2e/checkout.sbps.real.spec.tsは 完了 / 完了後 refund cleanup / キャンセル を確認できますが、手動 opt-in の再確認に限定します。 - SBPS real checkout は staging/local guard を持ち、production URL へ向いた実行は fail-closed で拒否します。production checkout は CI/CD で実行しません。
- SBPS live checkout を staging で手動実行する場合は以下のように opt-in します。
RUN_SBPS_E2E=true just storefront-e2e-real staging "tests/e2e/checkout.sbps.real.spec.ts --project=chromium"
- production preflight は staging smoke 成功後の導線にぶら下がるが、production 側で checkout を実行しない。production は health / read-only / prod-safe synthetic に限定します。
- ただし live refund cleanup は Vendure 側に
SB_PAYMENT_XML_API_BASIC_USERNAME/SB_PAYMENT_XML_API_BASIC_PASSWORDが入っていることが前提です。未設定なら監査で fail します。
試験環境用テストカードとの整合¶
checkout.sbps.real.spec.tsは、既定で VISA / EMV 3-Dセキュア / フリクションレス の試験カードを使います。- カード番号:
4000000000002701 - 有効期限:
2035/12 - セキュリティコード:
123 - 実装上の
pay_methodはcredit3d2固定で、試験環境案内の EMV 3-Dセキュア前提とは整合しています。 - ただし、CI / E2E が自動化しているのは フリクションレス完了導線と simulator のキャンセル導線です。challenge フロー用カードやブランド別差分 (AMEX の 4 桁セキュリティコード等)を常時 gate している状態ではありません。
3DセキュアPWは SBPS hosted 側の画面遷移で扱う想定で、現行 E2E は challenge password 入力まで自動化していません。- 「クレジットカード決済のテストは ¥2〜の少額で行う」は決済会社(SB Payment)からのお願いです。 staging / production smoke を問わず、SB Payment を経由する決済テストでは 注文合計を ¥2〜数円程度 にしてください(¥1 では処理が通らない場合があります)。 その他の決済方法(売掛・代引き・銀行振込)は ¥1 でテストしてください。 詳細は 決済方法ハンドラー実装 の「テスト時の決済金額(少額)」節を参照してください。
- 「テスト決済後は当日中に返金」は運用ルールとして有効であり、 現行実装では Vendure の refundOrder → SBPS XML API で返金を自動実行する導線を持ちます。
checkout.sbps.real.spec.tsの完了ケースは、注文完了後に Admin API から refund を発行し、 refund がSettledになるまで待機します。- ただし live 環境で実際に返金 API を叩くには
SB_PAYMENT_XML_API_BASIC_USERNAME/SB_PAYMENT_XML_API_BASIC_PASSWORDが必要です。未設定環境では refund は fail-closed で失敗します。 - Vendure Dashboard の
cancelOrderは plugin resolver override で先に SBPS 側を同期します。Authorizedは外部取消、Settledは未返金残高だけ外部返金を実行し、失敗時は mutation 自体を失敗させます。 order-detailには SBPS 決済情報ブロックを追加しており、Vendure が保持するPayment/Refund/metadataからtracking_id/sps_transaction_id/ 最新 callback/cancel/refund 応答を確認できます。
トラブルシューティング¶
2211(システムエラー)¶
確認項目:
pay_methodがcredit3d2になっているかcust_codeが order code ではなく、可能なら billing customer code / customer code を使っているかmerchant_id/service_id/hash_keyが一致しているかsps_hashcodeの計算順序が実装定義と一致しているか- hosted redirect form が
accept-charset="Shift_JIS"を明示しているか success_url/error_url/cancel_url/pagecon_urlがすべてcommerce-*の controller route を向いているかcredit3d2でもカード入力画面に進まずres_err_code=2211のままなら、アプリ側 URL 修正だけでなく SBPS 側の merchant/service/hash と direct-input 利用可否 を優先して確認する- SBPS の EMV 3-D セキュア導入ガイドでは、リンク型で
SBPS hosted 画面にカード番号を直接入力させる方式は別途申込が必要 と明記されている。
現在の staging live request (
NetB2B_00100012) はpay_method=credit3d2かつfree_csv=""で、TDS2 token を渡さず SBPS hosted 直接入力フローを使っているため、merchant/service に direct-input capability が未付与だとアプリ側 request 修正だけでは解消しない - direct-input capability が未付与の場合の代替は、SBPS docs にある
free_csvへTDS2INFO_TOKEN/TDS2INFO_TOKEN_KEYを詰める token 連携方式だが、これは別実装として扱う - sample 相当の payload / canonical controller URL でも再現する場合は、許可 URL 登録・利用申込・アカウント capability を SBPS 側で切り分ける
- staging の real checkout smoke は CI/CD の自動 gate でも実行できる。production では
実行しない。必要時は
RUN_SBPS_E2E=trueを明示して local/manual でも実行する
3D セキュア認証後に SBPS 画面で「エラーが発生しました」¶
症状:
FepBuyInfoReceive.doではカード入力 / ネットショッピング認証コード入力まで進む- 3D セキュア認証後、SBPS 側の
/f02/credit3d2/FepBridgeAuthorityResult.doで「エラーが発生しました」と表示される - SBPS HTML の「戻る」リンクだけが
/sb-payment/error/:trackingIdを向き、res_err_codeが空の場合がある
この場合は、まず 結果 CGI (pagecon_url) が実際に到達したか を切り分ける。
SBPS は 3D セキュア後に POST /sb-payment/callback へ結果通知し、
ここが失敗または期待形式で応答しないと Hosted 画面側がエラー表示になる。
一方で callback 到達ログも Payment.metadata.sbPaymentResponse も無い場合は、
アプリの callback 応答ではなく、SBPS 側の 3D セキュア結果処理で止まっている。
確認手順:
- production の現在 commit と health を確認する:
just env-status production - 対象注文の Payment metadata を確認し、
sbPaymentResponseが保存されているか見る。 保存されていなければ callback が未完了または fail-closed している。 - Vendure logs で以下を検索する:
flyctl logs --app ritsubi-ecommerce --no-tail | rg "SB Payment|Hash verification|Payment not found|<order-code>" /sb-payment/callbackがtext/csv; charset=Shift_JISでOKを返す実装になっているか確認する。 失敗時はNG,理由を返し、Express 既定のtext/html; charset=utf-8に戻さない。- SBPS 結果 CGI と画面返却の form POST は Shift-JIS かつ charset なしで届く場合がある。
/sb-payment/callback//sb-payment/cancel//sb-payment/success/:trackingId//sb-payment/error/:trackingId//sb-payment/payinfo/callbackでは Express 標準 body parser に渡す前に専用 middleware で復号する。 これにより、charset=Shift_JISの 415/500 と、free_csvBase64 内の+が空白へ変換される hash 不一致を防ぐ。 - 戻り電文の Payment 検索 ID は
free1/order_idを優先する。res_tracking_idは SBPS 払出 ID なので、Vendure のPayment.transactionIdとして先に使わない。 sps_hashcodeはキー名ソートではなく、SBPS の戻り電文項目定義順で検証する。 callback payload にorg.apache.struts.taglib.html.TOKENが含まれていても ハッシュ対象に含めない。
既知修正:
a421c93e0 fix(vendure): handle SBPS result callbacksfree1/order_id優先で Payment を解決- 戻り電文 hash を SBPS 項目順で検証
- 結果 CGI 応答を
OK,/NG,理由に変更 - 2026-05-21 の修正
- 結果 CGI 応答を
text/csv; charset=Shift_JISに固定 - SBPS 画面返却 route (
success/error/cancel) も Shift-JIS 専用 parser 対象に追加 free_csvは既定base64、契約上 3DES が必要な環境ではSB_PAYMENT_TDS2INFO_FREE_CSV_ENCODING=3des-base64を明示
2026-05-21 の staging dogfooding 結果:
- 対象:
NetB2B_00100097 - staging Vendure:
c18b425c1 - request:
pay_method=credit3d2amount=1776success_url/cancel_url/error_url/pagecon_urlはcommerce-staging.ritsubi-platform.com/sb-payment/*free_csv=""- 結果:
- カード入力とネットショッピング認証コード入力までは完了
/f02/credit3d2/FepBridgeAuthorityResult.doで SBPS hosted error- Vendure 側に
sbPaymentResponseは保存されていない - Vendure logs に callback hash / Payment lookup / SB Payment callback 系ログは無い
この状態の解消条件:
- 現行実装を維持する場合:
SBPS 側で merchant
59224/ service002に対して、 リンク型credit3d2の SBPS hosted 直接入力方式 が staging / production の両方で有効化されていることを SBPS に確認・反映してもらう。 - direct-input を使わない場合:
SBPS の EMV 3-D セキュア認証システムで
TDS2INFO_TOKEN/TDS2INFO_TOKEN_KEYを取得し、仕様どおりfree_csvに設定する token 連携方式へ切り替える。 この方式では SBPS から案内される token JavaScript URL と暗号化条件が必要。 2026-05-21 の実装では、VITE_PUBLIC_SB_PAYMENT_TDS2INFO_TOKEN_SCRIPT_URLが storefront に、SB_PAYMENT_TDS2INFO_3DES_KEY/SB_PAYMENT_TDS2INFO_3DES_IVが Vendure に設定されている場合、この token 連携方式を使う。Vendure 側の暗号化設定があるのに token が渡らない場合は、 hosted 直接入力へ fail-open せず決済開始を明示的に失敗させる。
2026-05-21 追試結果:
- staging Storefront
0.1.0+git.5223.f37325a/ Vendure imagevendure-381e81368c47b1a070ab9a76982f8367d7fe13bcで確認。 - 修正済み:
- Storefront の支払い方法コード
credit-cardでも TDS2INFO token を生成する。 - CSP
script-srcにVITE_PUBLIC_SB_PAYMENT_TDS2INFO_TOKEN_SCRIPT_URLの origin を追加し、stbtoken.sps-system.comの script / JSONP を許可する。 - SBPS 送信
amountは Storefront 表示と同じ整数円へ丸める。totalWithTax=177650の注文は SBPS 確認画面でも¥1,777と表示される。 - 実ブラウザ確認:
com_sbps_system_tds2infotoken.js読み込み: HTTP 200generateTds2Token: HTTP 200FepBuyInfoReceive.do: HTTP 200、カード入力画面へ遷移- SBPS 確認画面の支払い合計:
¥1,777 FepBridgeAuthorityResult.do: 依然として SBPS hosted error- この状態では、アプリ側の「token 未送信」「CSP block」「amount 1 円ズレ」は除去済み。
次の切り分けは SBPS 側で、対象取引の
free_csv復号・TDS2INFO token 解釈・ merchant/service の 3D セキュア利用設定を確認する。
2026-05-21 production deploy での追加注意:
SB_PAYMENT_TDS2INFO_3DES_KEY/SB_PAYMENT_TDS2INFO_3DES_IV/SB_PAYMENT_TDS2INFO_FREE_CSV_ENCODING=3des-base64が Vendure に入っている環境では、 Storefront も以下の公開 env を Worker vars に持っている必要がある。VITE_PUBLIC_SB_PAYMENT_TDS2INFO_TOKEN_SCRIPT_URLVITE_PUBLIC_SB_PAYMENT_MERCHANT_IDVITE_PUBLIC_SB_PAYMENT_SERVICE_ID- これらが欠けると Storefront は token 生成をスキップし、Vendure の
addPaymentToOrderでSB Payment TDS2INFO token is required before redirecting to hosted checkout.が返る。 - これは「カードのシミュレーション」用ではなく、本番
credit3d2の EMV 3-D セキュアで SBPS に渡す利用者情報 token 連携である。 scripts/ops/with-cloudflare-auth.shは production/staging の Storefront deploy 時に 上記公開 env を補完し、apps/storefront/scripts/cloudflare-deploy.mjsは 欠落時に deploy を止める。
2026-05-21 の callback / browser return parser 追試:
POST /sb-payment/callbackにContent-Type: application/x-www-form-urlencoded; charset=Shift_JISを送ると、以前は Express body-parser がunsupported charset "SHIFT_JIS"で HTTP 500 を返していた。- Vendure の body parser を明示登録に切り替え、SBPS 専用 middleware を
標準 parser より前に通すことで、同じ直接 POST はアプリ側の
NG,Hash verification failedまで到達する。 - SBPS が charset を省略する実電文でも同じ middleware に通す。
free_csvは 3DES 暗号文の Base64 なので、form の通常ルールに従って+を空白へ変換してはいけない。 /sb-payment/success/:trackingIdへの画面返却 POST も Shift-JIS で届く。 専用 middleware 対象外だと Express 標準 parser が HTTP 500 を返し、 SBPS hosted 画面ではFepBridgeAuthorityResult.doのエラーとして見える。- SBPS 接続支援サイトの結果通知テストで、
commerce-staging.ritsubi-platform.com宛のpagecon_urlはアクセス制限エラー(403)になった。 これは SBPS 通知元 IP61.215.213.47が Cloudflare で拒否されたため。 staging 追試ではSB_PAYMENT_CALLBACK_URL=https://ritsubi-ecommerce-staging.fly.dev/sb-payment/callbackに切り替えるとNetB2B_00100117がPaymentSettledまで到達した。 恒久対応では Cloudflare 側で SBPS 通知元 IP を許可し、commerce-*URL に戻す。
2026-05-21 の staging 成功条件:
- 対象注文:
NetB2B_00100117 - staging Vendure image:
deployment-01KS4M6KDAREAB85J55BWKE6MS - 一時 callback URL:
https://ritsubi-ecommerce-staging.fly.dev/sb-payment/callback free_csv:SB_PAYMENT_TDS2INFO_FREE_CSV_ENCODING=3des-base64- 結果:
- SBPS hosted 画面で「支払いが完了しました」まで到達
- Vendure の注文状態は
PaymentSettled - 決済 parser / hosted return parser / 結果 CGI 応答形式の修正後は、
Cloudflare 403 を回避すれば 3D セキュア後の
FepBridgeAuthorityResult.doエラーは再現しない
高速切り分け手順:
# callback parser / routing が壊れていないことだけを数秒で確認する。
# 期待値: HTTP 400 + `NG,Hash verification failed`。HTTP 500 なら parser/route の回帰。
curl -i -sS -X POST 'https://commerce-staging.ritsubi-platform.com/sb-payment/callback' \
-H 'Content-Type: application/x-www-form-urlencoded; charset=Shift_JIS' \
--data 'free1=NetB2B_00100106&res_result=OK'
# charset なしでも SBPS 専用 parser に入ることを確認する。
curl -i -sS -X POST 'https://commerce-staging.ritsubi-platform.com/sb-payment/callback' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'free1=NetB2B_00100106&res_result=OK&free_csv=abc+def'
# 画面返却 POST が parser で落ちないことを確認する。
# 期待値: HTTP 302。HTTP 500 なら success/error/cancel route が parser 対象外。
# 注意: SBPS hosted page と同じ Origin header を必ず付ける。付けないと
# Origin validator の bypass 漏れ (403) を見落とす (2026-06-09 の事例)。
curl -i -sS -X POST 'https://commerce-staging.ritsubi-platform.com/sb-payment/success/NetB2B_00100114' \
-H 'Origin: https://stbfep.sps-system.com' \
-H 'Content-Type: application/x-www-form-urlencoded; charset=Shift_JIS' \
--data 'res_result=OK'
この direct callback 確認が通らない状態で full checkout dogfood を繰り返さない。
2026-05-22 production の XML API 403:
- 症状: Dashboard から支払い取消を実行すると、Vendure の
SbPaymentLinkService.postSbXmlApi()がSB Payment XML API returned 403: ... You don't have permission to access /api/xmlapi.doで失敗した。 - 原因: SBPS 本番 XML API は通信元 IP 制限があり、Fly の通常 outbound IP は固定されない。
fly ips listに出る public ingress IP は inbound 用で、SBPS XML API の通信元 IP ではない。 - 対応: production Fly app
ritsubi-ecommerceのnrtregion に app-scoped static egress IP を割り当てた。 - 実行コマンド:
fly ips allocate-egress --app ritsubi-ecommerce -r nrt --yes - 割当 IPv4:
209.71.88.227 - 割当 IPv6:
2a09:8280:e625:1:0:a1:2053:0 - 既存 Machine へ反映されるまで 5〜10 分かかる場合がある。
- SBPS 側には本番 API 型の許可 IP として
209.71.88.227を申請・登録する。 SBPS 側の許可が完了するまでは、Vendure 側の取消・返金・売上確定 XML API は 403 のまま失敗する。 - 確認コマンド:
# production app の outbound IPv4 が static egress IP に変わったことを確認する。
fly ssh console -a ritsubi-ecommerce -C 'node -e "fetch(\"https://api.ipify.org\").then(r=>r.text()).then(console.log)"'
# egress IP 一覧を確認する。
fly ips list -a ritsubi-ecommerce
fly ips allocate-egress は非対話実行で --yes が必須。付けないと
yes flag must be specified when not running interactively で失敗する。
本番 SBPS XML API 向け固定 egress(確認済み)¶
- IPv4(固定):
209.71.88.227(ritsubi-ecommerce/nrt) - IPv6(固定):
2a09:8280:e625:1:0:a1:2053:0(ritsubi-ecommerce/nrt) - 参照 issue: #698
- 反映確認:
fly ips list -a ritsubi-ecommercefly ssh console -a ritsubi-ecommerce -C "node -e 'fetch(\\\"https://api.ipify.org\\\").then(r=>r.text()).then(console.log)'"
Cloudflare 経由の公開 URL を使う場合は、さらに SBPS 接続支援サイトの
「結果通知リクエスト」で SERVER STATUS OK(200) になることを確認する。
staging Vendure の反映は、ローカル Docker registry push で詰まる場合があるため、
短い検証サイクルでは次を使う。
flyctl deploy --config apps/vendure-server/fly.staging.toml \
-a ritsubi-ecommerce-staging --remote-only --depot --strategy rolling
SBPS への確認文面に含める情報:
- merchant_id:
59224 - service_id:
002 - 環境:
- staging FEP:
https://stbfep.sps-system.com - production FEP:
https://fep.sps-system.com - 支払方法:
credit3d2 - 直近再現注文:
- staging:
NetB2B_00100097 - production: 該当注文コード
- 事象:
カード入力・3D セキュア認証コード入力後、
/f02/credit3d2/FepBridgeAuthorityResult.doで hosted error。pagecon_urlへの結果 CGI が加盟店側に到達していない。 - 確認依頼:
- 上記 merchant/service でリンク型
credit3d2の hosted 直接入力方式が有効か - 無効な場合、有効化申込に必要な手続き
- token 連携方式が必須の場合、token JavaScript URL、
free_csvの暗号化要否・鍵・具体仕様 NetB2B_00100097の SBPS 側エラー詳細
callback が届かない¶
確認項目:
SB_PAYMENT_CALLBACK_URLが公開URLかcommerce-*ドメインの TLS / DNS が正常か- SBPS 側設定(許可IP・URL登録)に齟齬がないか
Storefront がログインへリダイレクトする¶
/sbps/success / /sbps/cancel / /sbps/error
は、未ログインでも表示できる導線として扱います。
もしログイン画面へ飛ぶ場合は、以下を最優先で確認します。
successUrl/errorUrl/cancelUrlが sbps landing route(/sbps/*)を指しているか- SBPS callback から Storefront への遷移時、途中で意図しない外部ドメインへ書き換わっていないか
- Storefront 側のルート保護(auth middleware)が
sbps/success|error|cancelを 例外扱いしているか
対処方針:
- まず
/sbps/success|error|cancelが 401/302 せずに到達できる前提を戻す - 到達できても表示自体が空白や別画面なら、URL の origin と メタ情報の保存・復元ロジックを合わせて再確認する
- それでも解消しない場合は、Cloudflare/WAF/CAPTCHA 側で
POST -> browser return系を阻害していないか別途確認する
2026-05-22 追加: 運用ノート(重要)¶
staging / production の通信経路差分(最新)¶
- staging 直打ち XML API:
https://stbfep.sps-system.com/api/xmlapi.doは現在 403 ではなく 実回路応答(res_result=NGなど)を返しており、現時点では staging の outbound IP 制限要件は確認されていない。- production 直打ち XML API:
productionはFlyの app-scoped static egress を209.71.88.227に固定済み。- ただし SBPS 側 allow-list に IP が反映されるまでは
SB Payment XML API returned 403...が継続し、取消・返金・売上確定は通らない。
直近の SBPS エラー解像度(FepBridgeAuthorityResult.do)¶
- 現象継続条件:
- ネットショッピング認証前までは通る
- Hosted の結果画面(
/f02/credit3d2/FepBridgeAuthorityResult.do)で 「エラーが発生しました」が出る - Vendure 側 metadata に
sbPaymentResponseが残らない - ここまで到達している時点では、callback/parser 層は一度成立している可能性が高く、
主要原因は SBPS 側設定(
credit3d2direct input capability、TDS2INFO運用方式) の分岐不足に寄ることが多い。 - そのため、アプリ側対策としては次を再確認し、最終的に SBPS 側の merchant/service 申請状態を一次切り分けとする。
free_csvの3des-base64方針VITE_PUBLIC_SB_PAYMENT_TDS2INFO_TOKEN_SCRIPT_URLとトークン生成可否- SBPS 接続時の cardholder info / EMV 3DS 運用種別
5分トリアージ(障害時)¶
just env-status staging/just env-status productionで env とコミットを確認SB_PAYMENT_CALLBACK_URL/SB_PAYMENT_CANCEL_URLが意図どおりか確認callbackにfree1とres_resultのみ投げてNG,Hash verification failedが返るか確認fly ips list -a ritsubi-ecommerce(-staging)とfly ssh ... ipifyで egress を確認SB_PAYMENT_TDS2INFO_*系の公開/秘密保持値が揃っているか確認- 上記で特定できない場合は SBPS 接続支援窓口へ merchant/service/再現注文を添えて照会
SBPS へ提出する再現情報(テンプレ)¶
- merchant_id:
59224 - service_id:
002 - 環境:
staging/production - 支払種別:
credit3d2 - 再現注文:
NetB2B_00100097など - 事象:
- ネットショッピング認証コード入力後、
/f02/credit3d2/FepBridgeAuthorityResult.doの hosted error、pagecon_url到達ログ不在 - 依頼事項:
- merchant/service に対して direct input が有効か
- 無効なら有効化申込要件の提示
- token 連携必須の場合の URL/鍵/復号仕様の仕様書提示
2026-06-09: hosted page 戻り POST が 403 Forbidden: Origin not allowed¶
- 症状: 顧客報告「本番で決済が失敗する /
Failed to load resource: 403 (Forbidden)」。SBPS hosted の/f02/credit3d2/FepBridgeAuthorityResult.do到達後、「戻る」相当の遷移で Storefront に戻れず決済導線が切れる。 - 切り分け: staging を実ブラウザ (
agent-browser) で一巡し、HAR を取得。 hosted page document 自体は HTTP 200 で、403 は SBPS hosted page から Vendure へ戻る POST で発生していた。
POST https://commerce-staging.ritsubi-platform.com/sb-payment/error/NetB2B_00100149
Origin: https://stbfep.sps-system.com (本番は https://fep.sps-system.com)
=> 403 {"error":"Forbidden: Origin not allowed"}
- 根本原因:
apps/vendure-server/src/middleware/origin-validator.middleware.tsが SameSite=lax 補完の CSRF 対策として POST のOrigin/Refererを storefront/admin allowlist と照合する。SBPS hosted page の戻り POST は 外部 origin (fep/stbfep.sps-system.com) のため拒否されるが、当該 browser-return route がORIGIN_VALIDATOR_BYPASS_ROUTESに含まれていなかった。 - 修正:
api-options.tsの bypass list に/sb-payment/{success,error,cancel}/・/sb-payment/payinfo/{success,error,cancel}を追加 (commit8f38c6d40)。これらは状態を変えない 302 リダイレクトのみ なので bypass で安全 (上記「Session cookie / Origin 検証」節を参照)。 回帰テストはorigin-validator.middleware.spec.tsに SBPS origin POST の bypass を固定済み。 - 確認: staging deploy 後、上の curl が 403 → 302
(
/checkout/complete?...&status=error) に変わること、かつ/shop-apiへの SBPS origin POST が依然 403 (CSRF guard 維持) を実測。 - 付随して発覚した別バグ (deploy ブロッカー):
@ritsubi/plugins/rule-engine/shippingの barrel import が production CJS でMODULE_NOT_FOUNDになり Fly release phase が abort。詳細と再発防止はpackages/plugins/AGENTS.mdの 「subpath export(barrel)」節を参照。 - 参照 issue: #1008。
セキュリティ運用¶
- 機微情報(
hash key、認証情報)は必ず Secrets Manager secret で管理 - ドキュメントや Issue に平文で貼らない
- テスト決済後は当日中に返金処理を行う
参考¶
決済種別の整理(混同防止)¶
SB Payment連携では、以下の2つを必ず分けて扱います。
1. 顧客が選ぶ決済方法(EC側の概念)¶
- 例:
credit-card,bank-transfer,cash-on-delivery - 管理場所:
- Vendure PaymentMethod(表示・選択)
- Eligibility Checker(顧客・条件ごとの可否)
2. SBPSへ送る決済種別(接続仕様の概念)¶
- 例:
pay_method=credit3d2 - 管理場所:
packages/plugins/src/payment-integration/sb-payment-link/sb-payment-link.service.ts
現在のマッピング¶
- 顧客選択:
credit-card - Vendure handler:
sb-payment-link - SBPS送信:
pay_method=credit3d2
運用ルール¶
pay_methodは「顧客向け表示の決済方法」ではなく「SBPS接続パラメータ」として扱う- 仕様会話では以下の語を使い分ける
顧客決済方法(UI/運用)SBPS決済種別(電文/接続)- 将来、SBPSで別手段を追加する場合は、マッピングを明示して要件合意後に実装する
「選択可能」と「許可」の違い¶
決済方式の混同を防ぐため、実運用では次の3層で整理します。
- 顧客が画面で選ぶ決済方法(UI上の選択肢)
- 顧客に許可される決済方法(ビジネスルールによる可否)
- SBPSへ送る決済種別(接続パラメータ)
顧客に許可される決済方法(可否判定)¶
顧客ごとの決済可否は、Vendureの paymentMethodEligibilityCheckers
で制御します。
- 代表的な判定要素:
- 顧客グループ
- 回収方法(SMILE連携情報)
- 取引条件
この可否判定の結果として「表示される(選べる)決済方法」が決まります。
現行フロー(クレジットカード)¶
- Eligibility Checker が
credit-cardを許可 - 顧客が
credit-cardを選択 sb-payment-linkhandler が実行- SBPSへ
pay_method=credit3d2で送信
実装上の責務分離¶
- 許可ロジック:
paymentMethodEligibilityCheckers - SBPS接続ロジック:
sb-payment-link(pay_method生成)
この2つは責務が異なるため、仕様変更時は別チケットで管理することを推奨します。
仮売上(オーソリ)+キャプチャー(pay_type=1)¶
pay_type とは¶
[!IMPORTANT]
pay_typeは SBPS 内部の接続パラメータです。顧客が選ぶ決済方法(クレジットカード・売掛・代引きなど)とは無関係です。 「どの決済方法を使うか」はpaymentMethodEligibilityCheckerが制御し、pay_typeは「クレジットカード決済を選んだ後、SBPS での課金をいつ確定するか」を制御します。
pay_type |
SBPS 名称 | Vendure 動作 |
|---|---|---|
0(デフォルト) |
即時売上 | Callback OK → 即 Settled(通常フロー) |
1 |
仮売上(オーソリ) | Callback OK → Authorized のまま保持 → 手動キャプチャーで Settled |
概要¶
pay_type は、注文の種別(order.customFields.orderLaneType)に基づいて注文ごとに自動的に決定されます。
環境変数や設定値での一律切り替えは廃止し、注文種別に応じて自動的に判定します。
注文種別 (orderLaneType) |
containsReservationItems |
pay_type |
動作 |
|---|---|---|---|
"RESERVATION" |
any | 1 |
仮売上(オーソリ) |
"NORMAL" |
any | 0 |
即時売上 |
| 未設定 | true |
1 |
仮売上(フォールバック) |
| 未設定 | その他 | 0 |
即時売上(デフォルト) |
予約注文(在庫確保前の注文)など、実際に出荷が確定するまで売上を確定させたくないシナリオに使用します。
SBPS 側の仮売上の有効期限は 原則90日(契約条件により異なる場合あり)です。 期限内にキャプチャーするか仮売上取消を実施する必要があります。
SBPS API オペレーション¶
| 操作 | Operation ID | 説明 |
|---|---|---|
| キャプチャー(実売上確定) | ST02-00101-101 |
仮売上 → 実売上 |
| 仮売上取消 | ST02-00111-101 |
仮売上を取り消す(返金ではなく取消) |
フロー¶
[購入] FepBuyInfoReceive.do (pay_type=1)
↓ Callback OK
[Authorized 状態に留まる]
↓ オペレーターが Dashboard で「売上確定」ボタンをクリック
sbCapturePayment mutation → settlePayment handler → ST02-00101-101 → Settled
取消の場合:
[Authorized 状態(pay_type=1 かつ Callback OK済み)]
↓ 注文取消
cancelPayment handler → ST02-00111-101(仮売上取消)→ Cancelled
設定方法¶
pay_type は注文種別に基づいて自動的に決定されるため、追加設定は不要です。
実装は packages/plugins/src/payment-integration/sb-payment-link/sb-payment-link.service.ts の
resolveSbPaymentPayType(order) 関数が担当しています。
// 注文の orderLaneType を確認するだけでよく、環境変数設定は不要
// RESERVATION → pay_type=1 (仮売上)
// NORMAL または未設定 → pay_type=0 (即時売上)
Admin API(sbCapturePayment mutation)¶
オペレーターが手動でキャプチャー(売上確定)を実行するための Admin API mutation です:
mutation CapturePayment($paymentId: ID!) {
sbCapturePayment(paymentId: $paymentId) {
... on SbCapturePaymentSuccess {
payment {
id
state
metadata
}
}
... on SbCapturePaymentError {
errorCode
message
}
}
}
- 引数:
paymentId(Payment.id) - 成功:
SbCapturePaymentSuccess.payment - 失敗:
SbCapturePaymentError.errorCode/message - 冪等性: 既に
sbPaymentCaptureResponse.OKが metadata にある場合は API 再呼び出しをスキップして成功を返す
Dashboard 操作¶
pay_type=1 の決済が Authorized 状態のとき、注文詳細の「SBPS 決済情報」ブロックに
「売上確定を実行」 ボタンが表示されます。このボタンをクリックすると:
sbCapturePaymentmutation が実行される- SBPS の ST02-00101-101 API が呼ばれる
- 成功時: Payment が
Settledに遷移し、返金操作が可能になる - 失敗時: エラーメッセージが表示され、Payment は
Authorizedのまま維持される
注文一覧では、複数選択メニューに 「SBPS 売上確定」 を表示します。この一括操作は
選択された注文のうち、次の条件をすべて満たす Payment だけを対象にします。
Payment.methodがcredit-cardまたは legacy 互換のsb-payment-linkPayment.state === "Authorized"Payment.metadata.sbPaymentPayType === "1"
対象外の注文はスキップし、対象件数が 0 の場合は sbCapturePayment を呼びません。
一括操作も注文詳細ボタンと同じく sbCapturePayment(paymentId) を経由し、
SBPS capture API と Vendure の Payment.state: Authorized -> Settled を同じ操作として扱います。
[!NOTE] 「売上確定」は UI 上の業務用語です。Vendure 標準の決済状態としては
Payment.stateのAuthorizedからSettledへの遷移です。注文全体は結果としてOrder.stateがPaymentSettledへ進みます。
実装上の注意点¶
processing_datetimeは SBPS の callback (res_process_date) から取得する必要があります。 取得できない場合はキャプチャー・仮売上取消 API の呼び出しを拒否します(現在時刻で代用しない)。- SBPS XML API の部分返金
amountも円単位です。Vendure の返金額・決済額は内部では 100倍の Money 値のまま保持し、SBPS API request を組み立てる直前にだけtoSbPaymentYenAmount()で円へ変換します。 - 二重キャプチャー対策: mutation 内で
sbPaymentCaptureResponseが OK 済みなら冪等 success を返します。 pay_type=0のAuthorized決済(Callback 前)に対して仮売上取消 API を誤送信しないよう、cancelPaymentはメタデータのsbPaymentPayTypeとsbPaymentResponseの両方を確認します。
Session cookie / Origin 検証 (SameSite=lax)¶
- Vendure の session cookie は全環境で
sameSite: "lax"を採用しています (apps/vendure-server/src/config/auth-options.ts)。SBPS hosted page など 外部決済からの cross-site redirect で session が落ちないようにする ための判断であり、SameSite=strict の checkout 完了画面認証エラー事故を 予防します。 lax採用に伴う CSRF 攻撃面の拡大は、apps/vendure-server/src/middleware/origin-validator.middleware.tsで POST/PUT/PATCH/DELETE のOrigin(なければReferer) を CORS と同じ allowlist と照合することで補完しています。- SBPS の callback 系は Origin/Referer が storefront/admin と一致しないため
apps/vendure-server/src/config/api-options.tsのORIGIN_VALIDATOR_BYPASS_ROUTESで検証から除外しています。除外対象は 2 系統あります: - server-to-server CGI (
/payments/sbps/...,/sb-payment/callback,/sb-payment/cancel,/sb-payment/payinfo/callback): SBPS サーバから直接 POST されるためOriginheader 自体が無い。実際の決済確定はここで行われ、SbPaymentLinkService内の hash / 結果コード検証が守る。 - hosted page browser return (
/sb-payment/{success,error,cancel}/:trackingId,/sb-payment/payinfo/{success,error,cancel}): SBPS hosted page から ブラウザが top-level POST するためOrigin: https://fep.sps-system.com(本番) /https://stbfep.sps-system.com(staging) が付く。これはredirectToBrowserReturnによる 状態を変えない 302 リダイレクト (trackingId 形式検証 → storefront/checkout/completeへ転送) のみで、 決済確定や注文 mutation を行わないため bypass しても安全。 ここに新しい endpoint を追加する際は、(1) server-to-server なら署名検証が 別途行われていること、(2) browser return なら状態変更を伴わないことを 必ず確認してください。両方ともbypassRoutesへの登録を忘れると403 Forbidden: Origin not allowedで決済導線が切れます (下記 2026-06-09 の事例)。 - rollout 手順: staging で SBPS 通常 / 異常系 (callback タイムアウト、
再決済) を一巡したのち production へ適用してください。Cookie の
name/domain/pathを変えていないため、既存 session の即時失効は発生しない 想定ですが、念のため deploy window と顧客通知をセットで運用します。 - rollback:
auth-options.tsのsameSiteをIS_PROD ? "strict" : "lax"に戻し再 deploy するだけでよく、追加 migration は不要です。