@waffo/pancake-ts 0.7.0 → 0.8.0

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/dist/index.js CHANGED
@@ -23,12 +23,10 @@ var BuyerHttpClient = class {
23
23
  this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);
24
24
  }
25
25
  /**
26
- * Send a Bearer-authenticated POST request and return the parsed `data` field.
26
+ * Send a Bearer-authenticated POST and return the full envelope plus HTTP status.
27
27
  *
28
- * @param path - API path
29
- * @param body - Request body object
30
- * @returns Parsed `data` field from the response
31
- * @throws {WaffoPancakeError} When the API returns errors
28
+ * Does NOT throw on `errors[]` or non-2xx status — caller inspects the result.
29
+ * Throws {@link WaffoPancakeError} only when the response body is not valid JSON.
32
30
  */
33
31
  async post(path, body) {
34
32
  const response = await this._fetch(`${this.baseUrl}${path}`, {
@@ -39,11 +37,13 @@ var BuyerHttpClient = class {
39
37
  },
40
38
  body: JSON.stringify(body)
41
39
  });
42
- const result = await response.json();
43
- if ("errors" in result && result.errors) {
44
- throw new WaffoPancakeError(response.status, result.errors);
40
+ let envelope;
41
+ try {
42
+ envelope = await response.json();
43
+ } catch {
44
+ throw new WaffoPancakeError(response.status, [{ message: `Non-JSON response from ${path}`, layer: "sdk" }]);
45
45
  }
46
- return result.data;
46
+ return { status: response.status, ...envelope };
47
47
  }
48
48
  };
49
49
 
@@ -159,48 +159,64 @@ var HttpClient = class {
159
159
  this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);
160
160
  }
161
161
  /**
162
- * Send a signed POST request and return the parsed `data` field.
162
+ * Send a signed POST and return the full envelope plus HTTP status.
163
163
  *
164
164
  * Behavior:
165
- * - Generates a deterministic `X-Idempotency-Key` from `merchantId + path + body` (same request produces same key)
166
- * - When `idempotencyWindow` is set, a floored timestamp is mixed into the key so identical params produce
167
- * a new key after the window elapses (useful for checkout where repeated creation is intentional)
168
- * - Auto-builds RSA-SHA256 signature (`X-Merchant-Id` / `X-Timestamp` / `X-Signature`)
169
- * - Unwraps the response envelope: returns `data` on success, throws `WaffoPancakeError` on failure
170
- *
171
- * @param path - API path (e.g. `/v1/actions/store/create-store`)
165
+ * - Builds RSA-SHA256 signature (`X-Merchant-Id` / `X-Timestamp` / `X-Signature`)
166
+ * - Attaches `X-Idempotency-Key` (deterministic `sha256(merchantId + path + body)`)
167
+ * unless `options.noIdempotency` is set
168
+ * - When `options.idempotencyWindow` is set, a floored timestamp is mixed into the
169
+ * key so identical params produce a new key after the window elapses
170
+ * - Does NOT throw on `errors[]` or non-2xx status — caller inspects the result
171
+ * - Throws {@link WaffoPancakeError} only on transport failures (non-JSON body)
172
+ *
173
+ * @param path - API path (e.g. `/v1/actions/store/create-store`, `/v1/graphql`)
172
174
  * @param body - Request body object
173
175
  * @param options - Optional settings
174
- * @param options.idempotencyWindow - Time window in seconds for idempotency key rotation (e.g. 60 = per-minute dedup)
175
- * @returns Parsed `data` field from the response
176
- * @throws {WaffoPancakeError} When the API returns errors
176
+ * @returns Parsed envelope with HTTP status
177
+ * @throws {WaffoPancakeError} When the response body is not valid JSON
177
178
  */
