@genesis-tech/genesispay-seller 0.8.0 → 0.11.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 (57) hide show
  1. package/CHANGELOG.md +96 -38
  2. package/README.md +142 -40
  3. package/dist/client.d.ts +23 -15
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/client.js +73 -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/entitlements.d.ts +19 -0
  12. package/dist/entitlements.d.ts.map +1 -0
  13. package/dist/entitlements.js +30 -0
  14. package/dist/entitlements.js.map +1 -0
  15. package/dist/errors.d.ts +9 -9
  16. package/dist/errors.d.ts.map +1 -1
  17. package/dist/errors.js +18 -18
  18. package/dist/errors.js.map +1 -1
  19. package/dist/genesispay-settlement.d.ts +30 -0
  20. package/dist/genesispay-settlement.d.ts.map +1 -0
  21. package/dist/genesispay-settlement.js +103 -0
  22. package/dist/genesispay-settlement.js.map +1 -0
  23. package/dist/index.d.ts +6 -4
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +4 -4
  26. package/dist/index.js.map +1 -1
  27. package/dist/invoices.d.ts +2 -2
  28. package/dist/invoices.d.ts.map +1 -1
  29. package/dist/invoices.js +7 -7
  30. package/dist/invoices.js.map +1 -1
  31. package/dist/mandate-gate.d.ts +5 -5
  32. package/dist/mandate-gate.d.ts.map +1 -1
  33. package/dist/mandate-gate.js +4 -4
  34. package/dist/mandate-gate.js.map +1 -1
  35. package/dist/mandates.d.ts +4 -4
  36. package/dist/mandates.d.ts.map +1 -1
  37. package/dist/mandates.js +9 -9
  38. package/dist/mandates.js.map +1 -1
  39. package/dist/payment-gate.js +1 -1
  40. package/dist/payment-gate.js.map +1 -1
  41. package/dist/plans.d.ts +2 -2
  42. package/dist/plans.d.ts.map +1 -1
  43. package/dist/plans.js +6 -6
  44. package/dist/plans.js.map +1 -1
  45. package/dist/products.d.ts +128 -0
  46. package/dist/products.d.ts.map +1 -0
  47. package/dist/products.js +287 -0
  48. package/dist/products.js.map +1 -0
  49. package/dist/resource.d.ts +2 -2
  50. package/dist/resource.d.ts.map +1 -1
  51. package/dist/resource.js +1 -1
  52. package/dist/resource.js.map +1 -1
  53. package/dist/webhooks.d.ts +25 -25
  54. package/dist/webhooks.d.ts.map +1 -1
  55. package/dist/webhooks.js +30 -29
  56. package/dist/webhooks.js.map +1 -1
  57. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,10 +1,68 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.11.0 — product-backed x402 resource gates
