@payclave/sdk-server 0.1.1 → 0.3.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 CHANGED
@@ -1,5 +1,42 @@
1
1
  # @payclave/sdk-server
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Added
6
+
7
+ - `checkInvoiceRefund`, which asks Payclave's refund policy whether a refund would be accepted before you send it.
8
+ - `recordInvoiceRefundReference` accepts `destinationAddress` and `destinationReason`, and `externalTransactionHash` is optional (omit it in test mode). Refund adjustments include `verificationStatus`, `destinationAddress`, `destinationSource`, `riskFlags`, `senderAddress`, `verifiedAt`, `verificationFailureReason`, and `failedTransactionHash`.
9
+ - `invoice.refund_verified` and `invoice.refund_failed` webhook event types.
10
+
11
+ ## 0.2.0
12
+
13
+ ### Changed
14
+
15
+ - Requests time out after 30 seconds unless `timeoutMs` is set.
16
+
17
+ ### Added
18
+
19
+ - Add typed rich-invoice draft creation, cursor listing, optimistic update, and issue methods.
20
+ - Expand invoice responses with itemization, customer and seller snapshots, hosted-invoice fields, and checkout-session history.
21
+ - Add exact-remainder recovery checkout, void, void-remaining, uncollectible, duplicate, and external refund-reference invoice methods.
22
+ - Expand rich invoices with verified payment and adjustment timelines and collection-state webhooks.
23
+ - Add typed payment-link create, list, retrieve, update, pause, resume, archive, duplicate, and linked-invoice methods.
24
+ - Support retry-safe idempotency keys on invoice updates, invoice issue, and payment-link mutations.
25
+ - Add invoice issue-email, resend, reminder scheduling, and durable delivery-history types and methods.
26
+ - Add reviewed single-use payment-link reservation release and completion/expiry webhook events.
27
+ - Add authenticated invoice PDF downloads with typed bytes, filename, and request ID.
28
+ - Add typed reconciliation schema and bounded summary readers with date, status, token, and exception filters.
29
+ - Add asynchronous reconciliation export create, list, retrieve, cancel, and authenticated streaming download helpers.
30
+ - Add webhook delivery detail, timed attempt history, and immutable idempotent replay helpers.
31
+ - Add typed webhook error categories, safe request/response evidence, and response-sanitization metadata.
32
+ - Add webhook endpoint list, retrieve, update, archive, signing-secret rotation, overlap-ending, and incident read helpers.
33
+ - Verify dual `v1` signatures during the 24-hour signing-secret overlap.
34
+
35
+ ### Fixed
36
+
37
+ - Add raw-request-body remediation guidance to webhook signature errors.
38
+ - Report the package version correctly in the SDK identification header.
39
+
3
40
  ## 0.1.1
4
41
 
5
42
  ### Fixed
package/README.md CHANGED
@@ -6,12 +6,23 @@ Use this package from your backend with a Payclave secret key. Never expose `sk_
6
6
 
7
7
  Package name: [`@payclave/sdk-server`](https://www.npmjs.com/package/@payclave/sdk-server).
8
8
 
9
+ ## Complete merchant reference
10
+
11
+ See the [TypeScript + Express reference application](https://github.com/ianmasae/Payclave/tree/main/examples/typescript-checkout)
12
+ for a standalone test-mode integration with SQLite, persistent event deduplication,
13
+ durable retries, and idempotent simulated fulfillment. It uses the published SDK.
14
+
15
+ - [Creating checkouts with stable keys](https://github.com/ianmasae/Payclave/blob/main/examples/typescript-checkout/src/checkout/service.ts)
16
+ - [Verifying raw-body webhook signatures and timestamps](https://github.com/ianmasae/Payclave/blob/main/examples/typescript-checkout/src/payclave/webhook.ts)
17
+ - [Matching invoices and fulfilling orders once](https://github.com/ianmasae/Payclave/blob/main/examples/typescript-checkout/src/webhooks/processor.ts)
18
+ - [Retrying durably after failures and restarts](https://github.com/ianmasae/Payclave/blob/main/examples/typescript-checkout/src/webhooks/worker.ts)
19
+
9
20
  ## Install
10
21
 
11
22
  ```bash