178
179
  async post(path, body, options) {
179
180
  const bodyStr = JSON.stringify(body);
180
- const now = Date.now();
181
- const timestampSec = Math.floor(now / 1e3);
181
+ const timestampSec = Math.floor(Date.now() / 1e3);
182
182
  const timestamp = timestampSec.toString();
183
183
  const signature = signRequest("POST", path, timestamp, bodyStr, this.privateKey);
184
- const idempotencyBase = `${this.merchantId}:${path}:${bodyStr}`;
185
- const idempotencyInput = options?.idempotencyWindow ? `${idempotencyBase}:${Math.floor(timestampSec / options.idempotencyWindow)}` : idempotencyBase;
184
+ const headers = {
185
+ "Content-Type": "application/json",
186
+ "X-Merchant-Id": this.merchantId,
187
+ "X-Timestamp": timestamp,
188
+ "X-Signature": signature
189
+ };
190
+ if (!options?.noIdempotency) {
191
+ headers["X-Idempotency-Key"] = computeIdempotencyKey(this.merchantId, path, bodyStr, timestampSec, options);
192
+ }
186
193
  const response = await this._fetch(`${this.baseUrl}${path}`, {
187
194
  method: "POST",
188
- headers: {
189
- "Content-Type": "application/json",
190
- "X-Merchant-Id": this.merchantId,
191
- "X-Timestamp": timestamp,
192
- "X-Signature": signature,
193
- "X-Idempotency-Key": createHash2("sha256").update(idempotencyInput).digest("hex")
194
- },
195
+ headers,
195
196
  body: bodyStr
196
197
  });
197
- const result = await response.json();
198
- if ("errors" in result && result.errors) {
199
- throw new WaffoPancakeError(response.status, result.errors);
198
+ let envelope;
199
+ try {
200
+ envelope = await response.json();
201
+ } catch {
202
+ throw new WaffoPancakeError(response.status, [{ message: `Non-JSON response from ${path}`, layer: "sdk" }]);
200
203
  }
201
- return result.data;
204
+ return { status: response.status, ...envelope };
202
205
  }
203
206
  };
207
+ function computeIdempotencyKey(merchantId, path, bodyStr, timestampSec, options) {
208
+ const base = `${merchantId}:${path}:${bodyStr}`;
209
+ const input = options?.idempotencyWindow ? `${base}:${Math.floor(timestampSec / options.idempotencyWindow)}` : base;
210
+ return createHash2("sha256").update(input).digest("hex");
211
+ }
212
+
213
+ // src/resources/internal.ts
214
+ function unwrapAction(r) {
215
+ if (r.errors?.length) {
216
+ throw new WaffoPancakeError(r.status, r.errors);
217
+ }
218
+ return { ...r.data, ...r.warnings ? { warnings: r.warnings } : {} };
219
+ }
204
220
 
205
221
  // src/validation.ts
206
222
  var SHORT_ID_REGEX = /^[A-Z]{2,5}_[0-9A-Za-z]{22}$/;
@@ -335,7 +351,7 @@ var AuthResource = class {
335
351
  validateShortId("productId", params.productId, "PROD");
336
352
  }
337
353
  validateRequired("buyerIdentity", params.buyerIdentity);
338
- return this.http.post("/v1/actions/auth/issue-session-token", params);
354
+ return unwrapAction(await this.http.post("/v1/actions/auth/issue-session-token", params));
339
355
  }
340
356
  };
341
357
 
