@genesis-tech/genesispay-seller 0.8.0 → 0.10.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.
Files changed (53) hide show
  1. package/CHANGELOG.md +69 -38
  2. package/README.md +77 -40
  3. package/dist/client.d.ts +18 -15
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/client.js +56 -39
  6. package/dist/client.js.map +1 -1
  7. package/dist/customers.d.ts +2 -2
  8. package/dist/customers.d.ts.map +1 -1
  9. package/dist/customers.js +5 -5
  10. package/dist/customers.js.map +1 -1
  11. package/dist/errors.d.ts +9 -9
  12. package/dist/errors.d.ts.map +1 -1
  13. package/dist/errors.js +18 -18
  14. package/dist/errors.js.map +1 -1
  15. package/dist/genesispay-settlement.d.ts +30 -0
  16. package/dist/genesispay-settlement.d.ts.map +1 -0
  17. package/dist/genesispay-settlement.js +103 -0
  18. package/dist/genesispay-settlement.js.map +1 -0
  19. package/dist/index.d.ts +5 -4
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +4 -4
  22. package/dist/index.js.map +1 -1
  23. package/dist/invoices.d.ts +2 -2
  24. package/dist/invoices.d.ts.map +1 -1
  25. package/dist/invoices.js +7 -7
  26. package/dist/invoices.js.map +1 -1
  27. package/dist/mandate-gate.d.ts +5 -5
  28. package/dist/mandate-gate.d.ts.map +1 -1
  29. package/dist/mandate-gate.js +4 -4
  30. package/dist/mandate-gate.js.map +1 -1
  31. package/dist/mandates.d.ts +4 -4
  32. package/dist/mandates.d.ts.map +1 -1
  33. package/dist/mandates.js +9 -9
  34. package/dist/mandates.js.map +1 -1
  35. package/dist/payment-gate.js +1 -1
  36. package/dist/payment-gate.js.map +1 -1
  37. package/dist/plans.d.ts +2 -2
  38. package/dist/plans.d.ts.map +1 -1
  39. package/dist/plans.js +6 -6
  40. package/dist/plans.js.map +1 -1
  41. package/dist/products.d.ts +80 -0
  42. package/dist/products.d.ts.map +1 -0
  43. package/dist/products.js +154 -0
  44. package/dist/products.js.map +1 -0
  45. package/dist/resource.d.ts +2 -2
  46. package/dist/resource.d.ts.map +1 -1
  47. package/dist/resource.js +1 -1
  48. package/dist/resource.js.map +1 -1
  49. package/dist/webhooks.d.ts +25 -25
  50. package/dist/webhooks.d.ts.map +1 -1
  51. package/dist/webhooks.js +30 -29
  52. package/dist/webhooks.js.map +1 -1
  53. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,10 +1,41 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.0 — BREAKING: every identifier is now `genesispay`
4
+
5
+ The legacy/visible name split is retired. This release renames the
6
+ public surface with **no compatibility window** — the old names are not accepted
7
+ alongside the new ones. Update all of the following at once:
8
+
9
+ - Client and errors renamed: `PeerPay` → `GenesisPay`, `PeerPay*Error` →
10
+ `GenesisPay*Error` (including `PeerPaySignatureVerificationError`).
11
+ - Webhook signature header `PEERPAY-SIGNATURE` → `GENESISPAY-SIGNATURE`.
12
+ **Verification fails silently if you do not update this** — the old header
13
+ simply stops arriving.
14
+ - Seller API keys now use the `gp_sk_` prefix. Keys issued as `pp_sk_` are
15
+ rejected and must be reissued from the dashboard.
16
+ - Environment variables `PEERPAY_*` → `GENESISPAY_*`.
17
+ - Checkout return parameters `peerpay_link_id` / `peerpay_status` →
18
+ `genesispay_link_id` / `genesispay_status`.
19
+ - `genesispay-settlement` module renamed to `genesispay-settlement`.
20
+
21
+ ### Also in this release — a real fix, not a rename
22
+
23
+ `PRODUCTION_FACILITATOR_BASE_URL` pointed at `peerpay-app-production.up.railway.app`,
24
+ a Railway-generated hostname that **never existed** — production has exactly one
25
+ domain. Any live key used via the zero-config path (`new GenesisPay({ apiKey })`
26
+ with no `baseUrl`) therefore resolved to a dead host. Both defaults now use the
27
+ custom domains, which are ours and survive a Railway service rename:
28
+
29
+ - live keys → `https://genesispay.finance`
30
+ - `DEFAULT_FACILITATOR_BASE_URL` → `https://dev.genesispay.finance`
31
+
32
+ Everything else in this release is a rename.
33
+
3
34
  ## Unreleased — renamed to `@genesis-tech/genesispay-seller`
4
35
 
5
- The `@genesis-tech/peerpay-seller` name was versioned here through 0.8.0 but
6
- never published to npm, so the first release carries the product's own name
7
- (ADR-0044). Every version below describes work done under the old name.
36
+ The legacy `@genesis-tech/genesispay-seller` name was versioned here through 0.8.0
37
+ but never published to npm, so the first npm release carried the product's own
38
+ name (ADR-0044). 0.8.0 was published on 2026-08-07. Every version below describes work done under the old name.
8
39
 
9
40
  ## 0.8.0
10
41
 
@@ -13,9 +44,9 @@ subscription surfaces.
13
44
 
14
45
  ### Added
15
46
 
