@waffo/pancake-ts 0.2.1 → 0.3.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/CHANGELOG.md CHANGED
@@ -4,6 +4,37 @@ All notable changes to `@waffo/pancake-ts` will be documented in this file.
4
4
 
5
5
  Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.3.0] - 2026-04-09
8
+
9
+ ### Breaking Changes
10
+
11
+ - **Checkout params simplified** — `storeId` and `productType` removed from `CreateCheckoutSessionParams`, `AnonymousCheckoutParams`, and `AuthenticatedCheckoutParams`. The server now derives both from `productId` automatically. Only `productId` + `currency` are required.
12
+ - **`CheckoutSessionProductType` enum removed** — No longer exported. Product type is determined server-side.
13
+ - **`IssueSessionTokenParams.storeId` now optional** — Provide either `storeId` or `productId` (at least one required). When `productId` is given, the server derives the store from the product.
14
+
15
+ ### Added
16
+
17
+ - **`IssueSessionTokenParams.productId`** — New optional field. When provided without `storeId`, the server derives the store from the product.
18
+
19
+ ### Changed
20
+
21
+ - **`checkout.authenticated.create()`** — Now sends `productId` (instead of `storeId`) to `issue-session-token` endpoint for parallel session token + checkout session creation.
22
+
23
+ ### Documentation
24
+
25
+ - **GraphQL type difference warning** — Added warnings in `graphql-guide.md`, `api-reference.md`, and external docs (EN/ZH/JA) clarifying that SDK TypeScript types reflect the REST API shape and differ from GraphQL schema types (e.g. `prices` is `Record<string, PriceInfo>` in REST but `[CurrencyPrice!]!` in GraphQL). Users should always use introspection for GraphQL queries.
26
+
27
+ ## [0.2.2] - 2026-04-03
28
+
29
+ ### Fixed
30
+
31
+ - **Checkout idempotency** — Checkout methods (`anonymous.create()`, `authenticated.create()`, `createSession()`) now use time-windowed idempotency keys (60-second window) instead of fully deterministic keys. Previously, identical checkout params always produced the same `X-Idempotency-Key`, causing the gateway to return cached (and potentially expired) sessions. Now, same params within the same minute are still deduped (protects against network retries), but a new key is generated after the window elapses.
32
+ - **Timestamp consistency** — `Date.now()` is now called once per request and shared between signature timestamp and idempotency key calculation, eliminating a theoretical edge case where the two could land in different seconds.
33
+
34
+ ### Internal
35
+
36
+ - **`PostOptions` interface** — Extracted inline `{ idempotencyWindow?: number }` into a named type in `types.ts` (not publicly exported).
37
+
7
38
  ## [0.2.1] - 2026-04-02
8
39
 
9
40
  ### Added