4
+
5
+ ### Added
6
+
7
+ - **`delivery` on `genesispay.products.create()` and `.update()`**. A product
8
+ can now be a redirect entitlement, a registered HTTP resource gate, or have
9
+ no delivery. The flat `fulfilmentUrl` fields remain compatible with existing
10
+ redirect integrations.
11
+ - **`genesispay.products.gate(productId)`**. `prime()` resolves the registered
12
+ gate product and canonical reusable payment link; `protect(request, handler)`
13
+ performs the x402 challenge/settlement exchange and runs the handler only
14
+ after GenesisPay confirms payment. Use `purchase.payment.attemptId` as the
15
+ idempotency key because confirmed payment retries may run a handler again.
16
+ - **`genesispay.entitlements.verify(entitlementId)`**. This seller-authorized
17
+ hard check is for a redirect product's redemption handler; require a valid,
18
+ non-simulated entitlement for the expected product before granting access.
19
+
20
+ ### Safety
21
+
22
+ - Product gates bind the pending attempt to the registered URL, upper-case HTTP
23
+ method, and a SHA-256 fingerprint of the raw request body. GenesisPay receives
24
+ the fingerprint, not the request data.
25
+ - Gate purchases never mint redirect entitlements. Redirect products retain
26
+ their signed 30-day redemption URLs unchanged.
27
+ - A product gate always derives amount, asset, destination and fees from the
28
+ canonical product link; callers cannot supply those money fields.
29
+
30
+ ## 0.9.0 — BREAKING: every identifier is now `genesispay`
31
+
32
+ The legacy/visible name split is retired. This release renames the
33
+ public surface with **no compatibility window** — the old names are not accepted
34
+ alongside the new ones. Update all of the following at once:
35
+
36
+ - Client and errors renamed: `PeerPay` → `GenesisPay`, `PeerPay*Error` →
37
+ `GenesisPay*Error` (including `PeerPaySignatureVerificationError`).
38
+ - Webhook signature header `PEERPAY-SIGNATURE` → `GENESISPAY-SIGNATURE`.
39
+ **Verification fails silently if you do not update this** — the old header
40
+ simply stops arriving.
41
+ - Seller API keys now use the `gp_sk_` prefix. Keys issued as `pp_sk_` are
42
+ rejected and must be reissued from the dashboard.
43
+ - Environment variables `PEERPAY_*` → `GENESISPAY_*`.
44
+ - Checkout return parameters `peerpay_link_id` / `peerpay_status` →
45
+ `genesispay_link_id` / `genesispay_status`.
46
+ - `genesispay-settlement` module renamed to `genesispay-settlement`.
47
+
48
+ ### Also in this release — a real fix, not a rename
49
+
50
+ `PRODUCTION_FACILITATOR_BASE_URL` pointed at `peerpay-app-production.up.railway.app`,
51
+ a Railway-generated hostname that **never existed** — production has exactly one
52
+ domain. Any live key used via the zero-config path (`new GenesisPay({ apiKey })`
53
+ with no `baseUrl`) therefore resolved to a dead host. Both defaults now use the
54
+ custom domains, which are ours and survive a Railway service rename:
55
+
56
+ - live keys → `https://genesispay.finance`
57
+ - `DEFAULT_FACILITATOR_BASE_URL` → `https://dev.genesispay.finance`
58
+
59
+ Everything else in this release is a rename.
60
+
3
61
  ## Unreleased — renamed to `@genesis-tech/genesispay-seller`
4
62
 
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.
63
+ The legacy `@genesis-tech/genesispay-seller` name was versioned here through 0.8.0
64
+ but never published to npm, so the first npm release carried the product's own
65
+ name (ADR-0044). 0.8.0 was published on 2026-08-07. Every version below describes work done under the old name.
8
66
 
9
67
  ## 0.8.0
10
68
 
@@ -13,9 +71,9 @@ subscription surfaces.
13
71
 
14
72
  ### Added
15
73
 
16
- - **`peerpay.customers.*`** — create, list, retrieve, update, and archive
74
+ - **`genesispay.customers.*`** — create, list, retrieve, update, and archive
17
75
  reusable billing contacts.
18
- - **`peerpay.invoices.*`** — create/update drafts, finalize an immutable invoice,
76
+ - **`genesispay.invoices.*`** — create/update drafts, finalize an immutable invoice,
19
77
  retrieve/list it, send it through the configured email provider, void it, or
20
78
  mark it uncollectible.
21
79
  - Finalized invoice responses include the hosted invoice URL, PDF URL, exact
@@ -40,7 +98,7 @@ Additive and backward compatible; no existing handler changes meaning.
40
98
  ### Added
41
99
 
42
100
  - **`amount` and `asset` on the `payment.confirmed` / `link.paid` payload**
43
- (`PeerPayPaymentEventData["link"]`). `event.data.link.asset` names the
101
+ (`GenesisPayPaymentEventData["link"]`). `event.data.link.asset` names the
44
102
  currency; `event.data.link.amount` is the field whose name matches its value.
45
103
  `amountUsdcMinor` is unchanged — same integer minor units, same name.
46
104
  - **`attempt.chainId` on the same payload** — the chain `attempt.txHash` is on.
@@ -101,14 +159,14 @@ and backward compatible: nothing is removed and no existing call changes meaning
101
159
  ### Behaviour changes
102
160
 
103
161
  - `checkout.create` with **both** `amount` and `amountUsdc` set to *different*
104
- amounts throws `PeerPayValidationError` and never sends the request. Equal
162
+ amounts throws `GenesisPayValidationError` and never sends the request. Equal
105
163
  values are fine — `"5.0"` and `"5.00"` compare as the same money, since the