12
- npm install @payclave/sdk-server
23
+ npm install @payclave/sdk-server@0.3.0
13
24
  # or
14
- bun add @payclave/sdk-server
25
+ bun add @payclave/sdk-server@0.3.0
15
26
  ```
16
27
 
17
28
  ## Create a checkout session
@@ -51,18 +62,59 @@ export async function POST(request: Request) {
51
62
  | `secretKey` | `string` | `sk_test_...` or `sk_live_...`. Required. |
52
63
  | `apiBaseUrl` | `string` | Override the API base URL. Defaults to `https://api.payclave.com`. |
53
64
  | `fetcher` | `(url, init) => Response` | Custom fetch implementation for tests or custom runtimes. |
54
- | `timeoutMs` | `number` | Per-request timeout. |
65
+ | `timeoutMs` | `number` | Per-request timeout in ms. Defaults to `30000`. |
55
66
 
56
67
  Returns typed helpers:
57
68
 
58
69
  - `createCheckoutSession(input)`
59
70
  - `getCheckoutSession(id)`
60
71
  - `createInvoice(input)`
72
+ - `createInvoiceDraft(input)`
73
+ - `listInvoices(input?)`
61
74
  - `getInvoice(id)`
75
+ - `downloadInvoicePDF(id)`
76
+ - `updateInvoiceDraft(id, input)`
77
+ - `issueInvoice(id, input)`
78
+ - `sendInvoice(id, input?)`
79
+ - `remindInvoice(id, input?)`
80
+ - `createInvoiceCheckoutSession(id, input?)`
81
+ - `voidInvoice(id, input?)`
82
+ - `voidInvoiceRemaining(id, input?)`
83
+ - `markInvoiceUncollectible(id, input?)`
84
+ - `duplicateInvoice(id, input?)`
85
+ - `recordInvoiceRefundReference(id, input)`
86
+ - `createPaymentLink(input)`
87
+ - `listPaymentLinks(input?)`
88
+ - `getPaymentLink(id)`
89
+ - `updatePaymentLink(id, input)`
90
+ - `pausePaymentLink(id, input?)`
91
+ - `resumePaymentLink(id, input?)`
92
+ - `archivePaymentLink(id, input?)`
93
+ - `duplicatePaymentLink(id, input?)`
94
+ - `releasePaymentLinkReservation(id, input)`
95
+ - `listPaymentLinkPayments(id, input?)`
62
96
  - `getPayment(id)`
63
97
  - `createWebhookEndpoint(input)`
98
+ - `listWebhookEndpoints(options?)`
99
+ - `getWebhookEndpoint(id, options?)`
100
+ - `updateWebhookEndpoint(id, input)`
101
+ - `archiveWebhookEndpoint(id, input)`
102
+ - `rotateWebhookSecret(id, input)`
103
+ - `endWebhookSecretOverlap(id, input)`
64
104
  - `createTestWebhook(input)`
65
105
  - `listWebhookDeliveries(input?)`
106
+ - `getWebhookDelivery(id, options?)`
107
+ - `listWebhookDeliveryAttempts(id, options?)`
108
+ - `replayWebhookDelivery(id, input)`
109
+ - `listWebhookIncidents(input?)`
110
+ - `getWebhookIncident(id, options?)`
111
+ - `getReconciliationSchema(version?)`
112
+ - `getReconciliationSummary(input)`
113
+ - `createReconciliationExport(input)`
114
+ - `listReconciliationExports(input?)`
115
+ - `getReconciliationExport(id, options?)`
116
+ - `cancelReconciliationExport(id, input?)`
117
+ - `downloadReconciliationExport(id, options?)`
66
118
 
67
119
  ### Authentication
68
120
 
