@genesis-tech/genesispay-seller 0.8.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 (60) hide show
  1. package/CHANGELOG.md +330 -0
  2. package/LICENSE +21 -0
  3. package/README.md +429 -0
  4. package/dist/client.d.ts +275 -0
  5. package/dist/client.d.ts.map +1 -0
  6. package/dist/client.js +541 -0
  7. package/dist/client.js.map +1 -0
  8. package/dist/customers.d.ts +41 -0
  9. package/dist/customers.d.ts.map +1 -0
  10. package/dist/customers.js +76 -0
  11. package/dist/customers.js.map +1 -0
  12. package/dist/errors.d.ts +63 -0
  13. package/dist/errors.d.ts.map +1 -0
  14. package/dist/errors.js +162 -0
  15. package/dist/errors.js.map +1 -0
  16. package/dist/index.d.ts +13 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +13 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/invoices.d.ts +80 -0
  21. package/dist/invoices.d.ts.map +1 -0
  22. package/dist/invoices.js +133 -0
  23. package/dist/invoices.js.map +1 -0
  24. package/dist/mandate-gate.d.ts +49 -0
  25. package/dist/mandate-gate.d.ts.map +1 -0
  26. package/dist/mandate-gate.js +109 -0
  27. package/dist/mandate-gate.js.map +1 -0
  28. package/dist/mandates.d.ts +172 -0
  29. package/dist/mandates.d.ts.map +1 -0
  30. package/dist/mandates.js +213 -0
  31. package/dist/mandates.js.map +1 -0
  32. package/dist/networks.d.ts +10 -0
  33. package/dist/networks.d.ts.map +1 -0
  34. package/dist/networks.js +22 -0
  35. package/dist/networks.js.map +1 -0
  36. package/dist/payment-gate.d.ts +67 -0
  37. package/dist/payment-gate.d.ts.map +1 -0
  38. package/dist/payment-gate.js +106 -0
  39. package/dist/payment-gate.js.map +1 -0
  40. package/dist/peerpay-settlement.d.ts +27 -0
  41. package/dist/peerpay-settlement.d.ts.map +1 -0
  42. package/dist/peerpay-settlement.js +100 -0
  43. package/dist/peerpay-settlement.js.map +1 -0
  44. package/dist/plans.d.ts +68 -0
  45. package/dist/plans.d.ts.map +1 -0
  46. package/dist/plans.js +127 -0
  47. package/dist/plans.js.map +1 -0
  48. package/dist/resource.d.ts +47 -0
  49. package/dist/resource.d.ts.map +1 -0
  50. package/dist/resource.js +71 -0
  51. package/dist/resource.js.map +1 -0
  52. package/dist/usdc-amount.d.ts +7 -0
  53. package/dist/usdc-amount.d.ts.map +1 -0
  54. package/dist/usdc-amount.js +21 -0
  55. package/dist/usdc-amount.js.map +1 -0
  56. package/dist/webhooks.d.ts +277 -0
  57. package/dist/webhooks.d.ts.map +1 -0
  58. package/dist/webhooks.js +223 -0
  59. package/dist/webhooks.js.map +1 -0
  60. package/package.json +58 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,330 @@