16
- - **`peerpay.customers.*`** — create, list, retrieve, update, and archive
47
+ - **`genesispay.customers.*`** — create, list, retrieve, update, and archive
17
48
  reusable billing contacts.
18
- - **`peerpay.invoices.*`** — create/update drafts, finalize an immutable invoice,
49
+ - **`genesispay.invoices.*`** — create/update drafts, finalize an immutable invoice,
19
50
  retrieve/list it, send it through the configured email provider, void it, or
20
51
  mark it uncollectible.
21
52
  - Finalized invoice responses include the hosted invoice URL, PDF URL, exact
@@ -40,7 +71,7 @@ Additive and backward compatible; no existing handler changes meaning.
40
71
  ### Added
41
72
 
42
73
  - **`amount` and `asset` on the `payment.confirmed` / `link.paid` payload**
43
- (`PeerPayPaymentEventData["link"]`). `event.data.link.asset` names the
74
+ (`GenesisPayPaymentEventData["link"]`). `event.data.link.asset` names the
44
75
  currency; `event.data.link.amount` is the field whose name matches its value.
45
76
  `amountUsdcMinor` is unchanged — same integer minor units, same name.
46
77
  - **`attempt.chainId` on the same payload** — the chain `attempt.txHash` is on.
@@ -101,14 +132,14 @@ and backward compatible: nothing is removed and no existing call changes meaning
101
132
  ### Behaviour changes
102
133
 
103
134
  - `checkout.create` with **both** `amount` and `amountUsdc` set to *different*
104
- amounts throws `PeerPayValidationError` and never sends the request. Equal
135
+ amounts throws `GenesisPayValidationError` and never sends the request. Equal
105
136
  values are fine — `"5.0"` and `"5.00"` compare as the same money, since the
106
137
  check is on minor units rather than on the strings. The server enforces the
107
138
  same rule (422) for callers that do not use this SDK.
108
139
  - `checkout.create` with **neither** field is still a **compile** error:
109
140
  `CheckoutAmountInput` is a union requiring one of the two names, so the
110
141
  guarantee the required `amountUsdc` property gave is not traded away. It also
111
- throws `PeerPayValidationError` at runtime, for JavaScript callers.
142
+ throws `GenesisPayValidationError` at runtime, for JavaScript callers.
112
143
  - Every create request sends the amount under **both** names. A backend older
113
144
  than 0.6.0 only knows `amountUsdc` and ignores `amount`, so 0.6.0 works
114
145
  unchanged against one.
@@ -132,7 +163,7 @@ real operation.
132
163
 
133
164
  ### Added
134
165
 
135
- - **`peerpay.mandates.list({ planId?, status?, limit?, startingAfter? })`** —
166
+ - **`genesispay.mandates.list({ planId?, status?, limit?, startingAfter? })`** —
136
167
  until now a mandate id arrived only on the `mandate.active` webhook, so a
137
168
  missed delivery meant a customer who wanted to cancel could not be served:
138
169
  `revoke` needs that id and there was no way to look it up. Returns
@@ -143,7 +174,7 @@ real operation.
143
174
  - `planId` takes the plan's **`publicId`**. An unknown or foreign plan returns
144
175
  an empty page rather than a 404, so it cannot be used to probe for plans.
145
176
  - **`subscriptionPlanId` on `Mandate`** and on the mandate webhook payload
146
- (`PeerPayMandateEventData`). Without it a `mandate.active` handler knew *that*
177
+ (`GenesisPayMandateEventData`). Without it a `mandate.active` handler knew *that*
147
178
  someone subscribed but not *to what*. `null` means the mandate was proposed
148
179
  directly rather than through a plan checkout — a valid state, not missing data.
149
180
  The server sets it only in the hosted `/subscribe/:planId` flow, so a mandate
@@ -152,7 +183,7 @@ real operation.
152
183
  ### Behaviour changes
153
184
 
154
185
  - A **400** whose body carries structured `issues` now throws
155
- `PeerPayValidationError` instead of `PeerPayConfigError` — the new list
186
+ `GenesisPayValidationError` instead of `GenesisPayConfigError` — the new list
156
187
  endpoint reports invalid query parameters that way. A 400 *without* `issues`
157
188
  is unchanged.
158
189
 
@@ -168,18 +199,18 @@ Additive except where noted under *Behaviour changes*.
168
199
 
169
200
  ### Added
170
201
 
171
- - **`peerpay.plans.*`** — `create`, `list`, `retrieve`, `archive` against the new
202
+ - **`genesispay.plans.*`** — `create`, `list`, `retrieve`, `archive` against the new
172
203
  `/api/v1/plans`. Subscription plans existed before but were reachable only from
173
204
  the dashboard, which is what actually blocked building subscriptions
174
205
  programmatically. Every plan carries **`checkoutUrl`**, the hosted
175
206
  `/subscribe/:publicId` flow to hand a customer — you no longer assemble it.
176
- - **`peerpay.mandates.*`** — `create`, `retrieve`, `charge`, `revoke`. There is
207
+ - **`genesispay.mandates.*`** — `create`, `retrieve`, `charge`, `revoke`. There is
177
208
  deliberately **no `activate`**: activating a mandate needs the payer's
178
209
  signature, not the seller key, so it belongs in the payer's frontend.
179
210
  `mandates.create` returns `{ mandate, permitTypedData, spender }`, and
180
211
  `permitTypedData` is passed through verbatim — a signature covers those exact
