mcpField
B
b2c-hooks

b2c-hooks

github.com/salesforcecommercecloud開発5790 回インストール今日更新
リポジトリ

b2c-hooks について

Salesforce B2C Commerce の開発者向けに、OCAPI/SCAPI、システム、カスタムフックの登録と実装を説明するスキルです。戻り値の挙動、リクエスト内のデータ受け渡し、注文決済の承認フローを扱います。

主な機能

  1. 01フック登録ファイルとスクリプト構成
  2. 02HookMgr の呼び出しとステータス処理
  3. 03OCAPI/SCAPI フックの挙動
  4. 04request.custom によるリクエスト内のデータ受け渡し
  5. 05注文決済の承認と注文確定

活用シーン

  1. 01OCAPI または SCAPI の動作を拡張
  2. 02注文計算などのシステム拡張を実装
  3. 03注文決済の承認と失敗処理を設計

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/salesforcecommercecloud/b2c-developer-tooling --skill b2c-hooks

インストール後、Claude Code で/b2c-hooksを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

evals/
trigger-evals.json
references/
OCAPI-SCAPI-HOOKS.md
ORDER-HOOK-LIFECYCLE.md
SYSTEM-HOOKS.md

詳細

B2C Commerce フック

フックは、スクリプトを登録してビジネスロジックをカスタマイズできる拡張ポイントです。B2C Commerce は、次の 2 種類のフックをサポートしています。

  1. OCAPI/SCAPI フック - before、after、modifyResponse フックで API リソースを拡張します
  2. システムフック - 注文計算、決済、その他のコア機能向けのカスタム拡張ポイントです

フックタイプの概要

タイプ目的例
OCAPI/SCAPIAPI の動作を拡張しますdw.ocapi.shop.basket.afterPOST
システムコアビジネスロジックdw.order.calculate
カスタム独自の拡張ポイントapp.checkout.validate

フックの登録

ファイル構成
my_cartridge/
├── package.json           # References hooks.json
└── cartridge/
    └── scripts/
        ├── hooks.json     # Hook registrations
        └── hooks/         # Hook implementations
            ├── basket.js
            └── order.js
package.json

フック設定ファイルを参照します。

{
  "name": "my_cartridge",
  "hooks": "./cartridge/scripts/hooks.json"
}
hooks.json

フックを実装するスクリプトとともに登録します。

{
  "hooks": [
    {
      "name": "dw.ocapi.shop.basket.afterPOST",
      "script": "./hooks/basket.js"
    },
    {
      "name": "dw.ocapi.shop.basket.modifyPOSTResponse",
      "script": "./hooks/basket.js"
    },
    {
      "name": "dw.order.calculate",
      "script": "./hooks/order.js"
    }
  ]
}
フックスクリプト

フックメソッド名(パッケージプレフィックスを除く)に一致する関数をエクスポートします。

// hooks/basket.js
var Status = require('dw/system/Status');

exports.afterPOST = function(basket) {
    // Called after basket creation
    // Returning a value would skip system implementation
};

exports.modifyPOSTResponse = function(basket, basketResponse) {
    // Modify the API response
    basketResponse.c_customField = 'value';
};

HookMgr API

dw.system.HookMgr を使用して、プログラムからフックを呼び出します。

var HookMgr = require('dw/system/HookMgr');

// Check if hook exists
if (HookMgr.hasHook('dw.order.calculate')) {
    // Call the hook
    var result = HookMgr.callHook('dw.order.calculate', 'calculate', basket);
}
メソッド説明
hasHook(extensionPoint)フックが登録されているか、デフォルト実装がある場合に true を返します
callHook(extensionPoint, functionName, args...)フックを呼び出し、結果または undefined を返します

Status オブジェクト

フックは、成功または失敗を示すために dw.system.Status を返します。

var Status = require('dw/system/Status');

// Success - continue processing
return new Status(Status.OK);

// Error - stop processing, rollback transaction
var status = new Status(Status.ERROR);
status.addDetail('error_code', 'INVALID_ADDRESS');
status.addDetail('message', 'Address validation failed');
return status;
ステータスHTTP レスポンス動作
Status.OK続行フックの実行を続行します
Status.ERROR400 Bad Requestトランザクションをロールバックし、処理を停止します
捕捉されない例外500 Internal Errorトランザクションをロールバックします

戻り値の動作(重要)

値を返す OCAPI/SCAPI フックは、システム実装と、その拡張ポイントに登録されている後続のすべてのフックをスキップします。

これはよくあるバグの原因です。たとえば、フックが Status.OK を返すと、システムの dw.order.calculate 実装が実行されず、カートの合計が正しくならないことがあります。

値を返す場合