1
+ # Changelog
2
+
3
+ ## Unreleased — renamed to `@genesis-tech/genesispay-seller`
4
+
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.
8
+
9
+ ## 0.8.0
10
+
11
+ Adds one-off commercial invoicing without changing the existing checkout or
12
+ subscription surfaces.
13
+
14
+ ### Added
15
+
16
+ - **`peerpay.customers.*`** — create, list, retrieve, update, and archive
17
+ reusable billing contacts.
18
+ - **`peerpay.invoices.*`** — create/update drafts, finalize an immutable invoice,
19
+ retrieve/list it, send it through the configured email provider, void it, or
20
+ mark it uncollectible.
21
+ - Finalized invoice responses include the hosted invoice URL, PDF URL, exact
22
+ payment URL, stablecoin asset, integer minor-unit totals, and verified payment
23
+ receipt fields.
24
+ - `invoices.send(publicId, { idempotencyKey })` requires a caller-supplied key so
25
+ transport retries cannot create duplicate email sends.
26
+
27
+ ### Safety
28
+
29
+ - A draft is not payable. Finalization creates one single-use payment link and
30
+ freezes the customer, line items, totals, asset, chain, and destination.
31
+ - `paid` is reported only from a confirmed, non-simulated GenesisPay payment.
32
+
33
+ ## 0.7.0
34
+
35
+ Carries the 0.6.0 amount rename one surface further: onto the **webhook
36
+ payload**. Same defect, same shape of fix — a handler settling an EURC payment
37
+ read `amountUsdc` and had nothing in the payload telling it those were euros.
38
+ Additive and backward compatible; no existing handler changes meaning.
39
+
40
+ ### Added
41
+
42
+ - **`amount` and `asset` on the `payment.confirmed` / `link.paid` payload**
43
+ (`PeerPayPaymentEventData["link"]`). `event.data.link.asset` names the
44
+ currency; `event.data.link.amount` is the field whose name matches its value.
45
+ `amountUsdcMinor` is unchanged — same integer minor units, same name.
46
+ - **`attempt.chainId` on the same payload** — the chain `attempt.txHash` is on.
47
+ Without it the hash cannot be resolved and a testnet delivery is byte-identical
48
+ to a mainnet one. `8453`/`84532` (Base mainnet/Sepolia) for a crypto
49
+ settlement, but the set is **open**: a pay-by-bank settlement reports the
50
+ provider's chain, which can be Gnosis, Polygon or Ethereum — a
51
+ `chainId !== 8453` test would book a real Gnosis settlement as test money.
52
+ It sits on the attempt rather than the link because the two can differ: a
53
+ pay-by-bank settlement mints on whatever chain the provider profile uses.
54
+ `chainId` and `txHash` are `null` together when there is nothing to resolve.
55
+ Optional in the type for the same reason `asset` is — but note the polarity
56
+ trap: `undefined !== 8453` is `true`, so branch on presence before comparing.
57
+
58
+ ### Deprecated
59
+
60
+ - **`amountUsdc` on the payment webhook payload.** Still sent on every delivery
61
+ with the same value, only marked `@deprecated`. Removed no earlier than 1.0.
62
+
63
+ ### Requirements
64
+
65
+ - A backend carrying the matching server change; older deliveries are handled
66
+ below.
67
+ - The webhook payload is **not** re-validated at runtime — `constructEvent`
68
+ verifies the signature and the envelope, then hands the body over under the
69
+ declared type. A delivery enqueued before this release is stored and retried
70
+ verbatim, so it can still arrive without `amount`/`asset` while its retry
71
+ window is open. `amount ?? amountUsdc` covers the first; the second is typed
72
+ **optional** precisely because it has no safe fallback — defaulting a missing
73
+ `asset` to `"USDC"` would book a euro payment as dollars, which is the bug
74
+ this release exists to remove. Ack such a delivery, alert, and resolve the
75
+ link through `checkout.retrieve` instead.
76
+
77
+ ## 0.6.0
78
+
79
+ Renames the checkout amount field. `amountUsdc` carried the **EUR** amount on a
80
+ link created with `asset: "EURC"` — the field name asserted a currency the value
81
+ did not have, which an integrator reported as a currency-confusion risk. Additive
82
+ and backward compatible: nothing is removed and no existing call changes meaning.
83
+
84
+ ### Added
85
+
86
+ - **`amount` on `checkout.create`** — the decimal amount in the link's `asset`.
87
+ Dollars under the default `asset: "USDC"`, euros under `asset: "EURC"`. The
88
+ currency comes from `asset`; the field name no longer claims one.
89
+ - **`amount` and `asset` on `CheckoutLink`** — `create()` now echoes the
90
+ settlement currency back, so an EURC link is confirmable without a second
91
+ `retrieve()`. `asset` moved up from `CheckoutSession`, which still has it.
92
+ - **`amount` on `POST /api/v1/links` and on every link response**, next to the
93
+ unchanged `amountUsdc` / `amountUsdcMinor`.
94
+
95
+ ### Deprecated
96
+
97
+ - **`amountUsdc` on `checkout.create` and on `CheckoutLink`/`CheckoutSession`.**
98
+ Still accepted, still returned, still carrying the same value — only marked
99
+ `@deprecated`. It is removed no earlier than 1.0.
100
+
101
+ ### Behaviour changes
102
+
103
+ - `checkout.create` with **both** `amount` and `amountUsdc` set to *different*
104
+ amounts throws `PeerPayValidationError` and never sends the request. Equal
105
+ values are fine — `"5.0"` and `"5.00"` compare as the same money, since the
106
+ check is on minor units rather than on the strings. The server enforces the
107
+ same rule (422) for callers that do not use this SDK.
108
+ - `checkout.create` with **neither** field is still a **compile** error:
109
+ `CheckoutAmountInput` is a union requiring one of the two names, so the
110
+ guarantee the required `amountUsdc` property gave is not traded away. It also
111
+ throws `PeerPayValidationError` at runtime, for JavaScript callers.
112
+ - Every create request sends the amount under **both** names. A backend older
113
+ than 0.6.0 only knows `amountUsdc` and ignores `amount`, so 0.6.0 works
114
+ unchanged against one.
115
+
116
+ ### Unchanged on purpose
117
+
118
+ - `gate({ amountUsdc })` keeps its name. An x402 gate prices a request in USDC on
119
+ Base, so the currency genuinely belongs in the field.
120
+ - The `amountUsdcMinor` field and the database column behind it are untouched.
121
+
122
+ ### Requirements
123
+
124
+ - None. Works against any backend; `amount` on responses needs a 0.6.0 backend,
125
+ and the SDK falls back to `amountUsdc` when it is absent.
126
+
127
+ ## 0.5.0
128
+
129
+ Closes the two subscription gaps 0.4.0 shipped with. Both only bite *after* the
130
+ sale, which is why they survived happy-path testing — and both were blocking a
131
+ real operation.
132
+
133
+ ### Added
134
+
135
+ - **`peerpay.mandates.list({ planId?, status?, limit?, startingAfter? })`** —
136
+ until now a mandate id arrived only on the `mandate.active` webhook, so a
137
+ missed delivery meant a customer who wanted to cancel could not be served:
138
+ `revoke` needs that id and there was no way to look it up. Returns
139
+ `{ mandates, hasMore }`.
140
+ - Pagination is **keyset**, not offset: `startingAfter` is the id of the last
141
+ mandate on the previous page, ordering is `createdAt DESC, id DESC`. Equal
142
+ timestamps therefore can't drop or duplicate a row across pages.
143
+ - `planId` takes the plan's **`publicId`**. An unknown or foreign plan returns
144
+ an empty page rather than a 404, so it cannot be used to probe for plans.
145
+ - **`subscriptionPlanId` on `Mandate`** and on the mandate webhook payload
146
+ (`PeerPayMandateEventData`). Without it a `mandate.active` handler knew *that*
147
+ someone subscribed but not *to what*. `null` means the mandate was proposed
148
+ directly rather than through a plan checkout — a valid state, not missing data.
149
+ The server sets it only in the hosted `/subscribe/:planId` flow, so a mandate
150
+ cannot be attributed to someone else's plan.
151
+
152
+ ### Behaviour changes
153
+
154
+ - A **400** whose body carries structured `issues` now throws
155
+ `PeerPayValidationError` instead of `PeerPayConfigError` — the new list
156
+ endpoint reports invalid query parameters that way. A 400 *without* `issues`
157
+ is unchanged.
158
+
159
+ ### Requirements
160
+
161
+ - Needs a backend carrying migration `0013`.
162
+
163
+ ## 0.4.0
164
+
165
+ Closes the rest of the 0.2.0 integration report: programmatic subscriptions, a
166
+ test-mode payment simulator, and the typing/error work the 0.3.0 review turned up.
167
+ Additive except where noted under *Behaviour changes*.
168
+
169
+ ### Added
170
+
171
+ - **`peerpay.plans.*`** — `create`, `list`, `retrieve`, `archive` against the new
172
+ `/api/v1/plans`. Subscription plans existed before but were reachable only from
173
+ the dashboard, which is what actually blocked building subscriptions
174
+ programmatically. Every plan carries **`checkoutUrl`**, the hosted
175
+ `/subscribe/:publicId` flow to hand a customer — you no longer assemble it.
176
+ - **`peerpay.mandates.*`** — `create`, `retrieve`, `charge`, `revoke`. There is
177
+ deliberately **no `activate`**: activating a mandate needs the payer's
178
+ signature, not the seller key, so it belongs in the payer's frontend.
179
+ `mandates.create` returns `{ mandate, permitTypedData, spender }`, and
180
+ `permitTypedData` is passed through verbatim — a signature covers those exact
181
+ bytes, so the SDK must not reshape them.
182
+ - **`peerpay.checkout.simulatePayment(publicId, { payerWallet? })`** — mints a
183
+ confirmed payment in test mode and fires the real `payment.confirmed` /
184
+ `link.paid` webhooks, so the paid path is testable without testnet USDC and
185
+ wallet UX. Test keys only (a live key gets 403), and the endpoint does not
186
+ exist at all on a mainnet deployment (404).
187
+ - **`attempts` on `CheckoutSession`** — id, status, `txHash`, `payerWallet`,
188
+ timestamps, `failureReason`. Building a receipt after `paid === true` no longer
189
+ needs a raw fetch.
190
+ - **`simulated` on every attempt** and on `data.attempt` in the webhook payload.
191
+ A simulated attempt has `txHash: null` — but so does a pending real one, so
192
+ this flag is the only honest signal. Real payments report `false`.
193
+ - **Typed webhook events.** `constructEvent` now returns a union discriminated on
194
+ `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
197
+ that want to model the open world.
198
+ - An **unknown event type is still accepted**, verified and returned — a newer
199
+ server must never make an older SDK reject deliveries. Only the signature or
200
+ a malformed envelope can reject.
201
+ - `PeerPayEvent` has no `{ type: string }` fallback member on purpose:
202
+ TypeScript disables discriminant narrowing for *every* member of a union as
203
+ soon as one member's discriminant is a non-literal. Use `PeerPayAnyEvent`
204
+ (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
207
+ `Retry-After`).
208
+
209
+ ### Behaviour changes
210
+
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`.
214
+ - **`expectedPayTo` now also covers created objects.** It previously guarded only
215
+ the `/api/v1/seller` config lookup, while `checkout.create` and `plans.create`
216
+ each freeze their own `destinationWallet` — the addresses money actually moves
217
+ to. Both now throw `PeerPayNetworkSafetyError` on a mismatch. If you set the
218
+ pin and create links for a wallet other than the pinned one, those calls will
219
+ start failing (which is the point).
220
+ - **An unknown `plan.status` now degrades to `archived`, not `active`.** A status
221
+ a given SDK version does not know must not read as "keep sending customers to
222
+ this `checkoutUrl`". Matches mandate status degrading to `pending_permit`.
223
+
224
+ ### Known gaps
225
+
226
+ - There is no `mandates.list()`: mandate ids currently arrive only on the
227
+ `mandate.active` webhook, so persist them — `mandates.revoke(id)` needs one.
228
+ - Mandates carry no plan reference, so "who subscribes to plan X" cannot be
229
+ answered yet. Both are tracked for the next release.
230
+
231
+ ### Requirements
232
+
233
+ - The new endpoints need a backend carrying migrations `0011` and `0012`.
234
+
235
+ ## 0.3.0
236
+
237
+ Closes the integration gaps reported against 0.2.0. Fully additive — no existing
238
+ call signature changes.
239
+
240
+ ### Added
241
+
242
+ - **Checkout correlation** — `checkout.create` accepts `metadata`
243
+ (`Record<string, string>`, max 20 keys, 500 chars per value, 4 KB serialized) and
244
+ `clientReferenceId` (max 200 chars). Both are echoed by `checkout.retrieve()` and
245
+ included in the `payment.confirmed` / `link.paid` webhook payloads, so a link
246
+ carries your own identifiers and you no longer need a side table to map a
247
+ `publicId` back to a buyer.
248
+ - **Return / cancel URLs** — `checkout.create` accepts `returnUrl` and `cancelUrl`.
249
+ After a confirmed payment the hosted checkout shows an explicit "back to …"
250
+ button with `?peerpay_link_id=<publicId>&peerpay_status=paid` appended (existing
251
+ query parameters on your URL are preserved). Deliberately not an automatic
252
+ timed redirect: the payer should be able to see the on-chain confirmation before
253
+ leaving. Both must be `https` (`http` is accepted for localhost only).
254
+ - **`checkout.retrieve(publicId)`** — reads a link's current state, including a
255
+ `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
258
+ broken key or an unreachable backend.
259
+ - **`constructEvent(rawBody, signatureHeader, secret, opts?)`** — webhook signature
260
+ verification, exported as a free function and as `peerpay.webhooks.constructEvent`.
261
+ Constant-time comparison, a replay window on the signature timestamp (default
262
+ 300 s, rejecting future timestamps as well as stale ones), and support for
263
+ multiple `v1=` values so an endpoint secret can be rotated without dropped
264
+ deliveries. Throws `PeerPaySignatureVerificationError`; no error message
265
+ contains the secret or the expected signature.
266
+
267
+ ### Note on `constructEvent` being async
268
+
269
+ Stripe's equivalent is synchronous. Ours is not: it is implemented on WebCrypto
270
+ instead of `node:crypto` so the SDK keeps working on Edge runtimes, Cloudflare
271
+ Workers and Bun. Remember the `await`.
272
+
273
+ ### Gotchas worth knowing before you upgrade
274
+
275
+ - `constructEvent` returns a **Promise** (see above).
276
+ - The `?peerpay_link_id=…&peerpay_status=paid` parameters appended to your
277
+ `returnUrl` are a UI hint, **not** proof of payment — they are unsigned and a
278
+ payer can navigate to that URL without paying. Fulfil on the webhook or on
279
+ `checkout.retrieve().paid`.
280
+ - Deduplicate webhook *retries* on `event.id` (stable across the up-to-three
281
+ attempts of one delivery). Note that a single-use link also emits both
282
+ `payment.confirmed` and `link.paid` for one settlement — those are two separate
283
+ deliveries with different ids, so branch on `event.type` and fulfil on one.
284
+ - `session.paid` on a `reusable` link means "ever paid", not "this buyer paid".
285
+
286
+ ### Not included
287
+
288
+ - `checkout.list()` — deferred until `GET /api/v1/links` supports `cursor`/`limit`;
289
+ shipping a method over the current unpaginated endpoint would freeze the wrong
290
+ signature.
291
+
292
+ ### Requirements
293
+
294
+ - The four new checkout fields need a backend carrying migration `0011`. Against an
295
+ older backend they simply come back `null`; nothing throws.
296
+
297
+ ## 0.2.0
298
+
299
+ ### Added
300
+
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
303
+ 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
305
+ TTL, failures never cached).
306
+ - `peerpay.checkout.create({ title, amountUsdc, … })` → hosted `payUrl` (the wallet
307
+ is defaulted server-side from the key).
308
+ - `peerpay.gate({ amountUsdc }).wrap(handler)` → x402 gate with `payTo` + network
309
+ resolved from the key and settlement auto-wired.
310
+ - Money-safety, all fail-closed: no `402` with a null destination; `503` when the
311
+ backend is unreachable (the paid handler never runs); asset-integrity check
312
+ (backend USDC/chainId must match the SDK's native table); mode↔network check (a
313
+ `live` key must not resolve to a testnet); optional `expectedPayTo` pin.
314
+ - Exports: `PeerPay`, `PeerPayConfigError`, `PeerPayNetworkSafetyError`, and the
315
+ related types.
316
+
317
+ ### Requirements
318
+
319
+ - `gate()` requires the backend endpoint `GET /api/v1/seller`. Deploy that before
320
+ upgrading a consumer, or `gate()` returns a clear "could not resolve seller" error.
321
+
322
+ ### Unchanged
323
+
324
+ - The low-level `createPaymentGate` / `peerPaySettlement` primitives are untouched and
325
+ remain exported. This release is purely additive.
326
+
327
+ ## 0.1.0
328
+
329
+ - Initial release: framework-agnostic x402 payment gate (`createPaymentGate`,
330
+ `peerPaySettlement`).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 GenesisTech (GenesisTechAT)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.