@@ -359,7 +375,7 @@ var BuyerSession = class {
359
375
  */
360
376
  async cancelSubscription(params) {
361
377
  validateShortId("orderId", params.orderId, "ORD");
362
- return this.http.post("/v1/actions/subscription-order/cancel-order", params);
378
+ return unwrapAction(await this.http.post("/v1/actions/subscription-order/cancel-order", params));
363
379
  }
364
380
  /**
365
381
  * Cancel a one-time order (only while payment is still pending).
@@ -372,7 +388,7 @@ var BuyerSession = class {
372
388
  */
373
389
  async cancelOnetimeOrder(params) {
374
390
  validateShortId("orderId", params.orderId, "ORD");
375
- return this.http.post("/v1/actions/onetime-order/cancel-order", params);
391
+ return unwrapAction(await this.http.post("/v1/actions/onetime-order/cancel-order", params));
376
392
  }
377
393
  /**
378
394
  * Reactivate a subscription that is in `canceling` status.
@@ -386,7 +402,7 @@ var BuyerSession = class {
386
402
  */
387
403
  async reactivateSubscription(params) {
388
404
  validateShortId("orderId", params.orderId, "ORD");
389
- return this.http.post("/v1/actions/subscription-order/reactivate-order", params);
405
+ return unwrapAction(await this.http.post("/v1/actions/subscription-order/reactivate-order", params));
390
406
  }
391
407
  /**
392
408
  * Submit a refund request for a payment.
@@ -406,7 +422,7 @@ var BuyerSession = class {
406
422
  validateRequired("reason", params.reason);
407
423
  validateAmountString("requestedAmount.amount", params.requestedAmount.amount);
408
424
  validateCurrencyCode("requestedAmount.currency", params.requestedAmount.currency);
409
- return this.http.post("/v1/actions/refund-ticket/create-ticket", params);
425
+ return unwrapAction(await this.http.post("/v1/actions/refund-ticket/create-ticket", params));
410
426
  }
411
427
  /**
412
428
  * Resubmit a previously rejected refund ticket with updated details.
@@ -428,7 +444,7 @@ var BuyerSession = class {
428
444
  validateRequired("reason", params.reason);
429
445
  validateAmountString("requestedAmount.amount", params.requestedAmount.amount);
430
446
  validateCurrencyCode("requestedAmount.currency", params.requestedAmount.currency);
431
- return this.http.post("/v1/actions/refund-ticket/resubmit-ticket", params);
447
+ return unwrapAction(await this.http.post("/v1/actions/refund-ticket/resubmit-ticket", params));
432
448
  }
433
449
  };
434
450
  var BuyerGraphQL = class {
@@ -448,7 +464,8 @@ var BuyerGraphQL = class {
448
464
  */
449
465
  async query(params) {
450
466
  validateRequired("query", params.query);
451
- return this.http.post("/v1/graphql", params);
467
+ const result = await this.http.post("/v1/graphql", params);
468
+ return { data: result.data, errors: result.errors, warnings: result.warnings };
452
469
  }
453
470
  };
454
471
 
@@ -481,7 +498,9 @@ var CheckoutAnonymousResource = class {
481
498
  */
482
499
  async create(params) {
483
500
  validateCheckoutCommon(params);
484
- return this.http.post("/v1/actions/checkout/create-session", params, { idempotencyWindow: 60 });
501
+ return unwrapAction(
502
+ await this.http.post("/v1/actions/checkout/create-session", params, { idempotencyWindow: 60 })
503
+ );
485
504
  }
486
505
  };
487
506
 
@@ -528,12 +547,16 @@ var CheckoutAuthenticatedResource = class {
528
547
  ),
529
548
  this.http.post("/v1/actions/checkout/create-session", sessionParams, { idempotencyWindow: 60 })
530
549
  ]);
550
+ const token = unwrapAction(tokenResult);
551
+ const session = unwrapAction(sessionResult);
552
+ const warnings = [...token.warnings ?? [], ...session.warnings ?? []];
531
553
  return {
532
- sessionId: sessionResult.sessionId,
533
- checkoutUrl: `${sessionResult.checkoutUrl}#token=${tokenResult.token}`,
534
- expiresAt: sessionResult.expiresAt,
535
- token: tokenResult.token,
536
- tokenExpiresAt: tokenResult.expiresAt
554
+ sessionId: session.sessionId,
555
+ checkoutUrl: `${session.checkoutUrl}#token=${token.token}`,
556
+ expiresAt: session.expiresAt,
557
+ token: token.token,
558
+ tokenExpiresAt: token.expiresAt,
559
+ ...warnings.length > 0 ? { warnings } : {}
537
560
  };
538
561
  }
539
562
  };