181
212
  bytes, so the SDK must not reshape them.
182
- - **`peerpay.checkout.simulatePayment(publicId, { payerWallet? })`** — mints a
213
+ - **`genesispay.checkout.simulatePayment(publicId, { payerWallet? })`** — mints a
183
214
  confirmed payment in test mode and fires the real `payment.confirmed` /
184
215
  `link.paid` webhooks, so the paid path is testable without testnet USDC and
185
216
  wallet UX. Test keys only (a live key gets 403), and the endpoint does not
@@ -192,29 +223,29 @@ Additive except where noted under *Behaviour changes*.
192
223
  this flag is the only honest signal. Real payments report `false`.
193
224
  - **Typed webhook events.** `constructEvent` now returns a union discriminated on
194
225
  `type`, so `if (event.type === "payment.confirmed")` narrows `event.data` with
195
- no cast. Also exported: `PEERPAY_EVENT_TYPES`, `isKnownPeerPayEventType`,
196
- `PeerPayEventType`, and `PeerPayAnyEvent`/`PeerPayUnknownEvent` for handlers
226
+ no cast. Also exported: `GENESISPAY_EVENT_TYPES`, `isKnownGenesisPayEventType`,
227
+ `GenesisPayEventType`, and `GenesisPayAnyEvent`/`GenesisPayUnknownEvent` for handlers
197
228
  that want to model the open world.
198
229
  - An **unknown event type is still accepted**, verified and returned — a newer
199
230
  server must never make an older SDK reject deliveries. Only the signature or
200
231
  a malformed envelope can reject.
201
- - `PeerPayEvent` has no `{ type: string }` fallback member on purpose:
232
+ - `GenesisPayEvent` has no `{ type: string }` fallback member on purpose:
202
233
  TypeScript disables discriminant narrowing for *every* member of a union as
203
- soon as one member's discriminant is a non-literal. Use `PeerPayAnyEvent`
234
+ soon as one member's discriminant is a non-literal. Use `GenesisPayAnyEvent`
204
235
  (assignable from any known event, no cast) when you need the open type.