106
164
  check is on minor units rather than on the strings. The server enforces the
107
165
  same rule (422) for callers that do not use this SDK.
108
166
  - `checkout.create` with **neither** field is still a **compile** error:
109
167
  `CheckoutAmountInput` is a union requiring one of the two names, so the
110
168
  guarantee the required `amountUsdc` property gave is not traded away. It also
111
- throws `PeerPayValidationError` at runtime, for JavaScript callers.
169
+ throws `GenesisPayValidationError` at runtime, for JavaScript callers.
112
170
  - Every create request sends the amount under **both** names. A backend older
113
171
  than 0.6.0 only knows `amountUsdc` and ignores `amount`, so 0.6.0 works
114
172
  unchanged against one.
@@ -132,7 +190,7 @@ real operation.
132
190
 
133
191
  ### Added
134
192
 
135
- - **`peerpay.mandates.list({ planId?, status?, limit?, startingAfter? })`** —
193
+ - **`genesispay.mandates.list({ planId?, status?, limit?, startingAfter? })`** —
136
194
  until now a mandate id arrived only on the `mandate.active` webhook, so a
137
195
  missed delivery meant a customer who wanted to cancel could not be served:
138
196
  `revoke` needs that id and there was no way to look it up. Returns
@@ -143,7 +201,7 @@ real operation.
143
201
  - `planId` takes the plan's **`publicId`**. An unknown or foreign plan returns
144
202
  an empty page rather than a 404, so it cannot be used to probe for plans.
145
203
  - **`subscriptionPlanId` on `Mandate`** and on the mandate webhook payload
146
- (`PeerPayMandateEventData`). Without it a `mandate.active` handler knew *that*
204
+ (`GenesisPayMandateEventData`). Without it a `mandate.active` handler knew *that*
147
205
  someone subscribed but not *to what*. `null` means the mandate was proposed
148
206
  directly rather than through a plan checkout — a valid state, not missing data.
149
207
  The server sets it only in the hosted `/subscribe/:planId` flow, so a mandate
@@ -152,7 +210,7 @@ real operation.
152
210
  ### Behaviour changes
153
211
 
154
212
  - A **400** whose body carries structured `issues` now throws
155
- `PeerPayValidationError` instead of `PeerPayConfigError` — the new list
213
+ `GenesisPayValidationError` instead of `GenesisPayConfigError` — the new list
156
214
  endpoint reports invalid query parameters that way. A 400 *without* `issues`
157
215
  is unchanged.
158
216
 
@@ -168,18 +226,18 @@ Additive except where noted under *Behaviour changes*.
168
226
 
169
227
  ### Added
170
228
 
171
- - **`peerpay.plans.*`** — `create`, `list`, `retrieve`, `archive` against the new
229
+ - **`genesispay.plans.*`** — `create`, `list`, `retrieve`, `archive` against the new
172
230
  `/api/v1/plans`. Subscription plans existed before but were reachable only from
173
231
  the dashboard, which is what actually blocked building subscriptions
174
232
  programmatically. Every plan carries **`checkoutUrl`**, the hosted
175
233
  `/subscribe/:publicId` flow to hand a customer — you no longer assemble it.
176
- - **`peerpay.mandates.*`** — `create`, `retrieve`, `charge`, `revoke`. There is
234
+ - **`genesispay.mandates.*`** — `create`, `retrieve`, `charge`, `revoke`. There is
177
235
  deliberately **no `activate`**: activating a mandate needs the payer's
178
236
  signature, not the seller key, so it belongs in the payer's frontend.
179
237
  `mandates.create` returns `{ mandate, permitTypedData, spender }`, and
180
238
  `permitTypedData` is passed through verbatim — a signature covers those exact
181
239
  bytes, so the SDK must not reshape them.
182
- - **`peerpay.checkout.simulatePayment(publicId, { payerWallet? })`** — mints a
240
+ - **`genesispay.checkout.simulatePayment(publicId, { payerWallet? })`** — mints a
183
241
  confirmed payment in test mode and fires the real `payment.confirmed` /
184
242
  `link.paid` webhooks, so the paid path is testable without testnet USDC and
185
243
  wallet UX. Test keys only (a live key gets 403), and the endpoint does not
