コンテンツにスキップ

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_methodcredit3d2 を使用します。
  • 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 直指定で切り分ける。
  • 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

決済フロー(現実装)

  1. Storefront から Vendure に決済作成を要求
  2. Vendure が SBPS 向けのリクエストパラメータを組み立てる
  3. sps_hashcode を計算して付与
  4. Vendure は payment.metadata.public に遷移情報を返す
  5. ブラウザは GET /sb-payment/redirect/:trackingId を開く
  6. サーバーが auto-submit HTML を返し、SBPS へ POST 遷移
  7. ユーザーが SBPS 画面で決済
  8. SBPS が POST /sb-payment/callback へ通知
  9. Hosted page の browser return は /sb-payment/success/:trackingId または /sb-payment/error/:trackingId に戻る
  10. Vendure が PaymentSettled へ遷移し、最終的に 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 で円へ戻した値と一致するか検証する。 不一致の場合は PaymentSettled に進めず、Amount verification failed として fail-closed する。これにより、Vendure Money 値を誤って100倍のまま送った取引や、 SBPS側の決済金額が注文金額とずれた取引を確定しない。

カード保存フロー(顧客決済情報登録)

  1. Vendure Dashboard / 運用API から buildSbpsCardRegistrationUrl(customerId) を呼ぶ
  2. 返却URL(/sb-payment/payinfo/register/:token)を顧客に案内する。:token は admin (Permission.UpdateCustomer) のみが発行できる HMAC 署名付き token で、customerId は URL に出さない。
  3. Vendure が FepPayInfoReceive.do 向け auto-submit form を返す
  4. 顧客が SBPS Hosted Page でカード情報を登録する
  5. SBPS が POST /sb-payment/payinfo/callback へサーバー通知する
  6. Vendure が res_sps_cust_no / res_sps_payment_noCustomer.customFields に保存する
  7. 次回の通常決済(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/:trackingIdlocal simulator を返す。
  • 現在のアプリ実装では apps/vendure-server/src/vendure-config.shared.tsSB_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" が含まれます。 この値は settlePayment handler の三段分岐(NOP / 冪等スキップ / capture API 呼び出し) に使われるため、simulator を使った staging 上でのオーソリ+キャプチャーのフル動作確認が可能です。 2026-05-15 時点で staging 注文 NetB2B_00100093 を使い、 sbCapturePayment mutation → 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/prodSB_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.do error
  • 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)を返す。:tokenbuildSbpsCardRegistrationUrl (admin Permission.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/cancel
  • 200 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 間に duplicate SB_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 確認チェックリスト

  1. https://commerce-staging.ritsubi-platform.com/shop-api200
  2. POST https://commerce-staging.ritsubi-platform.com/sb-payment/callback400(空POST時)
  3. SB_PAYMENT_CALLBACK_URLcommerce-staging を向いている
  4. SB_PAYMENT_CANCEL_URLorder-staging を向いている
  5. リンク型開始時に SBPS 入力画面へ遷移できる
  6. 最新 payment.metadata.sbPaymentRequest.cancel_urlhttps://commerce-staging.ritsubi-platform.com/sb-payment/cancel/:trackingId を向いている
  7. checkout.sbps.real.spec.ts/checkout/complete まで到達できる
  8. checkout.sbps.real.spec.ts/checkout/complete?status=cancelled まで到達できる

オーソリ+キャプチャー(pay_type=1)追加チェック

  1. 予約注文(orderLaneType: "RESERVATION")で addPaymentToOrder を実行し、 payment.metadata.sbPaymentPayType === "1" が返ること
  2. 通常注文(orderLaneType: "NORMAL" または未設定)で addPaymentToOrder を実行し、 payment.metadata.sbPaymentPayType === "0" が返ること
  3. 上記の仮売上 paymentId を使い sbCapturePayment mutation を実行し、 SbCapturePaymentSuccess が返ること
  4. payment.state === "Settled" かつ order.state === "PaymentSettled" に遷移すること
  5. 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_methodcredit3d2 固定で、試験環境案内の 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_methodcredit3d2 になっているか
  • 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_csvTDS2INFO_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/callbacktext/csv; charset=Shift_JISOK を返す実装になっているか確認する。 失敗時は 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_csv Base64 内の + が空白へ変換される 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 callbacks
  • free1 / 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=credit3d2
  • amount=1776
  • success_url / cancel_url / error_url / pagecon_urlcommerce-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 / service 002 に対して、 リンク型 credit3d2SBPS 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 image vendure-381e81368c47b1a070ab9a76982f8367d7fe13bc で確認。
  • 修正済み:
  • Storefront の支払い方法コード credit-card でも TDS2INFO token を生成する。
  • CSP script-srcVITE_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 200
  • generateTds2Token: HTTP 200
  • FepBuyInfoReceive.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_URL
  • VITE_PUBLIC_SB_PAYMENT_MERCHANT_ID
  • VITE_PUBLIC_SB_PAYMENT_SERVICE_ID
  • これらが欠けると Storefront は token 生成をスキップし、Vendure の addPaymentToOrderSB 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/callbackContent-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 通知元 IP 61.215.213.47 が Cloudflare で拒否されたため。 staging 追試では SB_PAYMENT_CALLBACK_URL=https://ritsubi-ecommerce-staging.fly.dev/sb-payment/callback に切り替えると NetB2B_00100117PaymentSettled まで到達した。 恒久対応では 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-ecommercenrt region に 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.227ritsubi-ecommerce / nrt
  • IPv6(固定): 2a09:8280:e625:1:0:a1:2053:0ritsubi-ecommerce / nrt
  • 参照 issue: #698
  • 反映確認:
  • fly ips list -a ritsubi-ecommerce
  • fly 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:
  • productionFly の 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 側設定(credit3d2 direct input capability、TDS2INFO 運用方式) の分岐不足に寄ることが多い。
  • そのため、アプリ側対策としては次を再確認し、最終的に SBPS 側の merchant/service 申請状態を一次切り分けとする。
  • free_csv3des-base64 方針
  • VITE_PUBLIC_SB_PAYMENT_TDS2INFO_TOKEN_SCRIPT_URL とトークン生成可否
  • SBPS 接続時の cardholder info / EMV 3DS 運用種別

5分トリアージ(障害時)

  1. just env-status staging / just env-status production で env とコミットを確認
  2. SB_PAYMENT_CALLBACK_URL / SB_PAYMENT_CANCEL_URL が意図どおりか確認
  3. callbackfree1res_result のみ投げて NG,Hash verification failed が返るか確認
  4. fly ips list -a ritsubi-ecommerce(-staging)fly ssh ... ipify で egress を確認
  5. SB_PAYMENT_TDS2INFO_* 系の公開/秘密保持値が揃っているか確認
  6. 上記で特定できない場合は 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} を追加 (commit 8f38c6d40)。これらは状態を変えない 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層で整理します。

  1. 顧客が画面で選ぶ決済方法(UI上の選択肢)
  2. 顧客に許可される決済方法(ビジネスルールによる可否)
  3. SBPSへ送る決済種別(接続パラメータ)

