@waffo/pancake-ts 0.17.0 → 0.19.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 +35 -0
- package/README.md +14 -12
- package/dist/index.cjs +40 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +87 -9
- package/dist/index.d.ts +87 -9
- package/dist/index.js +40 -9
- package/dist/index.js.map +1 -1
- package/docs/api-reference.md +15 -2
- package/docs/webhook-guide.md +20 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,41 @@ 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.19.0] - 2026-08-18
|
|
8
|
+
|
|
9
|
+
Subscription products can now charge for the trial period.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **`PriceInfo.trialAmount`** — trial period price as a display string, subscription products only. Omit it for a free trial. It requires `metadata.trialDays` on the product and must be lower than `amount`; the API rejects either violation with a 400.
|
|
14
|
+
- **`PriceSnapshot`** — the type `CreateCheckoutSessionParams.priceSnapshot` accepts. Same shape as `PriceInfo` without `trialAmount`.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- **`CreateCheckoutSessionParams.priceSnapshot` is typed `PriceSnapshot` rather than `PriceInfo`.** A session-level override replaces the regular period price; the trial price comes from the product version locked into the session, so a `trialAmount` passed here would be dropped server-side. Existing calls compile unchanged — the fields `PriceSnapshot` declares are exactly the two `PriceInfo` had before `trialAmount` was added.
|
|
19
|
+
|
|
20
|
+
## [0.18.0] - 2026-08-08
|
|
21
|
+
|
|
22
|
+
Customer sessions never reached the API, and webhook retries were rejected as replays.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- **Customer session requests now send `X-Environment`.** A session token carries no environment of its own, so the gateway requires the header next to the Bearer credential and rejects the request with a 400 without it. The header was missing, which made every `client.customer(...)` method unusable: `cancelSubscription`, `cancelOnetimeOrder`, `reactivateSubscription`, `createRefundTicket`, `resubmitRefundTicket`, and `graphql.query`. API Key requests were never affected — the gateway derives their environment from the key.
|
|
27
|
+
- **Webhook verification no longer rejects legitimate retries.** The signature timestamp is stamped once, before the first delivery attempt, and retries reuse the original header — so the last retry of a schedule arrives with a timestamp as old as the schedule itself (observed above 31 minutes). Against the old 5-minute window every late retry failed verification as a suspected replay. `verifyWebhook` now allows timestamps up to **45 minutes** old.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **`WaffoPancakeConfig.environment`** — `"test"` or `"prod"`, the environment customer sessions operate in.
|
|
32
|
+
- **`CustomerSessionOptions`** — second argument to `client.customer(token, options)`, overriding the config for a single session.
|
|
33
|
+
- **`VerifyWebhookOptions.futureToleranceMs`** — how far in the future a signature timestamp may be, default `60000` (1 minute). Raise it for a receiving server with known clock skew.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- **`client.customer(token)` requires an environment** from either the config or the per-session options, and throws `WaffoPancakeError` (400, `layer: "sdk"`) when neither supplies one. There is no default — guessing would route the call to the other environment. This turns a request that always failed at the gateway into a local error; no working call changes behavior. Migration: add `environment` to your client config, or pass `client.customer(token, { environment: "test" })`.
|
|
38
|
+
- `client.buyer(token, options)` (deprecated) accepts and forwards the same options.
|
|
39
|
+
- **`VerifyWebhookOptions.toleranceMs` default raised from `300000` to `2700000`, and the window is now asymmetric** — matching the gateway's API Key check, which pairs a wide past-facing window with a tight future-facing one. `toleranceMs` now means "how far in the past"; the future direction is `futureToleranceMs`. `toleranceMs: 0` still disables the check entirely. A captured request stays replayable for longer under the wider window, so keep your handler idempotent on the event `id` — that, not the window, is the real defense.
|
|
40
|
+
|
|
41
|
+
|
|
7
42
|
## [0.17.0] - 2026-08-03
|
|
8
43
|
|
|
9
44
|
`supportEmail` and `website` were never applied by the update-store endpoint — passing them was silently ignored.
|
package/README.md
CHANGED
|
@@ -40,13 +40,14 @@ res.json({ checkoutUrl: result.checkoutUrl });
|
|
|
40
40
|
|
|
41
41
|
## Configuration
|
|
42
42
|
|
|
43
|
-
| Parameter | Type | Required
|
|
44
|
-
| ------------------ | ---------------------------- |
|
|
45
|
-
| `merchantId` | `string` | Yes
|
|
46
|
-
| `privateKey` | `string` | Yes
|
|
47
|
-
| `baseUrl` | `string` | No
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
43
|
+
| Parameter | Type | Required | Description |
|
|
44
|
+
| ------------------ | ---------------------------- | --------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
45
|
+
| `merchantId` | `string` | Yes | Merchant ID in `MER_{base62}` format |
|
|
46
|
+
| `privateKey` | `string` | Yes | RSA private key in PEM format (auto-normalized, see [docs](docs/api-reference.md)) |
|
|
47
|
+
| `baseUrl` | `string` | No | API base URL override |
|
|
48
|
+
| `environment` | `"test" \| "prod"` | For customer sessions | Sent as `X-Environment`. No default — override per session with `client.customer(token, { environment })` |
|
|
49
|
+
| `fetch` | `typeof fetch` | No | Custom fetch implementation |
|
|
50
|
+
| `webhookPublicKey` | `string \| { test?, prod? }` | No | Custom webhook public key(s) |
|
|
50
51
|
|
|
51
52
|
The SDK auto-normalizes key formats: standard PEM, PKCS#1, literal `\n` from env vars, raw base64, and Windows line endings are all accepted.
|
|
52
53
|
|
|
@@ -73,7 +74,7 @@ Waffo supports two checkout modes based on whether the merchant knows the custom
|
|
|
73
74
|
|
|
74
75
|
Both modes support **dynamic pricing** and **trial control** at checkout time:
|
|
75
76
|
|
|
76
|
-
- `priceSnapshot` — override the product's stored price with a custom amount (e.g., coupon, volume discount)
|
|
77
|
+
- `priceSnapshot` — override the product's stored price with a custom amount (e.g., coupon, volume discount); for subscription products this replaces the regular period price only
|
|
77
78
|
- `withTrial` — explicitly enable or disable the trial period for subscriptions (`true` = force trial, `false` = skip trial, omit = use default rules)
|
|
78
79
|
|
|
79
80
|
### Authenticated Checkout (Recommended)
|
|
@@ -232,7 +233,7 @@ See [Webhook Guide](docs/webhook-guide.md) for event types, `WebhookEventData` f
|
|
|
232
233
|
|
|
233
234
|
Beyond checkout, you can let customers manage their own orders and subscriptions — for example, embedding a "Cancel Subscription" or "Request Refund" button in your site.
|
|
234
235
|
|
|
235
|
-
Issue a session token, then use `client.customer(token)` to get a session with self-service methods:
|
|
236
|
+
Issue a session token, then use `client.customer(token, options?)` to get a session with self-service methods:
|
|
236
237
|
|
|
237
238
|
```typescript
|
|
238
239
|
// Your backend — issue a session token for the customer
|
|
@@ -241,8 +242,9 @@ const { token } = await client.auth.issueSessionToken({
|
|
|
241
242
|
buyerIdentity: req.user.email,
|
|
242
243
|
});
|
|
243
244
|
|
|
244
|
-
// Create a customer session
|
|
245
|
-
|
|
245
|
+
// Create a customer session — environment is required here (or on the client config),
|
|
246
|
+
// because a session token carries none of its own
|
|
247
|
+
const customer = client.customer(token, { environment: "test" });
|
|
246
248
|
|
|
247
249
|
// Cancel a subscription
|
|
248
250
|
const { orderId, status } = await customer.cancelSubscription({ orderId: "ORD_xxx" });
|
|
@@ -545,7 +547,7 @@ try {
|
|
|
545
547
|
| `client.checkout.authenticated` | `create()` | Authenticated checkout (recommended) |
|
|
546
548
|
| `client.checkout.anonymous` | `create()` | Anonymous checkout |
|
|
547
549
|
| `client.checkout` | `createSession()` | Low-level checkout session |
|
|
548
|
-
| `client.customer(token)`
|
|
550
|
+
| `client.customer(token, opts?)` | `cancelSubscription()` `cancelOnetimeOrder()` `reactivateSubscription()` `createRefundTicket()` `resubmitRefundTicket()` | Customer self-service |
|
|
549
551
|
| `client.customer(token).graphql` | `query<T>()` | Customer-scoped GraphQL queries |
|
|
550
552
|
| `client.webhooks` | `verify<T>()` `add()` `update()` `remove()` | Webhook config + signature verification |
|
|
551
553
|
| `client.graphql` | `query<T>()` | Merchant GraphQL queries |
|
package/dist/index.cjs
CHANGED
|
@@ -62,16 +62,21 @@ var WaffoPancakeError = class extends Error {
|
|
|
62
62
|
var DEFAULT_BASE_URL = "https://api.waffo.ai";
|
|
63
63
|
var CustomerHttpClient = class {
|
|
64
64
|
token;
|
|
65
|
+
environment;
|
|
65
66
|
baseUrl;
|
|
66
67
|
_fetch;
|
|
67
|
-
constructor(token, config) {
|
|
68
|
+
constructor(token, environment, config) {
|
|
68
69
|
this.token = token;
|
|
70
|
+
this.environment = environment;
|
|
69
71
|
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
70
72
|
this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
71
73
|
}
|
|
72
74
|
/**
|
|
73
75
|
* Send a Bearer-authenticated POST and return the full envelope plus HTTP status.
|
|
74
76
|
*
|
|
77
|
+
* Sends `Authorization: Bearer <token>` and `X-Environment` — the gateway
|
|
78
|
+
* requires both to accept a session token.
|
|
79
|
+
*
|
|
75
80
|
* Does NOT throw on `errors[]` or non-2xx status — caller inspects the result.
|
|
76
81
|
* Throws {@link WaffoPancakeError} only when the response body is not valid JSON.
|
|
77
82
|
*/
|
|
@@ -80,7 +85,8 @@ var CustomerHttpClient = class {
|
|
|
80
85
|
method: "POST",
|
|
81
86
|
headers: {
|
|
82
87
|
"Content-Type": "application/json",
|
|
83
|
-
Authorization: `Bearer ${this.token}
|
|
88
|
+
Authorization: `Bearer ${this.token}`,
|
|
89
|
+
"X-Environment": this.environment
|
|
84
90
|
},
|
|
85
91
|
body: JSON.stringify(body)
|
|
86
92
|
});
|
|
@@ -1120,7 +1126,8 @@ var SubscriptionProductsResource = class {
|
|
|
1120
1126
|
|
|
1121
1127
|
// src/webhooks.ts
|
|
1122
1128
|
var import_node_crypto3 = require("crypto");
|
|
1123
|
-
var DEFAULT_TOLERANCE_MS =
|
|
1129
|
+
var DEFAULT_TOLERANCE_MS = 45 * 60 * 1e3;
|
|
1130
|
+
var DEFAULT_FUTURE_TOLERANCE_MS = 60 * 1e3;
|
|
1124
1131
|
var TEST_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
|
|
1125
1132
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxnmRY6yMMA3lVqmAU6ZG
|
|
1126
1133
|
b1sjL/+r/z6E+ZjkXaDAKiqOhk9rpazni0bNsGXwmftTPk9jy2wn+j6JHODD/WH/
|
|
@@ -1188,7 +1195,9 @@ function verifyWebhook(payload, signatureHeader, options) {
|
|
|
1188
1195
|
if (Number.isNaN(timestampMs)) {
|
|
1189
1196
|
throw new Error("Invalid timestamp in X-Waffo-Signature header");
|
|
1190
1197
|
}
|
|
1191
|
-
|
|
1198
|
+
const futureToleranceMs = options?.futureToleranceMs ?? DEFAULT_FUTURE_TOLERANCE_MS;
|
|
1199
|
+
const ageMs = Date.now() - timestampMs;
|
|
1200
|
+
if (ageMs > toleranceMs || ageMs < -futureToleranceMs) {
|
|
1192
1201
|
throw new Error("Webhook timestamp outside tolerance window (possible replay attack)");
|
|
1193
1202
|
}
|
|
1194
1203
|
}
|
|
@@ -1381,19 +1390,40 @@ var WaffoPancake = class {
|
|
|
1381
1390
|
* methods for order cancellation, subscription management, refund tickets,
|
|
1382
1391
|
* and scoped GraphQL queries.
|
|
1383
1392
|
*
|
|
1393
|
+
* Session tokens expire 5 minutes after issuance, so issue one right before
|
|
1394
|
+
* use rather than caching it.
|
|
1395
|
+
*
|
|
1384
1396
|
* @param token - Session token from `client.auth.issueSessionToken()`
|
|
1397
|
+
* @param options - Per-session overrides
|
|
1385
1398
|
* @returns A customer session with self-service methods
|
|
1399
|
+
* @throws {WaffoPancakeError} When no environment is available from either
|
|
1400
|
+
* `options.environment` or `WaffoPancakeConfig.environment`
|
|
1386
1401
|
*
|
|
1387
1402
|
* @example
|
|
1388
1403
|
* const { token } = await client.auth.issueSessionToken({
|
|
1389
1404
|
* storeId: "STO_xxx",
|
|
1390
1405
|
* buyerIdentity: "customer@example.com",
|
|
1391
1406
|
* });
|
|
1392
|
-
* const customer = client.customer(token);
|
|
1407
|
+
* const customer = client.customer(token, { environment: "test" });
|
|
1393
1408
|
* await customer.cancelSubscription({ orderId: "ORD_xxx" });
|
|
1409
|
+
*
|
|
1410
|
+
* @example
|
|
1411
|
+
* // Set the environment once on the client instead
|
|
1412
|
+
* const client = new WaffoPancake({ merchantId, privateKey, environment: "test" });
|
|
1413
|
+
* const customer = client.customer(token);
|
|
1394
1414
|
*/
|
|
1395
|
-
customer(token) {
|
|
1396
|
-
const
|
|
1415
|
+
customer(token, options) {
|
|
1416
|
+
const environment = options?.environment ?? this.config.environment;
|
|
1417
|
+
if (environment === void 0) {
|
|
1418
|
+
throw new WaffoPancakeError(400, [
|
|
1419
|
+
{
|
|
1420
|
+
message: "Missing required field: environment \u2014 set it on the client config or pass client.customer(token, { environment: 'test' | 'prod' })",
|
|
1421
|
+
layer: "sdk"
|
|
1422
|
+
}
|
|
1423
|
+
]);
|
|
1424
|
+
}
|
|
1425
|
+
validateEnum("environment", environment, ["test", "prod"]);
|
|
1426
|
+
const customerHttp = new CustomerHttpClient(token, environment, {
|
|
1397
1427
|
baseUrl: this.config.baseUrl,
|
|
1398
1428
|
fetch: this.config.fetch
|
|
1399
1429
|
});
|
|
@@ -1403,6 +1433,7 @@ var WaffoPancake = class {
|
|
|
1403
1433
|
* Create a customer session for self-service operations.
|
|
1404
1434
|
*
|
|
1405
1435
|
* @param token - Session token from `client.auth.issueSessionToken()`
|
|
1436
|
+
* @param options - Per-session overrides
|
|
1406
1437
|
* @returns A customer session with self-service methods
|
|
1407
1438
|
*
|
|
1408
1439
|
* @example
|
|
@@ -1412,8 +1443,8 @@ var WaffoPancake = class {
|
|
|
1412
1443
|
*
|
|
1413
1444
|
* @deprecated Use {@link WaffoPancake.customer} instead.
|
|
1414
1445
|
*/
|
|
1415
|
-
buyer(token) {
|
|
1416
|
-
return this.customer(token);
|
|
1446
|
+
buyer(token, options) {
|
|
1447
|
+
return this.customer(token, options);
|
|
1417
1448
|
}
|
|
1418
1449
|
};
|
|
1419
1450
|
|