@tuwaio/quasar-sdk 0.7.3 → 0.9.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/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { C as CreateInvoiceParams, i as CreateOptions, j as InvoiceWithCheckout, k as InvoiceDetails, L as ListInvoiceItemsParams, l as PaymentsList, R as Refund, m as InvoiceDocumentRef, n as ListInvoicesParams, o as Invoice, p as CorrectInvoiceParams, q as InvoiceCorrection, G as GetEventsParams, r as InvoiceLedger, f as RefundParams, g as RefundPreview, e as RefundStart, h as SubmitRefundParams, s as ListHeldTransfersParams, H as HeldTransfer, t as PaymentDocumentKind, u as PaymentDocument, v as QuoteParams, w as PaymentQuote, x as PaymentMethod, y as CreateSubscriptionParams, z as SubscriptionStart, A as ListSubscriptionsParams, D as Subscription, E as ListSubscriptionItemsParams, F as SubscriptionCharge, J as CancelSubscriptionParams, P as PaymentsEnvironment, K as InvoiceSettlement, M as InvoicePayment } from './errors-4kLHVSws.js';
2
+ export { B as Buyer, N as HeldTransferStatus, O as InvoiceAml, U as InvoiceAttempt, V as InvoiceLine, W as InvoiceLineInput, X as InvoiceLinesInput, b as InvoiceLocale, Y as InvoiceQuote, Z as InvoiceSeller, I as InvoiceStatus, _ as InvoiceTermsInput, $ as InvoiceWalletConfirmation, a0 as LedgerEvent, a1 as LedgerSummary, a2 as LedgerVerification, a as PaymentsCurrency, Q as QuasarRequestIssue, a3 as QuasarSDKError, a4 as RefundMode, d as SubscriptionInterval, S as SubscriptionPermission, c as SubscriptionStatus, T as TaxCategory } from './errors-4kLHVSws.js';
1
3
  import { FetchOptions } from 'ofetch';
2
4
  import { Transaction } from '@tuwaio/pulsar-core';
3
5
  export { Transaction, TransactionStatus, TransactionTracker, UpdatableTransactionFields } from '@tuwaio/pulsar-core';
@@ -18,6 +20,8 @@ declare const PULSAR_HISTORY_ENDPOINT = "/v1/engine/pulsar/history";
18
20
  * @returns The path, with the batch ID URL-encoded.
19
21
  */
20
22
  declare const pulsarSyncHashEndpoint: (txKey: string) => string;
23
+ /** Path prefix of the Payments API, which `PaymentsModule` calls. */
24
+ declare const PAYMENTS_ENDPOINT = "/v1/payments";
21
25
 
22
26
  /**
23
27
  * @file Configuration, query and response types of the Quasar client, and the Pulsar transaction types it syncs.
@@ -87,41 +91,9 @@ interface PaginatedResult<T> {
87
91
  }
88
92
 
89
93
  /**
90
- * @file HTTP client of the Quasar SDK: authentication headers, timeout and errors.
94
+ * @file HTTP client of the Quasar SDK: authentication headers and timeout; failures become `QuasarSDKError`.
91
95
  */
92
96
 
93
- /**
94
- * Error thrown by the methods of the {@link Quasar} client when a request fails: an HTTP error status, a timeout or a
95
- * network error.
96
- *
97
- * @example
98
- * ```typescript
99
- * try {
100
- * await quasar.pulsar.getHistory();
101
- * } catch (err) {
102
- * if (err instanceof QuasarSDKError) {
103
- * console.error(err.status); // e.g. 401
104
- * console.error(err.message); // "[Quasar SDK] Request Failed (401): <error of the API>"
105
- * console.error(err.originalError); // FetchError from ofetch
106
- * }
107
- * }
108
- * ```
109
- */
110
- declare class QuasarSDKError extends Error {
111
- /** HTTP status code of the response, or `undefined` when no response was received. */
112
- readonly status: number | undefined;
113
- /** The error thrown by `ofetch`. */
114
- readonly originalError: Error;
115
- /**
116
- * Creates the error.
117
- *
118
- * @param message - Message: `[Quasar SDK] Request Failed (<status>): <error>`, where `<error>` is the `error` field of
119
- * the response body or the message of the fetch error.
120
- * @param status - HTTP status code, or `undefined` when no response was received.
121
- * @param originalError - The error thrown by `ofetch`.
122
- */
123
- constructor(message: string, status: number | undefined, originalError: Error);
124
- }
125
97
  /**
126
98
  * HTTP client shared by the modules of the {@link Quasar} client. Sends every request with the `x-tuwa-secret-key`
127
99
  * header (and `x-internal-secret` when configured) through `ofetch`.
@@ -156,12 +128,387 @@ declare class QuasarClient {
156
128
  */
157
129
  request<T>(path: string, options?: Omit<FetchOptions<'json'>, 'baseURL' | 'timeout'>): Promise<T>;
158
130
  /**
159
- * Wraps an `ofetch` error in a {@link QuasarSDKError}. Side effect: logs a `console.error` on a 401 or 403 status.
131
+ * Downloads a file: its bytes, media type and the file name of its `Content-Disposition` header. Sends the same
132
+ * headers as {@link QuasarClient.request}.
160
133
  *
161
- * @param error - The error thrown by `ofetch`.
162
- * @returns The wrapped error.
134
+ * @param path - Endpoint path.
135
+ * @returns The bytes, `Content-Type` and file name (`null` when the response names none).
136
+ * @throws {QuasarSDKError} On an HTTP error status, a timeout or a network error.
163
137
  */