次の場合に限り、Status オブジェクトを返します。

  • エラー(Status.ERROR)で処理を停止したい場合
  • システム実装を意図的にスキップしたい場合
値を返さない場合

カート計算などのシステム実装が実行されるようにするには、何も返さないでください。

// Returning Status.OK skips system implementation
exports.afterPOST = function(basket) {
    doSomething(basket);
    return new Status(Status.OK);  // Skips dw.order.calculate
};

// No return value - system implementation runs
exports.afterPOST = function(basket) {
    doSomething(basket);
    // No return, or explicit: return;
};
まとめ
戻り値OCAPI/SCAPI の動作カスタムフックの動作
undefined(戻り値なし)システム実装と後続のフックが実行されますすべてのフックが実行されます
Status.OKシステム実装と後続のフックをスキップしますすべてのフックが実行されます
Status.ERROR処理を停止し、エラーを返しますすべてのフックが実行されます

デバッグのヒント:カートの合計が正しくない場合やフックが実行されない場合は、先行するフックが値を返していないか確認してください。

OCAPI/SCAPI フック

OCAPI と SCAPI は同じフックを共有します。Business Manager で有効にします。 Administration > Global Preferences > Feature Switches > Enable Salesforce Commerce Cloud API hook execution

フックタイプ
フック呼び出されるタイミング用途
before<METHOD>処理の前検証、アクセス制御
after<METHOD>処理の後(トランザクション内)データの変更、外部呼び出し
modify<METHOD>Responseレスポンスの送信前レスポンスプロパティの追加・変更
よくあるフックパターン
// Validation in beforePUT
exports.beforePUT = function(basket, addressDoc) {
    if (!isValidAddress(addressDoc)) {
        var status = new Status(Status.ERROR);
        status.addDetail('validation_error', 'Invalid address');
        return status;
    }
};

// External call in afterPOST (within transaction)
exports.afterPOST = function(basket, paymentDoc) {
    var result = callPaymentService(paymentDoc);
    request.custom.paymentResult = result; // Pass to modifyResponse
    // Returning a Status would skip system implementation
};

// Modify response
exports.modifyPOSTResponse = function(basket, basketResponse, paymentDoc) {
    basketResponse.c_paymentStatus = request.custom.paymentResult.status;
};
フック間でのデータの受け渡し

同じリクエスト内のフック間でデータを受け渡すには、request.custom を使用します。

// In afterPOST
exports.afterPOST = function(basket, doc) {
    request.custom.externalId = callExternalService();
};

// In modifyPOSTResponse
exports.modifyPOSTResponse = function(basket, response, doc) {
    response.c_externalId = request.custom.externalId;
};
SCAPI と OCAPI の判別
exports.afterPOST = function(basket) {
    if (request.isSCAPI()) {
        // SCAPI-specific logic
    } else {
        // OCAPI-specific logic
    }
};

ヘッドレス注文の決済:Order-PI 認証シームを使用する

ドキュメントに記載された Shopper Orders のライフサイクルを優先してください。デフォルトでは dw.ocapi.shop.order.afterPOST で決済を承認しないでください。

POST /orders
  -> order persists in CREATED
PATCH /orders/{orderNo}/payment-instruments/{paymentInstrumentId}
  -> platform updates the order PI
  -> dw.order.payment.authorizeCreditCard or dw.order.payment.authorize
  -> order.payment_instrument.afterPATCH(..., successfullyAuthorized)
  -> default afterPATCH places the order when authorized coverage >= order total

これにより、注文作成とゲートウェイ呼び出しが分離され、復旧に使える永続的な注文番号が保持されます。また、標準の Commerce 実装および決済拡張機能と同じ認証拡張ポイントを使用でき、最終的な対象範囲に基づく注文確定をプラットフォームに任せることができます。

承認のセマンティクス

order-PI の PATCH では、次のように決済フックを選択します。

  • 選択した決済方法が有効であり、注文に適用可能であることを確認します。 CREDIT_CARD の場合は、リクエストに paymentCard を含めます。
  • リクエストに paymentCard が含まれる場合、または注文の PI がクレジットカードタイプの場合は、dw.order.payment.authorizeCreditCard を呼び出します。
  • それ以外の場合は、dw.order.payment.authorize を呼び出します。
  • 保存済みの決済方法では、ドキュメントに記載されている顧客の決済手段 ID(OCAPI のドキュメントでは customer_payment_instrument_id と呼ばれ、生成された SCAPI クライアントではキャメルケースのプロパティとして公開される場合があります)を渡します。Commerce は注文顧客の決済手段からこれを解決し、承認用にその PI をコピーします。未知の ID が渡されると例外が発生します。リクエストの金額とカードセキュリティコードは、引き続き authorizeCreditCard に渡されます。
  • 顧客 PI への参照がない場合、承認入力には paymentCard や銀行口座情報などのリクエストデータが設定されます。paymentDetails 引数が、すでに order に紐付けられた永続的な PI と同じオブジェクトインスタンスであると想定しないでください。プロバイダーの状態がそこに保存されている場合は、永続的な決済手段を解決して検証します。
  • クライアントから渡された再利用可能な決済トークンを、所有権の代替として受け入れないでください。
  • 承認の実装では、決済トランザクションのプロセッサー、金額、タイプ、プロバイダーのトランザクション識別子を設定します。承認が成功した場合にのみ Status.OK を返し、それ以外の場合は Status.ERROR を返します。