@@ -192,29 +250,29 @@ Additive except where noted under *Behaviour changes*.
192
250
  this flag is the only honest signal. Real payments report `false`.
193
251
  - **Typed webhook events.** `constructEvent` now returns a union discriminated on
194
252
  `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
253
+ no cast. Also exported: `GENESISPAY_EVENT_TYPES`, `isKnownGenesisPayEventType`,
254
+ `GenesisPayEventType`, and `GenesisPayAnyEvent`/`GenesisPayUnknownEvent` for handlers
197
255
  that want to model the open world.
198
256
  - An **unknown event type is still accepted**, verified and returned — a newer
199
257
  server must never make an older SDK reject deliveries. Only the signature or
200
258
  a malformed envelope can reject.
201
- - `PeerPayEvent` has no `{ type: string }` fallback member on purpose:
259
+ - `GenesisPayEvent` has no `{ type: string }` fallback member on purpose:
202
260
  TypeScript disables discriminant narrowing for *every* member of a union as
203
- soon as one member's discriminant is a non-literal. Use `PeerPayAnyEvent`
261
+ soon as one member's discriminant is a non-literal. Use `GenesisPayAnyEvent`
204
262
  (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
263
+ - **`GenesisPayValidationError`** (422, with structured `issues: [{path, message}]`)
264
+ and **`GenesisPayRateLimitError`** (429, with `retryAfterSeconds` from
207
265
  `Retry-After`).
208
266
 
209
267
  ### Behaviour changes
210
268
 
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`.
269
+ - A 422 or 429 previously surfaced as `GenesisPayConfigError`. They now throw the
270
+ two classes above. If you branch on `GenesisPayConfigError` to catch bad input,
271
+ update that branch — both new classes extend `Error`, not `GenesisPayConfigError`.
214
272
  - **`expectedPayTo` now also covers created objects.** It previously guarded only
215
273
  the `/api/v1/seller` config lookup, while `checkout.create` and `plans.create`
216
274
  each freeze their own `destinationWallet` — the addresses money actually moves
217
- to. Both now throw `PeerPayNetworkSafetyError` on a mismatch. If you set the
275
+ to. Both now throw `GenesisPayNetworkSafetyError` on a mismatch. If you set the
218
276
  pin and create links for a wallet other than the pinned one, those calls will
219
277
  start failing (which is the point).
220
278
  - **An unknown `plan.status` now degrades to `archived`, not `active`.** A status
@@ -247,21 +305,21 @@ call signature changes.
247
305
  `publicId` back to a buyer.
248
306
  - **Return / cancel URLs** — `checkout.create` accepts `returnUrl` and `cancelUrl`.
249
307
  After a confirmed payment the hosted checkout shows an explicit "back to …"