@@ -84,14 +136,145 @@ This SDK is for backend code only and sends `Authorization: Bearer sk_test_...`
84
136
 
85
137
  ### Invoice input
86
138
 
87
- `createInvoice` accepts `amount`, `externalReference`, `metadata`, `expiresInMinutes`, `idempotencyKey`, and `signal`. Reuse the same `idempotencyKey` only when retrying the same invoice creation request after a timeout, network failure, `RATE_LIMIT_EXCEEDED`, or 5xx response.
139
+ `createInvoice` keeps the amount-only compatibility flow and accepts `amount`, `externalReference`, `metadata`, `expiresInMinutes`, `idempotencyKey`, and `signal`.
140
+
141
+ For itemized invoices, create a draft, then issue the returned version:
142
+
143
+ ```ts
144
+ const draft = await payclave.createInvoiceDraft({
145
+ customer: { name: "Ada Lovelace", email: "ada@example.com" },
146
+ lines: [
147
+ {
148
+ description: "Implementation",
149
+ quantity: "2.5",
150
+ unitAmount: "40",
151
+ },
152
+ ],
153
+ taxes: [{ label: "VAT", amount: "4.75" }],
154
+ dueAt: "2026-09-30T23:59:59Z",
155
+ idempotencyKey: "invoice_order_123",
156
+ })
157
+
158
+ const issued = await payclave.issueInvoice(draft.id, {
159
+ version: draft.version!,
160
+ send: true,
161
+ idempotencyKey: "invoice_order_123_issue",
162
+ })
163
+ ```
164
+
165
+ The server calculates line totals and invoice totals at the settlement token's 6-decimal scale. `updateInvoiceDraft` replaces editable commercial fields and requires the latest `version`; `listInvoices` supports `limit`, `startingAfter`, and `collectionState`. Set `reminderSchedule` on the draft to schedule conservative before-due, due-date, and after-due reminders. Passing `send: true` to `issueInvoice` queues the issue email in the same durable workflow; `sendInvoice` resends it and `remindInvoice` queues a manual payment reminder. Invoice detail responses include immutable delivery history and the next scheduled reminder.
166
+
167
+ For a partially paid invoice, `createInvoiceCheckoutSession` creates or reuses a checkout for the exact server-calculated remainder. Collection actions never move funds: `voidInvoiceRemaining` records the unpaid remainder as a write-off, and `recordInvoiceRefundReference` records a refund you sent, from your settlement wallet or another wallet such as an exchange account; Payclave never sends refunds. Call `checkInvoiceRefund` first: it reports whether the refund is allowed, whether it needs a `destinationReason`, and any warnings, such as a destination that is not the wallet the customer paid from or a refund of an overpayment. Where refund verification is available (always in test mode), the recorded adjustment starts as `verificationStatus: "pending"`, and Payclave verifies the transfer onchain before sending `invoice.refund_verified` or `invoice.refund_failed`. Issued invoice responses include all checkout sessions, independently verified payments, and adjustment records.
168
+
169
+ `downloadInvoicePDF(id)` returns authenticated PDF bytes, a safe filename, and the request ID. The PDF is generated from the API's current server-side invoice snapshot; save or stream the returned bytes from trusted backend code.
170
+
171
+ Reuse the same `idempotencyKey` only when retrying the same mutation after a timeout, network failure, `RATE_LIMIT_EXCEEDED`, or 5xx response.
172
+
173
+ ### Payment links
174
+
175
+ Create a fixed or customer-entered public payment page from trusted server code:
176
+
177
+ ```ts
178
+ const link = await payclave.createPaymentLink({
179
+ title: "Consulting deposit",
180
+ amountType: "fixed",
181
+ amount: "250.00",
182
+ currencyCode: "USDT",
183
+ usageType: "single_use",
184
+ collect: { email: true, reference: true },
185
+ successUrl: "https://merchant.example/orders/paid",
186
+ idempotencyKey: "consulting_deposit_123",
187
+ })
188
+
189
+ console.log(link.url)
190
+ ```
191
+
192
+ Payment links inherit the settlement wallet, Polygon network, and USDC or USDT token selected for the secret key's mode. Amounts use decimal strings with no more than 6 fractional digits. A live `successUrl` or `cancelUrl` must use HTTPS and match the merchant's reviewed website origin.
193
+
194
+ `updatePaymentLink` requires the latest `version`. Lifecycle and duplicate methods accept optional `idempotencyKey` and `signal` fields. `listPaymentLinks` supports cursor pagination plus status, amount type, usage type, currency, text, and UTC creation-time filters. `listPaymentLinkPayments` returns the invoices created from that link; the invoice and its independently verified payment records remain the source of payment truth.
195
+
196
+ Single-use links reserve one invoice atomically. After reviewing the late-payment warning and confirming that no payment evidence remains unresolved, an owner or admin can call `releasePaymentLinkReservation(id, { acknowledgeLatePaymentRisk: true })`. Payclave voids the original invoice before reopening the link so two collectible invoices cannot exist at once.
197
+
198
+ ### Reconciliation
199
+
200
+ Finance integrations can read the versioned data dictionary and a bounded summary without buffering export files:
201
+
202
+ ```ts
203
+ const schema = await payclave.getReconciliationSchema()
204
+ const summary = await payclave.getReconciliationSummary({
205
+ dateBasis: "invoice_created",
206
+ from: "2026-07-01T00:00:00Z",
207
+ to: "2026-08-01T00:00:00Z",
208
+ timezone: "Africa/Nairobi",
209
+ settlementTokens: ["USDT", "USDC"],
210
+ exceptionOnly: false,
211
+ })
212
+
213
+ console.log(schema.schemaVersion, summary.summaryByToken)
214
+ ```
215
+
216
+ The interval is half-open (`from <= timestamp < to`) and may span at most 366 days. Amount totals are decimal strings grouped by chain and token contract; the SDK never adds USDC and USDT together. A summary includes the server cutoff, row counts, warnings, and bounded exception records.
217
+
218
+ For a complete immutable pack, queue an export, poll its status, then consume the authenticated archive as a stream:
219
+
220
+ ```ts
221
+ const queued = await payclave.createReconciliationExport({
222
+ format: "csv",
223
+ dateBasis: "invoice_created",
224
+ from: "2026-07-01T00:00:00Z",
225
+ to: "2026-08-01T00:00:00Z",
226
+ timezone: "Africa/Nairobi",
227
+ settlementTokens: ["USDT", "USDC"],
228
+ idempotencyKey: "reconciliation-2026-07",
229
+ })
230
+
231
+ let current = queued
232
+ while (current.status === "queued" || current.status === "running") {
233
+ await new Promise((resolve) => setTimeout(resolve, 2_000))
234
+ current = await payclave.getReconciliationExport(queued.id)
235
+ }
236
+
237
+ if (current.status === "ready") {
238
+ const archive = await payclave.downloadReconciliationExport(current.id)
239
+ // Pipe archive.stream to durable storage without buffering the whole file.
240
+ console.log(archive.filename, archive.sha256)
241
+ }
242
+ ```
243
+
244
+ The archive contains the versioned manifest, token-separated summary, invoice, payment, onchain transaction, webhook delivery, and adjustment files. Downloads are authorized at request time. Payer-wallet PII is excluded by default; the public secret-key workflow does not bypass the separate elevated-permission and step-up requirement.
88
245
 