dw.order.payment.authorizeCreditCard、dw.order.payment.authorize、および order-PI の beforePATCH/afterPATCH フックを登録します。beforePATCH では、ゲートウェイを呼び出す前に、信頼できないリクエストを永続的な注文状態と照合して検証します。検証対象は、PI 数、決済方法、適用可能なプロセッサー、サーバー管理の決済配分、金額、所有権/トークンの出所、統合状態です。単一 PI のチェックアウトでは、要求金額が注文合計と一致することを求められます。分割決済の場合は、サーバー管理の各配分を検証します。

ドキュメントに記載されている afterPATCH の 4 つのパラメーターすべてを使用します。プラットフォームは決済承認の Status を successfullyAuthorized に集約します。同じリクエスト内でその区別が必要な場合に限り、より詳細なプロセッサーの結果を request.custom に保持します。

var Status = require('dw/system/Status');

exports.afterPATCH = function (order, paymentInstrument, newPaymentInstrument, successfullyAuthorized) {
    if (!successfullyAuthorized) {
        if (request.custom.deterministicPaymentDecline === true) {
            return new Status(Status.ERROR, 'PAYMENT_DECLINED');
        }
        return; // Preserve indeterminate/review state for reconciliation.
    }

    var validation = validateAuthorizedInstrument(order, paymentInstrument);
    if (validation) { return validation; }
    // Return undefined so the default placement implementation still runs.
};

successfullyAuthorized === true は、処理された決済手段に適用されます。これだけでは、複数の決済手段を使う注文の決済がすべて完了したことの証明にはなりません。承認済みの決済額が注文合計以上になった場合にのみ、デフォルトの実装が引き続き CREATED 状態の注文を確定するようにします。この処理によって注文は NEW に変わり、エクスポートできる状態になります。

ストアフロント/BFF での結果処理

返された注文ステータスを、注文確定の正式な結果として扱います。

  • NEW:注文の確定に成功しました。
  • CREATED:注文は確定されていません。複数の決済手段を承認している間は想定される状態の場合がありますが、拒否/審査/不明な状態を表している場合もあります。
  • 決定的な承認失敗:理由コード payment_auth_failure を指定して、Shopper Orders の POST /orders/{orderNo}/actions/fail?reopenBasket=true を呼び出し、Commerce に失敗した注文を永続化させてバスケットを復元します。
  • ゲートウェイの結果が不確定:自動的に再試行したり、バスケットを再度開いたりしないでください。重複承認を防ぐため、照合用に注文を CREATED のまま保持します。

この推奨フローでは、order POST フック間で失敗処理を調整するのではなく、永続的な状態遷移に Shopper Orders の fail アクションを使用します。

代替案:order.afterPOSTで承認して注文を確定する

チェックアウトで、注文の作成、すべてのPIの承認、Commerce側での注文確定または失敗への遷移を、注文作成トランザクション内のサーバーサイドフェーズで実行する必要がある場合、または決済プロセッサーが注文PI承認エンドポイントに対応できない場合は、dw.ocapi.shop.order.afterPOSTを使用します。これにより、ストアフロントのオーケストレーションをシンプルに保てます。外部ゲートウェイでの副作用はCommerceトランザクションとともにロールバックされないため、次のトレードオフを考慮してください。

  • 外部決済呼び出しは注文作成のプラットフォームトランザクション内で実行され、注文POSTのタイムアウト時間を消費します。
  • Status.ERRORを返すと注文作成がロールバックされるため、呼び出し元は注文と元のバスケット参照の両方を失う可能性があります。
  • 実装では、すべてのPIを手動で承認し、合計金額をカバーしていることを確認し、OrderMgr.placeOrder/failOrderを呼び出して、後続のプラットフォームまたはカートリッジの動作を維持する必要があります。
  • afterPOST内でOrderMgr.placeOrder()またはOrderMgr.failOrder()をTransaction.wrap()で囲まないでください。これらはすでにプラットフォームトランザクション内で実行されます。

この方法を選択する場合は、トランザクションのセマンティクスと、HTTPエラーを返しながら失敗した注文を永続化する2フック方式について、 注文フックのライフサイクルを参照してください。

