b2c-hooks

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

B2C Commerce Hooks

B2C Commerce Hooks

Hooks are extension points that allow you to customize business logic by registering scripts. B2C Commerce supports two types of hooks:
  1. OCAPI/SCAPI Hooks - Extend API resources with before, after, and modifyResponse hooks
  2. System Hooks - Custom extension points for order calculation, payment, and other core functionality
Hook是允许你通过注册脚本自定义业务逻辑的扩展点。B2C Commerce支持两类Hook:
  1. OCAPI/SCAPI Hooks - 通过before、after、modifyResponse Hook扩展API资源
  2. 系统Hook - 用于订单计算、支付等核心功能的自定义扩展点

Hook Types Overview

Hook类型总览

TypePurposeExamples
OCAPI/SCAPIExtend API behavior
dw.ocapi.shop.basket.afterPOST
SystemCore business logic
dw.order.calculate
CustomYour own extension points
app.checkout.validate
类型用途示例
OCAPI/SCAPI扩展API行为
dw.ocapi.shop.basket.afterPOST
系统核心业务逻辑
dw.order.calculate
自定义自定义扩展点
app.checkout.validate

Hook Registration

Hook注册

File Structure

文件结构

my_cartridge/
├── package.json           # References hooks.json
└── cartridge/
    └── scripts/
        ├── hooks.json     # Hook registrations
        └── hooks/         # Hook implementations
            ├── basket.js
            └── order.js
my_cartridge/
├── package.json           # 引用hooks.json
└── cartridge/
    └── scripts/
        ├── hooks.json     # Hook注册配置
        └── hooks/         # Hook实现代码
            ├── basket.js
            └── order.js

package.json

package.json

Reference the hooks configuration file:
json
{
  "name": "my_cartridge",
  "hooks": "./cartridge/scripts/hooks.json"
}
引用Hook配置文件:
json
{
  "name": "my_cartridge",
  "hooks": "./cartridge/scripts/hooks.json"
}

hooks.json

hooks.json

Register hooks with their implementing scripts:
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"
    }
  ]
}
注册Hook及其对应的实现脚本:
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"
    }
  ]
}

Hook Script

Hook脚本

Export functions matching the hook method name (without package prefix):
javascript
// 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';
};
导出与Hook方法名匹配的函数(不需要包前缀):
javascript
// hooks/basket.js
var Status = require('dw/system/Status');

exports.afterPOST = function(basket) {
    // 购物车创建完成后调用
    // 若返回值则会跳过系统实现
};

exports.modifyPOSTResponse = function(basket, basketResponse) {
    // 修改API响应
    basketResponse.c_customField = 'value';
};

HookMgr API

HookMgr API

Use
dw.system.HookMgr
to call hooks programmatically:
javascript
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);
}
MethodDescription
hasHook(extensionPoint)
Returns true if hook is registered or has default implementation
callHook(extensionPoint, functionName, args...)
Calls the hook, returns result or undefined
使用
dw.system.HookMgr
以编程方式调用Hook:
javascript
var HookMgr = require('dw/system/HookMgr');

// 检查Hook是否存在
if (HookMgr.hasHook('dw.order.calculate')) {
    // 调用Hook
    var result = HookMgr.callHook('dw.order.calculate', 'calculate', basket);
}
方法描述
hasHook(extensionPoint)
若Hook已注册或存在默认实现则返回true
callHook(extensionPoint, functionName, args...)
调用Hook,返回结果或undefined

Status Object

Status对象

Hooks return
dw.system.Status
to indicate success or failure:
javascript
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;
StatusHTTP ResponseBehavior
Status.OK
ContinuesHook execution continues
Status.ERROR
400 Bad RequestTransaction rolled back, processing stops
Uncaught exception500 Internal ErrorTransaction rolled back
Hook返回
dw.system.Status
来表示执行成功或失败:
javascript
var Status = require('dw/system/Status');

// 成功 - 继续后续处理
return new Status(Status.OK);

// 错误 - 停止处理,回滚事务
var status = new Status(Status.ERROR);
status.addDetail('error_code', 'INVALID_ADDRESS');
status.addDetail('message', '地址校验失败');
return status;
状态HTTP响应行为
Status.OK
继续执行Hook继续执行
Status.ERROR
400 Bad Request事务回滚,停止处理
未捕获异常500 Internal Error事务回滚

Return Value Behavior (Important)

返回值行为(重要)

OCAPI/SCAPI hooks that return ANY value will SKIP the system implementation and all subsequent registered hooks for that extension point.
This is a common source of bugs. For example, if a hook returns
Status.OK
, the system's
dw.order.calculate
implementation won't run, causing cart totals to be incorrect.
返回任意值的OCAPI/SCAPI Hook会跳过系统实现以及该扩展点下所有后续注册的Hook。
这是常见的Bug来源。例如,如果一个Hook返回
Status.OK
,系统的
dw.order.calculate
实现就不会运行,导致购物车总额计算错误。