顧客に許可される決済方法(可否判定)

顧客ごとの決済可否は、Vendureの paymentMethodEligibilityCheckers で制御します。

  • 代表的な判定要素:
  • 顧客グループ
  • 回収方法(SMILE連携情報)
  • 取引条件

この可否判定の結果として「表示される(選べる)決済方法」が決まります。

現行フロー(クレジットカード)

  1. Eligibility Checker が credit-card を許可
  2. 顧客が credit-card を選択
  3. sb-payment-link handler が実行
  4. SBPSへ pay_method=credit3d2 で送信

実装上の責務分離

  • 許可ロジック: paymentMethodEligibilityCheckers
  • SBPS接続ロジック: sb-payment-linkpay_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.tsresolveSbPaymentPayType(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
    }
  }
}
  • 引数: paymentIdPayment.id
  • 成功: SbCapturePaymentSuccess.payment
  • 失敗: SbCapturePaymentError.errorCode / message
  • 冪等性: 既に sbPaymentCaptureResponse.OK が metadata にある場合は API 再呼び出しをスキップして成功を返す

Dashboard 操作

pay_type=1 の決済が Authorized 状態のとき、注文詳細の「SBPS 決済情報」ブロックに 「売上確定を実行」 ボタンが表示されます。このボタンをクリックすると:

  1. sbCapturePayment mutation が実行される
  2. SBPS の ST02-00101-101 API が呼ばれる
  3. 成功時: Payment が Settled に遷移し、返金操作が可能になる
  4. 失敗時: エラーメッセージが表示され、Payment は Authorized のまま維持される

注文一覧では、複数選択メニューに 「SBPS 売上確定」 を表示します。この一括操作は 選択された注文のうち、次の条件をすべて満たす Payment だけを対象にします。

  • Payment.methodcredit-card または legacy 互換の sb-payment-link
  • Payment.state === "Authorized"
  • Payment.metadata.sbPaymentPayType === "1"

対象外の注文はスキップし、対象件数が 0 の場合は sbCapturePayment を呼びません。 一括操作も注文詳細ボタンと同じく sbCapturePayment(paymentId) を経由し、 SBPS capture API と Vendure の Payment.state: Authorized -> Settled を同じ操作として扱います。

[!NOTE] 「売上確定」は UI 上の業務用語です。Vendure 標準の決済状態としては Payment.stateAuthorized から Settled への遷移です。注文全体は結果として Order.statePaymentSettled へ進みます。

実装上の注意点

  • 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=0Authorized 決済(Callback 前)に対して仮売上取消 API を誤送信しないよう、 cancelPayment はメタデータの sbPaymentPayTypesbPaymentResponse の両方を確認します。
  • 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.tsORIGIN_VALIDATOR_BYPASS_ROUTES で検証から除外しています。除外対象は 2 系統あります:
  • server-to-server CGI (/payments/sbps/..., /sb-payment/callback, /sb-payment/cancel, /sb-payment/payinfo/callback): SBPS サーバから直接 POST されるため Origin header 自体が無い。実際の決済確定はここで行われ、 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.tssameSiteIS_PROD ? "strict" : "lax" に戻し再 deploy するだけでよく、追加 migration は不要です。