Skip to content

Commit 29b970a

Browse files
committed
Add b2c-hooks skill for hook registration and customization
Covers: - Hook registration via package.json and hooks.json - HookMgr API for calling and checking hooks - Status object for controlling hook flow - OCAPI/SCAPI hooks (before/after/modifyResponse patterns) - System hooks (calculate, payment, order) - Custom extension points - Passing data between hooks via request.custom - Remote includes in modifyResponse hooks - Custom properties (c_ prefix) in API responses - Error handling and circuit breaker - SCAPI vs OCAPI detection with request.isSCAPI() Reference files: - OCAPI-SCAPI-HOOKS.md: API hook patterns by resource - SYSTEM-HOOKS.md: Calculate, payment, order hooks Content validated against official documentation and Script API reference.
1 parent 6a4b18e commit 29b970a

3 files changed

Lines changed: 1056 additions & 0 deletions

File tree

Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
---
2+
name: b2c-hooks
3+
description: Hook registration, HookMgr, OCAPI/SCAPI hooks, and system extension points
4+
---
5+
6+
# B2C Commerce Hooks
7+
8+
Hooks are extension points that allow you to customize business logic by registering scripts. B2C Commerce supports two types of hooks:
9+
10+
1. **OCAPI/SCAPI Hooks** - Extend API resources with before, after, and modifyResponse hooks
11+
2. **System Hooks** - Custom extension points for order calculation, payment, and other core functionality
12+
13+
## Hook Types Overview
14+
15+
| Type | Purpose | Examples |
16+
|------|---------|----------|
17+
| OCAPI/SCAPI | Extend API behavior | `dw.ocapi.shop.basket.afterPOST` |
18+
| System | Core business logic | `dw.order.calculate` |
19+
| Custom | Your own extension points | `app.checkout.validate` |
20+
21+
## Hook Registration
22+
23+
### File Structure
24+
25+
```
26+
my_cartridge/
27+
├── package.json # References hooks.json
28+
└── cartridge/
29+
└── scripts/
30+
├── hooks.json # Hook registrations
31+
└── hooks/ # Hook implementations
32+
├── basket.js
33+
└── order.js
34+
```
35+
36+
### package.json
37+
38+
Reference the hooks configuration file:
39+
40+
```json
41+
{
42+
"name": "my_cartridge",
43+
"hooks": "./cartridge/scripts/hooks.json"
44+
}
45+
```
46+
47+
### hooks.json
48+
49+
Register hooks with their implementing scripts:
50+
51+
```json
52+
{
53+
"hooks": [
54+
{
55+
"name": "dw.ocapi.shop.basket.afterPOST",
56+
"script": "./hooks/basket.js"
57+
},
58+
{
59+
"name": "dw.ocapi.shop.basket.modifyPOSTResponse",
60+
"script": "./hooks/basket.js"
61+
},
62+
{
63+
"name": "dw.order.calculate",
64+
"script": "./hooks/order.js"
65+
}
66+
]
67+
}
68+
```
69+
70+
### Hook Script
71+
72+
Export functions matching the hook method name (without package prefix):
73+
74+
```javascript
75+
// hooks/basket.js
76+
var Status = require('dw/system/Status');
77+
78+
exports.afterPOST = function(basket) {
79+
// Called after basket creation
80+
return new Status(Status.OK);
81+
};
82+
83+
exports.modifyPOSTResponse = function(basket, basketResponse) {
84+
// Modify the API response
85+
basketResponse.c_customField = 'value';
86+
};
87+
```
88+
89+
## HookMgr API
90+
91+
Use `dw.system.HookMgr` to call hooks programmatically:
92+
93+
```javascript
94+
var HookMgr = require('dw/system/HookMgr');
95+
96+
// Check if hook exists
97+
if (HookMgr.hasHook('dw.order.calculate')) {
98+
// Call the hook
99+
var result = HookMgr.callHook('dw.order.calculate', 'calculate', basket);
100+
}
101+
```
102+
103+
| Method | Description |
104+
|--------|-------------|
105+
| `hasHook(extensionPoint)` | Returns true if hook is registered or has default implementation |
106+
| `callHook(extensionPoint, functionName, args...)` | Calls the hook, returns result or undefined |
107+
108+
## Status Object
109+
110+
Hooks return `dw.system.Status` to indicate success or failure:
111+
112+
```javascript
113+
var Status = require('dw/system/Status');
114+
115+
// Success - continue processing
116+
return new Status(Status.OK);
117+
118+
// Error - stop processing, rollback transaction
119+
var status = new Status(Status.ERROR);
120+
status.addDetail('error_code', 'INVALID_ADDRESS');
121+
status.addDetail('message', 'Address validation failed');
122+
return status;
123+
```
124+
125+
| Status | HTTP Response | Behavior |
126+
|--------|---------------|----------|
127+
| `Status.OK` | Continues | Hook execution continues |
128+
| `Status.ERROR` | 400 Bad Request | Transaction rolled back, processing stops |
129+
| Uncaught exception | 500 Internal Error | Transaction rolled back |
130+
131+
## OCAPI/SCAPI Hooks
132+
133+
OCAPI and SCAPI share the same hooks. Enable in Business Manager:
134+
**Administration > Global Preferences > Feature Switches > Enable Salesforce Commerce Cloud API hook execution**
135+
136+
### Hook Types
137+
138+
| Hook | When Called | Use Case |
139+
|------|-------------|----------|
140+
| `before<METHOD>` | Before processing | Validation, access control |
141+
| `after<METHOD>` | After processing (in transaction) | Data modification, external calls |
142+
| `modify<METHOD>Response` | Before response sent | Add/modify response properties |
143+
144+
### Common Hook Patterns
145+
146+
```javascript
147+
// Validation in beforePUT
148+
exports.beforePUT = function(basket, addressDoc) {
149+
if (!isValidAddress(addressDoc)) {
150+
var status = new Status(Status.ERROR);
151+
status.addDetail('validation_error', 'Invalid address');
152+
return status;
153+
}
154+
};
155+
156+
// External call in afterPOST (within transaction)
157+
exports.afterPOST = function(basket, paymentDoc) {
158+
var result = callPaymentService(paymentDoc);
159+
request.custom.paymentResult = result; // Pass to modifyResponse
160+
return new Status(Status.OK);
161+
};
162+
163+
// Modify response
164+
exports.modifyPOSTResponse = function(basket, basketResponse, paymentDoc) {
165+
basketResponse.c_paymentStatus = request.custom.paymentResult.status;
166+
};
167+
```
168+
169+
### Passing Data Between Hooks
170+
171+
Use `request.custom` to pass data between hooks in the same request:
172+
173+
```javascript
174+
// In afterPOST
175+
exports.afterPOST = function(basket, doc) {
176+
request.custom.externalId = callExternalService();
177+
};
178+
179+
// In modifyPOSTResponse
180+
exports.modifyPOSTResponse = function(basket, response, doc) {
181+
response.c_externalId = request.custom.externalId;
182+
};
183+
```
184+
185+
### Detect SCAPI vs OCAPI
186+
187+
```javascript
188+
exports.afterPOST = function(basket) {
189+
if (request.isSCAPI()) {
190+
// SCAPI-specific logic
191+
} else {
192+
// OCAPI-specific logic
193+
}
194+
};
195+
```
196+
197+
## System Hooks
198+
199+
### Calculate Hooks
200+
201+
| Extension Point | Function | Purpose |
202+
|-----------------|----------|---------|
203+
| `dw.order.calculate` | `calculate` | Full basket/order calculation |
204+
| `dw.order.calculateShipping` | `calculateShipping` | Shipping calculation |
205+
| `dw.order.calculateTax` | `calculateTax` | Tax calculation |
206+
207+
```javascript
208+
// hooks/calculate.js
209+
var Status = require('dw/system/Status');
210+
var HookMgr = require('dw/system/HookMgr');
211+
212+
exports.calculate = function(lineItemCtnr) {
213+
// Calculate shipping
214+
HookMgr.callHook('dw.order.calculateShipping', 'calculateShipping', lineItemCtnr);
215+
216+
// Calculate promotions, totals...
217+
218+
// Calculate tax
219+
HookMgr.callHook('dw.order.calculateTax', 'calculateTax', lineItemCtnr);
220+
221+
return new Status(Status.OK);
222+
};
223+
```
224+
225+
### Payment Hooks
226+
227+
| Extension Point | Function | Purpose |
228+
|-----------------|----------|---------|
229+
| `dw.order.payment.authorize` | `authorize` | Payment authorization |
230+
| `dw.order.payment.capture` | `capture` | Capture authorized payment |
231+
| `dw.order.payment.refund` | `refund` | Refund payment |
232+
| `dw.order.payment.validateAuthorization` | `validateAuthorization` | Check authorization validity |
233+
| `dw.order.payment.reauthorize` | `reauthorize` | Re-authorize expired auth |
234+
235+
### Order Hooks
236+
237+
| Extension Point | Function | Purpose |
238+
|-----------------|----------|---------|
239+
| `dw.order.createOrderNo` | `createOrderNo` | Custom order number generation |
240+
241+
```javascript
242+
var OrderMgr = require('dw/order/OrderMgr');
243+
var Site = require('dw/system/Site');
244+
245+
exports.createOrderNo = function() {
246+
var seqNo = OrderMgr.createOrderSequenceNo();
247+
var prefix = Site.current.ID;
248+
return prefix + '-' + seqNo;
249+
};
250+
```
251+
252+
## Custom Hooks
253+
254+
Create your own extension points:
255+
256+
```javascript
257+
// Define custom hook
258+
var HookMgr = require('dw/system/HookMgr');
259+
260+
function processCheckout(basket) {
261+
// Call custom hook if registered
262+
if (HookMgr.hasHook('app.checkout.validate')) {
263+
var status = HookMgr.callHook('app.checkout.validate', 'validate', basket);
264+
if (status && status.error) {
265+
return status;
266+
}
267+
}
268+
// Continue processing...
269+
}
270+
```
271+
272+
Register in hooks.json:
273+
274+
```json
275+
{
276+
"hooks": [
277+
{
278+
"name": "app.checkout.validate",
279+
"script": "./hooks/checkout.js"
280+
}
281+
]
282+
}
283+
```
284+
285+
Custom hooks always execute all registered implementations regardless of return value.
286+
287+
## Remote Includes in Hooks
288+
289+
Enhance API responses with data from other SCAPI endpoints:
290+
291+
```javascript
292+
var RESTResponseMgr = require('dw/system/RESTResponseMgr');
293+
294+
exports.modifyGETResponse = function(product, doc) {
295+
// Include Custom API response
296+
var include = RESTResponseMgr.createScapiRemoteInclude(
297+
'custom', // API family
298+
'my-api', // API name
299+
'v1', // Version
300+
'endpoint' // Endpoint
301+
);
302+
doc.c_additionalData = { value: [include] };
303+
};
304+
```
305+
306+
## Best Practices
307+
308+
### Do
309+
310+
- Return `Status` objects to control flow
311+
- Use `request.custom` to pass data between hooks
312+
- Check `request.isSCAPI()` when supporting both APIs
313+
- Keep hooks focused and performant
314+
- Use custom properties (`c_` prefix) in modifyResponse
315+
316+
### Don't
317+
318+
- Use transactions in calculate hooks (breaks SCAPI)
319+
- Modify standard response properties (only `c_` properties)
320+
- Rely on hook execution order across cartridges
321+
- Make slow external calls in beforeGET (affects caching)
322+
323+
## Error Handling
324+
325+
### Circuit Breaker
326+
327+
Too many hook errors triggers circuit breaker (HTTP 503):
328+
329+
```json
330+
{
331+
"title": "Hook Circuit Breaker",
332+
"type": "https://api.commercecloud.salesforce.com/.../hook-circuit-breaker",
333+
"detail": "Failure rate above threshold of '50%'",
334+
"extensionPointName": "dw.ocapi.shop.basket.afterPOST"
335+
}
336+
```
337+
338+
### Timeout
339+
340+
Hooks must complete within the SCAPI timeout (HTTP 504 on timeout).
341+
342+
## Detailed References
343+
344+
- [OCAPI/SCAPI Hooks](references/OCAPI-SCAPI-HOOKS.md) - API hook patterns and available hooks
345+
- [System Hooks](references/SYSTEM-HOOKS.md) - Calculate, payment, and order hooks

0 commit comments

Comments
 (0)