89
246
  ### Webhook endpoints
90
247
 
91
- `createWebhookEndpoint` accepts a `url`, optional `eventTypes`, and `signal`. Supported event types are exported as `PAYCLAVE_WEBHOOK_EVENT_TYPES` and currently include `invoice.paid`, `invoice.expired`, `payment.failed`, `payment.underpaid`, and `payment.overpaid`.
248
+ `createWebhookEndpoint` accepts a `url`, optional `eventTypes`, and `signal`. Supported event types are exported as `PAYCLAVE_WEBHOOK_EVENT_TYPES` and include invoice issue, paid, partial, past-due, overpayment, void, uncollectible, refund recorded, verified, and failed, expiry, late payment after collection closure, payment-link completion and expiry, and payment failure events.
249
+
250
+ `getWebhookDelivery` returns the immutable event payload, replay lineage, and complete attempt timeline. Attempts include start/completion timestamps, duration, status, a stable error category, safe request-header evidence, and a sanitized response excerpt capped at 4 KB. `listWebhookDeliveryAttempts` returns that timeline directly. `replayWebhookDelivery` requires an `idempotencyKey`, creates a new delivery linked to the source, and preserves the original event ID and payload. Confirm that your handler deduplicates by event ID before replaying.
251
+
252
+ Failure categories are `dns`, `connection`, `tls`, `timeout`, `blocked_destination`, `http_4xx`, `http_5xx`, `redirect`, `response_too_large`, `signing_secret`, `endpoint_disabled`, and `internal`. Payclave does not follow redirects, and every 2xx response counts as success.
92
253
 
