@waffo/pancake-ts 0.1.9 → 0.2.1

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,22 +4,39 @@ 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.1.9] - 2026-04-02
7
+ ## [0.2.1] - 2026-04-02
8
+
9
+ ### Added
10
+
11
+ - **Client-side input validation** — All resource methods now validate inputs before sending network requests. Checks include: required field presence, Short ID format (`STO_xxx`, `PROD_xxx`, etc.), ISO 4217 currency codes, ISO 3166-1 country codes, display-format amount strings, enum value ranges, and positive integers. Validation errors throw `WaffoPancakeError` with `status: 400` and `layer: "sdk"`, so developers catch them uniformly with API errors.
12
+ - **`ErrorLayer.Sdk`** — New `"sdk"` value in the `ErrorLayer` enum for client-side validation errors.
13
+
14
+ ### Fixed
15
+
16
+ - **Types** — `RefundTicketStatus` enum now includes all 9 statuses: added `UnderReview`, `Returned`, `Cancelled` (previously missing 3 values)
17
+ - **Types** — `RefundTicket.currentVersionId` corrected to `string | null` (was `string`)
18
+ - **Types** — `RefundTicket.versionNumber` corrected to `number | null` (was `number`)
19
+ - **Types** — `RefundTicket.versionData` corrected to `Record<string, unknown> | null` (was non-nullable)
20
+ - **Types** — `RefundTicket` now includes `createdAt` and `updatedAt` fields (previously missing)
21
+ - **Types** — `PriceInfo`, `Prices`, `WebhookEvent` JSDoc examples corrected from numeric amounts to display-format strings
22
+
23
+ ## [0.2.0] - 2026-04-02
8
24
 
9
25
  ### Added
10
26
 
11
27
  - **Checkout convenience methods** — `client.checkout.authenticated.create()` and `client.checkout.anonymous.create()` wrap the full checkout flow into a single call. Authenticated mode issues a session token, creates a checkout session, and returns a URL with the token appended as a URL fragment. Anonymous mode creates a session directly.
12
- - **Types** — `AuthenticatedCheckoutParams`, `AuthenticatedCheckoutResult`, `AnonymousCheckoutParams`
13
- - **Resources** — `CheckoutAnonymousResource`, `CheckoutAuthenticatedResource` (accessed via `client.checkout.anonymous` / `client.checkout.authenticated`)
28
+ - **Buyer self-service** — `client.buyer(token)` creates a session-token-authenticated buyer session with methods: `cancelSubscription()`, `cancelOnetimeOrder()`, `reactivateSubscription()`, `createRefundTicket()`, `resubmitRefundTicket()`, and `graphql.query()`.
29
+ - **Types** — `AuthenticatedCheckoutParams`, `AuthenticatedCheckoutResult`, `AnonymousCheckoutParams`, `CancelOnetimeOrderParams`, `CancelOnetimeOrderResult`, `ReactivateSubscriptionParams`, `ReactivateSubscriptionResult`, `CreateRefundTicketParams`, `ResubmitRefundTicketParams`, `RefundTicket`, `RequestedAmount`
30
+ - **Resources** — `CheckoutAnonymousResource`, `CheckoutAuthenticatedResource`, `BuyerSession`, `BuyerHttpClient`
14
31
 
15
32
  ### Changed
16
33
 
17
34
  - **Base URL** — Default API endpoint changed from `waffo-pancake-auth-service.vercel.app` to `api.waffo.ai`
18
35
  - **Package** — `docs/` directory now included in npm package (`files` field)
19
- - **Docs** — `docs/api-reference.md` synced with endpoint docs: fixed `storeId` required status, added `BillingDetail` conditional field rules, fixed price amount format (display string, not integer), added subscription product group UUID note
36
+ - **Docs** — `docs/api-reference.md` synced with endpoint docs: fixed `storeId` required status, added `BillingDetail` conditional field rules, fixed price amount format (display string, not integer), added subscription product group UUID note, added Buyer Self-Service section
20
37
  - **Docs** — `docs/graphql-guide.md` rewritten: corrected query names (`onetimeOrders` / `subscriptionOrders`), added product version queries, exchange rate query, webhook/email delivery log queries, 9 analytics queries (`orderStatistics`, `paymentStatistics`, `productStatistics`, `trendAnalysis`, `distributionAnalysis`, `customerAnalysis`, `taxAnalysis`, `subscriptionAnalysis`, `refundTicketAnalysis`), updated count query list and filter examples
21
38
  - **Docs** — `docs/webhook-guide.md` fixed `amount` / `taxAmount` type from `number` to `string` (display format), added retry mechanism section with delivery status table
22
- - **README** — Reorganized by use-case priority (checkout → webhooks → GraphQL → programmatic management), added checkout mode comparison and recommendation rationale
39
+ - **README** — Reorganized by use-case priority (checkout → webhooks → buyer self-service → GraphQL → programmatic management), added checkout mode comparison and recommendation rationale
23
40
 
24
41
  ## [0.1.8] - 2026-03-20
25
42
 