164
- private buildError;
138
+ requestFile(path: string): Promise<{
139
+ bytes: Uint8Array;
140
+ contentType: string | null;
141
+ filename: string | null;
142
+ }>;
143
+ }
144
+
145
+ /**
146
+ * @file Payments module of the Quasar client: invoices, refunds, documents, quotes, payment methods and
147
+ * subscriptions of a Payments app.
148
+ */
149
+
150
+ /**
151
+ * The Payments API (`/v1/payments`) of a Payments app, available as `quasar.payments` on a {@link Quasar} client.
152
+ * Quasar issues numbered invoices with PDF (and, when on, e-invoice) documents, checks the on-chain payment, records
153
+ * every step in a hash-chained ledger and sends `payments.v1` webhooks; money goes straight to your wallets. The app
154
+ * must be a Payments app, and the Quasar node must run Payments.
155
+ *
156
+ * Every method sends one request with the secret key in the `x-tuwa-secret-key` header.
157
+ *
158
+ * @example
159
+ * ```ts
160
+ * import { Quasar } from '@tuwaio/quasar-sdk';
161
+ *
162
+ * const quasar = new Quasar({ secretKey: process.env.QUASAR_SECRET_KEY ?? '' });
163
+ * const { invoice, payUrl } = await quasar.payments.createInvoice(
164
+ * {
165
+ * currency: 'EUR',
166
+ * lineItems: [{ name: 'Pro plan, October', quantity: '1', unitPrice: '49.00', taxRate: '21' }],
167
+ * buyer: { email: 'buyer@example.com', externalId: 'user_42' },
168
+ * },
169
+ * { idempotencyKey: 'order_42' },
170
+ * );
171
+ * ```
172
+ */
173
+ declare class PaymentsModule {
174
+ private readonly client;
175
+ /**
176
+ * Creates the module. The {@link Quasar} client creates it for you.
177
+ *
178
+ * @param client - The HTTP client of the Quasar client.
179
+ * @internal
180
+ */
181
+ constructor(client: QuasarClient);
182
+ /**
183
+ * Issues an invoice (`POST /v1/payments/invoices`): numbered, with its PDF and, when the app has e-invoices on and
184
+ * the buyer details allow, its UBL; the `invoice.issued` webhook follows.
185
+ *
186
+ * @param params - The lines (or a bare `amount`), the buyer and the terms. Dates go as ISO 8601.
187
+ * @param options - `idempotencyKey`: a retry with the same key returns the invoice issued first, with the same link.
188
+ * @returns The invoice, its checkout token and its payment page (also in the invoice view while it can be paid).
189
+ * @throws {QuasarSDKError} `invalid_request` with `issues` (400), `seller_details_missing` and other issuer
190
+ * refusals (400), an app that is not a Payments app or a node without Payments (403), a timeout or a network
191
+ * error.
192
+ */
193
+ createInvoice(params: CreateInvoiceParams, options?: CreateOptions): Promise<InvoiceWithCheckout>;
194
+ /**
195
+ * Reads an invoice with its five newest refunds, every refund still under way, and how many refunds and credit
196
+ * notes it has (`GET /v1/payments/invoices/:id`). The full lists are {@link PaymentsModule.listInvoiceRefunds} and
197
+ * {@link PaymentsModule.listInvoiceDocuments}. It also names every transfer credited to it (`payments`: an invoice
198
+ * paid in parts has several), the newest transactions that did not pay it (`attempts`, `attemptsTotal`) and what the
199
+ * wallets that confirmed it signed (`walletConfirmations`).
200
+ *
201
+ * @param invoiceId - The invoice.
202
+ * @returns The invoice.
203
+ * @throws {QuasarSDKError} `invoice_not_found` (404), a timeout or a network error.
204
+ */
205
+ getInvoice(invoiceId: string): Promise<InvoiceDetails>;
206
+ /**
207
+ * Lists the refunds of an invoice, newest first (`GET /v1/payments/invoices/:id/refunds`).
208
+ *
209
+ * @param invoiceId - The invoice.
210
+ * @param params - Paging.
211
+ * @returns One page; pass `nextCursor` as `cursor` for the next.
212
+ * @throws {QuasarSDKError} `invoice_not_found` (404), `invalid_request` or `invalid_cursor` (400), a timeout or a
213
+ * network error.
214
+ */
215
+ listInvoiceRefunds(invoiceId: string, params?: ListInvoiceItemsParams): Promise<PaymentsList<Refund>>;
216
+ /**
217
+ * Lists the documents issued for an invoice, newest first (`GET /v1/payments/invoices/:id/documents`): the invoice,
218
+ * its receipt and its credit notes, as PDF and UBL. Download them with {@link PaymentsModule.getDocument} and
219
+ * {@link PaymentsModule.getCreditNote}.
220
+ *
221
+ * @param invoiceId - The invoice.
222
+ * @param params - Paging.
223
+ * @returns One page; pass `nextCursor` as `cursor` for the next.
224
+ * @throws {QuasarSDKError} `invoice_not_found` (404), `invalid_request` or `invalid_cursor` (400), a timeout or a
225
+ * network error.
226
+ */
227
+ listInvoiceDocuments(invoiceId: string, params?: ListInvoiceItemsParams): Promise<PaymentsList<InvoiceDocumentRef>>;
228
+ /**
229
+ * Lists invoices, newest first (`GET /v1/payments/invoices`).
230
+ *
231
+ * @param params - Filters and paging.
232
+ * @returns One page; pass `nextCursor` as `cursor` for the next.
233
+ * @throws {QuasarSDKError} `invalid_request` or `invalid_cursor` (400), a timeout or a network error.
234
+ */
235
+ listInvoices(params?: ListInvoicesParams): Promise<PaymentsList<Invoice>>;
236
+ /**
237
+ * Cancels an open invoice (`POST /v1/payments/invoices/:id/cancel`): it stops being payable; its number stays
238
+ * taken. A payment that arrives later is held for you. An underpaid invoice is closed as expired: refund what arrived.
239
+ *
240
+ * @param invoiceId - The invoice.
241
+ * @param params - `reason`, recorded in the ledger.
242
+ * @returns The invoice.
243
+ * @throws {QuasarSDKError} `invoice_not_found` (404), an invoice that is not open (409), a timeout or a network
244
+ * error.
245
+ */
246
+ cancelInvoice(invoiceId: string, params?: {
247
+ reason?: string;
248
+ }): Promise<Invoice>;
249
+ /**
250
+ * Accepts a held payment (`POST /v1/payments/invoices/:id/release`): the invoice becomes paid and the buyer gets a
251
+ * receipt. A transfer Quasar could tie to no invoice is never an invoice hold: it is a {@link HeldTransfer} of its
252
+ * own, refunded with {@link PaymentsModule.refundHeldTransfer}.
253
+ *
254
+ * @param invoiceId - The invoice.
255
+ * @param params - `reason`, required, recorded in the ledger.
256
+ * @returns The invoice.
257
+ * @throws {QuasarSDKError} `invoice_not_found` (404); `invoice_not_held` or `invoice_partly_refunded` (refund the rest)
258
+ * (409); a timeout or a network error.
259
+ */
260
+ releaseInvoice(invoiceId: string, params: {
261
+ reason: string;
262
+ }): Promise<Invoice>;
263
+ /**
264
+ * Corrects the buyer details of an open or paid invoice (`POST /v1/payments/invoices/:id/correct`): a credit note
265
+ * for it and a new invoice with the corrected details. A paid invoice's payment carries over to the new one.
266
+ *
267
+ * @param invoiceId - The invoice.
268
+ * @param params - The corrected buyer and the reason the credit note gives.
269
+ * @returns The new invoice (with its checkout while it is open) and the one it replaces.
270
+ * @throws {QuasarSDKError} `invalid_request` (400), `invoice_not_found` (404), an invoice that cannot be corrected
271
+ * (409), a timeout or a network error.
272
+ */
273
+ correctInvoice(invoiceId: string, params: CorrectInvoiceParams): Promise<InvoiceCorrection>;
274
+ /**
275
+ * Gives an invoice, in any status, a new checkout token and payment page (`POST /v1/payments/invoices/:id/checkout`),
276
+ * for example when its link leaked. The earlier link stops working, also in the emails already sent, and so does the
277
+ * link of an invoice this one replaced; the current link needs no call: the invoice shows it (`payUrl`). The link of a
278
+ * settled invoice stops opening 90 days after it settled, unless its subscription still runs.
279
+ *
280
+ * @param invoiceId - The invoice.
281
+ * @returns The invoice with its new token and page.
282
+ * @throws {QuasarSDKError} `invoice_not_found` (404), a timeout or a network error.
283
+ */
284
+ replacePayLink(invoiceId: string): Promise<InvoiceWithCheckout>;
285
+ /**
286
+ * Reads a page of an invoice ledger, oldest first (`GET /v1/payments/invoices/:id/events`): up to 100 steps, with
287
+ * how many the ledger has, who created the invoice and when its quote was locked and its receipt issued. Every
288
+ * payment page a buyer opens locks a quote, so a ledger can be long.
289
+ *
290
+ * @param invoiceId - The invoice.
291
+ * @param params - `verify: true` adds the check of the whole hash chain; `limit` and `cursor` page it.
292
+ * @returns One page; pass `nextCursor` as `cursor` for the next.
293
+ * @throws {QuasarSDKError} `invoice_not_found` (404), `invalid_request` or `invalid_cursor` (400), a timeout or a
294
+ * network error.
295
+ */
296
+ getEvents(invoiceId: string, params?: GetEventsParams): Promise<InvoiceLedger>;
297
+ /**
298
+ * Shows what a refund would send, without asking for it (`POST /v1/payments/invoices/:id/refunds/preview`).
299
+ *
300
+ * @param invoiceId - The invoice.
301
+ * @param params - Amount (everything left when omitted), mode and reason, as for {@link PaymentsModule.refund}.
302
+ * @returns What it would send now, what the payment brought, and this amount at the rate of the payment.
303
+ * @throws {QuasarSDKError} What {@link PaymentsModule.refund} would be refused with, a timeout or a network error.
304
+ */
305
+ previewRefund(invoiceId: string, params?: RefundParams): Promise<RefundPreview>;
306
+ /**
307
+ * Asks for a refund of a paid or held invoice (`POST /v1/payments/invoices/:id/refunds`). Send the returned amount
308
+ * from your wallet to the payer, then pass the transaction to {@link PaymentsModule.submitRefund}.
309
+ *
310
+ * @param invoiceId - The invoice.
311
+ * @param params - Amount (everything left when omitted), mode and reason.
312
+ * @param options - `idempotencyKey`: a retry with the same key answers the refund asked for first while it waits for
313
+ * its transaction. Once it was sent, finished, failed or taken back the same key answers `refund_not_waiting` (409,
314
+ * the refund in `details.refund`), so nothing is sent twice: a new refund needs a new key. `idempotency_key_reused`
315
+ * (409): the key was used for another invoice, mode or amount.
316
+ * @returns The refund and what to send where.
317
+ * @throws {QuasarSDKError} `invalid_request` (400), `invoice_not_found` (404), an invoice that cannot be refunded or
318
+ * an amount above what is left (409), a timeout or a network error.
319
+ */
320
+ refund(invoiceId: string, params?: RefundParams, options?: CreateOptions): Promise<RefundStart>;
321
+ /**
322
+ * Takes back a refund still waiting for its transaction (`POST /v1/payments/refunds/:id/withdraw`): it holds its amount
323
+ * until it goes out, and nothing else ends it. A refund continued later is priced again at the rate of then. Call
324
+ * `reset()` on a refund store that holds it (`createRefundStore`): it would still offer to send it, and Quasar would
325
+ * refuse to record what was sent.
326
+ *
327
+ * @param refundId - The refund.
328
+ * @returns The refund, `failed` as `withdrawn`.
329
+ * @throws {QuasarSDKError} `refund_not_found` (404), `refund_not_waiting` (409), a timeout or a network error.
330
+ */
331
+ withdrawRefund(refundId: string): Promise<Refund>;
332
+ /**
333
+ * Reads a refund (`GET /v1/payments/refunds/:id`).
334
+ *
335
+ * @param refundId - The refund.
336
+ * @returns The refund.
337
+ * @throws {QuasarSDKError} `refund_not_found` (404), a timeout or a network error.
338
+ */
339
+ getRefund(refundId: string): Promise<Refund>;
340
+ /**
341
+ * Hands Quasar the refund transaction (`POST /v1/payments/refunds/:id/submit`); once it is final, the refund is
342
+ * confirmed, a credit note issued and `refund.confirmed` sent.
343
+ *
344
+ * @param refundId - The refund.
345
+ * @param params - The transaction and your wallet it was sent from.
346
+ * @returns The refund, `submitted`.
347
+ * @throws {QuasarSDKError} `invalid_request` (400), `refund_not_found` (404), `refund_not_submittable` or
348
+ * `transaction_already_used` (409: a transaction an invoice credited as a payment; one handed over as a payment
349
+ * that paid nothing becomes the refund's), a timeout or a network error.
350
+ */
351
+ submitRefund(refundId: string, params: SubmitRefundParams): Promise<Refund>;
352
+ /**
353
+ * Lists held transfers, newest first (`GET /v1/payments/held-transfers`, or `GET /v1/payments/invoices/:id/held-transfers`
354
+ * with `invoice`): transfers that reached your invoices without being tied to them by proof, to refund to their senders.
355
+ *
356
+ * @param params - One invoice, one state (`held` for those still to refund), or neither.
357
+ * @returns The held transfers, at most 100.
358
+ * @throws {QuasarSDKError} a timeout or a network error.
359
+ */
360
+ listHeldTransfers(params?: ListHeldTransfersParams): Promise<HeldTransfer[]>;
361
+ /**
362
+ * Asks for the refund of a held transfer, the whole of it to the wallet that sent it
363
+ * (`POST /v1/payments/held-transfers/:id/refund`): send it from your wallet, then hand the transaction over with
364
+ * {@link PaymentsModule.submitRefund}. The refund's `heldTransfer` names the transfer, which becomes `refunding`, then
365
+ * `refunded` once the refund is confirmed.
366
+ *
367
+ * @param heldTransferId - The held transfer.
368
+ * @returns The refund and what to send where.
369
+ * @throws {QuasarSDKError} `held_transfer_not_found` (404); `held_transfer_not_refundable` (it is not `held`) or
370
+ * `sender_blocked` (AML blocked the wallet that sent it) (409); a timeout or a network error.
371
+ */
372
+ refundHeldTransfer(heldTransferId: string): Promise<RefundStart>;
373
+ /**
374
+ * Downloads a stored document of an invoice byte for byte as issued
375
+ * (`GET /v1/payments/invoices/:id/documents/:kind`).
376
+ *
377
+ * @param invoiceId - The invoice.
378
+ * @param kind - `invoice` or `receipt` (PDF), or `invoice.xml` (UBL).
379
+ * @returns The bytes, media type and file name.
380
+ * @throws {QuasarSDKError} `invoice_not_found` or `document_not_found` (404), a timeout or a network error.
381
+ */
382
+ getDocument(invoiceId: string, kind: PaymentDocumentKind): Promise<PaymentDocument>;
383
+ /**
384
+ * Downloads a credit note of an invoice (`GET /v1/payments/invoices/:id/documents/credit-note/:ref`).
385
+ *
386
+ * @param invoiceId - The invoice.
387
+ * @param ref - The refund it credits, or its number.
388
+ * @param format - `pdf` (default) or `ubl`.
389
+ * @returns The bytes, media type and file name.
390
+ * @throws {QuasarSDKError} `invoice_not_found` or `document_not_found` (404), a timeout or a network error.
391
+ */
392
+ getCreditNote(invoiceId: string, ref: string, format?: 'pdf' | 'ubl'): Promise<PaymentDocument>;
393
+ /**
394
+ * Prices an amount in a payment method now (`POST /v1/payments/quotes`); nothing is locked.
395
+ *
396
+ * @param params - Amount, currency and method.
397
+ * @returns The price.
398
+ * @throws {QuasarSDKError} `invalid_request` (400), `method_not_found` (404), no fresh price (503), a timeout or a
399
+ * network error.
400
+ */
401
+ quote(params: QuoteParams): Promise<PaymentQuote>;
402
+ /**
403
+ * Lists the app's active payment methods, in their order (`GET /v1/payments/methods`).
404
+ *
405
+ * @returns The methods.
406
+ * @throws {QuasarSDKError} A timeout or a network error.
407
+ */
408
+ listMethods(): Promise<{
409
+ object: 'list';
410
+ data: PaymentMethod[];
411
+ }>;
412
+ /**
413
+ * Starts a subscription (`POST /v1/payments/subscriptions`): each period is one invoice, issued ahead of the period
414
+ * and paid like any invoice (`send_invoice`) or charged with the buyer's ERC-7715 permission (`auto_charge`).
415
+ *
416
+ * @param params - The plan, its interval and terms.
417
+ * @param options - `idempotencyKey`: a retry with the same key returns the subscription started first.
418
+ * @returns The subscription and, without a trial, its first invoice with the payment page.
419
+ * @throws {QuasarSDKError} `invalid_request` (400), `subscriptions_disabled` (403), `auto_charge_unavailable` with
420
+ * `details.reason` (400), a timeout or a network error.
421
+ */
422
+ createSubscription(params: CreateSubscriptionParams, options?: CreateOptions): Promise<SubscriptionStart>;
423
+ /**
424
+ * Lists subscriptions, newest first (`GET /v1/payments/subscriptions`).
425
+ *
426
+ * @param params - Filters and paging.
427
+ * @returns One page.
428
+ * @throws {QuasarSDKError} `invalid_request` or `invalid_cursor` (400), a timeout or a network error.
429
+ */
430
+ listSubscriptions(params?: ListSubscriptionsParams): Promise<PaymentsList<Subscription>>;
431
+ /**
432
+ * Reads a subscription (`GET /v1/payments/subscriptions/:id`). The invoices of its periods and its automatic
433
+ * charges are lists of their own: {@link PaymentsModule.listSubscriptionInvoices} and
434
+ * {@link PaymentsModule.listSubscriptionCharges}.
435
+ *
436
+ * @param subscriptionId - The subscription.
437
+ * @returns The subscription.
438
+ * @throws {QuasarSDKError} `subscription_not_found` (404), a timeout or a network error.
439
+ */
440
+ getSubscription(subscriptionId: string): Promise<Subscription>;
441
+ /**
442
+ * Lists the invoices of a subscription's periods, newest first (`GET /v1/payments/subscriptions/:id/invoices`).
443
+ *
444
+ * @param subscriptionId - The subscription.
445
+ * @param params - Paging.
446
+ * @returns One page; pass `nextCursor` as `cursor` for the next.
447
+ * @throws {QuasarSDKError} `subscription_not_found` (404), `invalid_request` or `invalid_cursor` (400), a timeout
448
+ * or a network error.
449
+ */
450
+ listSubscriptionInvoices(subscriptionId: string, params?: ListSubscriptionItemsParams): Promise<PaymentsList<Invoice>>;
451
+ /**
452
+ * Lists the automatic charges of a subscription, newest first (`GET /v1/payments/subscriptions/:id/charges`).
453
+ *
454
+ * @param subscriptionId - The subscription.
455
+ * @param params - Paging.
456
+ * @returns One page; pass `nextCursor` as `cursor` for the next.
457
+ * @throws {QuasarSDKError} `subscription_not_found` (404), `invalid_request` or `invalid_cursor` (400), a timeout
458
+ * or a network error.
459
+ */
460
+ listSubscriptionCharges(subscriptionId: string, params?: ListSubscriptionItemsParams): Promise<PaymentsList<SubscriptionCharge>>;
461
+ /**
462
+ * Cancels a subscription now (its open invoices are cancelled) or when the running period ends
463
+ * (`POST /v1/payments/subscriptions/:id/cancel`).
464
+ *
465
+ * @param subscriptionId - The subscription.
466
+ * @param params - `atPeriodEnd` and `reason`.
467
+ * @returns The subscription.
468
+ * @throws {QuasarSDKError} `subscription_not_found` (404), `subscription_not_active` (409), a timeout or a network
469
+ * error.
470
+ */
471
+ cancelSubscription(subscriptionId: string, params?: CancelSubscriptionParams): Promise<Subscription>;
472
+ /**
473
+ * Takes back a cancellation scheduled for the period end (`POST /v1/payments/subscriptions/:id/cancel/undo`): the
474
+ * subscription renews again (`subscription.cancel_unscheduled`). Nothing scheduled: nothing changes.
475
+ *
476
+ * @param subscriptionId - The subscription.
477
+ * @returns The subscription.
478
+ * @throws {QuasarSDKError} `subscription_not_found` (404), a canceled or ended subscription (409), a timeout or a
479
+ * network error.
480
+ */
481
+ undoSubscriptionCancel(subscriptionId: string): Promise<Subscription>;
482
+ /**
483
+ * Ends the automatic-charge permission of a subscription (`POST /v1/payments/subscriptions/:id/permission/end`), for a
484
+ * buyer who lost the wallet: Quasar charges it no more, and the buyer pays from the invoice links or gives a new
485
+ * permission. The delegation stays valid on chain until its wallet revokes it, but Quasar does not use it again.
486
+ *
487
+ * @param subscriptionId - The subscription.
488
+ * @returns The subscription.
489
+ * @throws {QuasarSDKError} `subscription_not_found` (404), `permission_not_active` (409), a timeout or a network error.
490
+ */
491
+ endSubscriptionPermission(subscriptionId: string): Promise<Subscription>;
492
+ /**
493
+ * Pauses a subscription (`POST /v1/payments/subscriptions/:id/pause`): its open invoices are cancelled and no period
494
+ * is billed until it resumes.
495
+ *
496
+ * @param subscriptionId - The subscription.
497
+ * @returns The subscription.
498
+ * @throws {QuasarSDKError} `subscription_not_found` (404), `subscription_not_active` (409), a timeout or a network
499
+ * error.
500
+ */
501
+ pauseSubscription(subscriptionId: string): Promise<Subscription>;
502
+ /**
503
+ * Resumes a paused subscription (`POST /v1/payments/subscriptions/:id/resume`) with a new period that starts now
504
+ * and is invoiced at once.
505
+ *
506
+ * @param subscriptionId - The subscription.
507
+ * @returns The subscription and the new period's invoice with its payment page.
508
+ * @throws {QuasarSDKError} `subscription_not_found` (404), `subscription_not_paused` or `subscription_over` (409),
509
+ * a timeout or a network error.
510
+ */
511
+ resumeSubscription(subscriptionId: string): Promise<SubscriptionStart>;
165
512
  }