@@ -567,7 +590,9 @@ var CheckoutResource = class {
567
590
  * // Redirect to session.checkoutUrl
568
591
  */
569
592
  async createSession(params) {
570
- return this.http.post("/v1/actions/checkout/create-session", params, { idempotencyWindow: 60 });
593
+ return unwrapAction(
594
+ await this.http.post("/v1/actions/checkout/create-session", params, { idempotencyWindow: 60 })
595
+ );
571
596
  }
572
597
  };
573
598
 
@@ -596,7 +621,8 @@ var GraphQLResource = class {
596
621
  */
597
622
  async query(params) {
598
623
  validateRequired("query", params.query);
599
- return this.http.post("/v1/graphql", params);
624
+ const result = await this.http.post("/v1/graphql", params, { noIdempotency: true });
625
+ return { data: result.data, errors: result.errors, warnings: result.warnings };
600
626
  }
601
627
  };
602
628
 
@@ -622,7 +648,7 @@ var OnetimeProductsResource = class {
622
648
  validateShortId("storeId", params.storeId, "STO");
623
649
  validateRequired("name", params.name);
624
650
  validatePrices("prices", params.prices);
625
- return this.http.post("/v1/actions/onetime-product/create-product", params);
651
+ return unwrapAction(await this.http.post("/v1/actions/onetime-product/create-product", params));
626
652
  }
627
653
  /**
628
654
  * Update a one-time product. Creates a new version; skips if unchanged.
@@ -648,7 +674,7 @@ var OnetimeProductsResource = class {
648
674
  validateShortId("id", params.id, "PROD");
649
675
  if (params.name !== void 0) validateRequired("name", params.name);
650
676
  if (params.prices) validatePrices("prices", params.prices);
651
- return this.http.post("/v1/actions/onetime-product/update-product", params);
677
+ return unwrapAction(await this.http.post("/v1/actions/onetime-product/update-product", params));
652
678
  }
653
679
  /**
654
680
  * Publish a one-time product's test version to production.
@@ -661,7 +687,7 @@ var OnetimeProductsResource = class {
661
687
  */
662
688
  async publish(params) {
663
689
  validateShortId("id", params.id, "PROD");
664
- return this.http.post("/v1/actions/onetime-product/publish-product", params);
690
+ return unwrapAction(await this.http.post("/v1/actions/onetime-product/publish-product", params));
665
691
  }
666
692
  /**
667
693
  * Update a one-time product's status (active/inactive).
@@ -678,7 +704,7 @@ var OnetimeProductsResource = class {
678
704
  async updateStatus(params) {
679
705
  validateShortId("id", params.id, "PROD");
680
706
  validateEnum("status", params.status, ["active", "inactive"]);
681
- return this.http.post("/v1/actions/onetime-product/update-status", params);
707
+ return unwrapAction(await this.http.post("/v1/actions/onetime-product/update-status", params));
682
708
  }
683
709
  };
684
710
 
@@ -704,7 +730,7 @@ var OrdersResource = class {
704
730
  */
705
731
  async cancelSubscription(params) {
706
732
  validateShortId("orderId", params.orderId, "ORD");
707
- return this.http.post("/v1/actions/subscription-order/cancel-order", params);
733
+ return unwrapAction(await this.http.post("/v1/actions/subscription-order/cancel-order", params));
708
734
  }
709
735
  };
710
736
 
@@ -730,7 +756,7 @@ var StoreMerchantsResource = class {
730
756
  validateShortId("storeId", params.storeId, "STO");
731
757
  validateRequired("email", params.email);
732
758
  validateEnum("role", params.role, ["admin", "member"]);
733
- return this.http.post("/v1/actions/store-merchant/add-merchant", params);
759
+ return unwrapAction(await this.http.post("/v1/actions/store-merchant/add-merchant", params));
734
760
  }