When to Return a Value

什么时候需要返回值

Return a
Status
object only when you want to:
  • Stop processing with an error (
    Status.ERROR
    )
  • Skip the system implementation intentionally
仅当你需要达成以下目的时才返回
Status
对象:
  • 以错误状态停止处理
    Status.ERROR
  • 主动跳过系统实现

When NOT to Return a Value

什么时候不需要返回值

To ensure system implementations run (like cart calculation), return nothing:
javascript
// 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;
};
为了保证系统实现正常运行(比如购物车计算),不要返回任何值
javascript
// 返回Status.OK会跳过系统实现
exports.afterPOST = function(basket) {
    doSomething(basket);
    return new Status(Status.OK);  // 会跳过dw.order.calculate
};

// 无返回值 - 系统实现正常运行
exports.afterPOST = function(basket) {
    doSomething(basket);
    // 无返回,或者显式写 return;
};

Summary

总结

Return ValueOCAPI/SCAPI BehaviorCustom Hook Behavior
undefined
(no return)
System implementation runs, subsequent hooks runAll hooks run
Status.OK
Skips system implementation and subsequent hooksAll hooks run
Status.ERROR
Stops processing, returns errorAll hooks run
Debugging tip: If cart totals are wrong or hooks aren't firing, check if an earlier hook is returning a value.
返回值OCAPI/SCAPI行为自定义Hook行为
undefined
(无返回)
系统实现运行,后续Hook执行所有Hook都执行
Status.OK
跳过系统实现和后续Hook所有Hook都执行
Status.ERROR
停止处理,返回错误所有Hook都执行
调试提示:如果购物车总额错误或者Hook没有触发,检查前面的Hook是否返回了值。

OCAPI/SCAPI Hooks

OCAPI/SCAPI Hooks

OCAPI and SCAPI share the same hooks. Enable in Business Manager: Administration > Global Preferences > Feature Switches > Enable Salesforce Commerce Cloud API hook execution
OCAPI和SCAPI共享相同的Hook。在Business Manager中开启: Administration > Global Preferences > Feature Switches > Enable Salesforce Commerce Cloud API hook execution

Hook Types

Hook类型

HookWhen CalledUse Case
before<METHOD>
Before processingValidation, access control
after<METHOD>
After processing (in transaction)Data modification, external calls
modify<METHOD>Response
Before response sentAdd/modify response properties
Hook调用时机使用场景
before<METHOD>
处理请求前校验、权限控制
after<METHOD>
处理请求后(事务内)数据修改、外部调用
modify<METHOD>Response
响应发送前添加/修改响应属性

Common Hook Patterns

常见Hook模式

javascript
// 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;
};
javascript
// 在beforePUT中做校验
exports.beforePUT = function(basket, addressDoc) {
    if (!isValidAddress(addressDoc)) {
        var status = new Status(Status.ERROR);
        status.addDetail('validation_error', '无效地址');
        return status;
    }
};

// 在afterPOST中调用外部服务(事务内)
exports.afterPOST = function(basket, paymentDoc) {
    var result = callPaymentService(paymentDoc);
    request.custom.paymentResult = result; // 传递给modifyResponse
    // 返回Status会跳过系统实现
};

// 修改响应
exports.modifyPOSTResponse = function(basket, basketResponse, paymentDoc) {
    basketResponse.c_paymentStatus = request.custom.paymentResult.status;
};

Passing Data Between Hooks

在Hook之间传递数据

Use
request.custom
to pass data between hooks in the same request:
javascript
// 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;
};
使用
request.custom
在同一个请求的不同Hook之间传递数据:
javascript
// 在afterPOST中
exports.afterPOST = function(basket, doc) {
    request.custom.externalId = callExternalService();
};

// 在modifyPOSTResponse中
exports.modifyPOSTResponse = function(basket, response, doc) {
    response.c_externalId = request.custom.externalId;
};

Detect SCAPI vs OCAPI

区分SCAPI和OCAPI

javascript
exports.afterPOST = function(basket) {
    if (request.isSCAPI()) {
        // SCAPI-specific logic
    } else {
        // OCAPI-specific logic
    }
};
javascript
exports.afterPOST = function(basket) {
    if (request.isSCAPI()) {
        // SCAPI专属逻辑
    } else {
        // OCAPI专属逻辑
    }
};

System Hooks

系统Hook

Calculate Hooks

计算类Hook

Extension PointFunctionPurpose
dw.order.calculate
calculate
Full basket/order calculation
dw.order.calculateShipping
calculateShipping
Shipping calculation
dw.order.calculateTax
calculateTax
Tax calculation
javascript
// 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.calculate
calculate
完整购物车/订单计算
dw.order.calculateShipping
calculateShipping
运费计算
dw.order.calculateTax
calculateTax
税费计算
javascript
// hooks/calculate.js
var Status = require('dw/system/Status');
var HookMgr = require('dw/system/HookMgr');