205
- - **`PeerPayValidationError`** (422, with structured `issues: [{path, message}]`)
206
- and **`PeerPayRateLimitError`** (429, with `retryAfterSeconds` from
236
+ - **`GenesisPayValidationError`** (422, with structured `issues: [{path, message}]`)
237
+ and **`GenesisPayRateLimitError`** (429, with `retryAfterSeconds` from
207
238
  `Retry-After`).
208
239
 
209
240
  ### Behaviour changes
210
241
 
211
- - A 422 or 429 previously surfaced as `PeerPayConfigError`. They now throw the
212
- two classes above. If you branch on `PeerPayConfigError` to catch bad input,
213
- update that branch — both new classes extend `Error`, not `PeerPayConfigError`.
242
+ - A 422 or 429 previously surfaced as `GenesisPayConfigError`. They now throw the
243
+ two classes above. If you branch on `GenesisPayConfigError` to catch bad input,
244
+ update that branch — both new classes extend `Error`, not `GenesisPayConfigError`.
214
245
  - **`expectedPayTo` now also covers created objects.** It previously guarded only
215
246
  the `/api/v1/seller` config lookup, while `checkout.create` and `plans.create`
216
247
  each freeze their own `destinationWallet` — the addresses money actually moves
217
- to. Both now throw `PeerPayNetworkSafetyError` on a mismatch. If you set the
248
+ to. Both now throw `GenesisPayNetworkSafetyError` on a mismatch. If you set the
218
249
  pin and create links for a wallet other than the pinned one, those calls will
219
250
  start failing (which is the point).
220
251
  - **An unknown `plan.status` now degrades to `archived`, not `active`.** A status
@@ -247,21 +278,21 @@ call signature changes.
247
278
  `publicId` back to a buyer.
248
279
  - **Return / cancel URLs** — `checkout.create` accepts `returnUrl` and `cancelUrl`.
249
280
  After a confirmed payment the hosted checkout shows an explicit "back to …"
250
- button with `?peerpay_link_id=<publicId>&peerpay_status=paid` appended (existing
281
+ button with `?genesispay_link_id=<publicId>&genesispay_status=paid` appended (existing
251
282
  query parameters on your URL are preserved). Deliberately not an automatic
252
283
  timed redirect: the payer should be able to see the on-chain confirmation before
253
284
  leaving. Both must be `https` (`http` is accepted for localhost only).
254
285
  - **`checkout.retrieve(publicId)`** — reads a link's current state, including a
255
286
  `paid` boolean and `confirmedPaymentCount`, for polling without a raw fetch
256
- against `/api/v1/links/:publicId`. A 404 throws the new `PeerPayNotFoundError`
257
- rather than `PeerPayConfigError`, so an unknown id is distinguishable from a
287
+ against `/api/v1/links/:publicId`. A 404 throws the new `GenesisPayNotFoundError`
288
+ rather than `GenesisPayConfigError`, so an unknown id is distinguishable from a
258
289
  broken key or an unreachable backend.
259
290
  - **`constructEvent(rawBody, signatureHeader, secret, opts?)`** — webhook signature
260
- verification, exported as a free function and as `peerpay.webhooks.constructEvent`.
291
+ verification, exported as a free function and as `genesispay.webhooks.constructEvent`.
261
292
  Constant-time comparison, a replay window on the signature timestamp (default
262
293
  300 s, rejecting future timestamps as well as stale ones), and support for
263
294
  multiple `v1=` values so an endpoint secret can be rotated without dropped
264
- deliveries. Throws `PeerPaySignatureVerificationError`; no error message
295
+ deliveries. Throws `GenesisPaySignatureVerificationError`; no error message
265
296
  contains the secret or the expected signature.
266
297
 
267
298
  ### Note on `constructEvent` being async
@@ -273,7 +304,7 @@ Workers and Bun. Remember the `await`.
273
304
  ### Gotchas worth knowing before you upgrade
274
305
 
275
306
  - `constructEvent` returns a **Promise** (see above).
276
- - The `?peerpay_link_id=…&peerpay_status=paid` parameters appended to your
307
+ - The `?genesispay_link_id=…&genesispay_status=paid` parameters appended to your
277
308
  `returnUrl` are a UI hint, **not** proof of payment — they are unsigned and a
278
309
  payer can navigate to that URL without paying. Fulfil on the webhook or on
279
310
  `checkout.retrieve().paid`.
@@ -298,20 +329,20 @@ Workers and Bun. Remember the `await`.
298
329
 
299
330
  ### Added
300
331
 
301
- - **`PeerPay` client** — a Stripe-like entry point that configures from just the
302
- seller API key. The key prefix (`pp_sk_test_…` / `pp_sk_live_…`) determines the
332
+ - **`GenesisPay` client** — a Stripe-like entry point that configures from just the
333
+ seller API key. The key prefix (`gp_sk_test_…` / `gp_sk_live_…`) determines the
303
334
  mode and base URL; the payout wallet and network are resolved from the key via the
304
- new PeerPay backend endpoint `GET /api/v1/seller` and cached (single-flight, 5-min
335
+ new GenesisPay backend endpoint `GET /api/v1/seller` and cached (single-flight, 5-min
305
336
  TTL, failures never cached).
306
- - `peerpay.checkout.create({ title, amountUsdc, … })` → hosted `payUrl` (the wallet
337
+ - `genesispay.checkout.create({ title, amountUsdc, … })` → hosted `payUrl` (the wallet
307
338
  is defaulted server-side from the key).
308
- - `peerpay.gate({ amountUsdc }).wrap(handler)` → x402 gate with `payTo` + network
339
+ - `genesispay.gate({ amountUsdc }).wrap(handler)` → x402 gate with `payTo` + network
309
340
  resolved from the key and settlement auto-wired.
310
341
  - Money-safety, all fail-closed: no `402` with a null destination; `503` when the
311
342
  backend is unreachable (the paid handler never runs); asset-integrity check
312
343
  (backend USDC/chainId must match the SDK's native table); mode↔network check (a
313
344
  `live` key must not resolve to a testnet); optional `expectedPayTo` pin.
314
- - Exports: `PeerPay`, `PeerPayConfigError`, `PeerPayNetworkSafetyError`, and the
345
+ - Exports: `GenesisPay`, `GenesisPayConfigError`, `GenesisPayNetworkSafetyError`, and the
315
346
  related types.
316
347
 
317
348
  ### Requirements
@@ -321,10 +352,10 @@ Workers and Bun. Remember the `await`.
321
352
 
322
353
  ### Unchanged
323
354
 
324
- - The low-level `createPaymentGate` / `peerPaySettlement` primitives are untouched and
355
+ - The low-level `createPaymentGate` / `genesisPaySettlement` primitives are untouched and
325
356
  remain exported. This release is purely additive.
326
357
 
327
358
  ## 0.1.0
328
359
 
329
360
  - Initial release: framework-agnostic x402 payment gate (`createPaymentGate`,
330
- `peerPaySettlement`).
361
+ `genesisPaySettlement`).
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @genesis-tech/genesispay-seller
2
2
 
3
- Framework-agnostic x402 payment gate for sellers, powered by GenesisPay (formerly PeerDirect).
3
+ Framework-agnostic x402 payment gate for sellers, powered by GenesisPay (formerly GenesisPay).
4
4
 
5
5
  Wrap any Web-standard `(Request) => Response` handler and it becomes a paid
6
6
  endpoint:
@@ -21,21 +21,21 @@ speaks the Fetch API.
21
21
  npm install @genesis-tech/genesispay-seller
22
22
  ```
23
23
 
24
- ## Quick start (legacy `PeerPay` client — recommended)
24
+ ## Quick start (legacy `GenesisPay` client — recommended)
25
25
 
26
- The `PeerPay` client configures from **just the seller API key**. The key prefix
27
- (`pp_sk_test_…` / `pp_sk_live_…`) determines the mode and base URL, and the payout
26
+ The `GenesisPay` client configures from **just the seller API key**. The key prefix
27
+ (`gp_sk_test_…` / `gp_sk_live_…`) determines the mode and base URL, and the payout
28
28
  wallet + network are resolved from the key via the GenesisPay backend and cached.
29
29
 
30
30
  ```ts
31
- import { PeerPay } from "@genesis-tech/genesispay-seller";
31
+ import { GenesisPay } from "@genesis-tech/genesispay-seller";
32
32
 
33
- const peerpay = new PeerPay({ apiKey: process.env.PEERPAY_SELLER_API_KEY! });
33
+ const genesispay = new GenesisPay({ apiKey: process.env.GENESISPAY_SELLER_API_KEY! });
34
34
 
35
35
  // Human hosted checkout — the wallet is defaulted server-side from the key.
36
36
  // `metadata` and `clientReferenceId` come back on retrieve() and on the webhook,
37
37
  // so you never need your own table just to map a link back to a buyer:
38
- const { publicId, payUrl } = await peerpay.checkout.create({
38
+ const { publicId, payUrl } = await genesispay.checkout.create({
39
39
  title: "50 credits",
40
40
  amount: "5.00", // decimal string in `asset` — see "Amounts and assets" below
41
41
  clientReferenceId: order.id,
@@ -45,13 +45,13 @@ const { publicId, payUrl } = await peerpay.checkout.create({
45
45
  });
46
46
 
47
47
  // Poll for settlement:
48
- const session = await peerpay.checkout.retrieve(publicId);
48
+ const session = await genesispay.checkout.retrieve(publicId);
49
49
  if (session.paid) fulfil(session.clientReferenceId);
50
- // An unknown id throws PeerPayNotFoundError, not PeerPayConfigError — so a
50
+ // An unknown id throws GenesisPayNotFoundError, not GenesisPayConfigError — so a
51
51
  // typo'd id is distinguishable from a broken key or an unreachable backend.
52
52
 
53
53
  // Agent x402 gate — payTo + network resolved from the key, settlement auto-wired:
54
- export const GET = peerpay.gate({ amountUsdc: "0.02" }).wrap(
54
+ export const GET = genesispay.gate({ amountUsdc: "0.02" }).wrap(
55
55
  async () => Response.json({ data: "the good stuff" }),
56
56
  );
57
57
  ```
@@ -63,7 +63,7 @@ amount is therefore a decimal dollar string — never a number:
63
63
 
64
64
  ```ts
65
65
  // 5 USDC / 5 dollars:
66
- await peerpay.checkout.create({ title: "Credits", amount: "5.00" });
66
+ await genesispay.checkout.create({ title: "Credits", amount: "5.00" });
67
67
  ```
68
68
 
69
69
  Buyers can use EUR or USD in the Privy/MoonPay flow; MoonPay converts the local
@@ -77,7 +77,7 @@ breaks.
77
77
 
78
78
  You may pass both, and the SDK sends both on the wire so a backend older than
79
79
  0.6.0 still finds the field it knows. Passing **two different amounts** throws
80
- `PeerPayValidationError` before the request leaves your process — a link for
80
+ `GenesisPayValidationError` before the request leaves your process — a link for
81
81
  money you did not mean must not be created because one field quietly won.
82
82
 
83
83
  `gate({ amountUsdc })` keeps its name deliberately: an x402 gate prices a request
@@ -89,20 +89,20 @@ Prefer a webhook over polling. Verification is one call — it checks the HMAC i
89
89
  constant time and enforces a replay window on the signature timestamp:
90
90
 
91
91
  ```ts
92
- import { constructEvent, PeerPaySignatureVerificationError } from "@genesis-tech/genesispay-seller";
92
+ import { constructEvent, GenesisPaySignatureVerificationError } from "@genesis-tech/genesispay-seller";
93
93
 
94
94
  export async function POST(request: Request) {
95
95
  const rawBody = await request.text(); // raw — never re-serialize before verifying
96
96
  try {
97
97
  const event = await constructEvent(
98
98
  rawBody,
99
- request.headers.get("PEERPAY-SIGNATURE") ?? "",
100
- process.env.PEERPAY_WEBHOOK_SECRET!,
99
+ request.headers.get("GENESISPAY-SIGNATURE") ?? "",
100
+ process.env.GENESISPAY_WEBHOOK_SECRET!,
101
101
  );
102
102
  if (event.type === "payment.confirmed") await fulfil(event.data);
103
103
  return new Response(null, { status: 204 });
104
104
  } catch (error) {
105
- if (error instanceof PeerPaySignatureVerificationError) {
105
+ if (error instanceof GenesisPaySignatureVerificationError) {
106
106
  return new Response("invalid signature", { status: 400 });
107
107
  }
108
108
  throw error;
@@ -113,7 +113,7 @@ export async function POST(request: Request) {
113
113
  `constructEvent` is **async** — unlike Stripe's synchronous equivalent. It is built
114
114
  on WebCrypto rather than `node:crypto` so the SDK also runs on Edge, Workers and
115
115
  Bun. It is available as a free function (a webhook route rarely has a client in
116
- scope) and as `peerpay.webhooks.constructEvent(…)`. Default replay tolerance is
116
+ scope) and as `genesispay.webhooks.constructEvent(…)`. Default replay tolerance is
117
117
  300 s in both directions; override with `{ toleranceSeconds }`. Multiple `v1=`
118
118
  values in the header are all checked, so you can rotate an endpoint secret without
119
119
  dropping deliveries.
@@ -172,14 +172,14 @@ build a draft, then finalize it. Finalization freezes the billing details and
172
172
  creates exactly one single-use payment link; a draft cannot be paid or emailed.
173
173
 
174
174
  ```ts
175
- const customer = await peerpay.customers.create({
175
+ const customer = await genesispay.customers.create({
176
176
  name: "Ada Lovelace",
177
177
  email: "ada@example.com",
178
178
  companyName: "Analytical Engines Ltd",
179
179
  countryCode: "GB",
180
180
  });
181
181
 
182
- const draft = await peerpay.invoices.create({
182
+ const draft = await genesispay.invoices.create({
183
183
  customerId: customer.publicId,
184
184
  asset: "EURC",
185
185
  dueAt: "2026-08-31T23:59:59.999Z",
@@ -189,8 +189,8 @@ const draft = await peerpay.invoices.create({
189
189
  ],
190
190
  });
191
191
 
192
- const invoice = await peerpay.invoices.finalize(draft.publicId);
193
- await peerpay.invoices.send(invoice.publicId, {
192
+ const invoice = await genesispay.invoices.finalize(draft.publicId);
193
+ await genesispay.invoices.send(invoice.publicId, {
194
194
  idempotencyKey: `invoice-${invoice.publicId}-initial`,
195
195
  });
196
196
 
@@ -212,7 +212,7 @@ customers. You do **not** need your own billing cron — renewals run on our
212
212
  scheduler.
213
213
 
214
214
  ```ts
215
- const plan = await peerpay.plans.create({
215
+ const plan = await genesispay.plans.create({
216
216
  title: "Pro",
217
217
  amountPerPeriod: "9.00",
218
218
  periodDays: 30,
@@ -224,7 +224,7 @@ redirect(plan.checkoutUrl);
224
224
 
225
225
  The customer signs one permit there; after that, renewals are charged without
226
226
  further signatures and emit `subscription.renewed` (or `subscription.past_due`).
227
- `peerpay.mandates.*` exposes the per-payer authorizations underneath —
227
+ `genesispay.mandates.*` exposes the per-payer authorizations underneath —
228
228
  `create`, `retrieve`, `charge` (per-use metering) and `revoke`.
229
229
 
230
230
  There is deliberately **no `mandates.activate`**: activation requires the payer's
@@ -237,7 +237,7 @@ server holding your secret key.
237
237
  let startingAfter: string | undefined;
238
238
  let hasMore = true;
239
239
  while (hasMore) {
240
- const page = await peerpay.mandates.list({
240
+ const page = await genesispay.mandates.list({
241
241
  planId: plan.publicId,
242
242
  status: "active",
243
243
  startingAfter,
@@ -245,7 +245,7 @@ while (hasMore) {
245
245
  const match = page.mandates.find(
246
246
  (m) => m.payerWallet.toLowerCase() === wallet.toLowerCase(),
247
247
  );
248
- if (match) return peerpay.mandates.revoke(match.id);
248
+ if (match) return genesispay.mandates.revoke(match.id);
249
249
  startingAfter = page.mandates.at(-1)?.id;
250
250
  hasMore = page.hasMore;
251
251
  }
@@ -259,21 +259,58 @@ Note: failed renewals are reported as events, but there is no automatic dunning
259
259
  (retry escalation, grace periods) yet — build that on
260
260
  `subscription.past_due` / `mandate.charge_failed` for now.
261
261
 
262
+ ### Products
263
+
264
+ A product is a catalogue entry; its payable instance is **one canonical
265
+ reusable checkout link**, minted idempotently — mint again (even concurrently)
266
+ and you get the same link with `created: false`.
267
+
268
+ ```ts
269
+ const product = await genesispay.products.create({
270
+ name: "Market data report",
271
+ price: "2.00",
272
+ sku: "MDR-1",
273
+ });
274
+
275
+ // Where a paying buyer's entitlement redirects (https only):
276
+ await genesispay.products.update(product.publicId, {
277
+ fulfilmentUrl: "https://your-site.example/download",
278
+ });
279
+
280
+ const { link, created } = await genesispay.products.createPaymentLink(
281
+ product.publicId,
282
+ );
283
+ // Share link.payUrl — every sale of this product settles through it.
284
+ ```
285
+
286
+ A confirmed purchase fires the `product.purchased` webhook. Its payload
287
+ carries the buyer's entitlement — `entitlement.redemptionPath` is a signed,
288
+ expiring redirect (~30 days) to your fulfilment URL, with `gp_*` parameters
289
+ (`gp_entitlement`, `gp_attempt`, `gp_simulated`, …) you can verify server-side
290
+ via `GET /api/v1/entitlements/verify`. Always check `simulated`: test-mode
291
+ purchases deliver end to end, marked, so your handler must not book them as
292
+ revenue.
293
+
294
+ `products.list({ includeArchived: true })`, `products.retrieve`,
295
+ `products.archive` complete the namespace. Archiving stops **new** link mints;
296
+ the existing canonical link stays payable. The price is copied onto the link
297
+ at mint — a later catalogue edit never changes what a buyer already sees.
298
+
262
299
  ### Testing the paid path
263
300
 
264
301
  ```ts
265
- const session = await peerpay.checkout.simulatePayment(publicId);
302
+ const session = await genesispay.checkout.simulatePayment(publicId);
266
303
  // session.paid === true, and the real webhooks have fired.
267
304
  ```
268
305
 
269
- Test keys only — a `pp_sk_live_…` key gets a 403, and on a mainnet deployment the
306
+ Test keys only — a `gp_sk_live_…` key gets a 403, and on a mainnet deployment the
270
307
  endpoint does not exist at all. The resulting attempt has `txHash: null` (no
271
308
  transaction happened) and `simulated: true`; branch on that flag rather than on
272
309
  the missing hash, because a *pending real* attempt has no hash either.
273
310
 
274
311
  ### Redirect parameters are not proof of payment
275
312
 
276
- The hosted checkout appends `?peerpay_link_id=…&peerpay_status=paid` to your
313
+ The hosted checkout appends `?genesispay_link_id=…&genesispay_status=paid` to your
277
314
  `returnUrl`. Those parameters are a **UI hint only** — they are not signed, and a
278
315
  payer can navigate to that URL directly without paying. Fulfil on the
279
316
  `payment.confirmed` webhook or on `checkout.retrieve(publicId).paid`, never on the
@@ -306,14 +343,14 @@ no wallet returns a `402` `payment_not_configured` — the paid handler never ru
306
343
  without a valid destination. It also refuses to advertise a wallet whose network
307
344
  doesn't match the SDK's native-USDC table or the key mode.
308
345
 
309
- The low-level `createPaymentGate` / `peerPaySettlement` primitives below remain
346
+ The low-level `createPaymentGate` / `genesisPaySettlement` primitives below remain
310
347
  available for advanced cases (custom wallet/network per gate, self-hosting).
311
348
 
312
349
  ## Quick start (Next.js route handler)
313
350
 
314
351
  ```ts
315
352
  // app/api/premium/route.ts
316
- import { createPaymentGate, peerPaySettlement } from "@genesis-tech/genesispay-seller";
353
+ import { createPaymentGate, genesisPaySettlement } from "@genesis-tech/genesispay-seller";
317
354
 
318
355
  const gate = createPaymentGate({
319
356
  amountUsdc: "0.10",
@@ -322,9 +359,9 @@ const gate = createPaymentGate({
322
359
  network: "base-sepolia", // or "base" for mainnet
323
360
  });
324
361
 
325
- const verifySettlement = peerPaySettlement({
326
- facilitatorBaseUrl: "https://your-peerpay-instance.example",
327
- apiKey: process.env.PEERPAY_SELLER_KEY!, // pp_sk_...
362
+ const verifySettlement = genesisPaySettlement({
363
+ facilitatorBaseUrl: "https://your-genesispay-instance.example",
364
+ apiKey: process.env.GENESISPAY_SELLER_KEY!, // gp_sk_...
328
365
  });
329
366
 
330
367
  export const GET = gate.wrap(
@@ -350,7 +387,7 @@ export const GET = gate.wrap(
350
387
 
351
388
  ```ts
352
389
  import { Hono } from "hono";
353
- import { createPaymentGate, peerPaySettlement } from "@genesis-tech/genesispay-seller";
390
+ import { createPaymentGate, genesisPaySettlement } from "@genesis-tech/genesispay-seller";
354
391
 
355
392
  const gate = createPaymentGate({
356
393
  amountUsdc: "0.05",
@@ -362,9 +399,9 @@ const gate = createPaymentGate({
362
399
  const gated = gate.wrap(
363
400
  async () => Response.json({ ok: true }),
364
401
  {
365
- verifySettlement: peerPaySettlement({
366
- facilitatorBaseUrl: "https://your-peerpay-instance.example",
367
- apiKey: process.env.PEERPAY_SELLER_KEY!,
402
+ verifySettlement: genesisPaySettlement({
403
+ facilitatorBaseUrl: "https://your-genesispay-instance.example",
404
+ apiKey: process.env.GENESISPAY_SELLER_KEY!,
368
405
  }),
369
406
  },
370
407
  );
@@ -392,12 +429,12 @@ The same wrapped handler drops straight into `Bun.serve({ fetch: gated })`.
392
429
  | `maxTimeoutSeconds` | no | Advertised authorization validity window (default 300). |
393
430
 
394
431
  `gate.wrap(handler, { verifySettlement })` requires a settlement hook. Use the
395
- built-in `peerPaySettlement({ facilitatorBaseUrl, apiKey })`, which POSTs the
432
+ built-in `genesisPaySettlement({ facilitatorBaseUrl, apiKey })`, which POSTs the
396
433
  payment to GenesisPay's facilitator (`/api/v1/facilitator/settle`). GenesisPay
397
434
  broadcasts the EIP-3009 authorization on-chain, verifies the USDC transfer,
398
435
  and returns the receipt. `facilitatorBaseUrl` is optional and defaults to the
399
436
  public development facilitator (`DEFAULT_FACILITATOR_BASE_URL`,
400
- `https://peerpay-app-development.up.railway.app`), so you can omit it in dev —
437
+ `https://dev.genesispay.finance`), so you can omit it in dev —
401
438
  set it explicitly for production. Or supply your own hook:
402
439
 
403
440
  ```ts
@@ -425,5 +462,5 @@ const verifySettlement: VerifySettlement = async ({ payment, requirement }) => {
425
462
  - The gate performs structural validation only (amount, destination, network,
426
463
  validity window). Actual money movement and on-chain verification happen in
427
464
  your `verifySettlement` hook.
428
- - Get a seller API key (`pp_sk_...`) from your GenesisPay dashboard under
465
+ - Get a seller API key (`gp_sk_...`) from your GenesisPay dashboard under
429
466
  Developers.
package/dist/client.d.ts CHANGED
@@ -3,17 +3,18 @@ import type { CustomersResource } from "./customers.js";
3
3
  import type { InvoicesResource } from "./invoices.js";
4
4
  import type { VerifySettlement } from "./payment-gate.js";
5
5
  import type { PlansResource } from "./plans.js";
6
+ import type { ProductsResource } from "./products.js";
6
7
  import type { PaymentGateNetwork } from "./networks.js";
7
- import type { ConstructEventOptions, PeerPayEvent } from "./webhooks.js";
8
+ import type { ConstructEventOptions, GenesisPayEvent } from "./webhooks.js";
8
9
  /**
9
- * The legacy `PeerPay` client — a Stripe-like entry point that owns the seller key and
10
+ * The legacy `GenesisPay` client — a Stripe-like entry point that owns the seller key and
10
11
  * resolves everything else (payout wallet, network, base URL) from it. The key
11
- * prefix decides the mode: `pp_sk_test_…` / legacy `pp_sk_…` → test,
12
- * `pp_sk_live_…` → live. See docs/archive/SELLER_SDK_DX_SPEC.md.
12
+ * prefix decides the mode: `gp_sk_test_…` / legacy `gp_sk_…` → test,
13
+ * `gp_sk_live_…` → live. See docs/archive/SELLER_SDK_DX_SPEC.md.
13
14
  */
14
- export type PeerPayKeyMode = "test" | "live";
15
- export type PeerPayClientOptions = {
16
- /** pp_sk_test_… | pp_sk_live_… | legacy pp_sk_… — the only required field. */
15
+ export type GenesisPayKeyMode = "test" | "live";
16
+ export type GenesisPayClientOptions = {
17
+ /** gp_sk_test_… | gp_sk_live_… | legacy gp_sk_… — the only required field. */
17
18
  apiKey: string;
18
19
  /** Overrides the mode→URL default (self-host / staging). Must be https (http only for localhost). */
19
20
  baseUrl?: string;
@@ -26,7 +27,7 @@ export type PeerPayClientOptions = {
26
27
  };
27
28
  export type SellerConfig = {
28
29
  id: string;
29
- mode: PeerPayKeyMode;
30
+ mode: GenesisPayKeyMode;
30
31
  payTo: string | null;
31
32
  hasPayTo: boolean;
32
33
  network: {
@@ -50,7 +51,7 @@ export type CheckoutAmountInput = {
50
51
  *
51
52
  * Passing the deprecated `amountUsdc` alongside it is allowed only when
52
53
  * both are the same amount; two different values throw
53
- * `PeerPayValidationError` before the request.
54
+ * `GenesisPayValidationError` before the request.
54
55
  */
55
56
  amount: string;
56
57
  /** @deprecated Use `amount` alone. */
@@ -179,9 +180,9 @@ export interface ResolvingGate {
179
180
  /** Warm the seller-config cache (and surface auth/wallet errors) before traffic. */
180
181
  prime(): Promise<void>;
181
182
  }
182
- export { PeerPayConfigError, PeerPayNetworkSafetyError, PeerPayNotFoundError, PeerPayRateLimitError, PeerPayValidationError, type PeerPayValidationIssue, } from "./errors.js";
183
- export declare class PeerPay {
184
- readonly mode: PeerPayKeyMode;
183
+ export { GenesisPayConfigError, GenesisPayNetworkSafetyError, GenesisPayNotFoundError, GenesisPayRateLimitError, GenesisPayValidationError, type GenesisPayValidationIssue, } from "./errors.js";
184
+ export declare class GenesisPay {
185
+ readonly mode: GenesisPayKeyMode;
185
186
  readonly baseUrl: string;
186
187
  readonly checkout: {
187
188
  create(input: CheckoutCreateInput): Promise<CheckoutLink>;
@@ -200,6 +201,8 @@ export declare class PeerPay {
200
201
  };
201
202
  /** Subscription plans and their hosted `checkoutUrl` — see ./plans.ts. */
202
203
  readonly plans: PlansResource;
204
+ /** The merchant's catalogue and its canonical payment links — see ./products.ts. */
205
+ readonly products: ProductsResource;
203
206
  /** Reusable billing contacts for one-off invoices. */
204
207
  readonly customers: CustomersResource;
205
208
  /** Draft, finalize, deliver, and collect one-off invoices. */
@@ -219,7 +222,7 @@ export declare class PeerPay {
219
222
  * and verification needs the endpoint secret rather than the API key.
220
223
  */
221
224
  readonly webhooks: {
222
- constructEvent(rawBody: string | Uint8Array, signatureHeader: string, secret: string, opts?: ConstructEventOptions): Promise<PeerPayEvent>;
225
+ constructEvent(rawBody: string | Uint8Array, signatureHeader: string, secret: string, opts?: ConstructEventOptions): Promise<GenesisPayEvent>;
223
226
  };
224
227
  private readonly apiKey;
225
228
  private readonly fetchFn;
@@ -233,7 +236,7 @@ export declare class PeerPay {
233
236
  * the key, the base URL or `fetch` into them.
234
237
  */
235
238
  private readonly request;
236
- constructor(options: PeerPayClientOptions);
239
+ constructor(options: GenesisPayClientOptions);
237
240
  /**
238
241
  * Resolve (and cache) the seller config from the key. Single-flight so a
239
242
  * cold-start burst shares one request; failures are never cached (the memo is
@@ -266,7 +269,7 @@ export declare class PeerPay {
266
269
  private createCheckout;
267
270
  /**
268
271
  * Fetch the current state of a checkout link. `paid` is the field to poll on;
269
- * an unknown publicId raises `PeerPayNotFoundError`, never a config error.
272
+ * an unknown publicId raises `GenesisPayNotFoundError`, never a config error.
270
273
  */
271
274
  private retrieveCheckout;
272
275
  /** Test-mode only — see the doc on `checkout.simulatePayment`. */