@adobe-commerce/aio-toolkit 1.2.4 → 1.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +145 -0
- package/README.md +169 -0
- package/dist/aio-toolkit-cli-workflow/bin/cli.js +2048 -0
- package/dist/aio-toolkit-cli-workflow/bin/cli.js.map +1 -0
- package/dist/aio-toolkit-cursor-context/bin/cli.js +16 -0
- package/dist/aio-toolkit-cursor-context/bin/cli.js.map +1 -1
- package/dist/index.d.mts +51 -6
- package/dist/index.d.ts +51 -6
- package/dist/index.js +209 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +213 -0
- package/dist/index.mjs.map +1 -1
- package/files/cursor-context/commands/aio-toolkit-analyze-adobe-commerce-module.md +612 -0
- package/files/cursor-context/commands/aio-toolkit-create-amazon-sqs-consumer.md +445 -0
- package/files/cursor-context/commands/aio-toolkit-create-event-consumer-action.md +6 -0
- package/files/cursor-context/commands/aio-toolkit-create-graphql-action.md +21 -7
- package/files/cursor-context/commands/aio-toolkit-create-openwhisk-action.md +326 -0
- package/files/cursor-context/commands/aio-toolkit-create-runtime-action.md +15 -5
- package/files/cursor-context/commands/aio-toolkit-create-shipping-carrier.md +681 -0
- package/files/cursor-context/commands/aio-toolkit-create-webhook-action.md +22 -9
- package/files/cursor-context/rules/aio-toolkit-create-adobe-commerce-client.mdc +252 -116
- package/files/cursor-context/rules/aio-toolkit-oop-best-practices.mdc +10 -4
- package/files/cursor-context/rules/aio-toolkit-setup-new-relic-telemetry.mdc +167 -2
- package/files/cursor-context/rules/aio-toolkit-use-abdb-collection.mdc +610 -0
- package/files/cursor-context/rules/aio-toolkit-use-abdb-repository.mdc +705 -0
- package/files/cursor-context/rules/aio-toolkit-use-adobe-auth.mdc +442 -0
- package/files/cursor-context/rules/aio-toolkit-use-amazon-sqs-publish.mdc +397 -0
- package/files/cursor-context/rules/aio-toolkit-use-file-repository.mdc +502 -0
- package/files/cursor-context/rules/aio-toolkit-use-publish-event.mdc +510 -0
- package/files/cursor-context/rules/aio-toolkit-use-runtime-api-gateway-service.mdc +542 -0
- package/package.json +4 -2
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
# AIO Toolkit: Create Shipping Carrier
|
|
2
|
+
|
|
3
|
+
**Command Name:** `aio-toolkit-create-shipping-carrier`
|
|
4
|
+
|
|
5
|
+
**Description:** Creates an Adobe Commerce OOPE (Out-of-Process Extensibility) custom shipping carrier using @adobe-commerce/aio-toolkit — including the carrier class, webhook action for rate computation, and optional Commerce API management methods.
|
|
6
|
+
|
|
7
|
+
## Workflow
|
|
8
|
+
|
|
9
|
+
This command creates a complete OOPE shipping carrier implementation: a carrier class in `lib/shipping-carriers/` and a `WebhookAction` in `actions/` that Commerce calls at checkout to retrieve available shipping rates.
|
|
10
|
+
|
|
11
|
+
### Step 1: Verify Prerequisites
|
|
12
|
+
|
|
13
|
+
1. Check if `@adobe-commerce/aio-toolkit` is installed in `package.json`
|
|
14
|
+
- If NOT installed, ask user if they want to install it: `npm install @adobe-commerce/aio-toolkit`
|
|
15
|
+
2. Detect project language (TypeScript or JavaScript)
|
|
16
|
+
- Check for `typescript` in dependencies + `tsconfig.json`
|
|
17
|
+
- Check for `.ts` files in `actions/` or `lib/`
|
|
18
|
+
- Default to JavaScript if ambiguous
|
|
19
|
+
3. Detect project structure
|
|
20
|
+
- Check for `application:` in `app.config.yaml` (root actions)
|
|
21
|
+
- Check for `extensions:` in `app.config.yaml` (extension point actions)
|
|
22
|
+
|
|
23
|
+
### Step 2: Collect Carrier Configuration
|
|
24
|
+
|
|
25
|
+
Ask the user:
|
|
26
|
+
|
|
27
|
+
1. **Carrier Class Name** (required)
|
|
28
|
+
- PascalCase class name, e.g., `FlatRateCarrier`, `ExpressCarrier`, `TablerateEmployee`
|
|
29
|
+
|
|
30
|
+
2. **Carrier Code** (required)
|
|
31
|
+
- Unique identifier registered in Commerce Admin, format: `oope_[name]`
|
|
32
|
+
- Must contain only alphanumeric characters and underscores
|
|
33
|
+
- Example: `oope_flat_rate`, `oope_express`, `oope_tablerate_employee`
|
|
34
|
+
|
|
35
|
+
3. **Carrier Display Title** (required)
|
|
36
|
+
- Customer-facing name shown at checkout, e.g., `Flat Rate Shipping`, `Express Delivery`
|
|
37
|
+
|
|
38
|
+
4. **Supported Stores** (comma-separated, or `all` for no restriction)
|
|
39
|
+
- Store view codes from Commerce Admin → Stores → All Stores
|
|
40
|
+
- Example: `default`, `en_us,en_ca`, `arcteryx_en`
|
|
41
|
+
|
|
42
|
+
5. **Supported Countries** (comma-separated ISO 3166-1 alpha-2 codes, or `all`)
|
|
43
|
+
- Example: `US,CA`, `US,CA,GB,AU`
|
|
44
|
+
|
|
45
|
+
6. **Sort Order** (default: `10`)
|
|
46
|
+
- Display order at checkout — lower numbers appear first
|
|
47
|
+
|
|
48
|
+
7. **Rate Methods** — How many rate methods does this carrier offer?
|
|
49
|
+
- For each method, ask:
|
|
50
|
+
- **Method code** (e.g., `standard`, `express`, `overnight`)
|
|
51
|
+
- **Method title** (e.g., `Standard Shipping (5-7 days)`)
|
|
52
|
+
- **Rate logic** — static price or computed from `params`?
|
|
53
|
+
- Static: ask for price and cost values
|
|
54
|
+
- Dynamic: ask which `params` fields drive the computation (e.g., `cart_total`, `destination_country`, `weight`)
|
|
55
|
+
|
|
56
|
+
8. **Signature Verification**
|
|
57
|
+
- **Disabled** — `SignatureVerification.DISABLED` (simplest, use for dev/testing)
|
|
58
|
+
- **Enabled with PUBLIC_KEY** — Requires `PUBLIC_KEY` env var
|
|
59
|
+
- **Enabled with PUBLIC_KEY_BASE64** — Requires base64-encoded key in env var
|
|
60
|
+
|
|
61
|
+
9. **Action Name** (default: `shipping-rates`)
|
|
62
|
+
- The webhook action name in `app.config.yaml`
|
|
63
|
+
|
|
64
|
+
10. **Action Location**
|
|
65
|
+
- Root application (`actions/`)
|
|
66
|
+
- Extension point (`[extension-path]/actions/`)
|
|
67
|
+
|
|
68
|
+
11. **Package Structure**
|
|
69
|
+
- Simple: `actions/[action-name]/index.[js/ts]`
|
|
70
|
+
- Packaged: `actions/[package]/[action-name]/index.[js/ts]`
|
|
71
|
+
|
|
72
|
+
12. **Commerce OOPE Management Methods** (optional)
|
|
73
|
+
- Does this carrier class also need methods to register/fetch/update/delete itself in Commerce via the OOPE REST API?
|
|
74
|
+
- If **Yes**, ask:
|
|
75
|
+
- **Connection type**: `Oauth1a` (recommended), `IMS`, or `Basic`
|
|
76
|
+
- Which methods are needed: `fetchCarrier`, `fetchAllCarriers`, `registerCarrier`, `updateCarrier`, `deleteCarrier`
|
|
77
|
+
- If **No**, skip the Commerce client integration
|
|
78
|
+
|
|
79
|
+
### Step 3: Confirm Configuration
|
|
80
|
+
|
|
81
|
+
Display summary:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
📋 Shipping Carrier Configuration
|
|
85
|
+
|
|
86
|
+
Carrier Class: [ClassName]
|
|
87
|
+
Carrier Code: [oope_code]
|
|
88
|
+
Display Title: [title]
|
|
89
|
+
Stores: [stores or all]
|
|
90
|
+
Countries: [countries or all]
|
|
91
|
+
Sort Order: [order]
|
|
92
|
+
Language: [JavaScript/TypeScript] (auto-detected)
|
|
93
|
+
|
|
94
|
+
Rate Methods:
|
|
95
|
+
- [method-code]: [method-title] — [static $X.XX | dynamic from params.[field]]
|
|
96
|
+
[... for each method]
|
|
97
|
+
|
|
98
|
+
Signature Verification: [Disabled/PUBLIC_KEY/PUBLIC_KEY_BASE64]
|
|
99
|
+
Action Name: [action-name]
|
|
100
|
+
Location: [Root/Extension]
|
|
101
|
+
Package: [package-name or simple]
|
|
102
|
+
Commerce OOPE Management: [Yes (Oauth1a/IMS/Basic) | No]
|
|
103
|
+
|
|
104
|
+
✅ Files to Create:
|
|
105
|
+
- lib/shipping-carriers/[carrier-name]/index.[js/ts]
|
|
106
|
+
- actions/[path]/index.[js/ts]
|
|
107
|
+
- Update app.config.yaml or ext.config.yaml
|
|
108
|
+
|
|
109
|
+
Should I proceed?
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Step 4: Generate Carrier Class
|
|
113
|
+
|
|
114
|
+
Create `lib/shipping-carriers/[carrier-name]/index.[js|ts]`.
|
|
115
|
+
|
|
116
|
+
Use kebab-case for the directory name derived from the carrier class name (e.g., `FlatRateCarrier` → `flat-rate`, `TablerateEmployee` → `tablerate-employee`).
|
|
117
|
+
|
|
118
|
+
#### A. Basic carrier class (no Commerce management)
|
|
119
|
+
|
|
120
|
+
**JavaScript:**
|
|
121
|
+
```javascript
|
|
122
|
+
/*
|
|
123
|
+
* <license header>
|
|
124
|
+
*/
|
|
125
|
+
|
|
126
|
+
const { ShippingCarrier } = require('@adobe-commerce/aio-toolkit');
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* [ClassName] — [display title] shipping carrier.
|
|
130
|
+
*
|
|
131
|
+
* Extends ShippingCarrier to configure the carrier and its available rate methods.
|
|
132
|
+
* Use getData() to retrieve the carrier configuration and addMethod() to build rates.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* const carrier = new [ClassName]();
|
|
136
|
+
* carrier.addMethod('standard', (method) => {
|
|
137
|
+
* method.setMethodTitle('Standard Shipping').setPrice(5.99).setCost(3.00);
|
|
138
|
+
* });
|
|
139
|
+
* const operations = new ShippingCarrierResponse(carrier).generate();
|
|
140
|
+
*/
|
|
141
|
+
class [ClassName] extends ShippingCarrier {
|
|
142
|
+
/**
|
|
143
|
+
* Carrier code registered in Adobe Commerce OOPE configuration.
|
|
144
|
+
* Must match the code configured in Commerce Admin.
|
|
145
|
+
*/
|
|
146
|
+
static CARRIER_CODE = '[oope_code]';
|
|
147
|
+
|
|
148
|
+
constructor() {
|
|
149
|
+
super([ClassName].CARRIER_CODE, (carrier) => {
|
|
150
|
+
carrier.setTitle('[display title]');
|
|
151
|
+
carrier.setStores([/* ['default'] or [] for all stores */]);
|
|
152
|
+
carrier.setCountries([/* ['US', 'CA'] or [] for all countries */]);
|
|
153
|
+
carrier.setSortOrder([sort_order]);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = { [ClassName] };
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**TypeScript:**
|
|
162
|
+
```typescript
|
|
163
|
+
/*
|
|
164
|
+
* <license header>
|
|
165
|
+
*/
|
|
166
|
+
|
|
167
|
+
import { ShippingCarrier } from '@adobe-commerce/aio-toolkit';
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* [ClassName] — [display title] shipping carrier.
|
|
171
|
+
*/
|
|
172
|
+
export class [ClassName] extends ShippingCarrier {
|
|
173
|
+
public static readonly CARRIER_CODE = '[oope_code]';
|
|
174
|
+
|
|
175
|
+
constructor() {
|
|
176
|
+
super([ClassName].CARRIER_CODE, (carrier) => {
|
|
177
|
+
carrier.setTitle('[display title]');
|
|
178
|
+
carrier.setStores([/* ['default'] or [] for all stores */]);
|
|
179
|
+
carrier.setCountries([/* ['US', 'CA'] or [] for all countries */]);
|
|
180
|
+
carrier.setSortOrder([sort_order]);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
#### B. Carrier class with Commerce OOPE management methods
|
|
187
|
+
|
|
188
|
+
When the user selects "Yes" for Commerce OOPE Management, extend the class with `AdobeCommerceClient` (lazy init) and the OOPE REST API methods. Do NOT use `@adobe-commerce/aio-services-kit` — use `AdobeCommerceClient` directly.
|
|
189
|
+
|
|
190
|
+
**JavaScript:**
|
|
191
|
+
```javascript
|
|
192
|
+
/*
|
|
193
|
+
* <license header>
|
|
194
|
+
*/
|
|
195
|
+
|
|
196
|
+
const { ShippingCarrier, AdobeCommerceClient } = require('@adobe-commerce/aio-toolkit');
|
|
197
|
+
const { AdobeCommerceClientBuilder } = require('@lib/adobe-commerce/client-builder');
|
|
198
|
+
|
|
199
|
+
class [ClassName] extends ShippingCarrier {
|
|
200
|
+
static CARRIER_CODE = '[oope_code]';
|
|
201
|
+
|
|
202
|
+
/** @type {AdobeCommerceClient|undefined} */
|
|
203
|
+
#client;
|
|
204
|
+
|
|
205
|
+
/** @type {Promise<AdobeCommerceClient>|undefined} */
|
|
206
|
+
#clientInitPromise;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @param {object} params - Action params (process.env equivalent)
|
|
210
|
+
*/
|
|
211
|
+
constructor(params) {
|
|
212
|
+
super([ClassName].CARRIER_CODE, (carrier) => {
|
|
213
|
+
carrier.setTitle('[display title]');
|
|
214
|
+
carrier.setStores([/* stores */]);
|
|
215
|
+
carrier.setCountries([/* countries */]);
|
|
216
|
+
carrier.setSortOrder([sort_order]);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
this.params = params;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Lazily initializes and returns the AdobeCommerceClient.
|
|
224
|
+
* @private
|
|
225
|
+
* @returns {Promise<AdobeCommerceClient>}
|
|
226
|
+
*/
|
|
227
|
+
async #getClient() {
|
|
228
|
+
if (this.#client) return this.#client;
|
|
229
|
+
if (this.#clientInitPromise) return this.#clientInitPromise;
|
|
230
|
+
|
|
231
|
+
this.#clientInitPromise = AdobeCommerceClientBuilder.generate(this.params);
|
|
232
|
+
this.#client = await this.#clientInitPromise;
|
|
233
|
+
this.#clientInitPromise = undefined;
|
|
234
|
+
|
|
235
|
+
return this.#client;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Fetches this carrier's configuration from Commerce.
|
|
240
|
+
* @returns {Promise<{success: boolean, statusCode?: number, message: any}>}
|
|
241
|
+
*/
|
|
242
|
+
async fetchCarrier() {
|
|
243
|
+
const client = await this.#getClient();
|
|
244
|
+
return client.get(`rest/V1/oope_shipping_carrier/${[ClassName].CARRIER_CODE}`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Fetches all registered OOPE shipping carriers from Commerce.
|
|
249
|
+
* @returns {Promise<{success: boolean, statusCode?: number, message: any}>}
|
|
250
|
+
*/
|
|
251
|
+
async fetchAllCarriers() {
|
|
252
|
+
const client = await this.#getClient();
|
|
253
|
+
return client.get('rest/V1/oope_shipping_carrier');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Registers this carrier in Commerce (creates the OOPE carrier record).
|
|
258
|
+
* Call this once during setup / deployment — not on every webhook request.
|
|
259
|
+
* @param {object} [payload] - Optional override payload; defaults to this.getData()
|
|
260
|
+
* @returns {Promise<{success: boolean, statusCode?: number, message: any}>}
|
|
261
|
+
*/
|
|
262
|
+
async registerCarrier(payload = undefined) {
|
|
263
|
+
const client = await this.#getClient();
|
|
264
|
+
const carrierData = payload || this.getData();
|
|
265
|
+
return client.post('rest/V1/oope_shipping_carrier', { carrier: carrierData });
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Updates this carrier's configuration in Commerce.
|
|
270
|
+
* @param {object} [payload] - Optional override payload; defaults to this.getData()
|
|
271
|
+
* @returns {Promise<{success: boolean, statusCode?: number, message: any}>}
|
|
272
|
+
*/
|
|
273
|
+
async updateCarrier(payload = undefined) {
|
|
274
|
+
const client = await this.#getClient();
|
|
275
|
+
const carrierData = payload || this.getData();
|
|
276
|
+
return client.put('rest/V1/oope_shipping_carrier', { carrier: carrierData });
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Deletes this carrier from Commerce.
|
|
281
|
+
* @returns {Promise<{success: boolean, statusCode?: number, message: any}>}
|
|
282
|
+
*/
|
|
283
|
+
async deleteCarrier() {
|
|
284
|
+
const client = await this.#getClient();
|
|
285
|
+
return client.delete(`rest/V1/oope_shipping_carrier/${[ClassName].CARRIER_CODE}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
module.exports = { [ClassName] };
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**TypeScript:**
|
|
293
|
+
```typescript
|
|
294
|
+
/*
|
|
295
|
+
* <license header>
|
|
296
|
+
*/
|
|
297
|
+
|
|
298
|
+
import { ShippingCarrier, type AdobeCommerceClient } from '@adobe-commerce/aio-toolkit';
|
|
299
|
+
import {
|
|
300
|
+
AdobeCommerceClientBuilder,
|
|
301
|
+
type AdobeCommerceClientParams,
|
|
302
|
+
} from '@lib/adobe-commerce/client-builder';
|
|
303
|
+
|
|
304
|
+
interface [ClassName]Params extends AdobeCommerceClientParams {
|
|
305
|
+
// add any additional params your carrier needs here
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export class [ClassName] extends ShippingCarrier {
|
|
309
|
+
public static readonly CARRIER_CODE = '[oope_code]';
|
|
310
|
+
|
|
311
|
+
private params: [ClassName]Params;
|
|
312
|
+
private client?: AdobeCommerceClient;
|
|
313
|
+
private clientInitPromise?: Promise<AdobeCommerceClient>;
|
|
314
|
+
|
|
315
|
+
constructor(params: [ClassName]Params) {
|
|
316
|
+
super([ClassName].CARRIER_CODE, (carrier) => {
|
|
317
|
+
carrier.setTitle('[display title]');
|
|
318
|
+
carrier.setStores([/* stores */]);
|
|
319
|
+
carrier.setCountries([/* countries */]);
|
|
320
|
+
carrier.setSortOrder([sort_order]);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
this.params = params;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
private async getClient(): Promise<AdobeCommerceClient> {
|
|
327
|
+
if (this.client) return this.client;
|
|
328
|
+
if (this.clientInitPromise) return this.clientInitPromise;
|
|
329
|
+
|
|
330
|
+
this.clientInitPromise = AdobeCommerceClientBuilder.generate(
|
|
331
|
+
this.params as AdobeCommerceClientParams
|
|
332
|
+
) as Promise<AdobeCommerceClient>;
|
|
333
|
+
|
|
334
|
+
this.client = await this.clientInitPromise;
|
|
335
|
+
this.clientInitPromise = undefined;
|
|
336
|
+
|
|
337
|
+
return this.client;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** Fetches this carrier's OOPE configuration from Commerce. */
|
|
341
|
+
public async fetchCarrier() {
|
|
342
|
+
const client = await this.getClient();
|
|
343
|
+
return client.get(`rest/V1/oope_shipping_carrier/${[ClassName].CARRIER_CODE}`);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** Fetches all registered OOPE shipping carriers from Commerce. */
|
|
347
|
+
public async fetchAllCarriers() {
|
|
348
|
+
const client = await this.getClient();
|
|
349
|
+
return client.get('rest/V1/oope_shipping_carrier');
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Registers this carrier in Commerce.
|
|
354
|
+
* Call this once during setup — not on every webhook request.
|
|
355
|
+
*/
|
|
356
|
+
public async registerCarrier(payload?: object) {
|
|
357
|
+
const client = await this.getClient();
|
|
358
|
+
const carrierData = payload || this.getData();
|
|
359
|
+
return client.post('rest/V1/oope_shipping_carrier', { carrier: carrierData });
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** Updates this carrier's configuration in Commerce. */
|
|
363
|
+
public async updateCarrier(payload?: object) {
|
|
364
|
+
const client = await this.getClient();
|
|
365
|
+
const carrierData = payload || this.getData();
|
|
366
|
+
return client.put('rest/V1/oope_shipping_carrier', { carrier: carrierData });
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** Deletes this carrier from Commerce. */
|
|
370
|
+
public async deleteCarrier() {
|
|
371
|
+
const client = await this.getClient();
|
|
372
|
+
return client.delete(`rest/V1/oope_shipping_carrier/${[ClassName].CARRIER_CODE}`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Step 5: Generate WebhookAction
|
|
378
|
+
|
|
379
|
+
Create `actions/[action-name]/index.[js|ts]`.
|
|
380
|
+
|
|
381
|
+
#### A. Static rates
|
|
382
|
+
|
|
383
|
+
**JavaScript:**
|
|
384
|
+
```javascript
|
|
385
|
+
/*
|
|
386
|
+
* <license header>
|
|
387
|
+
*/
|
|
388
|
+
|
|
389
|
+
const {
|
|
390
|
+
WebhookAction,
|
|
391
|
+
WebhookActionResponse,
|
|
392
|
+
ShippingCarrierResponse,
|
|
393
|
+
SignatureVerification,
|
|
394
|
+
} = require('@adobe-commerce/aio-toolkit');
|
|
395
|
+
const { [ClassName] } = require('@lib/shipping-carriers/[carrier-name]');
|
|
396
|
+
|
|
397
|
+
const name = '[action-name]';
|
|
398
|
+
|
|
399
|
+
exports.main = WebhookAction.execute(
|
|
400
|
+
name,
|
|
401
|
+
SignatureVerification.[DISABLED|ENABLED_WITH_PUBLIC_KEY|ENABLED_WITH_PUBLIC_KEY_BASE64],
|
|
402
|
+
[],
|
|
403
|
+
async (params, ctx) => {
|
|
404
|
+
const { logger } = ctx;
|
|
405
|
+
|
|
406
|
+
logger.info({ message: `${name}-start` });
|
|
407
|
+
|
|
408
|
+
const carrier = new [ClassName]();
|
|
409
|
+
|
|
410
|
+
carrier.addMethod('[method-code]', (method) => {
|
|
411
|
+
method
|
|
412
|
+
.setMethodTitle('[Method Title]')
|
|
413
|
+
.setPrice([price])
|
|
414
|
+
.setCost([cost])
|
|
415
|
+
.addAdditionalData('delivery_time', '[X-Y business days]');
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// [Add more methods as needed]
|
|
419
|
+
|
|
420
|
+
const operations = new ShippingCarrierResponse(carrier).generate();
|
|
421
|
+
|
|
422
|
+
logger.info({ message: `${name}-complete`, methods: carrier.getAddedMethods().length });
|
|
423
|
+
|
|
424
|
+
return WebhookActionResponse.success(operations);
|
|
425
|
+
}
|
|
426
|
+
);
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
#### B. Dynamic rates (computed from params)
|
|
430
|
+
|
|
431
|
+
```javascript
|
|
432
|
+
exports.main = WebhookAction.execute(
|
|
433
|
+
name,
|
|
434
|
+
SignatureVerification.[DISABLED|ENABLED],
|
|
435
|
+
[],
|
|
436
|
+
async (params, ctx) => {
|
|
437
|
+
const { logger } = ctx;
|
|
438
|
+
|
|
439
|
+
logger.info({ message: `${name}-start` });
|
|
440
|
+
|
|
441
|
+
// Access Commerce webhook payload fields
|
|
442
|
+
const cartTotal = params.cart_total || 0;
|
|
443
|
+
const destinationCountry = params.destination_country || 'US';
|
|
444
|
+
// [Access other params fields as needed]
|
|
445
|
+
|
|
446
|
+
const carrier = new [ClassName]();
|
|
447
|
+
|
|
448
|
+
// Compute rates based on params
|
|
449
|
+
const standardPrice = cartTotal >= 50 ? 0 : 5.99; // example: free over $50
|
|
450
|
+
|
|
451
|
+
carrier.addMethod('standard', (method) => {
|
|
452
|
+
method
|
|
453
|
+
.setMethodTitle(standardPrice === 0 ? 'Free Standard Shipping' : 'Standard Shipping (5-7 days)')
|
|
454
|
+
.setPrice(standardPrice)
|
|
455
|
+
.setCost(3.00)
|
|
456
|
+
.addAdditionalData('delivery_time', '5-7 business days');
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
// Example: only offer express for domestic
|
|
460
|
+
if (destinationCountry === 'US') {
|
|
461
|
+
carrier.addMethod('express', (method) => {
|
|
462
|
+
method
|
|
463
|
+
.setMethodTitle('Express Shipping (1-2 days)')
|
|
464
|
+
.setPrice(19.99)
|
|
465
|
+
.setCost(12.00)
|
|
466
|
+
.addAdditionalData('delivery_time', '1-2 business days');
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
logger.info({
|
|
471
|
+
message: `${name}-rates-computed`,
|
|
472
|
+
country: destinationCountry,
|
|
473
|
+
cart_total: cartTotal,
|
|
474
|
+
methods: carrier.getAddedMethods().length,
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
const operations = new ShippingCarrierResponse(carrier).generate();
|
|
478
|
+
return WebhookActionResponse.success(operations);
|
|
479
|
+
}
|
|
480
|
+
);
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
**TypeScript:** Same structure with `import` syntax and type annotations.
|
|
484
|
+
|
|
485
|
+
### Step 6: Update Configuration Files
|
|
486
|
+
|
|
487
|
+
Add action to `app.config.yaml` or `ext.config.yaml`.
|
|
488
|
+
|
|
489
|
+
**Basic (no signature verification):**
|
|
490
|
+
```yaml
|
|
491
|
+
[action-name]:
|
|
492
|
+
function: actions/[action-name]/index.[js/ts]
|
|
493
|
+
web: 'yes'
|
|
494
|
+
runtime: nodejs:22
|
|
495
|
+
inputs:
|
|
496
|
+
LOG_LEVEL: debug
|
|
497
|
+
annotations:
|
|
498
|
+
require-adobe-auth: true
|
|
499
|
+
final: true
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
**With signature verification:**
|
|
503
|
+
```yaml
|
|
504
|
+
[action-name]:
|
|
505
|
+
function: actions/[action-name]/index.[js/ts]
|
|
506
|
+
web: 'yes'
|
|
507
|
+
runtime: nodejs:22
|
|
508
|
+
inputs:
|
|
509
|
+
LOG_LEVEL: debug
|
|
510
|
+
PUBLIC_KEY: $PUBLIC_KEY # or PUBLIC_KEY_BASE64: $PUBLIC_KEY_BASE64
|
|
511
|
+
annotations:
|
|
512
|
+
require-adobe-auth: true
|
|
513
|
+
raw-http: true # REQUIRED for signature verification — populates __ow_body
|
|
514
|
+
final: true
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
> **`raw-http: true` is mandatory when signature verification is enabled.** Without it, `__ow_body` is not populated and signature verification will always fail.
|
|
518
|
+
|
|
519
|
+
**With Commerce OOPE management methods** (additional inputs needed):
|
|
520
|
+
```yaml
|
|
521
|
+
[action-name]:
|
|
522
|
+
function: actions/[action-name]/index.[js/ts]
|
|
523
|
+
web: 'yes'
|
|
524
|
+
runtime: nodejs:22
|
|
525
|
+
inputs:
|
|
526
|
+
LOG_LEVEL: debug
|
|
527
|
+
COMMERCE_BASE_URL: $COMMERCE_BASE_URL
|
|
528
|
+
# Oauth1a connection inputs:
|
|
529
|
+
OAUTH_CONSUMER_KEY: $OAUTH_CONSUMER_KEY
|
|
530
|
+
OAUTH_CONSUMER_SECRET: $OAUTH_CONSUMER_SECRET
|
|
531
|
+
OAUTH_ACCESS_TOKEN: $OAUTH_ACCESS_TOKEN
|
|
532
|
+
OAUTH_ACCESS_TOKEN_SECRET: $OAUTH_ACCESS_TOKEN_SECRET
|
|
533
|
+
# IMS connection inputs (use instead of Oauth1a if IMS):
|
|
534
|
+
# IMS_CLIENT_ID: $IMS_CLIENT_ID
|
|
535
|
+
# IMS_CLIENT_SECRET: $IMS_CLIENT_SECRET
|
|
536
|
+
# IMS_TECHNICAL_ACCOUNT_ID: $IMS_TECHNICAL_ACCOUNT_ID
|
|
537
|
+
# IMS_TECHNICAL_ACCOUNT_EMAIL: $IMS_TECHNICAL_ACCOUNT_EMAIL
|
|
538
|
+
# IMS_ORG_ID: $IMS_ORG_ID
|
|
539
|
+
annotations:
|
|
540
|
+
require-adobe-auth: true
|
|
541
|
+
final: true
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
### Step 7: Add Environment Variables (if applicable)
|
|
545
|
+
|
|
546
|
+
If signature verification is enabled, add to `.env`:
|
|
547
|
+
```bash
|
|
548
|
+
# Shipping webhook signature verification
|
|
549
|
+
PUBLIC_KEY=your-public-key
|
|
550
|
+
# or PUBLIC_KEY_BASE64=your-base64-encoded-public-key
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
If Commerce OOPE management methods are included, add to `.env`:
|
|
554
|
+
```bash
|
|
555
|
+
# Adobe Commerce connection
|
|
556
|
+
COMMERCE_BASE_URL=https://your-store.com # store root URL — do NOT include /rest or /V1
|
|
557
|
+
|
|
558
|
+
# Oauth1a (recommended)
|
|
559
|
+
OAUTH_CONSUMER_KEY=
|
|
560
|
+
OAUTH_CONSUMER_SECRET=
|
|
561
|
+
OAUTH_ACCESS_TOKEN=
|
|
562
|
+
OAUTH_ACCESS_TOKEN_SECRET=
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
### Step 8: Completion
|
|
566
|
+
|
|
567
|
+
Display:
|
|
568
|
+
|
|
569
|
+
```
|
|
570
|
+
✅ Shipping Carrier Created Successfully!
|
|
571
|
+
|
|
572
|
+
📁 Files Created:
|
|
573
|
+
- lib/shipping-carriers/[carrier-name]/index.[js/ts] ← carrier class
|
|
574
|
+
- actions/[action-name]/index.[js/ts] ← webhook action
|
|
575
|
+
|
|
576
|
+
📝 Configuration Updated:
|
|
577
|
+
- app.config.yaml or ext.config.yaml
|
|
578
|
+
|
|
579
|
+
🚀 Next Steps:
|
|
580
|
+
1. Register the carrier in Commerce Admin → Stores → Configuration → Shipping Methods
|
|
581
|
+
- Set Carrier Code to: [oope_code]
|
|
582
|
+
- Set the webhook URL to your deployed action endpoint
|
|
583
|
+
2. Test locally: aio app dev
|
|
584
|
+
3. Deploy: aio app deploy
|
|
585
|
+
4. Test by going to checkout — your carrier should appear in shipping options
|
|
586
|
+
|
|
587
|
+
[If Commerce management methods were added:]
|
|
588
|
+
5. Call carrier.registerCarrier(params) from a setup action to create the OOPE record in Commerce
|
|
589
|
+
|
|
590
|
+
📖 Documentation:
|
|
591
|
+
- ShippingCarrier, ShippingCarrierMethod, ShippingCarrierResponse: @adobe-commerce/aio-toolkit
|
|
592
|
+
- OOPE Shipping Carrier API: GET/POST/PUT /V1/oope_shipping_carrier, DELETE /V1/oope_shipping_carrier/{code}
|
|
593
|
+
|
|
594
|
+
⚠️ Important Notes:
|
|
595
|
+
- CARRIER_CODE must match exactly what is registered in Commerce Admin
|
|
596
|
+
- raw-http: true is required in app.config.yaml when signature verification is enabled
|
|
597
|
+
- registerCarrier() / updateCarrier() should be called from a setup/deploy action — not on every webhook invocation
|
|
598
|
+
- The OOPE Commerce API requires AdobeCommerceClient with appropriate connection credentials
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
---
|
|
602
|
+
|
|
603
|
+
### Key Features
|
|
604
|
+
|
|
605
|
+
- **Auto-detection**: Language (TS/JS) and project structure
|
|
606
|
+
- **Carrier class pattern**: Extends `ShippingCarrier`, static `CARRIER_CODE` constant, constructor configuration via callback
|
|
607
|
+
- **Static or dynamic rates**: Hardcoded prices or computed from Commerce webhook `params` payload
|
|
608
|
+
- **Signature verification**: Three modes — disabled, PUBLIC_KEY, PUBLIC_KEY_BASE64
|
|
609
|
+
- **Optional OOPE management**: `AdobeCommerceClient` with lazy init for fetch/register/update/delete — no `aio-services-kit` dependency
|
|
610
|
+
- **All 5 OOPE endpoints**: `GET /V1/oope_shipping_carrier`, `GET /V1/oope_shipping_carrier/{code}`, `POST`, `PUT`, `DELETE /{code}`
|
|
611
|
+
- **Lazy client init**: Single `clientInitPromise` guard prevents duplicate initializations
|
|
612
|
+
- **Commerce client builder**: Uses `lib/adobe-commerce/client-builder` pattern (see "Creating Adobe Commerce Client Operations" rule)
|
|
613
|
+
|
|
614
|
+
---
|
|
615
|
+
|
|
616
|
+
### OOPE Commerce API Reference
|
|
617
|
+
|
|
618
|
+
| Operation | Method | Endpoint | Body |
|
|
619
|
+
|---|---|---|---|
|
|
620
|
+
| List all carriers | `GET` | `rest/V1/oope_shipping_carrier` | — |
|
|
621
|
+
| Get by code | `GET` | `rest/V1/oope_shipping_carrier/{code}` | — |
|
|
622
|
+
| Create carrier | `POST` | `rest/V1/oope_shipping_carrier` | `{ carrier: ShippingCarrierData }` |
|
|
623
|
+
| Update carrier | `PUT` | `rest/V1/oope_shipping_carrier` | `{ carrier: ShippingCarrierData }` |
|
|
624
|
+
| Delete carrier | `DELETE` | `rest/V1/oope_shipping_carrier/{code}` | — |
|
|
625
|
+
|
|
626
|
+
**Response shape** (from `AdobeCommerceClient`):
|
|
627
|
+
```javascript
|
|
628
|
+
// Success
|
|
629
|
+
{ success: true, message: { code, title, active, stores, countries, ... } }
|
|
630
|
+
|
|
631
|
+
// Failure
|
|
632
|
+
{ success: false, statusCode: 401, message: 'Unauthorized' }
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
---
|
|
636
|
+
|
|
637
|
+
### ShippingCarrier Key Components
|
|
638
|
+
|
|
639
|
+
```javascript
|
|
640
|
+
// Carrier constructor
|
|
641
|
+
new ShippingCarrier(code, callback?)
|
|
642
|
+
// code: unique carrier code — only alphanumeric + underscores, must match Commerce Admin
|
|
643
|
+
// callback: optional setup function — called immediately with `this` for fluent config
|
|
644
|
+
|
|
645
|
+
// Configuration methods (all return `this` for chaining)
|
|
646
|
+
carrier.setTitle('Display Name')
|
|
647
|
+
carrier.setStores(['default', 'en_us']) // [] or omit for all stores
|
|
648
|
+
carrier.setCountries(['US', 'CA']) // [] or omit for all countries
|
|
649
|
+
carrier.setSortOrder(10) // lower = appears first at checkout
|
|
650
|
+
carrier.setActive(true) // default: true
|
|
651
|
+
carrier.setTrackingAvailable(true) // default: true
|
|
652
|
+
carrier.setShippingLabelsAvailable(true) // default: true
|
|
653
|
+
|
|
654
|
+
// Rate methods
|
|
655
|
+
carrier.addMethod('method-code', (method) => {
|
|
656
|
+
method
|
|
657
|
+
.setMethodTitle('Display Name')
|
|
658
|
+
.setPrice(9.99) // customer-facing price
|
|
659
|
+
.setCost(5.00) // merchant cost (does not affect customer price)
|
|
660
|
+
.addAdditionalData('key', 'value'); // arbitrary metadata
|
|
661
|
+
});
|
|
662
|
+
carrier.removeMethod('method-code') // marks method for removal
|
|
663
|
+
|
|
664
|
+
// Getters
|
|
665
|
+
carrier.getData() // returns carrier config as plain object (no methods)
|
|
666
|
+
carrier.getAddedMethods() // returns ShippingCarrierMethodData[] for added methods
|
|
667
|
+
carrier.getRemovedMethods() // returns string[] of method codes marked for removal
|
|
668
|
+
|
|
669
|
+
// Response
|
|
670
|
+
new ShippingCarrierResponse(carrier).generate()
|
|
671
|
+
// Returns WebhookActionResponseType[] — pass to WebhookActionResponse.success()
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
---
|
|
675
|
+
|
|
676
|
+
### Related Rules
|
|
677
|
+
|
|
678
|
+
- **"Creating Adobe Commerce Client Operations"** (`aio-toolkit-create-adobe-commerce-client.mdc`) — required when Commerce OOPE management methods are included; sets up `AdobeCommerceClient` and the `lib/adobe-commerce/client-builder` pattern
|
|
679
|
+
- **"Using AdobeAuth"** (`aio-toolkit-use-adobe-auth.mdc`) — use when the Commerce client uses `ImsConnection` with explicit S2S credentials
|
|
680
|
+
- **"Setting up New Relic Telemetry"** (`aio-toolkit-setup-new-relic-telemetry.mdc`) — add observability to the webhook action
|
|
681
|
+
- **"Using PublishEvent"** (`aio-toolkit-use-publish-event.mdc`) — publish events after processing a shipping rate request
|