@final-commerce/command-frame 0.1.43 → 0.1.46

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 (42) hide show
  1. package/README.md +34 -0
  2. package/dist/CommonTypes.d.ts +97 -35
  3. package/dist/CommonTypes.js +44 -0
  4. package/dist/actions/add-non-revenue-item/action.d.ts +2 -0
  5. package/dist/actions/add-non-revenue-item/action.js +4 -0
  6. package/dist/actions/add-non-revenue-item/mock.d.ts +2 -0
  7. package/dist/actions/add-non-revenue-item/mock.js +41 -0
  8. package/dist/actions/add-non-revenue-item/types.d.ts +36 -0
  9. package/dist/actions/add-non-revenue-item/types.js +1 -0
  10. package/dist/actions/add-product/mock.js +7 -5
  11. package/dist/actions/add-product/types.d.ts +2 -2
  12. package/dist/actions/edit-product/mock.js +3 -1
  13. package/dist/actions/extension-payment/action.d.ts +5 -0
  14. package/dist/actions/extension-payment/action.js +7 -0
  15. package/dist/actions/extension-payment/mock.d.ts +2 -0
  16. package/dist/actions/extension-payment/mock.js +11 -0
  17. package/dist/actions/extension-payment/types.d.ts +19 -0
  18. package/dist/actions/extension-payment/types.js +1 -0
  19. package/dist/actions/extension-refund/constants.d.ts +2 -0
  20. package/dist/actions/extension-refund/constants.js +2 -0
  21. package/dist/actions/extension-refund/extension-refund-listener.d.ts +11 -0
  22. package/dist/actions/extension-refund/extension-refund-listener.js +60 -0
  23. package/dist/actions/extension-refund/types.d.ts +32 -0
  24. package/dist/actions/extension-refund/types.js +5 -0
  25. package/dist/actions/get-current-company-custom-extensions/mock.js +2 -2
  26. package/dist/actions/get-custom-extensions/mock.js +2 -2
  27. package/dist/actions/redeem-payment/action.d.ts +5 -0
  28. package/dist/actions/redeem-payment/action.js +7 -0
  29. package/dist/actions/redeem-payment/mock.d.ts +2 -0
  30. package/dist/actions/redeem-payment/mock.js +4 -0
  31. package/dist/actions/redeem-payment/types.d.ts +12 -0
  32. package/dist/actions/redeem-payment/types.js +1 -0
  33. package/dist/actions/resume-parked-order/mock.js +3 -3
  34. package/dist/common-types/custom-extensions.d.ts +1 -1
  35. package/dist/demo/database.d.ts +1 -1
  36. package/dist/demo/database.js +68 -60
  37. package/dist/demo/mocks/custom-tables.js +3 -3
  38. package/dist/index.d.ts +9 -0
  39. package/dist/index.js +8 -0
  40. package/dist/projects/render/mocks.js +6 -0
  41. package/dist/projects/render/types.d.ts +4 -1
  42. package/package.json +5 -2
package/README.md CHANGED
@@ -13,6 +13,7 @@ The library provides three main capabilities:
13
13
  | **Commands** | Call host functions from the iframe (e.g. get products, open cash drawer) | Request/response per call |
14
14
  | **Pub/Sub** | Subscribe to real-time events from the host (e.g. cart changes, payments) | Page-scoped (while iframe is mounted) |
15
15
  | **Hooks** | Register business-logic callbacks that persist across all pages | Session-scoped (survives page navigation) |
16
+ | **Host → iframe refunds** | Render asks the extension to reverse redeem / gift-card payments before completing a POS refund | Parent `postMessage` + `requestId` (see below) |
16
17
 
17
18
  ## Installation
18
19
 
