@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.
Files changed (31) hide show
  1. package/CHANGELOG.md +145 -0
  2. package/README.md +169 -0
  3. package/dist/aio-toolkit-cli-workflow/bin/cli.js +2048 -0
  4. package/dist/aio-toolkit-cli-workflow/bin/cli.js.map +1 -0
  5. package/dist/aio-toolkit-cursor-context/bin/cli.js +16 -0
  6. package/dist/aio-toolkit-cursor-context/bin/cli.js.map +1 -1
  7. package/dist/index.d.mts +51 -6
  8. package/dist/index.d.ts +51 -6
  9. package/dist/index.js +209 -0
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +213 -0
  12. package/dist/index.mjs.map +1 -1
  13. package/files/cursor-context/commands/aio-toolkit-analyze-adobe-commerce-module.md +612 -0
  14. package/files/cursor-context/commands/aio-toolkit-create-amazon-sqs-consumer.md +445 -0
  15. package/files/cursor-context/commands/aio-toolkit-create-event-consumer-action.md +6 -0
  16. package/files/cursor-context/commands/aio-toolkit-create-graphql-action.md +21 -7
  17. package/files/cursor-context/commands/aio-toolkit-create-openwhisk-action.md +326 -0
  18. package/files/cursor-context/commands/aio-toolkit-create-runtime-action.md +15 -5
  19. package/files/cursor-context/commands/aio-toolkit-create-shipping-carrier.md +681 -0
  20. package/files/cursor-context/commands/aio-toolkit-create-webhook-action.md +22 -9
  21. package/files/cursor-context/rules/aio-toolkit-create-adobe-commerce-client.mdc +252 -116
  22. package/files/cursor-context/rules/aio-toolkit-oop-best-practices.mdc +10 -4
  23. package/files/cursor-context/rules/aio-toolkit-setup-new-relic-telemetry.mdc +167 -2
  24. package/files/cursor-context/rules/aio-toolkit-use-abdb-collection.mdc +610 -0
  25. package/files/cursor-context/rules/aio-toolkit-use-abdb-repository.mdc +705 -0
  26. package/files/cursor-context/rules/aio-toolkit-use-adobe-auth.mdc +442 -0
  27. package/files/cursor-context/rules/aio-toolkit-use-amazon-sqs-publish.mdc +397 -0
  28. package/files/cursor-context/rules/aio-toolkit-use-file-repository.mdc +502 -0
  29. package/files/cursor-context/rules/aio-toolkit-use-publish-event.mdc +510 -0
  30. package/files/cursor-context/rules/aio-toolkit-use-runtime-api-gateway-service.mdc +542 -0
  31. 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