735
761
  /**
736
762
  * Remove a merchant from a store.
@@ -747,7 +773,7 @@ var StoreMerchantsResource = class {
747
773
  async remove(params) {
748
774
  validateShortId("storeId", params.storeId, "STO");
749
775
  validateShortId("merchantId", params.merchantId, "MER");
750
- return this.http.post("/v1/actions/store-merchant/remove-merchant", params);
776
+ return unwrapAction(await this.http.post("/v1/actions/store-merchant/remove-merchant", params));
751
777
  }
752
778
  /**
753
779
  * Update a merchant's role in a store.
@@ -766,7 +792,7 @@ var StoreMerchantsResource = class {
766
792
  validateShortId("storeId", params.storeId, "STO");
767
793
  validateShortId("merchantId", params.merchantId, "MER");
768
794
  validateEnum("role", params.role, ["admin", "member"]);
769
- return this.http.post("/v1/actions/store-merchant/update-role", params);
795
+ return unwrapAction(await this.http.post("/v1/actions/store-merchant/update-role", params));
770
796
  }
771
797
  };
772
798
 
@@ -786,7 +812,7 @@ var StoresResource = class {
786
812
  */
787
813
  async create(params) {
788
814
  validateRequired("name", params.name);
789
- return this.http.post("/v1/actions/store/create-store", params);
815
+ return unwrapAction(await this.http.post("/v1/actions/store/create-store", params));
790
816
  }
791
817
  /**
792
818
  * Update an existing store's settings.
@@ -818,7 +844,7 @@ var StoresResource = class {
818
844
  */
819
845
  async update(params) {
820
846
  validateShortId("id", params.id, "STO");
821
- return this.http.post("/v1/actions/store/update-store", params);
847
+ return unwrapAction(await this.http.post("/v1/actions/store/update-store", params));
822
848
  }
823
849
  /**
824
850
  * Soft-delete a store. Only the owner can delete.
@@ -831,7 +857,7 @@ var StoresResource = class {
831
857
  */
832
858
  async delete(params) {
833
859
  validateShortId("id", params.id, "STO");
834
- return this.http.post("/v1/actions/store/delete-store", params);
860
+ return unwrapAction(await this.http.post("/v1/actions/store/delete-store", params));
835
861
  }
836
862
  };
837
863
 
@@ -857,7 +883,9 @@ var SubscriptionProductGroupsResource = class {
857
883
  async create(params) {
858
884
  validateShortId("storeId", params.storeId, "STO");
859
885
  validateRequired("name", params.name);
860
- return this.http.post("/v1/actions/subscription-product-group/create-group", params);
886
+ return unwrapAction(
887
+ await this.http.post("/v1/actions/subscription-product-group/create-group", params)
888
+ );
861
889
  }
862
890
  /**
863
891
  * Update a subscription product group. `productIds` is a full replacement.
@@ -873,7 +901,9 @@ var SubscriptionProductGroupsResource = class {
873
901
  */
874
902
  async update(params) {
875
903
  validateRequired("id", params.id);
876
- return this.http.post("/v1/actions/subscription-product-group/update-group", params);
904
+ return unwrapAction(
905
+ await this.http.post("/v1/actions/subscription-product-group/update-group", params)
906
+ );
877
907
  }
878
908
  /**
879
909
  * Hard-delete a subscription product group.
@@ -886,7 +916,9 @@ var SubscriptionProductGroupsResource = class {
886
916
  */
887
917
  async delete(params) {
888
918
  validateRequired("id", params.id);
889
- return this.http.post("/v1/actions/subscription-product-group/delete-group", params);
919
+ return unwrapAction(
920
+ await this.http.post("/v1/actions/subscription-product-group/delete-group", params)
921
+ );
890
922
  }
891
923
  /**
892
924
  * Publish a test-environment group to production (upsert).
@@ -899,7 +931,9 @@ var SubscriptionProductGroupsResource = class {
899
931
  */
900
932
  async publish(params) {
901
933
  validateRequired("id", params.id);
902
- return this.http.post("/v1/actions/subscription-product-group/publish-group", params);
934
+ return unwrapAction(
935
+ await this.http.post("/v1/actions/subscription-product-group/publish-group", params)
936
+ );
903
937
  }
904
938
  };
