コンテンツにスキップ

決済方法ハンドラー実装

概要

Vendureに複数の決済方法(Payment Method Handler)を実装し、B2B ECサイトで必要な決済手段を提供します。

実装済みの決済方法(2025-12-06時点):

  • SB Paymentクレジットカード (sb-payment-link) - SbPaymentIntegrationPlugin で登録済み(注文取消し同期・部分返金UI・カード保存導線・オーソリ+キャプチャーを含む)。SB Payment 連携のスコープは現時点でクレジットカードのみで、PayPay / コンビニ払いは対象外。詳細: SB Paymentリンク型決済
  • 売掛決済 (CREDIT_SALE) - CreditSalePlugin で登録済み
  • 代引き決済 (CASH_ON_DELIVERY) - StandardPaymentsPlugin で登録済み
  • 銀行振込/前入金 (BANK_TRANSFER) - StandardPaymentsPlugin で登録済み

テスト時の決済金額(少額)

決済会社(SB Payment)のお願いとして、テスト決済は以下の少額で行ってください。 実際の金銭が動くため、不必要に大きな金額を使わないことが原則です。 staging / production smoke を問わず、手動テスト・自動 E2E・CI smoke のすべてに適用します。

決済方法 handler code テスト金額
SB Payment クレジットカード credit-card / sb-payment-link ¥2〜(数円)
売掛決済 CREDIT_SALE ¥1
代引き決済 CASH_ON_DELIVERY ¥1
銀行振込 / 前入金 BANK_TRANSFER ¥1

[!IMPORTANT] SB Payment(クレジットカード)は ¥1 では決済会社の処理フローを通過しない場合があります。 テスト時は ¥2 以上の数円程度 で行ってください。 ¥1 を拒否するバリデーションは plugin に入っていないため、設定ミスは静かに通過します。

実装場所

現コード上は、決済ハンドラーは用途ごとに以下へ分かれて実装されています。 apps/vendure-server/src/vendure-config.shared.tspaymentOptions.paymentMethodHandlers には dummyPaymentHandler のみを静的記載し、各プラグインの configuration フックで追加登録します。

  • packages/plugins/src/payment-integration/sb-payment-link/sb-payment-link.handler.ts - SB Paymentクレジットカード(SbPaymentIntegrationPlugin で登録)
  • packages/plugins/src/b2b-extensions/payments/credit-sale-handler.ts - 売掛決済(CreditSalePlugin で登録)
  • packages/plugins/src/payment-integration/handlers/cash-on-delivery-handler.ts - 代引き決済(StandardPaymentsPlugin で登録)
  • packages/plugins/src/payment-integration/handlers/bank-transfer-handler.ts - 銀行振込/前入金(StandardPaymentsPlugin で登録)

Vendure 標準 Payment 状態と業務用語

Vendure は注文 (Order.state) と決済 (Payment.state) を分けて管理します。 「オーソリ」「売上確定」は Ritsubi 独自ステータスではなく、基本的には Vendure 標準の Payment.state に対応します。

業務用語 Vendure Payment.state 意味
オーソリ / 仮売上 Authorized 外部決済で与信・仮売上は成立したが、売上はまだ確定しない状態
売上確定 / キャプチャー / 入金確認済み Settled Vendure 上で決済完了として扱う状態
決済取消 Cancelled 決済を取り消した状態
決済失敗 Declined / Error 外部決済または handler 処理が失敗した状態

Order.state 側には PaymentAuthorized / PaymentSettled があります。これは注文全体の 状態であり、個々の決済の実体は Payment.state が正本です。UI や運用手順で 「売上確定」と書く場合は、原則として対象 PaymentAuthorized から Settled へ進める操作を指します。