250
- button with `?peerpay_link_id=<publicId>&peerpay_status=paid` appended (existing
308
+ button with `?genesispay_link_id=<publicId>&genesispay_status=paid` appended (existing
251
309
  query parameters on your URL are preserved). Deliberately not an automatic
252
310
  timed redirect: the payer should be able to see the on-chain confirmation before
253
311
  leaving. Both must be `https` (`http` is accepted for localhost only).
254
312
  - **`checkout.retrieve(publicId)`** — reads a link's current state, including a
255
313
  `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
314
+ against `/api/v1/links/:publicId`. A 404 throws the new `GenesisPayNotFoundError`
315
+ rather than `GenesisPayConfigError`, so an unknown id is distinguishable from a
258
316
  broken key or an unreachable backend.
259
317
  - **`constructEvent(rawBody, signatureHeader, secret, opts?)`** — webhook signature
260
- verification, exported as a free function and as `peerpay.webhooks.constructEvent`.
318
+ verification, exported as a free function and as `genesispay.webhooks.constructEvent`.
261
319
  Constant-time comparison, a replay window on the signature timestamp (default
262
320
  300 s, rejecting future timestamps as well as stale ones), and support for
263
321
  multiple `v1=` values so an endpoint secret can be rotated without dropped
264
- deliveries. Throws `PeerPaySignatureVerificationError`; no error message
322
+ deliveries. Throws `GenesisPaySignatureVerificationError`; no error message
265
323
  contains the secret or the expected signature.
266
324
 
267
325
  ### Note on `constructEvent` being async
@@ -273,7 +331,7 @@ Workers and Bun. Remember the `await`.
273
331
  ### Gotchas worth knowing before you upgrade
274
332
 
275
333
  - `constructEvent` returns a **Promise** (see above).
276
- - The `?peerpay_link_id=…&peerpay_status=paid` parameters appended to your
334
+ - The `?genesispay_link_id=…&genesispay_status=paid` parameters appended to your
277
335
  `returnUrl` are a UI hint, **not** proof of payment — they are unsigned and a
278
336
  payer can navigate to that URL without paying. Fulfil on the webhook or on
279
337
  `checkout.retrieve().paid`.
@@ -298,20 +356,20 @@ Workers and Bun. Remember the `await`.
298
356
 
299
357
  ### Added
300
358
 
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
359
+ - **`GenesisPay` client** — a Stripe-like entry point that configures from just the
360
+ seller API key. The key prefix (`gp_sk_test_…` / `gp_sk_live_…`) determines the
303
361
  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
362
+ new GenesisPay backend endpoint `GET /api/v1/seller` and cached (single-flight, 5-min
305
363
  TTL, failures never cached).
306
- - `peerpay.checkout.create({ title, amountUsdc, … })` → hosted `payUrl` (the wallet
364
+ - `genesispay.checkout.create({ title, amountUsdc, … })` → hosted `payUrl` (the wallet
307
365
  is defaulted server-side from the key).
308
- - `peerpay.gate({ amountUsdc }).wrap(handler)` → x402 gate with `payTo` + network
366
+ - `genesispay.gate({ amountUsdc }).wrap(handler)` → x402 gate with `payTo` + network
309
367
  resolved from the key and settlement auto-wired.
310
368
  - Money-safety, all fail-closed: no `402` with a null destination; `503` when the
311
369
  backend is unreachable (the paid handler never runs); asset-integrity check
312
370
  (backend USDC/chainId must match the SDK's native table); mode↔network check (a
313
371
  `live` key must not resolve to a testnet); optional `expectedPayTo` pin.
314
- - Exports: `PeerPay`, `PeerPayConfigError`, `PeerPayNetworkSafetyError`, and the
372
+ - Exports: `GenesisPay`, `GenesisPayConfigError`, `GenesisPayNetworkSafetyError`, and the
315
373
  related types.
316
374
 
317
375
  ### Requirements
@@ -321,10 +379,10 @@ Workers and Bun. Remember the `await`.
321
379
 
322
380
  ### Unchanged
323
381
 
324
- - The low-level `createPaymentGate` / `peerPaySettlement` primitives are untouched and
382
+ - The low-level `createPaymentGate` / `genesisPaySettlement` primitives are untouched and
325
383
  remain exported. This release is purely additive.
326
384
 
327
385
  ## 0.1.0
328
386
 
329
387
  - Initial release: framework-agnostic x402 payment gate (`createPaymentGate`,
330
- `peerPaySettlement`).
388
+ `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,123 @@ 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
+ // A redirect product creates a signed, expiring entitlement on every sale.
274
+ delivery: {
275
+ type: "redirect",
276
+ url: "https://your-site.example/download",
277
+ verifiedAt: null,
278
+ },
279
+ });
280
+
281
+ const { link, created } = await genesispay.products.createPaymentLink(
282
+ product.publicId,
283
+ );
284
+ // Share link.payUrl — every sale of this product settles through it.
285
+ ```
286
+
287
+ A confirmed purchase fires the `product.purchased` webhook. Its payload
288
+ carries the buyer's entitlement — `entitlement.redemptionPath` is a signed,
289
+ expiring redirect (~30 days) to your fulfilment URL, with `gp_*` parameters
290
+ (`gp_entitlement`, `gp_attempt`, `gp_simulated`, …) you can verify server-side
291
+ via `GET /api/v1/entitlements/verify`. Always check `simulated`: test-mode
292
+ purchases deliver end to end, marked, so your handler must not book them as
293
+ revenue.
294
+
295
+ Use the SDK rather than trusting `gp_*` query parameters from the browser:
296
+
297
+ ```ts
298
+ const verified = await genesispay.entitlements.verify(entitlementId);
299
+ if (
300
+ !verified.valid ||
301
+ verified.entitlement.simulated ||
302
+ verified.entitlement.productId !== product.publicId
303
+ ) {
304
+ throw new Error("invalid entitlement");
305
+ }
306
+ // Insert verified.entitlement.publicId under a unique constraint, then fulfil.
307
+ ```
308
+
309
+ ### Product-backed API gate
310
+
311
+ For an API you operate, define the resource and its price once in the catalogue.
312
+ The product link—not browser input or route code—is the authority for the USDC
313
+ amount, Base network, destination, fee snapshot and product metadata:
314
+
315
+ ```ts
316
+ const forecast = await genesispay.products.create({
317
+ name: "Forecast API call",
318
+ price: "0.02",
319
+ sku: "prediction-forecast-v1",
320
+ delivery: {
321
+ type: "gate",
322
+ method: "POST",
323
+ resourceUrl: "https://predictionengine.xyz/api/v1/forecast",
324
+ },
325
+ });
326
+
327
+ await genesispay.products.gate(forecast.publicId).prime();
328
+ ```
329
+
330
+ Protect the route with that product. Validate request shape before calling
331
+ `protect`, and make handler effects idempotent by `purchase.payment.attemptId`:
332
+
333
+ ```ts
334
+ const forecastGate = genesispay.products.gate("prod_...");
335
+
336
+ export async function POST(request: Request) {
337
+ const rawBody = await request.clone().text();
338
+ validateForecastJson(rawBody); // invalid requests never create a payment attempt
339
+
340
+ return forecastGate.protect(request, async (_request, purchase) => {
341
+ const cached = await readForecast(purchase.payment.attemptId);
342
+ if (cached) return Response.json(cached);
343
+
344
+ const result = await runForecast(rawBody);
345
+ await saveForecastOnce(purchase.payment.attemptId, result);
346
+ return Response.json(result);
347
+ });
348
+ }
349
+ ```
350
+
351
+ On the initial request, `protect` returns the standard `402 Payment Required`.
352
+ GenesisPay creates a pending attempt before that response and advertises a
353
+ reserved `gp_attempt` value in the x402 resource URL. On the signed retry, the
354
+ SDK sends only the request fingerprint to GenesisPay; it never sends forecast
355
+ inputs. A confirmed replay returns the same payment receipt with
356
+ `idempotentReplay: true`; handlers may therefore execute more than once and
357
+ must keep their own result cache.
358
+
359
+ `products.list({ includeArchived: true })`, `products.retrieve`,
360
+ `products.archive` complete the namespace. Archiving stops **new** link mints;
361
+ the existing canonical link stays payable. The price is copied onto the link
362
+ at mint — a later catalogue edit never changes what a buyer already sees.
363
+
262
364
  ### Testing the paid path
263
365
 
264
366
  ```ts