package/README.md CHANGED
@@ -28,9 +28,7 @@ const client = new WaffoPancake({
28
28
 
29
29
  // Create a checkout session — one call handles token + session + URL
30
30
  const result = await client.checkout.authenticated.create({
31
- storeId: "STO_xxx", // from Dashboard > Stores
32
31
  productId: "PROD_xxx", // from Dashboard > Products
33
- productType: "onetime",
34
32
  currency: "USD",
35
33
  buyerIdentity: req.user.email, // your user's identity
36
34
  });
@@ -85,18 +83,14 @@ The merchant provides buyer identity — the SDK issues a session token, creates
85
83
  ```typescript
86
84
  // Basic — buyer identity only
87
85
  const result = await client.checkout.authenticated.create({
88
- storeId: "STO_xxx",
89
86
  productId: "PROD_xxx",
90
- productType: "onetime",
91
87
  currency: "USD",
92
88
  buyerIdentity: "customer@example.com",
93
89
  });
94
90
 
95
91
  // With dynamic pricing — override stored price (e.g., coupon, volume discount)
96
92
  const result = await client.checkout.authenticated.create({
97
- storeId: "STO_xxx",
98
93
  productId: "PROD_xxx",
99
- productType: "onetime",
100
94
  currency: "USD",
101
95
  buyerIdentity: "customer@example.com",
102
96
  priceSnapshot: { amount: "19.99", taxCategory: "digital_goods" },
@@ -104,9 +98,7 @@ const result = await client.checkout.authenticated.create({
104
98
 
105
99
  // Subscription with trial control + billing detail pre-fill
106
100
  const result = await client.checkout.authenticated.create({
107
- storeId: "STO_xxx",
108
101
  productId: "PROD_xxx",
109
- productType: "subscription",
110
102
  currency: "USD",
111
103
  buyerIdentity: "customer@example.com",
112
104
  withTrial: true, // force enable trial (false = skip, omit = default rules)
@@ -125,17 +117,13 @@ No buyer identity required — the buyer fills in billing details manually on th
125
117
 
126
118
  ```typescript
127
119
  const result = await client.checkout.anonymous.create({
128
- storeId: "STO_xxx",
129
120
  productId: "PROD_xxx",
130
- productType: "onetime",
131
121
  currency: "USD",
132
122
  });
133
123
 
134
124
  // Also supports priceSnapshot and withTrial
135
125
  const result = await client.checkout.anonymous.create({
136
- storeId: "STO_xxx",
137
126
  productId: "PROD_xxx",
138
- productType: "subscription",
139
127
  currency: "USD",
140
128
  priceSnapshot: { amount: "4.99", taxCategory: "saas" },
141
129
  withTrial: false, // skip trial for this session
package/dist/index.cjs CHANGED
@@ -21,7 +21,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  BillingPeriod: () => BillingPeriod,
24
- CheckoutSessionProductType: () => CheckoutSessionProductType,
25
24
  EntityStatus: () => EntityStatus,
26
25
  Environment: () => Environment,
27
26
  ErrorLayer: () => ErrorLayer,
@@ -222,18 +221,26 @@ var HttpClient = class {
222
221
  *
223
222
  * Behavior:
224
223
  * - Generates a deterministic `X-Idempotency-Key` from `merchantId + path + body` (same request produces same key)
224
+ * - When `idempotencyWindow` is set, a floored timestamp is mixed into the key so identical params produce
225
+ * a new key after the window elapses (useful for checkout where repeated creation is intentional)
225
226
  * - Auto-builds RSA-SHA256 signature (`X-Merchant-Id` / `X-Timestamp` / `X-Signature`)
226
227
  * - Unwraps the response envelope: returns `data` on success, throws `WaffoPancakeError` on failure
227
228
  *
228
229
  * @param path - API path (e.g. `/v1/actions/store/create-store`)
229
230
  * @param body - Request body object
231
+ * @param options - Optional settings
232
+ * @param options.idempotencyWindow - Time window in seconds for idempotency key rotation (e.g. 60 = per-minute dedup)
230
233
  * @returns Parsed `data` field from the response
231
234
  * @throws {WaffoPancakeError} When the API returns errors
232
235
  */
233
- async post(path, body) {
236
+ async post(path, body, options) {
234
237
  const bodyStr = JSON.stringify(body);
235
- const timestamp = Math.floor(Date.now() / 1e3).toString();
238
+ const now = Date.now();
239
+ const timestampSec = Math.floor(now / 1e3);
240
+ const timestamp = timestampSec.toString();
236
241
  const signature = signRequest("POST", path, timestamp, bodyStr, this.privateKey);
242
+ const idempotencyBase = `${this.merchantId}:${path}:${bodyStr}`;
243
+ const idempotencyInput = options?.idempotencyWindow ? `${idempotencyBase}:${Math.floor(timestampSec / options.idempotencyWindow)}` : idempotencyBase;
237
244
  const response = await this._fetch(`${this.baseUrl}${path}`, {
238
245
  method: "POST",
239
246
  headers: {
@@ -241,7 +248,7 @@ var HttpClient = class {
241
248
  "X-Merchant-Id": this.merchantId,
242
249
  "X-Timestamp": timestamp,
243
250
  "X-Signature": signature,
244
- "X-Idempotency-Key": (0, import_node_crypto2.createHash)("sha256").update(`${this.merchantId}:${path}:${bodyStr}`).digest("hex")
251
+ "X-Idempotency-Key": (0, import_node_crypto2.createHash)("sha256").update(idempotencyInput).digest("hex")
245
252
  },
246
253
  body: bodyStr
247
254
  });
@@ -336,9 +343,7 @@ function validateBillingDetail(detail) {
336
343
  }
337
344
  }
338
345
  function validateCheckoutCommon(params) {
339
- validateShortId("storeId", params.storeId, "STO");
340
346
  validateShortId("productId", params.productId, "PROD");
341
- validateEnum("productType", params.productType, ["onetime", "subscription"]);
342
347
  validateCurrencyCode("currency", params.currency);
343
348
  if (params.priceSnapshot) {
344
349
  validateAmountString("priceSnapshot.amount", params.priceSnapshot.amount);
@@ -364,13 +369,31 @@ var AuthResource = class {
364
369
  * @returns Issued session token with expiration
365
370
  *
366
371
  * @example
372
+ * // By store ID
367
373
  * const { token, expiresAt } = await client.auth.issueSessionToken({
368
374
  * storeId: "STO_xxx",
369
375
  * buyerIdentity: "customer@example.com",
370
376
  * });
377
+ *
378
+ * @example
379
+ * // By product ID (store derived automatically)
380
+ * const { token, expiresAt } = await client.auth.issueSessionToken({
381
+ * productId: "PROD_xxx",
382
+ * buyerIdentity: "customer@example.com",
383
+ * });
371
384
  */
372
385
  async issueSessionToken(params) {
373
- validateShortId("storeId", params.storeId, "STO");
386
+ if (!params.storeId && !params.productId) {
387
+ throw new WaffoPancakeError(400, [
388
+ { message: "Missing required field: provide storeId or productId", layer: "sdk" }
389
+ ]);
390
+ }
391
+ if (params.storeId) {
392
+ validateShortId("storeId", params.storeId, "STO");
393
+ }
394
+ if (params.productId) {
395
+ validateShortId("productId", params.productId, "PROD");
396
+ }
374
397
  validateRequired("buyerIdentity", params.buyerIdentity);
375
398
  return this.http.post("/v1/actions/auth/issue-session-token", params);
376
399
  }
@@ -517,9 +540,7 @@ var CheckoutAnonymousResource = class {
517
540
  *
518
541
  * @example
519
542
  * const result = await client.checkout.anonymous.create({
520
- * storeId: "STO_xxx",
521
543
  * productId: "PROD_xxx",
522
- * productType: "onetime",
523
544
  * currency: "USD",
524
545
  * });
525
546
  * // Redirect to result.checkoutUrl
@@ -528,7 +549,8 @@ var CheckoutAnonymousResource = class {
528
549
  validateCheckoutCommon(params);
529
550
  return this.http.post(
530
551
  "/v1/actions/checkout/create-session",
531
- params
552
+ params,
553
+ { idempotencyWindow: 60 }
532
554
  );
533
555
  }
534
556
  };
@@ -552,9 +574,7 @@ var CheckoutAuthenticatedResource = class {
552
574
  *
553
575
  * @example
554
576
  * const result = await client.checkout.authenticated.create({
555
- * storeId: "STO_xxx",
556
577
  * productId: "PROD_xxx",
557
- * productType: "onetime",
558
578
  * currency: "USD",
559
579
  * buyerIdentity: "customer@example.com",
560
580
  * });
@@ -566,13 +586,13 @@ var CheckoutAuthenticatedResource = class {
566
586
  const { buyerIdentity, buyerEmail, ...sessionFields } = params;
567
587
  const [tokenResult, sessionResult] = await Promise.all([
568
588
  this.http.post("/v1/actions/auth/issue-session-token", {
569
- storeId: params.storeId,
589
+ productId: params.productId,
570
590
  buyerIdentity
571
- }),
591
+ }, { idempotencyWindow: 60 }),
572
592
  this.http.post("/v1/actions/checkout/create-session", {
573
593
  ...sessionFields,
574
594
  buyerEmail: buyerEmail ?? buyerIdentity
575
- })
595
+ }, { idempotencyWindow: 60 })
576
596
  ]);
577
597
  return {
578
598
  sessionId: sessionResult.sessionId,
@@ -606,16 +626,14 @@ var CheckoutResource = class {
606
626
  *
607
627
  * @example
608
628
  * const session = await client.checkout.createSession({
609
- * storeId: "STO_xxx",
610
629
  * productId: "PROD_xxx",
611
- * productType: "onetime",
612
630
  * currency: "USD",
613
631
  * buyerEmail: "customer@example.com",
614
632
  * });
615
633
  * // Redirect to session.checkoutUrl
616
634
  */
617
635
  async createSession(params) {
618
- return this.http.post("/v1/actions/checkout/create-session", params);
636
+ return this.http.post("/v1/actions/checkout/create-session", params, { idempotencyWindow: 60 });
619
637
  }
620
638
  };
621
639
 
@@ -1291,11 +1309,6 @@ var MediaType = /* @__PURE__ */ ((MediaType2) => {
1291
1309
  MediaType2["Video"] = "video";
1292
1310
  return MediaType2;
1293
1311
  })(MediaType || {});
1294
- var CheckoutSessionProductType = /* @__PURE__ */ ((CheckoutSessionProductType2) => {
1295
- CheckoutSessionProductType2["Onetime"] = "onetime";
1296
- CheckoutSessionProductType2["Subscription"] = "subscription";
1297
- return CheckoutSessionProductType2;
1298
- })(CheckoutSessionProductType || {});
1299
1312
  var ErrorLayer = /* @__PURE__ */ ((ErrorLayer2) => {
1300
1313
  ErrorLayer2["Gateway"] = "gateway";
1301
1314
  ErrorLayer2["User"] = "user";
@@ -1325,7 +1338,6 @@ var WebhookEventType = /* @__PURE__ */ ((WebhookEventType2) => {
1325
1338
  // Annotate the CommonJS export names for ESM import in node:
1326
1339
  0 && (module.exports = {
1327
1340
  BillingPeriod,
1328
- CheckoutSessionProductType,
1329
1341
  EntityStatus,
1330
1342
  Environment,
1331
1343
  ErrorLayer,