@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.
- package/CHANGELOG.md +69 -38
- package/README.md +77 -40
- package/dist/client.d.ts +18 -15
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +56 -39
- package/dist/client.js.map +1 -1
- package/dist/customers.d.ts +2 -2
- package/dist/customers.d.ts.map +1 -1
- package/dist/customers.js +5 -5
- package/dist/customers.js.map +1 -1
- package/dist/errors.d.ts +9 -9
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +18 -18
- package/dist/errors.js.map +1 -1
- package/dist/genesispay-settlement.d.ts +30 -0
- package/dist/genesispay-settlement.d.ts.map +1 -0
- package/dist/genesispay-settlement.js +103 -0
- package/dist/genesispay-settlement.js.map +1 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/invoices.d.ts +2 -2
- package/dist/invoices.d.ts.map +1 -1
- package/dist/invoices.js +7 -7
- package/dist/invoices.js.map +1 -1
- package/dist/mandate-gate.d.ts +5 -5
- package/dist/mandate-gate.d.ts.map +1 -1
- package/dist/mandate-gate.js +4 -4
- package/dist/mandate-gate.js.map +1 -1
- package/dist/mandates.d.ts +4 -4
- package/dist/mandates.d.ts.map +1 -1
- package/dist/mandates.js +9 -9
- package/dist/mandates.js.map +1 -1
- package/dist/payment-gate.js +1 -1
- package/dist/payment-gate.js.map +1 -1
- package/dist/plans.d.ts +2 -2
- package/dist/plans.d.ts.map +1 -1
- package/dist/plans.js +6 -6
- package/dist/plans.js.map +1 -1
- package/dist/products.d.ts +80 -0
- package/dist/products.d.ts.map +1 -0
- package/dist/products.js +154 -0
- package/dist/products.js.map +1 -0
- package/dist/resource.d.ts +2 -2
- package/dist/resource.d.ts.map +1 -1
- package/dist/resource.js +1 -1
- package/dist/resource.js.map +1 -1
- package/dist/webhooks.d.ts +25 -25
- package/dist/webhooks.d.ts.map +1 -1
- package/dist/webhooks.js +30 -29
- package/dist/webhooks.js.map +1 -1
- 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/
|
|
6
|
-
never published to npm, so the first release
|
|
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
|
-
- **`
|
|
47
|
+
- **`genesispay.customers.*`** — create, list, retrieve, update, and archive
|
|
17
48
|
reusable billing contacts.
|
|
18
|
-
- **`
|
|
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
|
-
(`
|
|
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 `
|
|
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 `
|
|
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
|
-
- **`
|
|
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
|
-
(`
|
|
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
|
-
`
|
|
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
|
-
- **`
|
|
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
|
-
- **`
|
|
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
|
-
- **`
|
|
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: `
|
|
196
|
-
`
|
|
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
|
-
- `
|
|
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 `
|
|
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
|
-
- **`
|
|
206
|
-
and **`
|
|
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 `
|
|
212
|
-
two classes above. If you branch on `
|
|
213
|
-
update that branch — both new classes extend `Error`, not `
|
|
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 `
|
|
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 `?
|
|
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 `
|
|
257
|
-
rather than `
|
|
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 `
|
|
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 `
|
|
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 `?
|
|
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
|
-
- **`
|
|
302
|
-
seller API key. The key prefix (`
|
|
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
|
|
335
|
+
new GenesisPay backend endpoint `GET /api/v1/seller` and cached (single-flight, 5-min
|
|
305
336
|
TTL, failures never cached).
|
|
306
|
-
- `
|
|
337
|
+
- `genesispay.checkout.create({ title, amountUsdc, … })` → hosted `payUrl` (the wallet
|
|
307
338
|
is defaulted server-side from the key).
|
|
308
|
-
- `
|
|
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: `
|
|
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` / `
|
|
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
|
-
`
|
|
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
|
|
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 `
|
|
24
|
+
## Quick start (legacy `GenesisPay` client — recommended)
|
|
25
25
|
|
|
26
|
-
The `
|
|
27
|
-
(`
|
|
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 {
|
|
31
|
+
import { GenesisPay } from "@genesis-tech/genesispay-seller";
|
|
32
32
|
|
|
33
|
-
const
|
|
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
|
|
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
|
|
48
|
+
const session = await genesispay.checkout.retrieve(publicId);
|
|
49
49
|
if (session.paid) fulfil(session.clientReferenceId);
|
|
50
|
-
// An unknown id throws
|
|
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 =
|
|
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
|
|
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
|
-
`
|
|
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,
|
|
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("
|
|
100
|
-
process.env.
|
|
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
|
|
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 `
|
|
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
|
|
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
|
|
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
|
|
193
|
-
await
|
|
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
|
|
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
|
-
`
|
|
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
|
|
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
|
|
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
|
|
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 `
|
|
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 `?
|
|
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` / `
|
|
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,
|
|
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 =
|
|
326
|
-
facilitatorBaseUrl: "https://your-
|
|
327
|
-
apiKey: process.env.
|
|
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,
|
|
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:
|
|
366
|
-
facilitatorBaseUrl: "https://your-
|
|
367
|
-
apiKey: process.env.
|
|
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 `
|
|
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://
|
|
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 (`
|
|
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,
|
|
8
|
+
import type { ConstructEventOptions, GenesisPayEvent } from "./webhooks.js";
|
|
8
9
|
/**
|
|
9
|
-
* The legacy `
|
|
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: `
|
|
12
|
-
* `
|
|
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
|
|
15
|
-
export type
|
|
16
|
-
/**
|
|
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:
|
|
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
|
-
* `
|
|
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 {
|
|
183
|
-
export declare class
|
|
184
|
-
readonly mode:
|
|
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<
|
|
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:
|
|
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 `
|
|
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`. */
|