package/README.md CHANGED
@@ -66,32 +66,54 @@ Waffo supports two checkout modes based on whether the merchant knows the buyer'
66
66
 
67
67
  > **We recommend authenticated checkout whenever possible.** The most important reason: authenticated checkout binds the order to the `buyerIdentity` you provide, which is a **merchant-controlled stable identifier**. Even if the buyer changes the email on the checkout form, the order is still tied to the identity you specified. In anonymous mode, the buyer self-reports their email on the form — if they enter a different address, the system treats them as a new user, which means **previous orders become unlinked** and **subscription trial periods can be exploited** (a new email = a new user = a fresh trial).
68
68
  >
69
- > Anonymous checkout also uses the `shopper` role, which can **only create orders** (no cancellation, subscription management, or refund tickets) with a **1-minute single-use session**.
70
- >
71
- > | | Authenticated (`customer`) | Anonymous (`shopper`) |
69
+ > | | Authenticated | Anonymous |
72
70
  > |---|---|---|
73
71
  > | **Identity** | Merchant-provided, stable across orders | Self-reported email, may vary |
74
- > | **Permissions** | Create orders, cancel orders, manage subscriptions, submit refund tickets | Create orders **only** |
75
- > | **Session** | 5-minute TTL, auto-refreshes on each API call | 1-minute TTL, **single-use** (consumed on first API call) |
76
- > | **Subscriptions** | Fully supported — buyers can manage, cancel, or reactivate | Not practical — buyer has no session to manage the subscription afterward |
72
+ > | **Form** | Pre-filled from merchant-provided identity | Empty, buyer fills manually |
73
+ > | **Post-purchase** | Full self-service (see [Buyer Self-Service](#buyer-self-service)) | Create orders only no post-purchase self-service |
74
+ > | **Session** | 5-minute TTL, auto-refreshes | 1-minute, single-use |
75
+
76
+ Both modes support **dynamic pricing** and **trial control** at checkout time:
77
+
78
+ - `priceSnapshot` — override the product's stored price with a custom amount (e.g., coupon, volume discount)
79
+ - `withTrial` — explicitly enable or disable the trial period for subscriptions (`true` = force trial, `false` = skip trial, omit = use default rules)
77
80
 
78
81
  ### Authenticated Checkout (Recommended)
79
82
 
80
83
  The merchant provides buyer identity — the SDK issues a session token, creates a checkout session, and returns a checkout URL with the token appended as a URL fragment. One call does everything.
81
84
 
82
85
  ```typescript
86
+ // Basic — buyer identity only
87
+ const result = await client.checkout.authenticated.create({
88
+ storeId: "STO_xxx",
89
+ productId: "PROD_xxx",
90
+ productType: "onetime",
91
+ currency: "USD",
92
+ buyerIdentity: "customer@example.com",
93
+ });
94
+
95
+ // With dynamic pricing — override stored price (e.g., coupon, volume discount)
83
96
  const result = await client.checkout.authenticated.create({
84
97
  storeId: "STO_xxx",
85
98
  productId: "PROD_xxx",
86
99
  productType: "onetime",
87
100
  currency: "USD",
88
101
  buyerIdentity: "customer@example.com",
89
- // Optional: pre-fill billing details
102
+ priceSnapshot: { amount: "19.99", taxCategory: "digital_goods" },
103
+ });
104
+
105
+ // Subscription with trial control + billing detail pre-fill
106
+ const result = await client.checkout.authenticated.create({
107
+ storeId: "STO_xxx",
108
+ productId: "PROD_xxx",
109
+ productType: "subscription",
110
+ currency: "USD",
111
+ buyerIdentity: "customer@example.com",
112
+ withTrial: true, // force enable trial (false = skip, omit = default rules)
90
113
  billingDetail: { country: "US", isBusiness: false },
91
114
  });
92
- // result.checkoutUrl = "https://pancake.waffo.ai/store/{slug}/checkout/{sessionId}#token={JWT}"
93
115
 
94
- // Frontend open in a new tab (recommended)
116
+ // result.checkoutUrl = "https://pancake.waffo.ai/store/{slug}/checkout/{sessionId}#token={JWT}"
95
117
  window.open(result.checkoutUrl, "_blank", "noopener,noreferrer");
96
118
  ```
97
119
 
@@ -108,7 +130,16 @@ const result = await client.checkout.anonymous.create({
108
130
  productType: "onetime",
109
131
  currency: "USD",
110
132
  });
111
- // result.checkoutUrl = "https://pancake.waffo.ai/store/{slug}/checkout/{sessionId}"
133
+
134
+ // Also supports priceSnapshot and withTrial
135
+ const result = await client.checkout.anonymous.create({
136
+ storeId: "STO_xxx",
137
+ productId: "PROD_xxx",
138
+ productType: "subscription",
139
+ currency: "USD",
140
+ priceSnapshot: { amount: "4.99", taxCategory: "saas" },
141
+ withTrial: false, // skip trial for this session
142
+ });
112
143
 
113
144
  window.open(result.checkoutUrl, "_blank", "noopener,noreferrer");
114
145
  ```
@@ -195,6 +226,57 @@ const event = client.webhooks.verify(rawBody, sig, { environment: "prod" });
195
226
 
196
227
  See [Webhook Guide](docs/webhook-guide.md) for event types, dual-environment key architecture, key resolution chain, retry mechanism, and best practices.
197
228
 
229
+ ## Buyer Self-Service
230
+
231
+ Beyond checkout, you can let buyers manage their own orders and subscriptions — for example, embedding a "Cancel Subscription" or "Request Refund" button in your site.
232
+
233
+ Issue a session token, then use `client.buyer(token)` to get a session with self-service methods:
234
+
235
+ ```typescript
236
+ // Your backend — issue a session token for the buyer
237
+ const { token } = await client.auth.issueSessionToken({
238
+ storeId: "STO_xxx",
239
+ buyerIdentity: req.user.email,
240
+ });
241
+
242
+ // Create a buyer session
243
+ const buyer = client.buyer(token);
244
+
245
+ // Cancel a subscription
246
+ const { orderId, status } = await buyer.cancelSubscription({ orderId: "ORD_xxx" });
247
+ // status: "canceling" (active) or "canceled" (pending)
248
+
249
+ // Reactivate a canceled subscription
250
+ await buyer.reactivateSubscription({ orderId: "ORD_xxx" });
251
+
252
+ // Cancel a one-time order (while payment is pending)
253
+ await buyer.cancelOnetimeOrder({ orderId: "ORD_yyy" });
254
+
255
+ // Submit a refund request
256
+ const { ticket } = await buyer.createRefundTicket({
257
+ paymentId: "PAY_xxx",
258
+ reason: "Product not as described",
259
+ requestedAmount: { amount: "29.00", currency: "USD" },
260
+ });
261
+
262
+ // Resubmit a rejected refund ticket
263
+ await buyer.resubmitRefundTicket({
264
+ ticketId: "TKT_xxx",
265
+ paymentId: "PAY_xxx",
266
+ reason: "Updated reason with more detail",
267
+ requestedAmount: { amount: "29.00", currency: "USD" },
268
+ });
269
+
270
+ // Query the buyer's own orders via GraphQL
271
+ const result = await buyer.graphql.query({
272
+ query: `query { orders { id status createdAt } }`,
273
+ });
274
+ ```
275
+
276
+ The token is scoped to the specified store and buyer identity — buyers can only access their own data. Token TTL is 5 minutes and auto-refreshes on each API call.
277
+
278
+ > **Note**: This uses the same `buyerIdentity` as `checkout.authenticated.create()`. Orders placed via authenticated checkout are automatically tied to this identity, so the buyer can manage them later with a token issued here.
279
+
198
280
  ## GraphQL — Typed Queries
199
281
 
200
282
  ```typescript
@@ -361,8 +443,10 @@ try {
361
443
  | `client.checkout.authenticated` | `create()` | Authenticated checkout (recommended) |
362
444
  | `client.checkout.anonymous` | `create()` | Anonymous checkout |
363
445
  | `client.checkout` | `createSession()` | Low-level checkout session |
446
+ | `client.buyer(token)` | `cancelSubscription()` `cancelOnetimeOrder()` `reactivateSubscription()` `createRefundTicket()` `resubmitRefundTicket()` | Buyer self-service |
447
+ | `client.buyer(token).graphql` | `query<T>()` | Buyer-scoped GraphQL queries |
364
448
  | `client.webhooks` | `verify<T>()` | Webhook signature verification |
365
- | `client.graphql` | `query<T>()` | Typed GraphQL queries |
449
+ | `client.graphql` | `query<T>()` | Merchant GraphQL queries |
366
450
  | `client.auth` | `issueSessionToken()` | Issue a buyer session token (JWT) |
367
451
  | `client.stores` | `create()` `update()` `delete()` | Store management |
368
452
  | `client.storeMerchants` | `add()` `remove()` `updateRole()` | Store members (coming soon) |
@@ -430,7 +514,8 @@ npm run build # tsup → ESM + CJS + DTS
430
514
  src/
431
515
  ├── index.ts # Unified export entry
432
516
  ├── client.ts # WaffoPancake main class
433
- ├── http-client.ts # HTTP client (auto-signing + idempotency)
517
+ ├── http-client.ts # HTTP client (API Key, auto-signing + idempotency)
518
+ ├── buyer-http-client.ts # HTTP client (Bearer token, buyer self-service)
434
519
  ├── signing.ts # RSA-SHA256 request signing
435
520
  ├── errors.ts # WaffoPancakeError
436
521
  ├── webhooks.ts # Webhook verification (embedded keys)
@@ -443,6 +528,7 @@ src/
443
528
  ├── onetime-products.ts
444
529
  ├── subscription-products.ts
445
530
  ├── subscription-product-groups.ts
531
+ ├── buyer.ts
446
532
  ├── orders.ts
447
533
  ├── checkout.ts
448
534
  ├── checkout-anonymous.ts