166
513
 
167
514
  /**
@@ -216,7 +563,7 @@ declare class PulsarModule {
216
563
  * Sends the hash of the transaction that executed an EIP-5792 batch (`POST /v1/engine/pulsar/sync/:txKey/hash`). A
217
564
  * batch is synced by its batch ID (`TransactionTracker.EIP5792`), which only the wallet can resolve; with the hash,
218
565
  * Quasar tracks it on-chain until it reaches a final status. Get the hashes in the browser with
219
- * {@link watchBatchHashes} and send each through a Server Action. Without a hash, Quasar marks the batch `Failed`
566
+ * `watchBatchHashes` from `@tuwaio/quasar-sdk/pulsar` and send each through a Server Action. Without a hash, Quasar marks the batch `Failed`
220
567
  * an hour after it was synced.
221
568
  *
222
569
  * @param txKey - The batch ID, the `txKey` of the transaction.
@@ -256,51 +603,6 @@ declare class PulsarModule {
256
603
  getHistory(query?: HistoryQuery): Promise<PaginatedResult<Transaction>>;
257
604
  }
258
605
 
259
- /**
260
- * @file Hashes of EIP-5792 batches, read from a Pulsar store in the browser.
261
- */
262
-
263
- /**
264
- * The part of a Pulsar store that {@link watchBatchHashes} reads: the store returned by `createPulsarStore` of
265
- * `@tuwaio/pulsar-core` has it.
266
- *
267
- * @typeParam T - The transaction type of the store.
268
- */
269
- interface PulsarPoolStore<T extends Transaction> {
270
- /** Returns the current state of the store. */
271
- getState: () => {
272
- transactionsPool: Record<string, T>;
273
- };
274
- /** Calls `listener` with the new state on every change; returns the function that removes it. */
275
- subscribe: (listener: (state: {
276
- transactionsPool: Record<string, T>;
277
- }) => void) => () => void;
278
- }
279
- /**
280
- * Watches a Pulsar store for EIP-5792 batches whose transaction became known and calls `onHash` once for each, so the
281
- * app can send the hash to Quasar with `PulsarModule.syncHash` (through a Server Action, since that call needs the
282
- * secret key). A batch is reported once Quasar has it (`syncStatus: 'synced'`) and Pulsar wrote its `hash`; batches
283
- * created more than an hour ago are skipped, because Quasar no longer waits for them. When `onHash` rejects, the batch
284
- * is reported again on the next change of the store.
285
- *
286
- * Side effects: subscribes to the store until the returned function is called; keeps the reported batch IDs in memory.
287
- *
288
- * @param store - The Pulsar store, for example the one `createPulsarStore` returns.
289
- * @param onHash - Called with each batch and its hash; return its promise so a failure is retried.
290
- * @returns A function that stops watching.
291
- *
292
- * @example
293
- * ```ts
294
- * import { watchBatchHashes } from '@tuwaio/quasar-sdk';
295
- *
296
- * // In the browser, next to the store; `syncBatchHash` is a Server Action that calls `quasar.pulsar.syncHash`
297
- * const stop = watchBatchHashes(pulsarStore, (tx) => syncBatchHash(tx.txKey, tx.hash));
298
- * ```
299
- */
300
- declare function watchBatchHashes<T extends Transaction>(store: PulsarPoolStore<T>, onHash: (tx: T & {
301
- hash: `0x${string}`;
302
- }) => void | Promise<void>): () => void;
303
-
304
606
  /**
305
607
  * @file The `Quasar` client, the entry point of the Quasar API.
306
608
  */