フック間のデータ受け渡しに使うrequest.custom

request.customは、フックフェーズ間でリクエストスコープのデータを受け渡す目的にのみ使用してください。同一リクエスト内でauthorize*からafterPATCHへ詳細な結果を渡すためだけに、永続メタデータを追加しないでください。

システムフック

計算フック
拡張ポイント関数目的
dw.order.calculatecalculateバスケット/注文の全体計算
dw.order.calculateShippingcalculateShipping配送計算
dw.order.calculateTaxcalculateTax税額計算
// hooks/calculate.js
var Status = require('dw/system/Status');
var HookMgr = require('dw/system/HookMgr');

exports.calculate = function(lineItemCtnr) {
    // Calculate shipping
    HookMgr.callHook('dw.order.calculateShipping', 'calculateShipping', lineItemCtnr);

    // Calculate promotions, totals...

    // Calculate tax
    HookMgr.callHook('dw.order.calculateTax', 'calculateTax', lineItemCtnr);

    return new Status(Status.OK);
};
決済フック
拡張ポイント関数目的
dw.order.payment.authorizeauthorize決済承認
dw.order.payment.authorizeCreditCardauthorizeCreditCard注文PIのPOST/PATCHによって呼び出されるクレジットカード承認
dw.order.payment.capturecapture承認済み決済の売上確定
dw.order.payment.refundrefund決済の返金
dw.order.payment.validateAuthorizationvalidateAuthorization承認の有効性を確認
dw.order.payment.reauthorizereauthorize期限切れの承認を再承認
注文フック
拡張ポイント関数目的
dw.order.createOrderNocreateOrderNoカスタム注文番号の生成
var OrderMgr = require('dw/order/OrderMgr');
var Site = require('dw/system/Site');

exports.createOrderNo = function() {
    var seqNo = OrderMgr.createOrderSequenceNo();
    var prefix = Site.current.ID;
    return prefix + '-' + seqNo;
};

カスタムフック

独自の拡張ポイントを作成します。

// Define custom hook
var HookMgr = require('dw/system/HookMgr');

function processCheckout(basket) {
    // Call custom hook if registered
    if (HookMgr.hasHook('app.checkout.validate')) {
        var status = HookMgr.callHook('app.checkout.validate', 'validate', basket);
        if (status && status.error) {
            return status;
        }
    }
    // Continue processing...
}

hooks.jsonに登録します。

{
  "hooks": [
    {
      "name": "app.checkout.validate",
      "script": "./hooks/checkout.js"
    }
  ]
}

カスタムフックは、戻り値にかかわらず、登録されているすべての実装を常に実行します。

フック内のリモートインクルード

他のSCAPIエンドポイントからのデータでAPIレスポンスを拡張します。

var RESTResponseMgr = require('dw/system/RESTResponseMgr');

exports.modifyGETResponse = function(product, doc) {
    // Include Custom API response
    var include = RESTResponseMgr.createScapiRemoteInclude(
        'custom',           // API family
        'my-api',           // API name
        'v1',               // Version
        'endpoint'          // Endpoint
    );
    doc.c_additionalData = { value: [include] };
};

ベストプラクティス

  • システム実装が実行されるよう、OCAPI/SCAPIフックからundefined(戻り値なし)を返します
  • 処理を停止する必要がある場合にのみStatus.ERRORを返します
  • Status.OKを返すと、システム実装と後続のフックがスキップされます
  • フック間でデータを渡すにはrequest.customを使用します
  • 両方のAPIに対応する場合はrequest.isSCAPI()を確認します
  • フックの役割を絞り、パフォーマンスを保ちます
  • modifyResponseではカスタムプロパティ(c_プレフィックス)を使用します
  • calculateフックでのトランザクションは避けてください(SCAPIが動作しなくなります)
  • beforeGETで時間のかかる外部呼び出しは避けてください(キャッシュに影響します)

エラー処理

サーキットブレーカー

フックエラーが多すぎると、サーキットブレーカーが作動します(HTTP 503)。

{
  "title": "Hook Circuit Breaker",
  "type": "https://api.commercecloud.salesforce.com/.../hook-circuit-breaker",
  "detail": "Failure rate above threshold of '50%'",
  "extensionPointName": "dw.ocapi.shop.basket.afterPOST"
}
タイムアウト

フックはSCAPIのタイムアウト時間内に完了する必要があります(タイムアウト時はHTTP 504)。

詳細リファレンス

  • OCAPI/SCAPI Hooks - API フックのパターンと利用可能なフック
  • System Hooks - 計算、支払い、注文のフック
  • Order Hook Lifecycle - ライフサイクルの各フェーズ、ロールバックの動作、2 フックパターン

コメント