@avvio/payments 0.1.0 → 0.5.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/index.d.ts CHANGED
@@ -35,7 +35,6 @@ export type PayoutFailureCode =
35
35
  | 'authorization_not_completed'
36
36
  | 'returned_by_bank'
37
37
  | 'execution_failed'
38
- | 'canceled_by_platform'
39
38
  | 'unknown';
40
39
 
41
40
  /** Informational only. Never branch integration behaviour on it. */
@@ -55,6 +54,12 @@ export interface Payout {
55
54
  * wallet. Absent on routings that settle on acceptance, so its presence is
56
55
  * the signal — call `getFunding()`.
57
56
  */
57
+ /**
58
+ * Waiting on YOU to fund it, right now. Call `getFunding(payoutId)`, send the
59
+ * money, then `confirmFunding`. Absent once it is on its way, and absent
60
+ * entirely where accepting the payout already paid it — so its presence is
61
+ * the instruction, not a flag to interpret.
62
+ */
58
63
  requiresFunding?: boolean;
59
64
  /** When this payout last changed. Your `updatedSince` watermark. */
60
65
  updatedAt?: string;
@@ -68,6 +73,12 @@ export interface Payout {
68
73
  sourceAmount: Money | null;
69
74
  destinationAmount: Money | null;
70
75
  destinationAccountId: string | null;
76
+ /**
77
+ * Already inside `sourceAmount` (deducted from the send, not added on top).
78
+ * The same value the event `data` carries. Null means the rail has not
79
+ * disclosed it, which is not zero.
80
+ */
81
+ fee: Money | null;
71
82
  rate: string | null;
72
83
  reference: string | null;
73
84
  endUser: EndUser | null;
@@ -92,14 +103,31 @@ export interface CorridorField {
92
103
  title: string;
93
104
  type: string;
94
105
  pattern?: string;
106
+ /**
107
+ * A check the server applies beyond `pattern`, present only on fields that
108
+ * have one. `clabe`: the Mexican CLABE check digit (digit 18 over digits
109
+ * 1-17, weights 3,7,1 repeating, each product mod 10, then (10 - sum mod 10)
110
+ * mod 10). A value that matches `pattern` but fails this is refused with
111
+ * `400 VALIDATION_ERROR`.
112
+ */
113
+ checksum?: 'clabe';
95
114
  required: boolean;
96
115
  description?: string;
116
+ options?: { value: unknown; label: string }[];
97
117
  }
98
118
 
99
119
  export interface Corridor {
100
120
  currency: string;
121
+ /** Only when the routing publishes one; the sandbox does not. Key on `currency`. */
122
+ country?: string;
101
123
  fields: CorridorField[];
102
- limits?: { min: string; max: string };
124
+ limits?: { min?: string; max?: string };
125
+ /**
126
+ * Always `null` today: settlement windows and cutoffs are not published per
127
+ * corridor yet. `Payout.expectedSettlementAt` carries what the network
128
+ * reports for one payout.
129
+ */
130
+ settlement: null;
103
131
  }
104
132
 
105
133
  export interface BeneficiaryMethod {
@@ -117,6 +145,9 @@ export interface Beneficiary {
117
145
  name: string;
118
146
  email?: string;
119
147
  country?: string;
148
+ /** Both are settable through `updateBeneficiary`, and both come back here. */
149
+ type?: 'individual' | 'business';
150
+ phone?: string | null;
120
151
  externalId?: string;
121
152
  endUserId?: string | null;
122
153
  paymentMethods: BeneficiaryMethod[];
@@ -144,6 +175,7 @@ export type PayoutsErrorType =
144
175
  | 'UNAUTHORIZED'
145
176
  | 'FORBIDDEN'
146
177
  | 'NOT_FOUND'
178
+ | 'ROUTE_NOT_FOUND'
147
179
  | 'RATE_LIMITED'
148
180
  | 'BAD_REQUEST'
149
181
  | 'PROVIDER_REJECTED'
@@ -155,8 +187,32 @@ export type PayoutsErrorType =
155
187
  | 'CONFLICT'
156
188
  | 'ACCOUNT_BLOCKED'
157
189
  | 'INSUFFICIENT_BALANCE'
190
+ | 'CRYPTO_PAYOUTS_DISABLED'
191
+ | 'WALLET_NOT_PROVISIONED'
158
192
  | 'ORDERS_TEMPORARILY_UNAVAILABLE'
193
+ | 'PAYOUT_ACCOUNT_PROVIDER_UNAVAILABLE'
194
+ // Controls on API payouts. Neither is retried into success by the caller:
195
+ // a cap is split or raised, a refusal is final.
196
+ | 'PAYOUT_LIMIT_EXCEEDED'
197
+ | 'PAYOUT_REFUSED'
198
+ // Quote-accept and payout links, when the organization has an approval
199
+ // threshold or a velocity cap: only `POST /payouts` enforces them.
200
+ | 'USE_POST_PAYOUTS'
159
201
  | 'INTERNAL'
202
+ | 'PAYOUT_OUTCOME_UNKNOWN'
203
+ | 'PAYOUT_LINKS_UNAVAILABLE'
204
+ // PARKED request-signing error types:
205
+ // | 'SIGNATURE_CHALLENGE'
206
+ // | 'SIGNATURE_CHALLENGE_EXPIRED'
207
+ // | 'SIGNATURE_REQUIRED'
208
+ // | 'SIGNATURE_INVALID'
209
+ // | 'SIGNATURE_REPLAYED'
210
+ // | 'SIGNATURE_TIMESTAMP_SKEW'
211
+ // | 'SIGNATURE_TIMESTAMP_INVALID'
212
+ // | 'PUBLIC_KEY_INVALID'
213
+ // | 'KEY_RETIRED'
214
+ | 'KEY_EXPIRED'
215
+ | 'KEY_IP_NOT_ALLOWED'
160
216
  // Idempotency
161
217
  | 'IDEMPOTENCY_KEY_REQUIRED'
162
218
  | 'IDEMPOTENCY_KEY_INVALID'
@@ -168,11 +224,23 @@ export type PayoutsErrorType =
168
224
  | 'DESTINATION_ACCOUNT_NOT_FOUND'
169
225
  | 'RATE_DRIFT_EXCEEDED'
170
226
  | 'QUOTE_UNVERIFIABLE'
227
+ | 'QUOTE_NOT_POSITIVE'
171
228
  | 'EXACT_OUTPUT_UNSUPPORTED'
172
229
  | 'PAYOUT_NOT_CANCELABLE'
230
+ // Asking where to send funds for a payout that settles from your balance.
231
+ // `requiresFunding` on the payout is the branch, and this means that branch
232
+ // is wrong, so retrying never helps.
233
+ | 'FUNDING_NOT_APPLICABLE'
173
234
  | 'INSUFFICIENT_SCOPE'
174
235
  | 'INDICATIVE_PRICING_UNAVAILABLE'
175
236
  | 'PAYOUT_LINK_UNUSABLE'
237
+ // Batch payouts
238
+ | 'BATCH_NOT_FOUND'
239
+ | 'PAYOUT_BATCH_NOT_CONFIRMABLE'
240
+ | 'PAYOUT_BATCH_AWAITING_APPROVAL'
241
+ | 'PAYOUT_BATCH_NOT_CANCELABLE'
242
+ | 'PAYOUT_BATCH_DUPLICATE_REFERENCE'
243
+ | 'MASS_PAYOUTS_DISABLED'
176
244
  // Raised by this client, with no HTTP response behind them. `TIMEOUT` is the
177
245
  // important one: it is an UNKNOWN outcome, not a failure, and the payout may
178
246
  // exist. `CORRIDOR_UNAVAILABLE` is raised here too, and is in the table.
@@ -198,7 +266,9 @@ export declare class PayoutsError extends Error {
198
266
  }
199
267
 
200
268
  export interface PayoutsClientOptions {
201
- /** Server-side only. Never ship to a browser or a mobile app. */
269
+ // PARKED request-signing option:
270
+ // privateKeyPem?: string;
271
+ /** Complete bearer credential: `avvio_live_*` or `avvio_test_*`. */
202
272
  apiKey?: string;
203
273
  orgId?: string;
204
274
  baseUrl?: string;
@@ -206,8 +276,30 @@ export interface PayoutsClientOptions {
206
276
  }
207
277
 
208
278
  export interface PayoutArgs {
279
+ /**
280
+ * How much. Read as what you SEND, unless `amountLeg` says otherwise — in
281
+ * which case it is what the beneficiary RECEIVES, in their currency.
282
+ */
209
283
  amount: string;
210
284
  destinationAccountId: string;
285
+ /**
286
+ * Which side of the payout `amount` describes.
287
+ *
288
+ * `'source'` (the default) takes every fee out of what you sent, so the
289
+ * beneficiary gets less than the figure you named.
290
+ *
291
+ * `'destination'` pays them that figure exactly, in THEIR currency, and adds
292
+ * the fees to your debit instead.
293
+ *
294
+ * `'source_net'` keeps the figure in YOUR currency and means it the same way
295
+ * — "send them $200 worth". We convert at the market rate from `quote()` and
296
+ * lock that, so the fees land on your debit rather than on what they receive.
297
+ *
298
+ * The two locking modes need `capabilities.exactOutput` on the corridors
299
+ * call; elsewhere they are refused with `EXACT_OUTPUT_UNSUPPORTED` rather
300
+ * than silently pricing the other side.
301
+ */
302
+ amountLeg?: 'source' | 'destination' | 'source_net';
211
303
  /**
212
304
  * What you told the payer they would receive. The send is REFUSED if the
213
305
  * binding quote drifts past `maxRateDrift` from it. Without this you send at
@@ -247,10 +339,104 @@ export interface PayoutArgs {
247
339
  */
248
340
  allowDuplicate?: boolean;
249
341
  reference?: string;
342
+ /** From `listPaymentReasons()`. Validated whenever sent; will be required for INR/GHS/CNY/BRL from a dated future release. */
250
343
  purposeOfPayment?: string;
251
344
  endUser?: EndUser;
252
345
  }
253
346
 
347
+ /**
348
+ * The batch lifecycle. A batch tracks CREATION, not settlement: `completed`
349
+ * means every line either became a payout or was refused — read the `counts`.
350
+ */
351
+ export type PayoutBatchStatus =
352
+ | 'received'
353
+ | 'validating'
354
+ | 'awaiting_confirmation'
355
+ | 'creating'
356
+ | 'completed'
357
+ | 'canceled'
358
+ | 'failed'
359
+ | (string & {});
360
+
361
+ export type PayoutBatchItemStatus =
362
+ | 'received'
363
+ | 'invalid'
364
+ | 'validated'
365
+ | 'creating'
366
+ | 'created'
367
+ | 'create_failed'
368
+ | 'canceled'
369
+ /**
370
+ * Outcome unknown (the process died mid-create). NEVER auto-retried —
371
+ * contact support with the batchId rather than resubmitting the line.
372
+ */
373
+ | 'requires_review'
374
+ | (string & {});
375
+
376
+ export interface PayoutBatch {
377
+ batchId: string;
378
+ /** Your own run id, echoed back. */
379
+ externalReferenceId: string | null;
380
+ status: PayoutBatchStatus;
381
+ autoCommit: boolean;
382
+ /** Lines by status. The values sum to the number of lines submitted. */
383
+ counts: Record<PayoutBatchItemStatus, number>;
384
+ /** Present only when `status` is `failed` — the run-level reason. */
385
+ error?: unknown;
386
+ /**
387
+ * Advisory. What the valid lines will draw from your balance, set when
388
+ * validation finishes; null while validation runs and when any line locks
389
+ * the destination side. Compare against `getBalance()` BEFORE confirming.
390
+ */
391
+ estimatedSourceTotal: string | null;
392
+ createdAt: string;
393
+ updatedAt: string;
394
+ completedAt: string | null;
395
+ }
396
+
397
+ export interface PayoutBatchItem {
398
+ /** Zero-based position in the `items` array you submitted. */
399
+ index: number;
400
+ status: PayoutBatchItemStatus;
401
+ /** The line you submitted, echoed back verbatim — join on content. */
402
+ instruction: unknown;
403
+ /** Present on `invalid` and `create_failed` lines. */
404
+ errors?: { code: string; message: string }[];
405
+ /** Present once `created` — the payout to track from here on. */
406
+ payoutId?: string;
407
+ }
408
+
409
+ export interface PayoutBatchArgs {
410
+ /**
411
+ * 1–1,000 payout instructions, each exactly a `POST /payouts` body (the
412
+ * WIRE shape — `expectDestination`, `maxDriftBps` — not the Node-only
413
+ * `PayoutArgs` conveniences, which are translated by `payout()` alone).
414
+ */
415
+ items: unknown[];
416
+ /**
417
+ * Your own run id (a payroll file name, a cycle). Required, and unique per
418
+ * organization: a second batch with the same id is refused with
419
+ * `PAYOUT_BATCH_DUPLICATE_REFERENCE` naming the original, so a submit job
420
+ * that re-runs the same file under a fresh idempotency key cannot pay
421
+ * twice. A corrected resubmission is a new run and needs its own id.
422
+ * 1-128 characters: letters, digits, space, `. _ : -`.
423
+ */
424
+ externalReferenceId: string;
425
+ /**
426
+ * True (the default): a run with zero validation errors proceeds straight
427
+ * to creation. False, or any errors found: the batch holds at
428
+ * `awaiting_confirmation` for `confirmPayoutBatch` / `cancelPayoutBatch`.
429
+ */
430
+ autoCommit?: boolean;
431
+ /**
432
+ * Covers the RUN: resubmitting the same file under the same key returns the
433
+ * same batch, never a second payroll. Persist it before you send.
434
+ */
435
+ idempotencyKey?: string;
436
+ /** Repeat a byte-identical run on purpose. Sets `X-Allow-Duplicate: true`. */
437
+ allowDuplicate?: boolean;
438
+ }
439
+
254
440
  export declare class PayoutsClient {
255
441
  constructor(opts?: PayoutsClientOptions);
256
442
 
@@ -259,8 +445,8 @@ export declare class PayoutsClient {
259
445
  * suite — `expect(avvio.mode).toBe('test')` before a test that sends is the
260
446
  * cheapest guard there is against a live key reaching a payroll fixture.
261
447
  *
262
- * A key that is not recognisably `ak_test_` reports `'live'`, because the two
263
- * wrong answers are not equally bad.
448
+ * The constructor rejects unrecognized and retired credential formats before
449
+ * deriving this value.
264
450
  */
265
451
  readonly mode: 'test' | 'live';
266
452
 
@@ -272,7 +458,7 @@ export declare class PayoutsClient {
272
458
  * caller following the instruction had no typed path to the field and had to
273
459
  * reach for an unsound cast.
274
460
  */
275
- corridors(): Promise<{
461
+ corridors(currency?: string): Promise<{
276
462
  corridors: Corridor[];
277
463
  capabilities: { exactOutput: boolean; indicativePricing: boolean };
278
464
  }>;
@@ -299,9 +485,7 @@ export declare class PayoutsClient {
299
485
  idempotencyKey?: string;
300
486
  }): Promise<Beneficiary>;
301
487
 
302
- listBeneficiaries(args?: {
303
- endUserId?: string;
304
- }): Promise<{
488
+ listBeneficiaries(args?: { endUserId?: string }): Promise<{
305
489
  /** 'organization' or the end user the list was scoped to. Undeclared previously. */
306
490
  scope: string;
307
491
  recipients: Beneficiary[];
@@ -313,6 +497,75 @@ export declare class PayoutsClient {
313
497
  nextCursor?: string | null;
314
498
  }>;
315
499
 
500
+ /** One beneficiary, by the id we returned. `NOT_FOUND` if it is not yours. */
501
+ getBeneficiary(recipientId: string): Promise<Beneficiary>;
502
+
503
+ /**
504
+ * One beneficiary, by YOUR id for them — the same `externalId` that makes
505
+ * creation idempotent. Unique per organization, so this is exactly one
506
+ * beneficiary or a `NOT_FOUND`, rather than paging your whole book.
507
+ */
508
+ getBeneficiaryByExternalId(externalId: string): Promise<Beneficiary>;
509
+
510
+ /**
511
+ * Correct a beneficiary's own details. Only the fields you pass are changed.
512
+ *
513
+ * **Bank details are NOT editable.** The rail validated that account, and
514
+ * swapping it underneath would send the next payout somewhere you never
515
+ * registered — a wrong account is a new payment method, and the old one is
516
+ * deleted with `deleteBeneficiaryMethod`.
517
+ */
518
+ updateBeneficiary(
519
+ recipientId: string,
520
+ patch: {
521
+ type?: 'individual' | 'business';
522
+ name?: string;
523
+ email?: string;
524
+ phone?: string;
525
+ /** ISO-3166 alpha-2. */
526
+ country?: string;
527
+ },
528
+ opts?: { idempotencyKey?: string },
529
+ ): Promise<Beneficiary>;
530
+
531
+ /**
532
+ * Remove a beneficiary and every payment method on it. Payouts already sent
533
+ * are history, not references — this cancels nothing in flight.
534
+ */
535
+ deleteBeneficiary(
536
+ recipientId: string,
537
+ opts?: { idempotencyKey?: string },
538
+ ): Promise<{ success: true }>;
539
+
540
+ /**
541
+ * Remove one way of paying a beneficiary — an account that closed, or one
542
+ * entered wrong. Returns the beneficiary without it; the
543
+ * `destinationAccountId` that method carried stops being payable.
544
+ */
545
+ deleteBeneficiaryMethod(
546
+ recipientId: string,
547
+ methodId: string,
548
+ opts?: { idempotencyKey?: string },
549
+ ): Promise<Beneficiary>;
550
+
551
+ /**
552
+ * The full account details behind one payment method — lists carry only
553
+ * `last4`. Deliberately loose: the fields are the corridor's own, so they
554
+ * differ by rail exactly as `requirements()` does.
555
+ */
556
+ getBeneficiaryMethodDetails(
557
+ recipientId: string,
558
+ methodId: string,
559
+ ): Promise<Record<string, unknown>>;
560
+
561
+ /**
562
+ * The stated payment reasons this organization may use — the vocabulary
563
+ * `purposeOfPayment` on a payout, and `paymentReason` on a quote you accept,
564
+ * are validated against. Read it rather than guessing: a rejected value is a
565
+ * 400 on a payout you have already promised somebody.
566
+ */
567
+ paymentReasons(): Promise<{ payment_reasons: Record<string, unknown>[] }>;
568
+
316
569
  /**
317
570
  * Every event since a watermark, paged for you. At-least-once — `since` is
318
571
  * inclusive, so a resumed run re-reads the row at your watermark. Dedupe on
@@ -322,8 +575,17 @@ export declare class PayoutsClient {
322
575
  since?: string;
323
576
  limit?: number;
324
577
  payoutId?: string;
578
+ /** Event types to include; a string (`'payout.completed,payout.returned'`) or an array. */
579
+ type?: PayoutEventType | PayoutEventType[] | string;
325
580
  }): AsyncIterableIterator<PayoutEvent>;
326
581
 
582
+ /** Every audit event matching a filter, newest first, paged for you. */
583
+ eachAuditEvent(
584
+ args?: AuditEventFilters & {
585
+ limit?: number;
586
+ },
587
+ ): AsyncIterableIterator<AuditEvent>;
588
+
327
589
  /** Every payout matching a filter, paged for you. */
328
590
  eachPayout(args?: {
329
591
  limit?: number;
@@ -401,15 +663,55 @@ export declare class PayoutsClient {
401
663
  limit?: number;
402
664
  /** Everything that ever happened to one payout. */
403
665
  payoutId?: string;
666
+ /**
667
+ * Event types to include; a string (`'payout.completed,payout.returned'`)
668
+ * or an array. Unknown values are a 400, never an empty page.
669
+ */
670
+ type?: PayoutEventType | PayoutEventType[] | string;
404
671
  }): Promise<{
405
672
  data: PayoutEvent[];
406
673
  hasMore: boolean;
407
674
  nextSince: string | null;
408
675
  }>;
409
676
 
677
+ /**
678
+ * Payouts and batch runs waiting on your approvers. `payout()` and
679
+ * `confirmPayoutBatch()` answer 202 with a `PendingApproval` when the
680
+ * organization's policy holds them; this is that queue. Approving and
681
+ * rejecting are dashboard actions — a key cannot, so there is no method.
682
+ */
683
+ listApprovals(args?: {
684
+ status?: PayoutApprovalStatus;
685
+ /** 1–100, default 50. */
686
+ limit?: number;
687
+ }): Promise<{ data: PayoutApproval[] }>;
688
+
689
+ /** One approval, by the `approvalId` a 202 gave you. */
690
+ getApproval(approvalId: string): Promise<PayoutApproval>;
691
+
692
+ /**
693
+ * A PAGE of audit events, newest first — who did what, from where, with which
694
+ * credential. `id` is the cursor. A read-only key may read this.
695
+ */
696
+ listAuditEvents(
697
+ args?: AuditEventFilters & {
698
+ /** 1–100, default 50. */
699
+ limit?: number;
700
+ /** A `nextCursor` we issued: rows strictly older than it. */
701
+ cursor?: string;
702
+ },
703
+ ): Promise<{
704
+ data: AuditEvent[];
705
+ hasMore: boolean;
706
+ nextCursor: string | null;
707
+ }>;
708
+
410
709
  /**
411
710
  * How to fund a payout that came back with `requiresFunding: true`. Your funds
412
711
  * stay in your wallet until you move them.
712
+ *
713
+ * A pure read — poll it freely. It no longer returns `signableOperations`:
714
+ * that are a dashboard action — an API key cannot sign them.
413
715
  */
414
716
  getFunding(payoutId: string): Promise<{
415
717
  payoutId: string;
@@ -417,19 +719,22 @@ export declare class PayoutsClient {
417
719
  currency: string;
418
720
  depositAddress: string;
419
721
  network: string;
420
- expiresAt: string;
421
- /** Present when you can sign through this API instead of broadcasting. */
422
- signableOperations?: unknown[];
722
+ /**
723
+ * Normally `null`. There is no countdown on the deposit address the rail
724
+ * decides when an unfunded payout is over and reports that as the payout's
725
+ * own status, so read `getPayout()` before sending against instructions you
726
+ * fetched a while ago. Declared as a non-null `string` until now, so a
727
+ * TypeScript caller had no reason not to build a timer on it.
728
+ */
729
+ expiresAt: string | null;
423
730
  instructions: string;
424
731
  }>;
425
732
 
426
- /** Proof you sent the funds: a hash you broadcast, or operations you signed. */
733
+ /** Proof you sent the funds: the transaction hash you broadcast. */
427
734
  confirmFunding(
428
735
  payoutId: string,
429
736
  proof: {
430
- transactionHash?: string;
431
- signedOperations?: unknown[];
432
- tamperProofSignature?: string;
737
+ transactionHash: string;
433
738
  idempotencyKey?: string;
434
739
  },
435
740
  ): Promise<Payout>;
@@ -466,40 +771,134 @@ export declare class PayoutsClient {
466
771
  updatedSince?: string;
467
772
  }): Promise<{ data: Payout[]; hasMore: boolean; nextCursor: string | null }>;
468
773
 
774
+ /**
775
+ * Submit up to 1,000 payouts as one run. `202` means RECEIVED, not paid:
776
+ * every line is validated first (nothing priced, nothing debited), then the
777
+ * valid lines become ordinary payouts, each with its own `payoutId`.
778
+ */
779
+ createPayoutBatch(args: PayoutBatchArgs): Promise<PayoutBatch>;
780
+
781
+ /** A PAGE of batches, newest first — not an array. */
782
+ listPayoutBatches(args?: {
783
+ status?: PayoutBatchStatus;
784
+ /** Exact match on your own run id. */
785
+ externalReferenceId?: string;
786
+ /** 1–100, default 50. */
787
+ limit?: number;
788
+ /** A `nextCursor` we issued. Anything else is a 400, never an empty page. */
789
+ cursor?: string;
790
+ }): Promise<{
791
+ data: PayoutBatch[];
792
+ hasMore: boolean;
793
+ nextCursor: string | null;
794
+ }>;
795
+
796
+ /** One run, with its counts. Authoritative for the batch, not the payouts. */
797
+ getPayoutBatch(batchId: string): Promise<PayoutBatch>;
798
+
799
+ /**
800
+ * The lines of a run, instruction echoed back verbatim. `status: 'invalid'`
801
+ * is the review screen after `awaiting_confirmation`; `'created'` joins the
802
+ * run to the payout ledger. The CSV export (`?format=csv`) is a plain file
803
+ * download — this method returns the JSON page.
804
+ */
805
+ listPayoutBatchItems(
806
+ batchId: string,
807
+ args?: {
808
+ status?: PayoutBatchItemStatus;
809
+ /** 1–1,000, default 100. */
810
+ limit?: number;
811
+ /** The `nextCursor` from your previous page — the last line index. */
812
+ cursor?: string;
813
+ },
814
+ ): Promise<{
815
+ data: PayoutBatchItem[];
816
+ hasMore: boolean;
817
+ nextCursor: string | null;
818
+ }>;
819
+
820
+ /**
821
+ * Proceed with the valid lines of a held run. Only legal from
822
+ * `awaiting_confirmation` — anything else is `PAYOUT_BATCH_NOT_CONFIRMABLE`.
823
+ */
824
+ confirmPayoutBatch(
825
+ batchId: string,
826
+ opts?: { idempotencyKey?: string },
827
+ ): Promise<PayoutBatch>;
828
+
829
+ /**
830
+ * Stop a run before any payout exists. Once creation begins the run is
831
+ * committed and this is `PAYOUT_BATCH_NOT_CANCELABLE` — cancel individual
832
+ * payouts via `cancelPayout` instead.
833
+ */
834
+ cancelPayoutBatch(
835
+ batchId: string,
836
+ opts?: { idempotencyKey?: string },
837
+ ): Promise<PayoutBatch>;
838
+
469
839
  fundingAccounts(): Promise<unknown>;
470
840
 
471
- /** What you can currently send. */
472
- balance(): Promise<{ currency: string; amount: string }>;
841
+ /**
842
+ * What your organization is bound by, read live. Read it first: caps,
843
+ * approval threshold, features, rate limits, idempotency windows and the
844
+ * currencies that need a `purposeOfPayment`.
845
+ */
846
+ getPolicy(): Promise<Policy>;
473
847
 
474
848
  /**
475
- * Why the balance is what it is: every movement with the running balance
476
- * after it. Reconcile against this rather than trusting a single number.
849
+ * Everything a payout can draw on (cached up to 60 s): what the payment
850
+ * network holds for you (`provider`) plus the USD stablecoins in your own
851
+ * wallet (`wallet`, per chain, at face value), summed per currency into
852
+ * `balances` with USD as the `amount` headline. Size payouts against that,
853
+ * knowing wallet figures are face value before the network and conversion
854
+ * cost of moving them onto the settling rail — a payout for exactly
855
+ * `amount` can fail to fund when the money must be gathered from several
856
+ * chains first.
857
+ * `ledger` is our own append-only record of the network-held part, with
858
+ * what holds currently reserve. Never summed with the others; a difference
859
+ * from `provider` is what `listBalanceTransactions()` explains.
860
+ * `unavailable` names any source that failed to read: when non-empty the
861
+ * figures are a floor, not the balance — retry before concluding you
862
+ * cannot fund a payout.
477
863
  */
478
- balanceHistory(limit?: number): Promise<{
864
+ balance(): Promise<{
479
865
  currency: string;
480
- balance: string;
481
- entries: {
482
- id: string;
483
- /**
484
- * `reversal` is a returned payout crediting the balance back. It is a
485
- * SEPARATE appended entry, not a rewrite of the original debit an entry
486
- * already emitted never changes.
487
- */
488
- type: 'funding' | 'payout' | 'reversal';
489
- /**
490
- * What this ENTRY records, frozen at the moment it was written — not the
491
- * payout's current status, which would make a historical row mutate.
492
- * `DEBITED` money left, `RETURNED` money came back, `COMPLETED` funding
493
- * landed. For a payout's live fate, read `getPayout()`.
494
- */
495
- status: 'DEBITED' | 'RETURNED' | 'COMPLETED' | (string & {});
496
- amount: string;
497
- balanceAfter: string;
498
- reference?: string;
499
- at: string;
500
- }[];
866
+ amount: string;
867
+ balances: Money[];
868
+ provider: Money[];
869
+ /** Always empty in sandbox. */
870
+ wallet: WalletBalance[];
871
+ /** Sources that failed to read; non-empty results are not cached, so retry. */
872
+ unavailable: Array<'network' | 'wallet'>;
873
+ ledger: LedgerBalance[];
501
874
  }>;
502
875
 
876
+ /**
877
+ * A PAGE of balance transactions, newest first — not an array. Every change
878
+ * to what you can spend, each with `balanceAfter`; `id` is the cursor and
879
+ * the dedupe key. Served on every environment; an empty page means no rows
880
+ * yet, not an error.
881
+ */
882
+ listBalanceTransactions(
883
+ args?: BalanceTransactionFilters & {
884
+ /** 1–100, default 100. */
885
+ limit?: number;
886
+ /** A `nextCursor` we issued: rows strictly older than it. */
887
+ cursor?: string;
888
+ },
889
+ ): Promise<{
890
+ data: BalanceTransaction[];
891
+ hasMore: boolean;
892
+ nextCursor: string | null;
893
+ }>;
894
+
895
+ /** Every balance transaction matching a filter, newest first, paged for you. */
896
+ eachBalanceTransaction(
897
+ args?: BalanceTransactionFilters & {
898
+ limit?: number;
899
+ },
900
+ ): AsyncIterableIterator<BalanceTransaction>;
901
+
503
902
  /** Credit a sandbox balance. Test keys only. */
504
903
  fund(amount?: string, idempotencyKey?: string): Promise<{ balance: string }>;
505
904
 
@@ -528,13 +927,10 @@ export declare class PayoutsClient {
528
927
 
529
928
  /**
530
929
  * Register a sandbox webhook endpoint. The `secret` is returned ONCE and is
531
- * not retrievable afterwards. Test keys only.
930
+ * not retrievable afterwards. Test keys only. Sends no `Idempotency-Key` and
931
+ * is never retried: a stored replay would keep the secret.
532
932
  */
533
- createWebhookEndpoint(args: {
534
- url: string;
535
- events?: string[];
536
- idempotencyKey?: string;
537
- }): Promise<{
933
+ createWebhookEndpoint(args: { url: string; events?: string[] }): Promise<{
538
934
  id: string;
539
935
  url: string;
540
936
  events: string[];
@@ -542,7 +938,53 @@ export declare class PayoutsClient {
542
938
  warning: string;
543
939
  }>;
544
940
 
545
- /** What we sent, what came back, and what we retried. */
941
+ /**
942
+ * The webhook endpoints registered for your organization.
943
+ *
944
+ * Read-only by design, and there is no create/pause/delete counterpart here:
945
+ * a key that could repoint its own webhook URL could redirect every payout
946
+ * notification. The signing secret is never returned — it is shown once, at
947
+ * creation.
948
+ */
949
+ webhookEndpoints(): Promise<
950
+ {
951
+ id: string;
952
+ url: string;
953
+ events: string[];
954
+ /** Non-null while the endpoint is paused or auto-disabled. */
955
+ disabledAt: string | null;
956
+ disabledReason: 'paused_by_owner' | 'auto_disabled_after_failures' | null;
957
+ /** Retry ladders exhausted since the last accepted delivery. */
958
+ consecutiveFailures: number;
959
+ lastSuccessAt: string | null;
960
+ lastFailureAt: string | null;
961
+ createdAt: string;
962
+ }[]
963
+ >;
964
+
965
+ /**
966
+ * The 50 most recent delivery attempts for one endpoint, newest first. No
967
+ * payloads — replay from the dashboard if you need the body, which re-fires
968
+ * under the same `eventId` so your dedupe still holds.
969
+ */
970
+ webhookEndpointDeliveries(endpointId: string): Promise<
971
+ {
972
+ id: string;
973
+ /** The event id: the `svix-id` we sent and the feed row's `id`. Dedupe on this. */
974
+ eventId: string;
975
+ eventType: string;
976
+ attempts: number;
977
+ /** Null until an attempt is accepted. */
978
+ deliveredAt: string | null;
979
+ /** When we will try again. Retries back off over roughly 70 hours. */
980
+ nextAttemptAt: string | null;
981
+ /** Your server's response on the most recent failure. */
982
+ lastError: string | null;
983
+ createdAt: string;
984
+ }[]
985
+ >;
986
+
987
+ /** What we sent, what came back, and what we retried. Sandbox endpoints. */
546
988
  webhookDeliveries(endpointId: string): Promise<unknown>;
547
989
  }
548
990
 
@@ -580,8 +1022,175 @@ export declare function stableKey(...parts: (string | number)[]): string;
580
1022
  * so they cannot be confused, and `scripts/sdk-contract.ts` asserts this one
581
1023
  * against a real delivered event.
582
1024
  */
1025
+ /** What your organization is bound by, from `getPolicy()`. `null` on a cap or threshold means not set. */
1026
+ export interface Policy {
1027
+ /** The id you addressed; a test key sends the live organization id and gets it back. */
1028
+ organizationId: string;
1029
+ /** `test`: nothing reaches a payment network. */
1030
+ mode: 'test' | 'live';
1031
+ /** Effective feature names, sorted. `mass_payouts` enables batches, `developer` enables webhook endpoints. */
1032
+ features: string[];
1033
+ /** USD caps; over one is `422 PAYOUT_LIMIT_EXCEEDED`. Two-decimal strings. */
1034
+ limits: {
1035
+ maxSinglePayoutUsd: string | null;
1036
+ maxDailyPayoutUsd: string | null;
1037
+ /** Counted against `endUser.id`; when set, a payout without an `endUser` is refused. */
1038
+ maxDailyPerEndUserUsd: string | null;
1039
+ };
1040
+ /** When `thresholdUsd` is set, a send above it answers 202 and needs `requiredApprovals` humans. */
1041
+ approvals: {
1042
+ thresholdUsd: string | null;
1043
+ requiredApprovals: number | null;
1044
+ appliesTo: ('payouts' | 'batches' | 'payout_links')[];
1045
+ };
1046
+ purposeOfPayment: {
1047
+ /** Destination currencies that require `purposeOfPayment` on a payout. */
1048
+ requiredForCurrencies: string[];
1049
+ };
1050
+ /**
1051
+ * What your routing can state about its fees BEFORE a quote exists. `null`
1052
+ * on a number means "not published before a quote", never zero. The binding
1053
+ * figure is `fee` on the quote and the payout.
1054
+ */
1055
+ fees: {
1056
+ payout: {
1057
+ /** Basis points of the send for corridors not in `byCurrency`. */
1058
+ bps: number | null;
1059
+ /** Fixed component in USD, two decimals. */
1060
+ fixedUsd: string | null;
1061
+ /** Corridors priced differently from the default, by destination currency. */
1062
+ byCurrency: Record<string, { bps: number; fixedUsd: string | null }>;
1063
+ /** Prose; do not branch on it. */
1064
+ note: string;
1065
+ };
1066
+ };
1067
+ /** Requests per minute per credential, by bucket. */
1068
+ rateLimits: {
1069
+ default: number;
1070
+ payouts: number;
1071
+ batches: number;
1072
+ reads: number;
1073
+ };
1074
+ idempotency: {
1075
+ /** An API key must send `Idempotency-Key` on every money route. */
1076
+ required: boolean;
1077
+ replayWindowDays: number;
1078
+ nearDuplicateWindowMinutes: number;
1079
+ };
1080
+ /** Relative paths with your organization id filled in. */
1081
+ links: { corridors: string; events: string; balanceTransactions: string };
1082
+ }
1083
+
1084
+ export interface LedgerBalance {
1085
+ currency: string;
1086
+ /** What a payout can draw on right now. */
1087
+ available: string;
1088
+ /** Reserved by holds not yet converted to payouts or released. */
1089
+ held: string;
1090
+ /** `available` + `held`. */
1091
+ total: string;
1092
+ }
1093
+
1094
+ /** One USD stablecoin position in your own wallet, on one chain. */
1095
+ export interface WalletBalance {
1096
+ currency: 'USDC' | 'USDT';
1097
+ /** Chain name: Ethereum, Base, Arbitrum, Optimism, Solana. */
1098
+ network: string;
1099
+ /** Token units, up to six decimals. Counted into `balances` at face value. */
1100
+ amount: string;
1101
+ }
1102
+
1103
+ export type BalanceTransactionType =
1104
+ | 'funding'
1105
+ | 'payout'
1106
+ | 'payout_return'
1107
+ | 'hold'
1108
+ | 'hold_release'
1109
+ | 'adjustment';
1110
+
1111
+ export interface BalanceTransactionFilters {
1112
+ /** One type, several, or a comma-separated string. Unknown values are a 400. */
1113
+ type?: BalanceTransactionType | BalanceTransactionType[] | string;
1114
+ /** Everything that moved for one payout. */
1115
+ orderId?: string;
1116
+ currency?: string;
1117
+ /** Inclusive, ISO-8601 with a timezone. */
1118
+ createdAfter?: string;
1119
+ /** Inclusive, ISO-8601 with a timezone. */
1120
+ createdBefore?: string;
1121
+ }
1122
+
1123
+ /**
1124
+ * One append-only change to what you can spend. `amount` is signed and always
1125
+ * means "how this row changed `available`".
1126
+ */
1127
+ export interface BalanceTransaction {
1128
+ /** Monotonic, and the cursor. A string: it can exceed 2^53. */
1129
+ id: string;
1130
+ type: BalanceTransactionType;
1131
+ amount: string;
1132
+ /** Fee portion of `amount`. Null means not known, which is not zero. */
1133
+ fee: string | null;
1134
+ /** The amount less the fee, carrying the amount's sign. Absent when `fee` is null. */
1135
+ net?: string;
1136
+ currency: string;
1137
+ /** `available` after this row. */
1138
+ balanceAfter: string;
1139
+ orderId: string | null;
1140
+ /**
1141
+ * The quote snapshot that sized this row. A `hold` has no `orderId` (it is
1142
+ * placed before the network answers); its `hold_release` and `payout` rows
1143
+ * share this `snapshotId`, which is the join. Null on `funding`/`adjustment`.
1144
+ */
1145
+ snapshotId: string | null;
1146
+ batchId: string | null;
1147
+ reference: string | null;
1148
+ endUserId: string | null;
1149
+ reason: string | null;
1150
+ description: string | null;
1151
+ createdAt: string;
1152
+ }
1153
+
1154
+ /**
1155
+ * Every event the feed and the webhooks carry. `payout.returned` is its OWN
1156
+ * type, not a flavour of `payout.failed` — it is the one event that reverses
1157
+ * something you already booked. New types are added without a major version;
1158
+ * ignore ones you do not handle.
1159
+ */
1160
+ export type PayoutEventType =
1161
+ | 'payout.pending'
1162
+ | 'payout.processing'
1163
+ | 'payout.completed'
1164
+ | 'payout.failed'
1165
+ | 'payout.returned'
1166
+ | 'payout.canceled'
1167
+ | 'payout_batch.awaiting_confirmation'
1168
+ | 'payout_batch.completed'
1169
+ | 'payout_batch.canceled'
1170
+ | 'payout_batch.failed'
1171
+ | 'payout_approval.pending'
1172
+ | 'payout_approval.approved'
1173
+ | 'payout_approval.rejected'
1174
+ | 'payout_approval.expired'
1175
+ | 'payout_approval.executed'
1176
+ | 'payout_approval.execution_failed'
1177
+ | 'webhook_endpoint.disabled'
1178
+ | (string & {});
1179
+
1180
+ /**
1181
+ * The `data` of an event, by family. Discriminate on the row's `type`:
1182
+ * `payout.*` → `WebhookPayout`, `payout_batch.*` → `PayoutBatch`,
1183
+ * `payout_approval.*` → `PayoutApprovalEvent`, `webhook_endpoint.*` →
1184
+ * `WebhookEndpointDisabled`.
1185
+ */
1186
+ export type EventData =
1187
+ | WebhookPayout
1188
+ | PayoutBatch
1189
+ | PayoutApprovalEvent
1190
+ | WebhookEndpointDisabled;
1191
+
583
1192
  export interface PayoutEvent {
584
- /** Stable. Dedupe on this — the feed is at-least-once. */
1193
+ /** Stable. Dedupe on this — the feed is at-least-once. Equals the webhook `svix-id`. */
585
1194
  id: string;
586
1195
  /**
587
1196
  * The cursor, as a decimal STRING. A 64-bit sequence past 2^53 is not
@@ -589,23 +1198,121 @@ export interface PayoutEvent {
589
1198
  * unrecoverable.
590
1199
  */
591
1200
  sequence: string;
592
- /**
593
- * `payout.returned` is its OWN type, not a flavour of `payout.failed` — it is
594
- * the one event that reverses something you already booked. New types are
595
- * added without a major version; ignore ones you do not handle.
596
- */
597
- type:
598
- | 'payout.pending'
599
- | 'payout.processing'
600
- | 'payout.completed'
601
- | 'payout.failed'
602
- | 'payout.returned'
603
- | (string & {});
604
- payoutId: string;
605
- status: PayoutStatus;
1201
+ type: PayoutEventType;
1202
+ /** Null on batch, approval and endpoint events. */
1203
+ payoutId: string | null;
1204
+ /** Set on `payout_batch.*` rows and on a batch approval. */
1205
+ batchId: string | null;
1206
+ /** The resource's status after this transition; a `PayoutStatus` on `payout.*` rows. */
1207
+ status: PayoutStatus | string;
606
1208
  failureCode?: PayoutFailureCode;
607
1209
  fundsReturned?: boolean;
608
1210
  createdAt: string;
1211
+ /** The `data` shape version. Same value as the `Avvio-Webhook-Version` header. */
1212
+ apiVersion: number;
1213
+ /** The webhook body for this event, verbatim. */
1214
+ data: EventData;
1215
+ }
1216
+
1217
+ export type PayoutApprovalStatus =
1218
+ | 'pending'
1219
+ | 'approved'
1220
+ | 'rejected'
1221
+ | 'expired'
1222
+ | 'executing'
1223
+ | 'executed'
1224
+ | 'execution_failed'
1225
+ | 'execution_unknown';
1226
+
1227
+ /** One request for M-of-N human approval. A PRE-payout resource, not a payout status. */
1228
+ export interface PayoutApproval {
1229
+ id: string;
1230
+ kind: 'payout' | 'payout_batch';
1231
+ status: PayoutApprovalStatus;
1232
+ /** M, snapshotted when the request was created. */
1233
+ requiredApprovals: number;
1234
+ /** Approve votes so far. */
1235
+ approvals: number;
1236
+ /** Present once a `payout` approval is `executed` — the payout it became. */
1237
+ payoutId?: string;
1238
+ /** Present on `payout_batch` approvals. */
1239
+ batchId?: string;
1240
+ amount: string | null;
1241
+ currency: string | null;
1242
+ destinationAccountId?: string;
1243
+ /** Present on `execution_failed`. */
1244
+ error?: string;
1245
+ createdAt: string;
1246
+ /** A `pending` request expires 24 hours after creation. */
1247
+ expiresAt: string | null;
1248
+ updatedAt: string;
1249
+ }
1250
+
1251
+ /** The `data` of a `payout_approval.*` event. */
1252
+ export interface PayoutApprovalEvent {
1253
+ approval: PayoutApproval;
1254
+ /** On `payout_approval.executed` — the payout the approval became. */
1255
+ payoutId?: string;
1256
+ }
1257
+
1258
+ /** The `data` of `webhook_endpoint.disabled`, delivered to your OTHER endpoints. */
1259
+ export interface WebhookEndpointDisabled {
1260
+ endpointId: string;
1261
+ url: string;
1262
+ reason: 'auto_disabled_after_failures';
1263
+ consecutiveFailures: number;
1264
+ lastSuccessAt: string | null;
1265
+ disabledAt: string;
1266
+ }
1267
+
1268
+ /**
1269
+ * The 202 body from `payout()` and `confirmPayoutBatch()` when the
1270
+ * organization's policy holds the instruction for its approvers. Nothing was
1271
+ * priced or sent.
1272
+ */
1273
+ export interface PendingApproval {
1274
+ status: 'pending_approval';
1275
+ approvalId: string;
1276
+ /** Same value as `approvalId`; kept for the dashboard client. */
1277
+ approvalRequestId?: string;
1278
+ requiredApprovals: number;
1279
+ expiresAt: string;
1280
+ }
1281
+
1282
+ export interface AuditEventFilters {
1283
+ /** One action, e.g. `payout.create`. */
1284
+ action?: string;
1285
+ /** Everything done to one payout, batch, approval, beneficiary, key or endpoint. */
1286
+ resourceId?: string;
1287
+ /** A key prefix. An unknown prefix is an empty page, not a 404. */
1288
+ apiKey?: string;
1289
+ actorUserId?: string;
1290
+ /** Inclusive, ISO-8601 with a timezone. */
1291
+ createdAfter?: string;
1292
+ /** Inclusive, ISO-8601 with a timezone. */
1293
+ createdBefore?: string;
1294
+ }
1295
+
1296
+ /** One audited mutation. Append-only; never updated. */
1297
+ export interface AuditEvent {
1298
+ /** Monotonic, and the cursor. A decimal string. */
1299
+ id: string;
1300
+ /** e.g. `payout.create`, `payout_batch.confirm`, `recipient.delete`, `api_key.rotate`. New actions are added without a version. */
1301
+ action: string;
1302
+ resourceType: string;
1303
+ /** Null when the request was refused before a resource existed. */
1304
+ resourceId: string | null;
1305
+ /** The key's prefix as you named it. Null for a dashboard action. */
1306
+ apiKeyPrefix: string | null;
1307
+ /** The dashboard user. Null for an API-key action. */
1308
+ actorUserId: string | null;
1309
+ ip: string | null;
1310
+ /** The `x-request-id` of that request; joins to `BalanceTransaction.requestId`. */
1311
+ requestId: string | null;
1312
+ outcome: 'ok' | 'error';
1313
+ /** The error `type` when `outcome` is `error`. */
1314
+ errorType: string | null;
1315
+ createdAt: string;
609
1316
  }
610
1317
 
611
1318
  export interface WebhookPayout {
@@ -628,25 +1335,29 @@ export interface WebhookPayout {
628
1335
  completedAt: string | null;
629
1336
  }
630
1337
 
1338
+ /**
1339
+ * A signed delivery. The envelope is the feed row: the same `id`, `sequence`,
1340
+ * `type`, `createdAt`, `apiVersion` and `data` come back from `listEvents()`,
1341
+ * so a delivery is the trigger to read the feed from the right place.
1342
+ */
631
1343
  export interface WebhookEvent {
632
- /** New types are added without a major version return 2xx for unknown ones. */
633
- /**
634
- * Every type actually delivered, confirmed by capturing raw bodies this
635
- * omitted `payout.returned`, `payout.canceled` and `payout.processing`, all
636
- * three of which arrive. The `(string & {})` escape made the omission
637
- * invisible: an exhaustive switch compiled with no `payout.returned` case,
638
- * and a bank return was silently ignored the exact failure the separate
639
- * type exists to prevent.
640
- */
641
- type:
642
- | 'payout.pending'
643
- | 'payout.processing'
644
- | 'payout.completed'
645
- | 'payout.failed'
646
- | 'payout.returned'
647
- | 'payout.canceled'
648
- | (string & {});
649
- data: WebhookPayout;
1344
+ /** The event id. Equals the `svix-id` header and the feed row's `id`. Dedupe on it. */
1345
+ id: string;
1346
+ /** The feed cursor for this event, as a decimal string. */
1347
+ sequence: string;
1348
+ /**
1349
+ * Every type actually delivered. An earlier version omitted `payout.returned`,
1350
+ * `payout.canceled` and `payout.processing`, all three of which arrive; the
1351
+ * `(string & {})` escape made the omission invisible. Return 2xx for types
1352
+ * you do not handle.
1353
+ */
1354
+ type: PayoutEventType;
1355
+ createdAt: string;
1356
+ /** Which body shape `data` is. Also the `Avvio-Webhook-Version` header. */
1357
+ apiVersion: number;
1358
+ /** False from a sandbox organization's endpoint. */
1359
+ livemode: boolean;
1360
+ data: EventData;
650
1361
  }
651
1362
 
652
1363
  /**
@@ -678,3 +1389,11 @@ export declare function createWebhookHandler(opts: {
678
1389
  /** Reject timestamps outside this window. Defaults to 300. */
679
1390
  toleranceSeconds?: number;
680
1391
  }): (req: any, res: any) => Promise<void>;
1392
+
1393
+ // PARKED request-signing helper. The runtime implementation remains available
1394
+ // for a coordinated restoration, but it is intentionally absent from the
1395
+ // current bearer-key type surface.
1396
+ // export function generateSigningKeyPair(): {
1397
+ // publicKeyPem: string;
1398
+ // privateKeyPem: string;
1399
+ // };