@@ -326,6 +628,8 @@ declare class Quasar {
326
628
  private readonly client;
327
629
  /** Syncs Pulsar transactions to Quasar and reads their history. */
328
630
  readonly pulsar: PulsarModule;
631
+ /** Invoices, refunds, documents and subscriptions of a Payments app. */
632
+ readonly payments: PaymentsModule;
329
633
  /**
330
634
  * Creates the client. Sends no request.
331
635
  *
@@ -335,5 +639,235 @@ declare class Quasar {
335
639
  constructor(config: QuasarConfig);
336
640
  }
337
641
 
338
- export { BASE_API_URL, PULSAR_HISTORY_ENDPOINT, PULSAR_SYNC_ENDPOINT, PulsarModule, Quasar, QuasarSDKError, pulsarSyncHashEndpoint, watchBatchHashes };
339
- export type { HistoryQuery, PaginatedResult, PulsarPoolStore, QuasarConfig };
642
+ /**
643
+ * @file Verification of Quasar webhooks: the HMAC-SHA256 signature of the raw body and the time deliveries were sent;
644
+ * and the types of their bodies.
645
+ */
646
+
647
+ /** The header that carries the signature of a webhook: the hex HMAC-SHA256 of the raw body, keyed with the secret. */
648
+ declare const WEBHOOK_SIGNATURE_HEADER = "x-quasar-signature";
649
+ /** `apiVersion` of payment webhook bodies. */
650
+ declare const PAYMENT_WEBHOOK_VERSION = "payments.v1";
651
+ /** Types of payment webhook events. */
652
+ type PaymentWebhookEventType = 'invoice.issued' | 'invoice.processing' | 'invoice.paid' | 'invoice.underpaid' | 'invoice.held' | 'invoice.transfer_held' | 'invoice.transfer_hold_voided' | 'invoice.released' | 'invoice.expired' | 'invoice.cancelled' | 'invoice.refunded' | 'invoice.partially_refunded' | 'invoice.replaced' | 'refund.confirmed' | 'refund.failed' | 'payment.reverted' | 'payment.unverified' | 'subscription.created' | 'subscription.renewed' | 'subscription.past_due' | 'subscription.paused' | 'subscription.resumed' | 'subscription.cancel_scheduled' | 'subscription.cancel_unscheduled' | 'subscription.canceled' | 'subscription.ended' | 'subscription.permission_granted' | 'subscription.charge_failed' | 'subscription.permission_revoked' | 'subscription.invoice_refused';
653
+ /**
654
+ * What a hold is about (`data.hold` of `invoice.held`): an invoice paid in a way that waits for your decision. `reason`
655
+ * (`late`, `aml`, `quote_expired`), its transaction (`txHash`) and network (`chainId`), the wallets that sent it
656
+ * (`senders`) and whether it came late (`late`). Release it with {@link PaymentsModule.releaseInvoice} or refund it; a refund to a wallet AML blocks now
657
+ * is refused (`sender_blocked`).
658
+ */
659
+ interface PaymentWebhookHold {
660
+ /** Why the payment is held; `null` when Quasar did not record why. */
661
+ reason: 'late' | 'aml' | 'quote_expired' | null;
662
+ /** The transaction of the held payment. */
663
+ txHash: string | null;
664
+ /** CAIP-2 chain of the transaction; Quasar versions before November 10, 2026 leave it out. */
665
+ chainId?: string | null;
666
+ /** The wallets that sent it. */
667
+ senders?: string[];
668
+ /** It arrived after the invoice expired or was cancelled, or was handed over after `dueAt`. */
669
+ late?: true;
670
+ }
671
+ /**
672
+ * A transfer that reached the invoice's address without being tied to it by proof (`data.heldTransfer` of
673
+ * `invoice.transfer_held`): it paid nothing and the invoice stays as it was. It is a record of its own, which you
674
+ * refund as a whole to the wallet that sent it ({@link PaymentsModule.refundHeldTransfer}): `reason` is
675
+ * `payer_mismatch` (another wallet than the one the quote was for), `payer_unconfirmed` (the quoted wallet never
676
+ * confirmed the invoice), `reference_missing` (a Solana Pay transfer without the invoice's reference whose amount does
677
+ * not tie it to the invoice either), `already_paid`, `quote_changed` or `too_many_parts`.
678
+ */
679
+ interface PaymentWebhookHeldTransfer {
680
+ /** Not set: the transfer is held. */
681
+ voided?: undefined;
682
+ /** The held transfer ({@link HeldTransfer}). */
683
+ id: string | null;
684
+ /** Its transaction. */
685
+ txHash: string | null;
686
+ /** CAIP-2 chain of the transaction; Quasar versions before November 10, 2026 leave it out. */
687
+ chainId?: string | null;
688
+ /** What it carried, in base units of its token. */
689
+ amount: string | null;
690
+ /** The wallets that sent it. */
691
+ senders: string[];
692
+ /** Why it is tied to no invoice. */
693
+ reason: 'payer_mismatch' | 'payer_unconfirmed' | 'reference_missing' | 'already_paid' | 'quote_changed' | 'too_many_parts' | null;
694
+ /** It arrived after the invoice expired or was cancelled. */
695
+ late?: true;
696
+ /** Set when AML blocked its sender: Quasar refunds it to no one. */
697
+ screening?: {
698
+ status: 'blocked';
699
+ reasons: unknown[];
700
+ };
701
+ }
702
+ /**
703
+ * A held transfer Quasar voided (`data.heldTransfer` of `invoice.transfer_hold_voided`): the same transaction was
704
+ * proven to pay another invoice of yours since (`paidInvoice`), so there is nothing to refund.
705
+ */
706
+ interface PaymentWebhookHeldTransferVoided {
707
+ /** The held transfer. */
708
+ id: string | null;
709
+ /** Always `true`. */
710
+ voided: true;
711
+ /** Its transaction. */
712
+ txHash: string | null;
713
+ /** CAIP-2 chain of the transaction; Quasar versions before November 10, 2026 leave it out. */
714
+ chainId?: string | null;
715
+ /** The invoice the transaction paid instead. */
716
+ paidInvoice: string | null;
717
+ }
718
+ /**
719
+ * The body of a payment webhook (`payments.v1`). `id`, `type` and `createdAt` are those of the ledger event, so
720
+ * deduplicate by `id`; `data.invoice` is the invoice when the delivery was sent, so a retry carries its current state.
721
+ */
722
+ interface PaymentWebhookEvent {
723
+ /** Event ID: the same for every delivery and retry of the event. */
724
+ id: string;
725
+ /** Always `event`. */
726
+ object: 'event';
727
+ /** Always `payments.v1`. */
728
+ apiVersion: typeof PAYMENT_WEBHOOK_VERSION;
729
+ /** What happened. */
730
+ type: PaymentWebhookEventType;
731
+ /** When it happened (ISO 8601). */
732
+ createdAt: string;
733
+ /** When this delivery was sent; signed with the body, it lets {@link verifyWebhook} refuse old replays. */
734
+ sentAt: string;
735
+ /** Live or test. */
736
+ environment: PaymentsEnvironment;
737
+ /** What the event is about. */
738
+ data: {
739
+ /** The invoice now (subscription events carry the invoice of their period, when there is one). */
740
+ invoice?: Invoice;
741
+ /** The payment, once the invoice has one. */
742
+ settlement?: InvoiceSettlement;
743
+ /**
744
+ * Every transfer credited to the invoice so far, on `invoice.paid`, `invoice.underpaid`, `invoice.held` and
745
+ * `invoice.released`: an invoice paid in parts has several.
746
+ */
747
+ payments?: InvoicePayment[];
748
+ /** The refund of a refund event. */
749
+ refund?: Refund;
750
+ /** Why AML held the payment, on a hold by AML. */
751
+ aml?: {
752
+ decision: 'block';
753
+ reasons: unknown[];
754
+ };
755
+ /** What `invoice.held` holds. Quasar versions before October 29, 2026 leave it out. */
756
+ hold?: PaymentWebhookHold;
757
+ /**
758
+ * The transfer of `invoice.transfer_held` and `invoice.transfer_hold_voided`. The refund of a held transfer names
759
+ * it in `refund.heldTransfer`.
760
+ */
761
+ heldTransfer?: PaymentWebhookHeldTransfer | PaymentWebhookHeldTransferVoided;
762
+ /** The transaction to check, in `payment.reverted` and `payment.unverified`. */
763
+ transaction?: {
764
+ txHash: string | null;
765
+ chainId: string | null;
766
+ };
767
+ /** The subscription of a subscription event. */
768
+ subscription?: Subscription;
769
+ };
770
+ }
771
+ /** The body of a transaction webhook: a synced Pulsar transaction reached a final status. */
772
+ interface TransactionWebhookEvent {
773
+ /** Key of the Pulsar transaction. */
774
+ txKey: string;
775
+ /** Its on-chain hash, when it has one. */
776
+ hash?: string;
777
+ /** Final status: `Success`, `Failed` or `Replaced`. */
778
+ status: string;
779
+ /** The same as `status`; also sent in the `x-quasar-event` header. */
780
+ action: string;
781
+ /** The `type` of the Pulsar transaction. */
782
+ txType?: string;
783
+ /** Its chain: the EVM chain ID (`"1"`) or the Solana CAIP-2 chain ID. */
784
+ chainId: string;
785
+ /** Unix time in seconds when the webhook was created. */
786
+ timestamp: number;
787
+ /**
788
+ * When this delivery attempt was sent (ISO 8601), signed with the body. Quasar engines released before Payments omit
789
+ * it; {@link verifyWebhook} then checks `timestamp`.
790
+ */
791
+ sentAt?: string;
792
+ /** The `payload` of the Pulsar transaction. */
793
+ metadata?: unknown;
794
+ }
795
+ /** A verified webhook body: a payment event or a transaction webhook. Tell them apart with {@link isPaymentWebhookEvent}. */
796
+ type QuasarWebhook = PaymentWebhookEvent | TransactionWebhookEvent;
797
+ /** Why {@link verifyWebhook} refused a delivery. */
798
+ type QuasarWebhookErrorCode = 'missing_secret' | 'missing_signature' | 'invalid_signature' | 'invalid_body' | 'invalid_options' | 'stale';
799
+ /** Error thrown by {@link verifyWebhook}: answer the delivery with 400 (or 401) and do not act on it. */
800
+ declare class QuasarWebhookError extends Error {
801
+ /** Why the delivery was refused. */
802
+ readonly code: QuasarWebhookErrorCode;
803
+ /**
804
+ * Creates the error.
805
+ *
806
+ * @param code - Why the delivery was refused.
807
+ * @param message - What to log.
808
+ */
809
+ constructor(code: QuasarWebhookErrorCode, message: string);
810
+ }
811
+ /** What {@link verifyWebhook} takes. */
812
+ interface VerifyWebhookParams {
813
+ /** The request body exactly as received, before any parsing: a string or its bytes. */
814
+ body: string | Uint8Array;
815
+ /** The value of the `x-quasar-signature` header. */
816
+ signature: string | null | undefined;
817
+ /** The signing secret of the webhook endpoint. */
818
+ secret: string;
819
+ /**
820
+ * How far the send time may be from now, in seconds: a finite number above 0, or `false` to turn the check off
821
+ * (`0`, a negative number, `NaN` or `Infinity` throw `invalid_options`). Defaults to `300`. The send time is
822
+ * `sentAt`; a transaction webhook of an engine that sends no `sentAt` is checked by its `timestamp`, the time it was
823
+ * created, which its retries reuse, so a retry later than the tolerance is refused.
824
+ */
825
+ toleranceSeconds?: number | false;
826
+ /** The current time, for tests. Defaults to now. */
827
+ now?: Date;
828
+ }
829
+ /**
830
+ * Whether a verified webhook is a payment event (`apiVersion: 'payments.v1'`) rather than a transaction webhook.
831
+ *
832
+ * @param event - A body returned by {@link verifyWebhook}.
833
+ * @returns `true` for a payment event.
834
+ */
835
+ declare function isPaymentWebhookEvent(event: QuasarWebhook): event is PaymentWebhookEvent;
836
+ /**
837
+ * Verifies a Quasar webhook and returns its body. Checks the signature (`x-quasar-signature`, the hex HMAC-SHA256 of
838
+ * the raw body keyed with the endpoint's signing secret) in constant time, parses the body and refuses a delivery
839
+ * whose send time (`sentAt`, falling back to `timestamp` for older transaction webhooks) is further from now than
840
+ * `toleranceSeconds` (a replay of an old delivery). Uses Web Crypto, so it runs in Node.js 20+, edge runtimes and
841
+ * Workers. Payment events are delivered at least once: deduplicate them by `id`.
842
+ *
843
+ * @param params - The raw body, the signature header, the secret and the tolerance.
844
+ * @returns The body: a {@link PaymentWebhookEvent} or a {@link TransactionWebhookEvent}.
845
+ * @throws {QuasarWebhookError} `missing_secret`, `missing_signature`, `invalid_signature`, `invalid_body` (not a
846
+ * JSON object), `invalid_options` (an invalid `toleranceSeconds`) or `stale` (an event outside the tolerance or
847
+ * missing its send time).
848
+ *
849
+ * @example
850
+ * ```ts
851
+ * import { isPaymentWebhookEvent, verifyWebhook } from '@tuwaio/quasar-sdk';
852
+ *
853
+ * export async function POST(request: Request) {
854
+ * try {
855
+ * const event = await verifyWebhook({
856
+ * body: await request.text(),
857
+ * signature: request.headers.get('x-quasar-signature'),
858
+ * secret: process.env.QUASAR_WEBHOOK_SECRET ?? '',
859
+ * });
860
+ * if (isPaymentWebhookEvent(event) && event.type === 'invoice.paid') {
861
+ * await fulfil(event.data.invoice?.metadata?.orderId);
862
+ * }
863
+ * return new Response(null, { status: 204 });
864
+ * } catch {
865
+ * return new Response(null, { status: 400 });
866
+ * }
867
+ * }
868
+ * ```
869
+ */
870
+ declare function verifyWebhook(params: VerifyWebhookParams): Promise<QuasarWebhook>;
871
+
872
+ export { BASE_API_URL, CancelSubscriptionParams, CorrectInvoiceParams, CreateInvoiceParams, CreateOptions, CreateSubscriptionParams, GetEventsParams, HeldTransfer, Invoice, InvoiceCorrection, InvoiceDetails, InvoiceDocumentRef, InvoiceLedger, InvoicePayment, InvoiceSettlement, InvoiceWithCheckout, ListHeldTransfersParams, ListInvoiceItemsParams, ListInvoicesParams, ListSubscriptionItemsParams, ListSubscriptionsParams, PAYMENTS_ENDPOINT, PAYMENT_WEBHOOK_VERSION, PULSAR_HISTORY_ENDPOINT, PULSAR_SYNC_ENDPOINT, PaymentDocument, PaymentDocumentKind, PaymentMethod, PaymentQuote, PaymentsEnvironment, PaymentsList, PaymentsModule, PulsarModule, Quasar, QuasarWebhookError, QuoteParams, Refund, RefundParams, RefundPreview, RefundStart, SubmitRefundParams, Subscription, SubscriptionCharge, SubscriptionStart, WEBHOOK_SIGNATURE_HEADER, isPaymentWebhookEvent, pulsarSyncHashEndpoint, verifyWebhook };
873
+ export type { HistoryQuery, PaginatedResult, PaymentWebhookEvent, PaymentWebhookEventType, PaymentWebhookHeldTransfer, PaymentWebhookHeldTransferVoided, PaymentWebhookHold, QuasarConfig, QuasarWebhook, QuasarWebhookErrorCode, TransactionWebhookEvent, VerifyWebhookParams };