SBPS クレジットカードでは、pay_type=1 の仮売上だけが手動売上確定の対象です。 この場合は Vendure の Payment.stateSettled にするだけでなく、SBPS 側の capture API も同じ操作内で成功させる必要があります。そのため Dashboard からは 標準 mutation を直接呼ばず、sbCapturePayment(paymentId) を使います。詳細は SB Paymentリンク型決済 を参照してください。

売掛決済ハンドラー(Credit Sale)

機能

  • 顧客コード必須チェック(requireCustomerCode オプション、デフォルト true
  • 与信限度額チェック(enforceCreditLimit / creditLimit オプション)
  • 月次請求への処理キュー化(Authorized 状態で記録、実際の請求は月次処理で実施)
  • 即座に Authorized 状態にする(実際の決済は月次請求時に処理)

コード

  • ハンドラーコード: CREDIT_SALE
  • 表示名: 売掛決済(掛売り)

設定

パラメータ デフォルト 説明
requireCustomerCode boolean true 得意先コード未設定時にエラーにする
enforceCreditLimit boolean false 与信限度額チェックを有効にする
creditLimit int なし 与信限度額(Vendure Money 単位、0 は無視)

使用方法

  1. Vendure Dashboard → Settings → Payment Methods
  2. 新しい決済方法を作成
  3. Handler で CREDIT_SALE を選択
  4. 有効化して保存

実装詳細

export const CreditSaleHandler = new PaymentMethodHandler({
  code: PAYMENT_METHODS.CREDIT_SALE,
  description: [
    { languageCode: LanguageCode.ja, value: "売掛決済(掛売り)" },
    { languageCode: LanguageCode.en, value: "Credit Sale" },
  ],
  args: {
    requireCustomerCode: { type: "boolean", defaultValue: true, ... },
    enforceCreditLimit: { type: "boolean", defaultValue: false, ... },
    creditLimit: { type: "int", ... },
  },
  // ...
});

将来実装予定

  • 月次請求の自動登録フロー(現在は Authorized 状態で記録のみ)

代引き決済ハンドラー(Cash on Delivery)

機能

  • SMILE回収方法に基づく決済可否判定(Eligibility Checkerで制御)
  • 代引き手数料は 顧客から徴収しない 方針のため、ハンドラーは surcharge を加算しない(Issue #821)

コード

  • ハンドラーコード: CASH_ON_DELIVERY
  • 表示名: 代引き決済

設定パラメータ

なし。手数料計算・サーチャージ加算は行わない。

注意事項

  • 代引き手数料は顧客から徴収しない方針のため、checkout / Dashboard / メールいずれの画面でも代引き手数料の金額表示・サーチャージ加算は行わない。
  • 玄関先でのクレジットカード決済(タッチ決済)は配送業者側の機能であり、Vendure側の処理には影響しません
  • ストア側では通常の代引き決済として扱います(現金決済とカード決済の区別は不要)

銀行振込/前入金ハンドラー(Bank Transfer/Prepayment)

機能

  • 前入金確認待ち状態の管理
  • 請求書PDF自動発行(将来実装予定)
  • 入金確認後の注文確定
  • 銀行振込と前入金を統合(BANK_TRANSFERで統一)

コード

  • ハンドラーコード: BANK_TRANSFER
  • 表示名: 銀行振込/前入金

設定パラメータ

Vendure Dashboard上で以下のパラメータを設定できます:

パラメータ デフォルト値 説明
requiresInvoice boolean true 前入金の場合、請求書PDFを自動発行する

決済フロー

  1. 決済作成時 (createPayment)
  2. 入金確認待ち状態(PENDING_PAYMENT)で作成
  3. 請求書PDF発行が必要な場合は発行(将来実装)

  4. 入金確認後 (settlePayment)

  5. 管理者が入金確認を行った後に呼ばれる
  6. 状態を PAID(入金確認済み)に更新

  7. キャンセル時 (cancelPayment)

  8. 状態を CANCELLED に更新

使用方法

  1. Vendure Dashboard → Settings → Payment Methods
  2. 新しい決済方法を作成
  3. Handler で BANK_TRANSFER を選択
  4. 請求書発行設定を選択:
  5. 請求書発行が必要: true(前入金の場合)
  6. 有効化して保存

将来実装予定

  • 請求書PDF自動発行機能

Vendure設定への統合(現状)

apps/vendure-server/src/vendure-config.shared.ts では、 dummyPaymentHandler のみを静的登録しています。現コード上は追加の決済ハンドラーを各プラグインが configuration フックで登録しており、 SbPaymentIntegrationPluginsb-payment-linkStandardPaymentsPluginBANK_TRANSFERCASH_ON_DELIVERYCreditSalePluginCREDIT_SALE を追加します。

paymentOptions: {
  paymentMethodHandlers: [
    dummyPaymentHandler,
    // Additional handlers are appended by payment plugins via configuration hooks
  ],
  paymentMethodEligibilityCheckers: [
    // B2BPaymentEligibilityPlugin.configuration() が
    // paymentCollectionMethodEligibilityChecker を push する
  ],
}

決済方法制御(Eligibility Checker)

決済可否は PaymentMethodCollectionAssignment (PMCA) を正本 に判定します。 SMILE の 回収方法コード (Customer.customFields.collectionMethod) をキーに、 許可する PaymentMethod の code リストを直接保持するエンティティで、Dashboard 上では /payment-methods/collection-mapping から編集できます。

判定本体は paymentCollectionMethodEligibilityChecker (packages/plugins/src/b2b-extensions/payments/payment-collection-method-eligibility.ts) で、B2BPaymentEligibilityPlugin.configuration() (packages/plugins/src/b2b-extensions/index.ts) が Vendure config に登録します。apps/vendure-server/src/vendure-config.shared.tspaymentMethodEligibilityCheckers は空配列で開始し、Plugin 側の hook が追記する 構成です。

判定ロジック

  1. checkout の Customer.customFields.collectionMethod を読む。
  2. 正規化したコードで PaymentMethodCollectionAssignmentService.findOne() を呼ぶ。
  3. assignment の enabledtrue かつ allowedPaymentMethodCodes に当該 PaymentMethod の code が含まれていれば eligible、それ以外は ineligible。
  4. collectionMethod 未設定 / 該当 assignment 無し / enabled=false のケースは それぞれ「未割当」「不許可」として明示的に弾く(fail-open しない)。

SMILE CSV のコード側 回収方法 は import 時に Customer.customFields.collectionMethod に格納されます (packages/plugins/src/system-integration/smile/services/smile-customer-row.parser.ts)。 旧内部名 collectionMethod1 は参照しません。

旧経路は廃止済み: 以前は scope: "payment" の AccessPolicy / [決済ポリシー:回収方法]:1〜4 CustomerGroup / PaymentEligibilityEvaluatorService を経由していましたが、PMCA 一本化に伴い該当コードは削除されています。DB に残った 旧 CustomerGroup は OBSOLETE_COLLECTION_METHOD_GROUP_PREFIX (apps/vendure-server/src/data/seeders/policies.ts, apps/vendure-server/src/observability/runtime-drift.service.ts) による drift 検知用途のみで、判定には影響しません。移行 migration は apps/vendure-server/src/migrations/1779800400000_migrate_payment_policies_to_collection_assignments.ts および同フォルダの repair migration を参照してください。

Schema 公開範囲(admin-only)

paymentMethodCollectionAssignment(s) Query と upsertPaymentMethodCollectionAssignment / deletePaymentMethodCollectionAssignment Mutation は Admin schema にのみ 公開しています (packages/contract/schema/admin.graphql)。Shop schema には 意図的に公開しません。理由:

  • 判定はサーバ側 checker で完結し、Storefront は Vendure 標準 eligiblePaymentMethods を呼ぶだけで eligible な PaymentMethod 一覧を取得できる。
  • マッピングそのものは運用情報であり、未認証クライアントに開示する理由がない。
  • 公開を拡げると意図しない経路で許可決済方法が外部に漏れる導線が生まれるため、 PMCA は admin 操作の正本に閉じる方針を維持する。

Storefront 側で eligibility 結果以上の情報(許可決済方法のラベル一覧など)が 必要になった場合は、Vendure の eligiblePaymentMethods 拡張で要件を吸収し、 PMCA エンティティ自体を shop schema に出さないこと。

Storefront の表示責務

  • checkout は eligiblePaymentMethods のうち isEligible=true の支払い方法だけを 顧客向け候補として表示します。isEligible=false の方法は disabled 行として出さず、 顧客が選べる手段だけを見せます。
  • 以前選択していた支払い方法が現在の候補から外れた場合は、現在選択可能な候補へ差し替えます。 注文確定直前にも同じ候補集合を前提に submit guard を行い、候補外 method は fail-closed で拒否します。
  • マイページの「選択可能な支払い方法」欄も eligiblePaymentMethodsnormalizePaymentMethods() を使い、現在その顧客が選択可能な方法だけを確認専用で表示します。
  • マイページでは支払い方法を変更できません。変更希望はお問い合わせへ案内し、 Storefront 側に顧客の決済可否を直接変更する mutation やローカル保存経路を追加しません。
  • 請求書ダウンロードと注文ごとの支払状態は注文履歴・注文詳細に統合し、 実体の薄い「支払・請求」専用ページや主要メニュー導線は置きません。

テスト

現コード上の主なテストは以下に分かれて実装されています。

  • packages/plugins/src/b2b-extensions/payments/credit-sale-handler.spec.ts
  • packages/plugins/src/payment-integration/handlers/cash-on-delivery-handler.spec.ts
  • packages/plugins/src/payment-integration/handlers/bank-transfer-handler.spec.ts

テスト実行

# プラグインのテストを実行
pnpm --filter @ritsubi/plugins test

テスト内容

  • 各ハンドラーの createPayment メソッドのテスト
  • 各ハンドラーの settlePayment メソッドのテスト
  • 各ハンドラーの cancelPayment メソッドのテスト

決済方法別の購入 smoke(Storefront)

各決済方法でストアの注文が実際に成立することは、Storefront の smoke で一括保証します。

  • spec: apps/storefront/tests/e2e/smoke/payment-methods-purchase.real.spec.ts
  • タグ: @smoke:auth @smoke:payment-purchase(SBPS は加えて @sbps-real
  • 実行入口: just storefront-payment-purchase-smoke <env>
  • 対象決済:
  • local: 代引き (cash-on-delivery) / 売掛 (credit-sale) / 銀行振込 (bank-transfer) の非リダイレクト 3 種。
  • staging: 上記 3 種に加え、staging secret の RUN_SBPS_E2E=true のとき SBPS クレジットカード (credit-card) を含む 4 種。
  • eligibility: 決済方法ごとに許可顧客が異なるため、E2E_DIRECT_PAYMENT_*(代引き)/ E2E_PRICE_SPECIAL_*(売掛)/ E2E_DIRECT_CUSTOMER_*(銀行振込)を method と 1:1 で対応させる。
  • 安全策: 実注文を生成するため @smoke:prod-safe を持たず production smoke では実行されない。 SBPS は実課金を伴うため staging/local 限定ガードで production URL への誤実行を fail-closed で止め、 完了後に同日返金する。テスト金額は上表の少額方針(SBPS は ¥2〜、その他 ¥1)に従う。

詳細な運用入口は monitoring-operations.md を参照。

関連ドキュメント

実装履歴

  • 2025-01-XX: 売掛決済、代引き決済、銀行振込/前入金ハンドラーの実装完了
  • 2025-01-XX: PREPAYMENT定数を削除し、BANK_TRANSFERで統一