exports.calculate = function(lineItemCtnr) {
    // 计算运费
    HookMgr.callHook('dw.order.calculateShipping', 'calculateShipping', lineItemCtnr);

    // 计算优惠、总额...

    // 计算税费
    HookMgr.callHook('dw.order.calculateTax', 'calculateTax', lineItemCtnr);

    return new Status(Status.OK);
};

Payment Hooks

支付类Hook

Extension PointFunctionPurpose
dw.order.payment.authorize
authorize
Payment authorization
dw.order.payment.capture
capture
Capture authorized payment
dw.order.payment.refund
refund
Refund payment
dw.order.payment.validateAuthorization
validateAuthorization
Check authorization validity
dw.order.payment.reauthorize
reauthorize
Re-authorize expired auth
扩展点函数用途
dw.order.payment.authorize
authorize
支付授权
dw.order.payment.capture
capture
捕获已授权的支付金额
dw.order.payment.refund
refund
退款
dw.order.payment.validateAuthorization
validateAuthorization
检查授权有效性
dw.order.payment.reauthorize
reauthorize
重新授权过期的授权

Order Hooks

订单类Hook

Extension PointFunctionPurpose
dw.order.createOrderNo
createOrderNo
Custom order number generation
javascript
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;
};
扩展点函数用途
dw.order.createOrderNo
createOrderNo
自定义订单号生成
javascript
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;
};

Custom Hooks

自定义Hook

Create your own extension points:
javascript
// 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...
}
Register in hooks.json:
json
{
  "hooks": [
    {
      "name": "app.checkout.validate",
      "script": "./hooks/checkout.js"
    }
  ]
}
Custom hooks always execute all registered implementations regardless of return value.
创建你自己的扩展点:
javascript
// 定义自定义Hook
var HookMgr = require('dw/system/HookMgr');

function processCheckout(basket) {
    // 如果自定义Hook已注册则调用
    if (HookMgr.hasHook('app.checkout.validate')) {
        var status = HookMgr.callHook('app.checkout.validate', 'validate', basket);
        if (status && status.error) {
            return status;
        }
    }
    // 继续处理...
}
在hooks.json中注册:
json
{
  "hooks": [
    {
      "name": "app.checkout.validate",
      "script": "./hooks/checkout.js"
    }
  ]
}
自定义Hook无论返回值是什么,都会执行所有已注册的实现。

Remote Includes in Hooks

Hook中的远程包含

Enhance API responses with data from other SCAPI endpoints:
javascript
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] };
};
用其他SCAPI接口的数据丰富API响应:
javascript
var RESTResponseMgr = require('dw/system/RESTResponseMgr');

exports.modifyGETResponse = function(product, doc) {
    // 包含自定义API的响应
    var include = RESTResponseMgr.createScapiRemoteInclude(
        'custom',           // API族
        'my-api',           // API名称
        'v1',               // 版本
        'endpoint'          // 接口路径
    );
    doc.c_additionalData = { value: [include] };
};

Best Practices

最佳实践

  • Return
    undefined
    (no return) from OCAPI/SCAPI hooks to ensure system implementations run
  • Only return
    Status.ERROR
    when you need to stop processing
  • Returning
    Status.OK
    skips system implementation and subsequent hooks
  • Use
    request.custom
    to pass data between hooks
  • Check
    request.isSCAPI()
    when supporting both APIs
  • Keep hooks focused and performant
  • Use custom properties (
    c_
    prefix) in modifyResponse
  • Avoid transactions in calculate hooks (breaks SCAPI)
  • Avoid slow external calls in beforeGET (affects caching)
  • OCAPI/SCAPI Hook返回
    undefined
    (无返回值)以保证系统实现正常运行
  • 仅当你需要停止处理时才返回
    Status.ERROR
  • 返回
    Status.OK
    会跳过系统实现和后续Hook
  • 使用
    request.custom
    在Hook之间传递数据
  • 同时支持两类API时检查
    request.isSCAPI()
  • 保持Hook逻辑专注且高性能
  • 在modifyResponse中使用自定义属性(
    c_
    前缀)
  • 避免在计算Hook中使用事务(会破坏SCAPI)
  • 避免在beforeGET中调用慢的外部接口(会影响缓存)

Error Handling

错误处理

Circuit Breaker

熔断器

Too many hook errors triggers circuit breaker (HTTP 503):
json
{
  "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"
}
Hook错误过多会触发熔断器(返回HTTP 503):
json
{
  "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"
}

Timeout

超时

Hooks must complete within the SCAPI timeout (HTTP 504 on timeout).
Hook必须在SCAPI超时时间内完成(超时返回HTTP 504)。

Detailed References

详细参考

  • OCAPI/SCAPI Hooks - API hook patterns and available hooks
  • System Hooks - Calculate, payment, and order hooks
  • OCAPI/SCAPI Hooks - API Hook模式和可用Hook列表
  • 系统Hook - 计算、支付和订单类Hook