b2c-hooks
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseB2C 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:
- OCAPI/SCAPI Hooks - Extend API resources with before, after, and modifyResponse hooks
- System Hooks - Custom extension points for order calculation, payment, and other core functionality
Hook是允许你通过注册脚本自定义业务逻辑的扩展点。B2C Commerce支持两类Hook:
- OCAPI/SCAPI Hooks - 通过before、after、modifyResponse Hook扩展API资源
- 系统Hook - 用于订单计算、支付等核心功能的自定义扩展点
Hook Types Overview
Hook类型总览
| Type | Purpose | Examples |
|---|---|---|
| OCAPI/SCAPI | Extend API behavior | |
| System | Core business logic | |
| Custom | Your own extension points | |
| 类型 | 用途 | 示例 |
|---|---|---|
| OCAPI/SCAPI | 扩展API行为 | |
| 系统 | 核心业务逻辑 | |
| 自定义 | 自定义扩展点 | |
Hook Registration
Hook注册
File Structure
文件结构
my_cartridge/
├── package.json # References hooks.json
└── cartridge/
└── scripts/
├── hooks.json # Hook registrations
└── hooks/ # Hook implementations
├── basket.js
└── order.jsmy_cartridge/
├── package.json # 引用hooks.json
└── cartridge/
└── scripts/
├── hooks.json # Hook注册配置
└── hooks/ # Hook实现代码
├── basket.js
└── order.jspackage.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 to call hooks programmatically:
dw.system.HookMgrjavascript
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);
}| Method | Description |
|---|---|
| Returns true if hook is registered or has default implementation |
| Calls the hook, returns result or undefined |
使用以编程方式调用Hook:
dw.system.HookMgrjavascript
var HookMgr = require('dw/system/HookMgr');
// 检查Hook是否存在
if (HookMgr.hasHook('dw.order.calculate')) {
// 调用Hook
var result = HookMgr.callHook('dw.order.calculate', 'calculate', basket);
}| 方法 | 描述 |
|---|---|
| 若Hook已注册或存在默认实现则返回true |
| 调用Hook,返回结果或undefined |
Status Object
Status对象
Hooks return to indicate success or failure:
dw.system.Statusjavascript
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;| Status | HTTP Response | Behavior |
|---|---|---|
| Continues | Hook execution continues |
| 400 Bad Request | Transaction rolled back, processing stops |
| Uncaught exception | 500 Internal Error | Transaction rolled back |
Hook返回来表示执行成功或失败:
dw.system.Statusjavascript
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响应 | 行为 |
|---|---|---|
| 继续执行 | Hook继续执行 |
| 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 , the system's implementation won't run, causing cart totals to be incorrect.
Status.OKdw.order.calculate返回任意值的OCAPI/SCAPI Hook会跳过系统实现以及该扩展点下所有后续注册的Hook。
这是常见的Bug来源。例如,如果一个Hook返回,系统的实现就不会运行,导致购物车总额计算错误。
Status.OKdw.order.calculateWhen to Return a Value
什么时候需要返回值
Return a object only when you want to:
Status- 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 Value | OCAPI/SCAPI Behavior | Custom Hook Behavior |
|---|---|---|
| System implementation runs, subsequent hooks run | All hooks run |
| Skips system implementation and subsequent hooks | All hooks run |
| Stops processing, returns error | All 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行为 |
|---|---|---|
| 系统实现运行,后续Hook执行 | 所有Hook都执行 |
| 跳过系统实现和后续Hook | 所有Hook都执行 |
| 停止处理,返回错误 | 所有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类型
| Hook | When Called | Use Case |
|---|---|---|
| Before processing | Validation, access control |
| After processing (in transaction) | Data modification, external calls |
| Before response sent | Add/modify response properties |
| Hook | 调用时机 | 使用场景 |
|---|---|---|
| 处理请求前 | 校验、权限控制 |
| 处理请求后(事务内) | 数据修改、外部调用 |
| 响应发送前 | 添加/修改响应属性 |
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 to pass data between hooks in the same request:
request.customjavascript
// 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;
};使用在同一个请求的不同Hook之间传递数据:
request.customjavascript
// 在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 Point | Function | Purpose |
|---|---|---|
| | Full basket/order calculation |
| | Shipping calculation |
| | 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);
};| 扩展点 | 函数 | 用途 |
|---|---|---|
| | 完整购物车/订单计算 |
| | 运费计算 |
| | 税费计算 |
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 Point | Function | Purpose |
|---|---|---|
| | Payment authorization |
| | Capture authorized payment |
| | Refund payment |
| | Check authorization validity |
| | Re-authorize expired auth |
| 扩展点 | 函数 | 用途 |
|---|---|---|
| | 支付授权 |
| | 捕获已授权的支付金额 |
| | 退款 |
| | 检查授权有效性 |
| | 重新授权过期的授权 |
Order Hooks
订单类Hook
| Extension Point | Function | Purpose |
|---|---|---|
| | 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;
};| 扩展点 | 函数 | 用途 |
|---|---|---|
| | 自定义订单号生成 |
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 (no return) from OCAPI/SCAPI hooks to ensure system implementations run
undefined - Only return when you need to stop processing
Status.ERROR - Returning skips system implementation and subsequent hooks
Status.OK - Use to pass data between hooks
request.custom - Check when supporting both APIs
request.isSCAPI() - Keep hooks focused and performant
- Use custom properties (prefix) in modifyResponse
c_ - Avoid transactions in calculate hooks (breaks SCAPI)
- Avoid slow external calls in beforeGET (affects caching)
- OCAPI/SCAPI Hook返回(无返回值)以保证系统实现正常运行
undefined - 仅当你需要停止处理时才返回
Status.ERROR - 返回会跳过系统实现和后续Hook
Status.OK - 使用在Hook之间传递数据
request.custom - 同时支持两类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