905
939
 
@@ -927,7 +961,9 @@ var SubscriptionProductsResource = class {
927
961
  validateRequired("name", params.name);
928
962
  validateEnum("billingPeriod", params.billingPeriod, ["weekly", "monthly", "quarterly", "yearly"]);
929
963
  validatePrices("prices", params.prices);
930
- return this.http.post("/v1/actions/subscription-product/create-product", params);
964
+ return unwrapAction(
965
+ await this.http.post("/v1/actions/subscription-product/create-product", params)
966
+ );
931
967
  }
932
968
  /**
933
969
  * Update a subscription product. Creates a new version; skips if unchanged.
@@ -956,7 +992,9 @@ var SubscriptionProductsResource = class {
956
992
  if (params.billingPeriod !== void 0)
957
993
  validateEnum("billingPeriod", params.billingPeriod, ["weekly", "monthly", "quarterly", "yearly"]);
958
994
  if (params.prices) validatePrices("prices", params.prices);
959
- return this.http.post("/v1/actions/subscription-product/update-product", params);
995
+ return unwrapAction(
996
+ await this.http.post("/v1/actions/subscription-product/update-product", params)
997
+ );
960
998
  }
961
999
  /**
962
1000
  * Publish a subscription product's test version to production.
@@ -969,7 +1007,9 @@ var SubscriptionProductsResource = class {
969
1007
  */
970
1008
  async publish(params) {
971
1009
  validateShortId("id", params.id, "PROD");
972
- return this.http.post("/v1/actions/subscription-product/publish-product", params);
1010
+ return unwrapAction(
1011
+ await this.http.post("/v1/actions/subscription-product/publish-product", params)
1012
+ );
973
1013
  }
974
1014
  /**
975
1015
  * Update a subscription product's status (active/inactive).
@@ -986,7 +1026,9 @@ var SubscriptionProductsResource = class {
986
1026
  async updateStatus(params) {
987
1027
  validateShortId("id", params.id, "PROD");
988
1028
  validateEnum("status", params.status, ["active", "inactive"]);
989
- return this.http.post("/v1/actions/subscription-product/update-status", params);
1029
+ return unwrapAction(
1030
+ await this.http.post("/v1/actions/subscription-product/update-status", params)
1031
+ );
990
1032
  }
991
1033
  };
992
1034
 
@@ -1143,7 +1185,7 @@ var WebhooksResource = class {
1143
1185
  validateShortId("storeId", params.storeId, "STO");
1144
1186
  validateRequired("channel", params.channel);
1145
1187
  validateRequired("url", params.url);
1146
- return this.http.post("/v1/actions/store/add-webhook", params);
1188
+ return unwrapAction(await this.http.post("/v1/actions/store/add-webhook", params));
1147
1189
  }
1148
1190
  /**
1149
1191
  * Update an existing webhook (only `url`, `events`, and `secret` are mutable).
@@ -1163,7 +1205,7 @@ var WebhooksResource = class {
1163
1205
  */
1164
1206
  async update(params) {
1165
1207
  validateRequired("id", params.id);
1166
- return this.http.post("/v1/actions/store/update-webhook", params);
1208
+ return unwrapAction(await this.http.post("/v1/actions/store/update-webhook", params));
1167
1209
  }
1168
1210
  /**
1169
1211
  * Hard-delete a webhook. Historical `webhook_deliveries` rows are retained
@@ -1177,7 +1219,7 @@ var WebhooksResource = class {
1177
1219
  */
1178
1220
  async remove(params) {
1179
1221
  validateRequired("id", params.id);
1180
- return this.http.post("/v1/actions/store/remove-webhook", params);
1222
+ return unwrapAction(await this.http.post("/v1/actions/store/remove-webhook", params));
1181
1223
  }
1182
1224
  /**
1183
1225
  * Verify and parse an incoming webhook event.