265
- const session = await peerpay.checkout.simulatePayment(publicId);
367
+ const session = await genesispay.checkout.simulatePayment(publicId);
266
368
  // session.paid === true, and the real webhooks have fired.
267
369
  ```
268
370
 
269
- Test keys only — a `pp_sk_live_…` key gets a 403, and on a mainnet deployment the
371
+ Test keys only — a `gp_sk_live_…` key gets a 403, and on a mainnet deployment the
270
372
  endpoint does not exist at all. The resulting attempt has `txHash: null` (no
271
373
  transaction happened) and `simulated: true`; branch on that flag rather than on
272
374
  the missing hash, because a *pending real* attempt has no hash either.
273
375
 
274
376
  ### Redirect parameters are not proof of payment
275
377
 
276
- The hosted checkout appends `?peerpay_link_id=…&peerpay_status=paid` to your
378
+ The hosted checkout appends `?genesispay_link_id=…&genesispay_status=paid` to your
277
379
  `returnUrl`. Those parameters are a **UI hint only** — they are not signed, and a
278
380
  payer can navigate to that URL directly without paying. Fulfil on the
279
381
  `payment.confirmed` webhook or on `checkout.retrieve(publicId).paid`, never on the
@@ -306,14 +408,14 @@ no wallet returns a `402` `payment_not_configured` — the paid handler never ru
306
408
  without a valid destination. It also refuses to advertise a wallet whose network
307
409
  doesn't match the SDK's native-USDC table or the key mode.
308
410
 
309
- The low-level `createPaymentGate` / `peerPaySettlement` primitives below remain
411
+ The low-level `createPaymentGate` / `genesisPaySettlement` primitives below remain
310
412
  available for advanced cases (custom wallet/network per gate, self-hosting).
311
413
 
312
414
  ## Quick start (Next.js route handler)
313
415
 
314
416
  ```ts