@@ -109,6 +110,39 @@ hooks.register('cart', async (event, hostCommands) => {
109
110
  hooks.unregister('my-extension:cart-log');
110
111
  ```
111
112
 
113
+ ## Host-initiated extension refunds (redeem / gift card)
114
+
115
+ **Extensions that accept redeem / extension payments must implement a refund listener.** When staff refund an order paid with `paymentType: "redeem"`, Render (host) `postMessage`s into your iframe **before** it records the refund locally. If your app does not handle this message, redeem refunds will time out or fail.
116
+
117
+ ### What you should do
118
+
119
+ 1. **Recommended:** call **`installExtensionRefundListener`** once when your extension boots (e.g. next to your `RenderClient` setup). Pass an `async` handler that calls your provider (gift card API, wallet, etc.) and returns an **`ExtensionRefundResponse`** (`success`, optional `error`, optional `extensionTransactionId` for receipts / support).
120
+ 2. The helper validates `event.source === window.parent`, parses params, and replies with the same **`PostMessageResponse`** envelope as the rest of Command Frame (`requestId`, `success`, `data` / `error`).
121
+ 3. **Alternative:** implement a `window` `message` listener yourself using the same contract (action name: **`extensionRefundRequest`**, or import **`EXTENSION_REFUND_REQUEST_ACTION`** from this package).
122
+
123
+ Exported APIs: `installExtensionRefundListener`, `EXTENSION_REFUND_REQUEST_ACTION`, types **`ExtensionRefundParams`** / **`ExtensionRefundResponse`**.
124
+
125
+ ```typescript
126
+ import {
127
+ installExtensionRefundListener,
128
+ type ExtensionRefundParams,
129
+ type ExtensionRefundResponse
130
+ } from '@final-commerce/command-frame';
131
+
132
+ const unsubscribe = installExtensionRefundListener(async (params: ExtensionRefundParams): Promise<ExtensionRefundResponse> => {
133
+ // params.paymentType === "redeem", params.amount in major currency units, params.saleId, params.processor, etc.
134
+ const ok = await myGiftCardProvider.refund(params);
135
+ return ok
136
+ ? { success: true, extensionTransactionId: ok.providerRefundId }
137
+ : { success: false, error: 'Refund declined' };
138
+ });
139
+
140
+ // on teardown (optional)
141
+ // unsubscribe();
142
+ ```
143
+
144
+ **Full protocol, edge cases, and manual handling:** **[Extension refund documentation](./src/actions/extension-refund/README.md)**.
145
+
112
146
  ## Development & Testing
113
147
 
114
148
  ### Demo Mode / Mocking
@@ -1,4 +1,47 @@
1
1
  export * from "./common-types";
2
+ export declare enum CurrencyCode {
3
+ USD = "USD",
4
+ EUR = "EUR",
5
+ GBP = "GBP",
6
+ CAD = "CAD",
7
+ AUD = "AUD",
8
+ NZD = "NZD",
9
+ CHF = "CHF",
10
+ CNY = "CNY",
11
+ INR = "INR",
12
+ MXN = "MXN",
13
+ BRL = "BRL",
14
+ ZAR = "ZAR",
15
+ SGD = "SGD",
16
+ HKD = "HKD",
17
+ SEK = "SEK",
18
+ NOK = "NOK",
19
+ DKK = "DKK",
20
+ PLN = "PLN",
21
+ THB = "THB",
22
+ MYR = "MYR",
23
+ PHP = "PHP",
24
+ IDR = "IDR",
25
+ AED = "AED",
26
+ SAR = "SAR",
27
+ ILS = "ILS",
28
+ TRY = "TRY",
29
+ RUB = "RUB",
30
+ JPY = "JPY",
31
+ KRW = "KRW",
32
+ VND = "VND",
33
+ CLP = "CLP",
34
+ ISK = "ISK",
35
+ HUF = "HUF",
36
+ TWD = "TWD",
37
+ KWD = "KWD",
38
+ BHD = "BHD",
39
+ OMR = "OMR",
40
+ JOD = "JOD",
41
+ TND = "TND",
42
+ LYD = "LYD",
43
+ IQD = "IQD"
44
+ }
2
45
  export declare enum CFProductType {
3
46
  SIMPLE = "simple",
4
47
  VARIABLE = "variable"
@@ -51,7 +94,7 @@ export interface CFTax {
51
94
  id: string;
52
95
  name: string;
53
96
  percentage: number;
54
- amount: string;
97
+ amount: number;
55
98
  taxTableName: string;
56
99
  taxTableId: string;
57
100
  }
@@ -82,11 +125,11 @@ export interface CFCategory {
82
125
  }
83
126
  export interface CFProductVariant {
84
127
  sku: string;
85
- price: string;
86
- salePrice: string;
128
+ price: number;
129
+ salePrice: number;
87
130
  isOnSale: boolean;
88
131
  barcode?: string;
89
- costPrice?: string;
132
+ costPrice?: number;
90
133
  manageStock: boolean;
91
134
  externalId?: string;
92
135
  inventory?: CFInventory[];
@@ -121,8 +164,10 @@ export interface CFProduct {
121
164
  sku?: string;
122
165
  productType: CFProductType;
123
166
  variants: CFProductVariant[];
124
- minPrice?: string;
125
- maxPrice?: string;
167
+ currency: CurrencyCode;
168
+ minorUnits: number;
169
+ minPrice?: number;
170
+ maxPrice?: number;
126
171
  status?: string;
127
172
  isDeleted?: boolean;
128
173
  }
@@ -172,40 +217,42 @@ export interface CFActiveCustomer extends CFCustomer {
172
217
  id?: string;
173
218
  }
174
219
  export interface CFTip {
175
- amount: string;
220
+ amount: number;
176
221
  percentage: number;
177
222
  }
178
223
  export interface CFSummary {
179
- discountTotal: string;
180
- shippingTotal?: string | null;
181
- total: string;
182
- totalTaxes: string;
183
- subTotal: string;
224
+ discountTotal: number;
225
+ shippingTotal?: number | null;
226
+ total: number;
227
+ totalTaxes: number;
228
+ subTotal: number;
184
229
  taxes: CFTax[];
185
230
  tip?: CFTip | null;
186
231
  isTaxInclusive: boolean;
232
+ /** Portion of order total that is non-revenue (e.g. gift card load), same string money format as `total` */
233
+ nonRevenueTotal?: string;
187
234
  }
188
235
  export interface CFCartDiscountItem {
189
236
  label: string;
190
- amount: string;
237
+ amount: number;
191
238
  percentage: number;
192
239
  }
193
240
  export interface CFCartFeeItem {
194
241
  id: string;
195
242
  label: string;
196
- amount: string;
243
+ amount: number;
197
244
  percentage: number;
198
245
  taxTableId?: string;
199
- tax?: string;
246
+ tax?: number;
200
247
  taxName: string;
201
248
  }
202
249
  export interface CFTipPayment {
203
- amount: string;
250
+ amount: number;
204
251
  tipTo: string;
205
252
  percentage: number;
206
253
  }
207
254
  export interface CFRefundedTipPayment {
208
- amount: string;
255
+ amount: number;
209
256
  percentage: number;
210
257
  transactionId: string;
211
258
  tipTo: string;
@@ -213,11 +260,11 @@ export interface CFRefundedTipPayment {
213
260
  export interface CFPaymentMethod {
214
261
  transactionId: string;
215
262
  paymentType: string;
216
- amount: string;
263
+ amount: number;
217
264
  timestamp: string;
218
265
  processor: string;
219
266
  saleId?: string;
220
- change?: string | null;
267
+ change?: number | null;
221
268
  tip?: CFTipPayment | null;
222
269
  cashRounding?: number;
223
270
  emv?: string | null;
@@ -233,13 +280,13 @@ export interface CFPosDataItem {
233
280
  }
234
281
  export interface CFDiscountDetail {
235
282
  percentage: number;
236
- amount: string;
283
+ amount: number;
237
284
  label?: string;
238
285
  }
239
286
  export interface CFFeeDetail {
240
287
  percentage: number;
241
- amount: string;
242
- tax: string;
288
+ amount: number;
289
+ tax: number;
243
290
  taxTableId: string;
244
291
  label?: string;
245
292
  }
@@ -258,12 +305,12 @@ export interface CFLineItem {
258
305
  internalId?: string;
259
306
  name: string;
260
307
  quantity: number;
261
- price: string;
308
+ price: number;
262
309
  taxes: CFTax[];
263
310
  discount: CFDiscountLineItem;
264
311
  fee: CFFeeLineItem;
265
- totalTax: string;
266
- total: string;
312
+ totalTax: number;
313
+ total: number;
267
314
  metadata: CFMetadataItem[];
268
315
  image: string;
269
316
  sku: string;
@@ -277,11 +324,11 @@ export interface CFLineItem {
277
324
  export interface CFCustomSale {
278
325
  customSaleId: string;
279
326
  name: string;
280
- price: string;
327
+ price: number;
281
328
  quantity: number;
282
329
  applyTaxes: boolean;
283
- total: string;
284
- totalTax: string;
330
+ total: number;
331
+ totalTax: number;
285
332
  taxes: CFTax[];
286
333
  discount: {
287
334
  cartDiscount: CFDiscountDetail;
@@ -298,11 +345,11 @@ export interface CFRefundedLineItem {
298
345
  internalId?: string;
299
346
  name: string;
300
347
  quantity: number;
301
- price: string;
348
+ price: number;
302
349
  taxes: CFTax[];
303
350
  discount: CFDiscountLineItem;
304
- totalTax: string;
305
- total: string;
351
+ totalTax: number;
352
+ total: number;
306
353
  image: string;
307
354
  sku: string;
308
355
  note?: string;
@@ -321,12 +368,15 @@ export interface CFRefundItem {
321
368
  timestamp: string | undefined;
322
369
  summary?: CFSummary;
323
370
  refundPayment: CFPaymentMethod[];
324
- balance?: string;
371
+ balance?: number;
325
372
  receiptId?: string;
326
- currency?: string;
373
+ currency: CurrencyCode;
374
+ minorUnits: number;
327
375
  }
328
376
  export interface CFOrder {
329
377
  _id: string;
378
+ currency: CurrencyCode;
379
+ minorUnits: number;
330
380
  receiptId?: string;
331
381
  companyId: string;
332
382
  externalId: string | null;
@@ -348,8 +398,10 @@ export interface CFOrder {
348
398
  shipping: CFAddress | null;
349
399
  lineItems: CFLineItem[];
350
400
  customSales: CFCustomSale[];
401
+ /** Gift card / liability purchase lines (not product revenue) */
402
+ nonRevenueItems?: CFNonRevenueItem[];
351
403
  refund?: CFRefundItem[];
352
- balance: string;
404
+ balance: number;
353
405
  signature?: string | null;
354
406
  }
355
407
  export interface CFActiveUserRole {
@@ -420,7 +472,6 @@ export interface CFActiveOrder extends CFOrder {
420
472
  outlet?: CFActiveOutlet;
421
473
  isDeleted?: boolean;
422
474
  newOrder?: boolean;
423
- currency?: string;
424
475
  station?: CFActiveStation;
425
476
  }
426
477
  export interface CFActiveCustomSales {
@@ -433,6 +484,15 @@ export interface CFActiveCustomSales {
433
484
  discount?: any;
434
485
  fee?: any;
435
486
  }
487
+ /** Non-revenue cart line (e.g. gift card load) — aligned with Render NonRevenueItem */
488
+ export interface CFNonRevenueItem {
489
+ id: string;
490
+ amount: number | string;
491
+ label?: string;
492
+ metadata?: Record<string, unknown>;
493
+ applyTaxes?: boolean;
494
+ taxTableId?: string;
495
+ }
436
496
  export interface CFActiveCart extends CFActiveEntity {
437
497
  tax?: number;
438
498
  total: number;
@@ -441,6 +501,8 @@ export interface CFActiveCart extends CFActiveEntity {
441
501
  customFee?: CFCustomFee[];
442
502
  products: CFActiveProduct[];
443
503
  customSales?: CFActiveCustomSales[];
504
+ /** Gift card / liability lines — included in cart total */
505
+ nonRevenueItems?: CFNonRevenueItem[];
444
506
  remainingBalance?: number;
445
507
  amountToBeCharged: number;
446
508
  customer?: Partial<CFCustomer | null> | null;
@@ -1,4 +1,48 @@
1
1
  export * from "./common-types";
2
+ export var CurrencyCode;
3
+ (function (CurrencyCode) {
4
+ CurrencyCode["USD"] = "USD";
5
+ CurrencyCode["EUR"] = "EUR";
6
+ CurrencyCode["GBP"] = "GBP";
7
+ CurrencyCode["CAD"] = "CAD";
8
+ CurrencyCode["AUD"] = "AUD";
9
+ CurrencyCode["NZD"] = "NZD";
10
+ CurrencyCode["CHF"] = "CHF";
11
+ CurrencyCode["CNY"] = "CNY";
12
+ CurrencyCode["INR"] = "INR";
13
+ CurrencyCode["MXN"] = "MXN";
14
+ CurrencyCode["BRL"] = "BRL";
15
+ CurrencyCode["ZAR"] = "ZAR";
16
+ CurrencyCode["SGD"] = "SGD";
17
+ CurrencyCode["HKD"] = "HKD";
18
+ CurrencyCode["SEK"] = "SEK";
19
+ CurrencyCode["NOK"] = "NOK";
20
+ CurrencyCode["DKK"] = "DKK";
21
+ CurrencyCode["PLN"] = "PLN";
22
+ CurrencyCode["THB"] = "THB";
23
+ CurrencyCode["MYR"] = "MYR";
24
+ CurrencyCode["PHP"] = "PHP";
25
+ CurrencyCode["IDR"] = "IDR";
26
+ CurrencyCode["AED"] = "AED";
27
+ CurrencyCode["SAR"] = "SAR";
28
+ CurrencyCode["ILS"] = "ILS";
29
+ CurrencyCode["TRY"] = "TRY";
30
+ CurrencyCode["RUB"] = "RUB";
31
+ CurrencyCode["JPY"] = "JPY";
32
+ CurrencyCode["KRW"] = "KRW";
33
+ CurrencyCode["VND"] = "VND";
34
+ CurrencyCode["CLP"] = "CLP";
35
+ CurrencyCode["ISK"] = "ISK";
36
+ CurrencyCode["HUF"] = "HUF";
37
+ CurrencyCode["TWD"] = "TWD";
38
+ CurrencyCode["KWD"] = "KWD";
39
+ CurrencyCode["BHD"] = "BHD";
40
+ CurrencyCode["OMR"] = "OMR";
41
+ CurrencyCode["JOD"] = "JOD";
42
+ CurrencyCode["TND"] = "TND";
43
+ CurrencyCode["LYD"] = "LYD";
44
+ CurrencyCode["IQD"] = "IQD";
45
+ })(CurrencyCode || (CurrencyCode = {}));
2
46
  // Enums
3
47
  export var CFProductType;
4
48
  (function (CFProductType) {
@@ -0,0 +1,2 @@
1
+ import type { AddNonRevenueItem } from "./types";
2
+ export declare const addNonRevenueItem: AddNonRevenueItem;
@@ -0,0 +1,4 @@
1
+ import { commandFrameClient } from "../../client";
2
+ export const addNonRevenueItem = async (params) => {
3
+ return await commandFrameClient.call("addNonRevenueItem", params);
4
+ };
@@ -0,0 +1,2 @@
1
+ import { AddNonRevenueItem } from "./types";
2
+ export declare const mockAddNonRevenueItem: AddNonRevenueItem;
@@ -0,0 +1,41 @@
1
+ import { MOCK_CART } from "../../demo/database";
2
+ export const mockAddNonRevenueItem = async (params) => {
3
+ console.log("[Mock] addNonRevenueItem called", params);
4
+ const amount = Number(params.amount);
5
+ if (!Number.isFinite(amount) || amount <= 0) {
6
+ throw new Error("amount must be a positive number");
7
+ }
8
+ const lineId = `nr-mock-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
9
+ const refId = params.id;
10
+ const metadata = {
11
+ ...(params.metadata ?? {}),
12
+ refId
13
+ };
14
+ const row = {
15
+ id: lineId,
16
+ amount,
17
+ applyTaxes: params.applyTaxes === true,
18
+ ...(params.taxTableId ? { taxTableId: params.taxTableId } : {}),
19
+ ...(params.label !== undefined ? { label: params.label } : {}),
20
+ metadata
21
+ };
22
+ if (!MOCK_CART.nonRevenueItems) {
23
+ MOCK_CART.nonRevenueItems = [];
24
+ }
25
+ MOCK_CART.nonRevenueItems.push(row);
26
+ MOCK_CART.total += amount;
27
+ MOCK_CART.amountToBeCharged = MOCK_CART.total;
28
+ MOCK_CART.remainingBalance = MOCK_CART.total;
29
+ MOCK_CART.subtotal = (MOCK_CART.subtotal ?? 0) + amount;
30
+ return {
31
+ success: true,
32
+ id: lineId,
33
+ refId,
34
+ amount,
35
+ label: params.label,
36
+ metadata,
37
+ ...(params.applyTaxes === true ? { applyTaxes: true } : {}),
38
+ ...(params.taxTableId ? { taxTableId: params.taxTableId } : {}),
39
+ timestamp: new Date().toISOString()
40
+ };
41
+ };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Add a non-revenue line to the host cart (e.g. gift card liability / load).
3
+ * Extensions send id, amount, and optional label/metadata; host sums into cart total.
4
+ */
5
+ export interface AddNonRevenueItemParams {
6
+ /**
7
+ * Reference id from the extension (e.g. product/sku key). Stored in cart line metadata as refId.
8
+ * The cart uses a separate unique line id per add so multiple lines can share the same refId.
9
+ */
10
+ id: string;
11
+ /** Amount in major currency units (e.g. dollars), same as cart totals */
12
+ amount: number;
13
+ /** Short label for receipts/UI */
14
+ label?: string;
15
+ /** Extra fields (customTableId, cardCode, etc.) */
16
+ metadata?: Record<string, unknown>;
17
+ /**
18
+ * When true, line may be taxed (requires taxTableId when taxing is implemented). Default false — e.g. gift card load is typically not taxed.
19
+ */
20
+ applyTaxes?: boolean;
21
+ taxTableId?: string;
22
+ }
23
+ export interface AddNonRevenueItemResponse {
24
+ success: true;
25
+ /** Unique cart line id (use for remove / correlation) */
26
+ id: string;
27
+ /** Same as request `id` — extension reference */
28
+ refId: string;
29
+ amount: number;
30
+ label?: string;
31
+ metadata?: Record<string, unknown>;
32
+ applyTaxes?: boolean;
33
+ taxTableId?: string;
34
+ timestamp: string;
35
+ }
36
+ export type AddNonRevenueItem = (params: AddNonRevenueItemParams) => Promise<AddNonRevenueItemResponse>;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,10 +1,12 @@
1
- import { CFProductType } from "../../CommonTypes";
1
+ import { CFProductType, CurrencyCode } from "../../CommonTypes";
2
2
  export const mockAddProduct = async (params) => {
3
3
  console.log("[Mock] addProduct called", params);
4
4
  const hasVariants = params.variants && params.variants.length > 0;
5
5
  return {
6
6
  product: {
7
7
  _id: "mock_product_" + Date.now(),
8
+ currency: CurrencyCode.USD,
9
+ minorUnits: 2,
8
10
  name: params.name,
9
11
  description: params.description,
10
12
  categories: params.categories || [],
@@ -14,15 +16,15 @@ export const mockAddProduct = async (params) => {
14
16
  sku: params.sku,
15
17
  productType: hasVariants ? CFProductType.VARIABLE : CFProductType.SIMPLE,
16
18
  attributes: [],
17
- minPrice: params.price || "0",
18
- maxPrice: params.price || "0",
19
+ minPrice: params.price || 0,
20
+ maxPrice: params.price || 0,
19
21
  variants: hasVariants
20
22
  ? params.variants.map((v, i) => ({ ...v, _id: `mock_variant_${Date.now()}_${i}` }))
21
23
  : [{
22
24
  _id: `mock_variant_${Date.now()}_0`,
23
25
  sku: params.sku || "",
24
- price: params.price || "0",
25
- salePrice: "0",
26
+ price: params.price || 0,
27
+ salePrice: 0,
26
28
  isOnSale: false,
27
29
  manageStock: params.manageStock || false,
28
30
  attributes: [],
@@ -7,9 +7,9 @@ export interface AddProductParams {
7
7
  images?: string[];
8
8
  status?: 'active' | 'inactive';
9
9
  /** For simple products: set price directly */
10
- price?: string;
10
+ price?: number;
11
11
  sku?: string;
12
- costPrice?: string;
12
+ costPrice?: number;
13
13
  manageStock?: boolean;
14
14
  /** For variable products: provide variants array */
15
15
  variants?: Omit<CFProductVariant, '_id'>[];
@@ -1,9 +1,11 @@
1
- import { CFProductType } from "../../CommonTypes";
1
+ import { CFProductType, CurrencyCode } from "../../CommonTypes";
2
2
  export const mockEditProduct = async (params) => {
3
3
  console.log("[Mock] editProduct called", params);
4
4
  return {
5
5
  product: {
6
6
  _id: params.productId,
7
+ currency: CurrencyCode.USD,
8
+ minorUnits: 2,
7
9
  name: params.changes.name || "Updated Product",
8
10
  description: params.changes.description,
9
11
  categories: params.changes.categories || [],
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Generic extension payment — host handles wire action `extensionPayment` and routes by `paymentType`.
3
+ */
4
+ import type { ExtensionPayment } from "./types";
5
+ export declare const extensionPayment: ExtensionPayment;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Generic extension payment — host handles wire action `extensionPayment` and routes by `paymentType`.
3
+ */
4
+ import { commandFrameClient } from "../../client";
5
+ export const extensionPayment = async (params) => {
6
+ return await commandFrameClient.call("extensionPayment", params);
7
+ };
@@ -0,0 +1,2 @@
1
+ import { ExtensionPayment } from "./types";
2
+ export declare const mockExtensionPayment: ExtensionPayment;
@@ -0,0 +1,11 @@
1
+ import { MOCK_ORDERS } from "../../demo/database";
2
+ export const mockExtensionPayment = async (params) => {
3
+ const paymentType = params?.paymentType ?? "redeem";
4
+ return {
5
+ success: true,
6
+ amount: params?.amount ?? null,
7
+ paymentType,
8
+ order: MOCK_ORDERS[0],
9
+ timestamp: new Date().toISOString()
10
+ };
11
+ };
@@ -0,0 +1,19 @@
1
+ import { CFOrder } from "../../CommonTypes";
2
+ /** Params for extension-initiated payments; host routes by `paymentType`. */
3
+ export interface ExtensionPaymentParams {
4
+ paymentType: string;
5
+ processor?: string;
6
+ amount?: number;
7
+ label?: string;
8
+ referenceId?: string;
9
+ extensionId?: string;
10
+ metadata?: Record<string, unknown>;
11
+ }
12
+ export interface ExtensionPaymentResponse {
13
+ success: boolean;
14
+ amount: number | null;
15
+ paymentType: string;
16
+ order: CFOrder | null;
17
+ timestamp: string;
18
+ }
19
+ export type ExtensionPayment = (params?: ExtensionPaymentParams) => Promise<ExtensionPaymentResponse>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ /** postMessage `action` for host → iframe extension refund requests. */
2
+ export declare const EXTENSION_REFUND_REQUEST_ACTION = "extensionRefundRequest";
@@ -0,0 +1,2 @@
1
+ /** postMessage `action` for host → iframe extension refund requests. */
2
+ export const EXTENSION_REFUND_REQUEST_ACTION = "extensionRefundRequest";
@@ -0,0 +1,11 @@
1
+ import type { ExtensionRefundParams, ExtensionRefundResponse } from "./types";
2
+ /**
3
+ * Install a message listener in the **extension iframe** so the host can request refunds.
4
+ * Validates `event.source === window.parent` before handling.
5
+ *
6
+ * Replies with {@link PostMessageResponse}, using `event.origin` as the target origin when posting back to the parent.
7
+ *
8
+ * @param handler - Perform the extension-side refund (API call, etc.)
9
+ * @returns Unsubscribe function
10
+ */
11
+ export declare function installExtensionRefundListener(handler: (params: ExtensionRefundParams) => Promise<ExtensionRefundResponse>): () => void;
@@ -0,0 +1,60 @@
1
+ import { EXTENSION_REFUND_REQUEST_ACTION } from "./constants";
2
+ function isRecord(value) {
3
+ return typeof value === "object" && value !== null;
4
+ }
5
+ /**
6
+ * Install a message listener in the **extension iframe** so the host can request refunds.
7
+ * Validates `event.source === window.parent` before handling.
8
+ *
9
+ * Replies with {@link PostMessageResponse}, using `event.origin` as the target origin when posting back to the parent.
10
+ *
11
+ * @param handler - Perform the extension-side refund (API call, etc.)
12
+ * @returns Unsubscribe function
13
+ */
14
+ export function installExtensionRefundListener(handler) {
15
+ const onMessage = async (event) => {
16
+ const raw = event.data;
17
+ if (!isRecord(raw) || raw.action !== EXTENSION_REFUND_REQUEST_ACTION) {
18
+ return;
19
+ }
20
+ const requestId = raw.requestId;
21
+ if (typeof requestId !== "string") {
22
+ return;
23
+ }
24
+ if (event.source !== window.parent || !event.source) {
25
+ return;
26
+ }
27
+ const source = event.source;
28
+ const replyOrigin = typeof event.origin === "string" ? event.origin : "*";
29
+ const params = raw.params;
30
+ try {
31
+ if (!params || typeof params.paymentType !== "string" || typeof params.amount !== "number" || typeof params.saleId !== "string") {
32
+ const errPayload = {
33
+ requestId,
34
+ success: false,
35
+ error: "Invalid extension refund params"
36
+ };
37
+ source.postMessage(errPayload, replyOrigin);
38
+ return;
39
+ }
40
+ const result = await handler(params);
41
+ const payload = {
42
+ requestId,
43
+ success: true,
44
+ data: result
45
+ };
46
+ source.postMessage(payload, replyOrigin);
47
+ }
48
+ catch (e) {
49
+ const message = e instanceof Error ? e.message : String(e);
50
+ const errPayload = {
51
+ requestId,
52
+ success: false,
53
+ error: message
54
+ };
55
+ source.postMessage(errPayload, replyOrigin);
56
+ }
57
+ };
58
+ window.addEventListener("message", onMessage);
59
+ return () => window.removeEventListener("message", onMessage);
60
+ }