|
| 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