315
417
  // app/api/premium/route.ts
316
- import { createPaymentGate, peerPaySettlement } from "@genesis-tech/genesispay-seller";
418
+ import { createPaymentGate, genesisPaySettlement } from "@genesis-tech/genesispay-seller";
317
419
 
318
420
  const gate = createPaymentGate({
319
421
  amountUsdc: "0.10",
@@ -322,9 +424,9 @@ const gate = createPaymentGate({
322
424
  network: "base-sepolia", // or "base" for mainnet
323
425
  });
324
426
 
325
- const verifySettlement = peerPaySettlement({
326
- facilitatorBaseUrl: "https://your-peerpay-instance.example",
327
- apiKey: process.env.PEERPAY_SELLER_KEY!, // pp_sk_...
427
+ const verifySettlement = genesisPaySettlement({
428
+ facilitatorBaseUrl: "https://your-genesispay-instance.example",
429
+ apiKey: process.env.GENESISPAY_SELLER_KEY!, // gp_sk_...
328
430
  });
329
431
 
330
432
  export const GET = gate.wrap(
@@ -350,7 +452,7 @@ export const GET = gate.wrap(
350
452
 
351
453
  ```ts
352
454
  import { Hono } from "hono";
353
- import { createPaymentGate, peerPaySettlement } from "@genesis-tech/genesispay-seller";
455
+ import { createPaymentGate, genesisPaySettlement } from "@genesis-tech/genesispay-seller";
354
456
 
355
457
  const gate = createPaymentGate({
356
458
  amountUsdc: "0.05",
@@ -362,9 +464,9 @@ const gate = createPaymentGate({
362
464
  const gated = gate.wrap(
363
465
  async () => Response.json({ ok: true }),
364
466
  {
365
- verifySettlement: peerPaySettlement({
366
- facilitatorBaseUrl: "https://your-peerpay-instance.example",
367
- apiKey: process.env.PEERPAY_SELLER_KEY!,
467
+ verifySettlement: genesisPaySettlement({
468
+ facilitatorBaseUrl: "https://your-genesispay-instance.example",
469
+ apiKey: process.env.GENESISPAY_SELLER_KEY!,
368
470
  }),
369
471
  },
370
472
  );
@@ -392,12 +494,12 @@ The same wrapped handler drops straight into `Bun.serve({ fetch: gated })`.
392
494
  | `maxTimeoutSeconds` | no | Advertised authorization validity window (default 300). |
393
495
 
394
496
  `gate.wrap(handler, { verifySettlement })` requires a settlement hook. Use the
395
- built-in `peerPaySettlement({ facilitatorBaseUrl, apiKey })`, which POSTs the
497
+ built-in `genesisPaySettlement({ facilitatorBaseUrl, apiKey })`, which POSTs the
396
498
  payment to GenesisPay's facilitator (`/api/v1/facilitator/settle`). GenesisPay
397
499
  broadcasts the EIP-3009 authorization on-chain, verifies the USDC transfer,
398
500
  and returns the receipt. `facilitatorBaseUrl` is optional and defaults to the
399
501
  public development facilitator (`DEFAULT_FACILITATOR_BASE_URL`,
400
- `https://peerpay-app-development.up.railway.app`), so you can omit it in dev —
502
+ `https://dev.genesispay.finance`), so you can omit it in dev —
401
503
  set it explicitly for production. Or supply your own hook:
402
504
 
403
505
  ```ts
@@ -425,5 +527,5 @@ const verifySettlement: VerifySettlement = async ({ payment, requirement }) => {
425
527
  - The gate performs structural validation only (amount, destination, network,
426
528
  validity window). Actual money movement and on-chain verification happen in
427
529
  your `verifySettlement` hook.
428
- - Get a seller API key (`pp_sk_...`) from your GenesisPay dashboard under
530
+ - Get a seller API key (`gp_sk_...`) from your GenesisPay dashboard under
429
531
  Developers.