93
254
  Payclave returns the endpoint `signingSecret` once when the endpoint is created. Store it securely and use it to verify incoming webhook deliveries.
94
255
 
256
+ Rotate a secret without interrupting deliveries:
257
+
258
+ ```ts
259
+ const rotated = await payclave.rotateWebhookSecret(endpointId, {
260
+ idempotencyKey: "rotate_webhook_2026_08",
261
+ })
262
+
263
+ // Store rotated.signingSecret immediately; it is returned only once.
264
+ await payclave.createTestWebhook({ endpointId })
265
+
266
+ await payclave.endWebhookSecretOverlap(endpointId, {
267
+ idempotencyKey: "end_webhook_overlap_2026_08",
268
+ })
269
+ ```
270
+
271
+ The previous secret remains valid for at most 24 hours. During that overlap,
272
+ Payclave signs each delivery with the new secret first and the previous secret
273
+ second. A successful test event marks the current secret as tested; Payclave
274
+ sends a reminder six hours before expiry only if no successful test has been
275
+ recorded. Endpoint reads expose version timestamps, never hashes, ciphertext,
276
+ or a previously returned secret.
277
+
95
278
  ## Webhook signatures
96
279
 
97
280
  Verify Payclave webhooks against the exact raw request body:
@@ -117,9 +300,11 @@ export async function POST(request: Request) {
117
300
  }
118
301
  ```
119
302
 
120
- `constructPayclaveWebhookEvent` throws `PayclaveError` when the signature, timestamp, or payload is invalid. Use `verifyPayclaveWebhookSignature` when you only need a boolean.
303
+ `constructPayclaveWebhookEvent` throws `PayclaveError` when the signature, timestamp, or payload is invalid. A signature mismatch links to the [raw-body integration guide](https://www.payclave.com/developers/docs#webhooks). Use `verifyPayclaveWebhookSignature` when you only need a boolean.
304
+
305
+ Webhook deliveries include `X-Payclave-Signature`, `X-Payclave-Timestamp`, `X-Payclave-Delivery`, and `X-Payclave-Event` headers. The normal signature format is `t=<unix_timestamp>,v1=<hex_hmac_sha256>`. During a rotation overlap it is exactly `t=<timestamp>,v1=<new signature>,v1=<old signature>`. The verifier checks every `v1` value in constant time, so it accepts the matching old or new endpoint secret during migration.
121
306
 
122
- Webhook deliveries include `X-Payclave-Signature`, `X-Payclave-Timestamp`, `X-Payclave-Delivery`, and `X-Payclave-Event` headers. The signature format is `t=<unix_timestamp>,v1=<hex_hmac_sha256>`.
307
+ Any 2xx response acknowledges a delivery. After the first four failed attempts, Payclave retries after 1 minute, 5 minutes, 30 minutes, and 2 hours. A fifth failed attempt ends automatic retries.
123
308
 
124
309
  ## Errors
125
310