@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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@avvio/payments",
3
- "version": "0.1.0",
4
- "description": "Pay out to your own customers from your Avvio balance. CLI, MCP server, and Node client zero dependencies.",
3
+ "version": "0.5.0",
4
+ "description": "Pay out to your own customers from your Avvio balance. CLI, MCP server, and Node client \u2014 zero dependencies.",
5
5
  "license": "MIT",
6
6
  "engines": {
7
7
  "node": ">=18"
@@ -11,7 +11,7 @@
11
11
  "url": "git+https://github.com/anzolabs/anzolabs-B2B-backend.git",
12
12
  "directory": "packages/avvio-payments"
13
13
  },
14
- "homepage": "https://avvio-docs.pages.dev",
14
+ "homepage": "https://docs.avvio.xyz",
15
15
  "bugs": {
16
16
  "url": "https://github.com/anzolabs/anzolabs-B2B-backend/issues",
17
17
  "email": "support@avvio.xyz"
@@ -47,9 +47,15 @@
47
47
  "test": "node --test test/*.test.js"
48
48
  },
49
49
  "keywords": [
50
- "payouts",
50
+ "avvio",
51
51
  "cross-border",
52
- "mcp"
52
+ "cross-border-payments",
53
+ "mass-payments",
54
+ "mcp",
55
+ "payout-api",
56
+ "payouts",
57
+ "stablecoin",
58
+ "usdc"
53
59
  ],
54
60
  "types": "index.d.ts"
55
61
  }
package/src/cli.js CHANGED
@@ -20,8 +20,10 @@ avvio-payments — pay out to your customers from your balance
20
20
  mcp Run as an MCP server over stdio (for agents)
21
21
 
22
22
  Discovery
23
+ policy Your caps, approval threshold, features and rate limits: read first
23
24
  corridors Currencies you can pay out to
24
25
  requirements <CCY> Fields a beneficiary in that currency needs
26
+ payment-reasons What --purpose may say, where one is required
25
27
 
26
28
  Pricing
27
29
  quote --amount 200 --to MXN What the recipient gets, and the fee
@@ -30,30 +32,52 @@ avvio-payments — pay out to your customers from your balance
30
32
  beneficiary create --name "Maria Gonzalez" --email maria@example.com \\
31
33
  --currency MXN --end-user emp_42 \\
32
34
  --external-id emp42_maria \\
33
- --field clabeNumber=012345678901234567
35
+ --field clabeNumber=012180000080004471
34
36
  [--country MX] [--external-id <your id>] external-id makes a repeat
35
37
  create return the existing
36
38
  beneficiary instead of
37
39
  registering a second account
38
40
  beneficiary list [--end-user emp_42] [--limit 50] [--cursor <id>]
41
+ beneficiary get <id> | beneficiary get --external-id emp42_maria
42
+ beneficiary update <id> [--name] [--email] [--phone] [--country] [--type]
43
+ Contact details only. Bank details are not
44
+ editable — register a new method instead
45
+ beneficiary delete <id> Removes them and every method on them
46
+ beneficiary method delete|details <id> <methodId>
47
+ Drop one account, or read the whole one on file
39
48
 
40
49
  Sandbox
41
50
  fund [--amount 5000] Credit your test balance
42
- balance [--history] What you can send; --history explains every change
51
+ balance What you can send (network figure and our ledger)
43
52
 
44
53
  Paying
45
54
  pay --amount 200 --to <destinationAccountId> --end-user emp_42 \\
46
55
  [--expect <destinationAmount from quote>] [--end-user-name "Ana Lopez"] \\
47
56
  [--reference ZZ-1] [--idempotency-key k1] [--max-drift-bps 200]
57
+ [--exact] --amount is what they RECEIVE, in their currency
58
+ [--worth] --amount is what they receive, in YOURS; fees on top
48
59
  cancel <payoutId> stop a payout that has not been funded yet
49
60
  status <payoutId> [--watch] --watch polls until it stops moving
50
61
  payouts Recent payouts
51
62
 
52
63
  Reconciliation
53
- events [--since <sequence>] [--limit N] [--payout-id ID] [--follow]
64
+ events [--since <sequence>] [--limit N] [--payout-id ID] [--type a,b] [--follow]
54
65
  Every transition, in order. Carry the returned
55
- nextSince back as --since; --follow polls and
56
- prints new rows as they land (--interval SECONDS)
66
+ nextSince back as --since; --type narrows to the
67
+ families you book; --follow polls and prints new
68
+ rows as they land (--interval SECONDS)
69
+ approvals [--status pending] Payouts and runs waiting on your approvers
70
+ (a 202 from pay or a batch confirm). Deciding
71
+ one is a dashboard action, not a command
72
+ approval <approvalId> One approval; executed ones name the payoutId
73
+ audit-events [--cursor <id>] [--limit N] [--action payout.create]
74
+ [--resource-id ID] [--api-key <prefix>] [--actor <userId>]
75
+ [--after ISO] [--before ISO]
76
+ Who did what, with which credential, newest first
77
+ balance-transactions [--cursor <id>] [--limit N] [--type payout,funding]
78
+ [--order-id ID] [--currency USD] [--after ISO] [--before ISO]
79
+ Every change to your balance, newest first, with
80
+ balanceAfter. Carry nextCursor back as --cursor
57
81
 
58
82
  Funding
59
83
  funding Where to wire money to top up your balance
@@ -61,19 +85,24 @@ avvio-payments — pay out to your customers from your balance
61
85
  yourself (requiresFunding: true)
62
86
  funding confirm <payoutId> --tx <hash>
63
87
  Report the transfer you already sent
88
+ this API. Takes a quote — not for a loop
64
89
 
65
90
  Payout links
66
91
  link create --amount --to --end-user
67
92
  Mint a one-time link; the recipient enters their
68
93
  own bank details (--reference, --expires MINUTES)
69
94
 
70
- Webhooks (sandbox)
71
- webhook create --url <url> Register an endpoint, print its signing secret
72
- (--events a,b to filter; localhost ok in sandbox)
73
- webhook deliveries <id> What we sent, what came back, what we retried
95
+ Webhooks
96
+ webhook create --url <url> Sandbox only. Register an endpoint, print its
97
+ signing secret (--events a,b; localhost ok)
98
+ webhook deliveries <id> Sandbox endpoint: what we sent and retried
99
+ webhook endpoints Endpoints registered for your org (read-only —
100
+ registering and pausing are dashboard actions)
101
+ webhook attempts <id> Last 50 delivery attempts for a registered
102
+ endpoint: eventId, attempts, lastError
74
103
 
75
104
  Configuration (environment)
76
- AVVIO_API_KEY required Server-side only. Never ship it to a browser or a phone.
105
+ AVVIO_API_KEY required Complete avvio_live_* or avvio_test_* bearer key
77
106
  AVVIO_ORG_ID required
78
107
  AVVIO_BASE_URL optional
79
108
 
@@ -145,13 +174,26 @@ async function doctor(json) {
145
174
  const add = (name, ok, detail) => checks.push({ name, ok, detail });
146
175
 
147
176
  const key = process.env.AVVIO_API_KEY;
177
+ // PARKED request-signing configuration:
178
+ // const privateKey = process.env.AVVIO_PRIVATE_KEY;
148
179
  const org = process.env.AVVIO_ORG_ID;
149
180
  const base = process.env.AVVIO_BASE_URL || '(default)';
150
181
 
151
182
  add('AVVIO_API_KEY set', !!key, key ? `${key.slice(0, 16)}…` : 'missing');
183
+ const bearerKey =
184
+ /^avvio_(?:live|test)_[0-9a-f]{32}_[A-Za-z0-9_-]{43}$/.test(key || '');
185
+ add(
186
+ 'AVVIO_API_KEY bearer format',
187
+ bearerKey,
188
+ bearerKey
189
+ ? 'valid complete bearer key'
190
+ : 'legacy or unrecognized credential',
191
+ );
192
+ // PARKED request-signing check:
193
+ // add('AVVIO_PRIVATE_KEY set', !!privateKey, ...);
152
194
  add('AVVIO_ORG_ID set', !!org, org || 'missing');
153
195
  add('AVVIO_BASE_URL', true, base);
154
- if (!key || !org) {
196
+ if (!key || !bearerKey || !org) {
155
197
  report(checks, json);
156
198
  process.exitCode = 1;
157
199
  return;
@@ -272,7 +314,7 @@ Send your first payout — five commands, about two minutes.
272
314
  4. Create who you're paying
273
315
  avvio-payments beneficiary create --name "Maria Gonzalez" \\
274
316
  --email maria@example.com --currency MXN --end-user employee_42 \\
275
- --field clabeNumber=012345678901234567
317
+ --field clabeNumber=012180000080004471
276
318
 
277
319
  5. Price it, then send it
278
320
  avvio-payments quote --amount 200 --to MXN
@@ -367,19 +409,98 @@ async function run(argv) {
367
409
  json,
368
410
  );
369
411
  }
370
- throw new Error('Usage: beneficiary <create|list>');
412
+ if (sub === 'get') {
413
+ // Two ways in, because a partner holds one of two ids: ours, or theirs.
414
+ // `--external-id` is the one they already have in their own database.
415
+ const id = positional[2];
416
+ const external = flags['external-id'];
417
+ if (external && external !== true) {
418
+ return out(await client.getBeneficiaryByExternalId(external), json);
419
+ }
420
+ if (!id) {
421
+ throw new Error(
422
+ 'Usage: beneficiary get <recipientId> | beneficiary get --external-id <your id>',
423
+ );
424
+ }
425
+ return out(await client.getBeneficiary(id), json);
426
+ }
427
+ if (sub === 'update') {
428
+ const id = positional[2];
429
+ if (!id) {
430
+ throw new Error(
431
+ 'Usage: beneficiary update <recipientId> [--name] [--email] [--phone] [--country] [--type]',
432
+ );
433
+ }
434
+ const patch = {};
435
+ for (const [flag, key] of [
436
+ ['name', 'name'],
437
+ ['email', 'email'],
438
+ ['phone', 'phone'],
439
+ ['country', 'country'],
440
+ ['type', 'type'],
441
+ ]) {
442
+ if (flags[flag] !== undefined && flags[flag] !== true) {
443
+ patch[key] = flags[flag];
444
+ }
445
+ }
446
+ if (!Object.keys(patch).length) {
447
+ // An empty PATCH is a round trip that changes nothing and reads like
448
+ // it worked. Say which fields exist rather than letting someone
449
+ // conclude the update was applied.
450
+ throw new Error(
451
+ 'Nothing to update. Pass at least one of --name --email --phone --country --type.\n' +
452
+ 'Bank details are not editable: register a new payment method and delete the old one.',
453
+ );
454
+ }
455
+ return out(await client.updateBeneficiary(id, patch), json);
456
+ }
457
+ if (sub === 'delete') {
458
+ const id = positional[2];
459
+ if (!id) throw new Error('Usage: beneficiary delete <recipientId>');
460
+ return out(await client.deleteBeneficiary(id), json);
461
+ }
462
+ if (sub === 'method') {
463
+ const action = positional[2];
464
+ const id = positional[3];
465
+ const methodId = positional[4];
466
+ if (!id || !methodId || !['delete', 'details'].includes(action)) {
467
+ throw new Error(
468
+ 'Usage: beneficiary method <delete|details> <recipientId> <methodId>',
469
+ );
470
+ }
471
+ return out(
472
+ action === 'delete'
473
+ ? await client.deleteBeneficiaryMethod(id, methodId)
474
+ : await client.getBeneficiaryMethodDetails(id, methodId),
475
+ json,
476
+ );
477
+ }
478
+ throw new Error(
479
+ 'Usage: beneficiary <create|list|get|update|delete|method>',
480
+ );
371
481
  }
372
482
 
483
+ case 'payment-reasons':
484
+ return out(await client.paymentReasons(), json);
485
+
373
486
  case 'pay': {
374
487
  if (!flags.amount || !flags.to) {
375
488
  throw new Error(
376
- 'Usage: pay --amount 200 --to <destinationAccountId> [--expect 3410.00]',
489
+ 'Usage: pay --amount 200 --to <destinationAccountId> [--expect 3410.00] [--exact|--worth]',
377
490
  );
378
491
  }
379
492
  return out(
380
493
  await client.payout({
381
494
  amount: flags.amount,
382
495
  destinationAccountId: flags.to,
496
+ // `--exact` reads better at a prompt than `--amount-leg destination`.
497
+ // `--worth` is the same promise with the figure left in the sender's
498
+ // own currency, which is how a wage is denominated.
499
+ ...(flags.worth
500
+ ? { amountLeg: 'source_net' }
501
+ : flags.exact
502
+ ? { amountLeg: 'destination' }
503
+ : {}),
383
504
  reference: flags.reference,
384
505
  purposeOfPayment: flags.purpose,
385
506
  expectDestination: flags.expect,
@@ -443,6 +564,7 @@ async function run(argv) {
443
564
  since: flags.since,
444
565
  limit: flags.limit ? Number(flags.limit) : undefined,
445
566
  payoutId: flags['payout-id'],
567
+ type: flags.type,
446
568
  };
447
569
  if (!flags.follow) return out(await client.listEvents(args), json);
448
570
 
@@ -478,6 +600,37 @@ async function run(argv) {
478
600
  case 'payouts':
479
601
  return out(await client.listPayouts(), json);
480
602
 
603
+ case 'approvals':
604
+ return out(
605
+ await client.listApprovals({
606
+ status: flags.status,
607
+ limit: flags.limit ? Number(flags.limit) : undefined,
608
+ }),
609
+ json,
610
+ );
611
+
612
+ case 'approval': {
613
+ const id = positional[1];
614
+ if (!id) throw new Error('Usage: approval <approvalId>');
615
+ return out(await client.getApproval(id), json);
616
+ }
617
+
618
+ case 'audit-events':
619
+ // Newest first, one page per call; carry nextCursor back as --cursor.
620
+ return out(
621
+ await client.listAuditEvents({
622
+ cursor: flags.cursor,
623
+ limit: flags.limit ? Number(flags.limit) : undefined,
624
+ action: flags.action,
625
+ resourceId: flags['resource-id'],
626
+ apiKey: flags['api-key'],
627
+ actorUserId: flags.actor,
628
+ createdAfter: flags.after,
629
+ createdBefore: flags.before,
630
+ }),
631
+ json,
632
+ );
633
+
481
634
  case 'cancel': {
482
635
  // The recovery an operator actually reaches for: a payout created for the
483
636
  // wrong amount, before any money has been sent.
@@ -494,13 +647,26 @@ async function run(argv) {
494
647
  return out(res, json);
495
648
  }
496
649
 
650
+ case 'policy':
651
+ // Caps, approval threshold, features, rate limits: read before sending.
652
+ return out(await client.getPolicy(), json);
653
+
497
654
  case 'balance':
498
- // `--history` answers "why did my balance change", which a single number
499
- // never can.
655
+ return out(await client.balance(), json);
656
+
657
+ case 'balance-transactions':
658
+ // "Why did my balance change": every ledger row, holds included, newest
659
+ // first. One page per call; carry nextCursor back as --cursor.
500
660
  return out(
501
- flags.history
502
- ? await client.balanceHistory(flags.limit ? Number(flags.limit) : undefined)
503
- : await client.balance(),
661
+ await client.listBalanceTransactions({
662
+ cursor: flags.cursor,
663
+ limit: flags.limit ? Number(flags.limit) : undefined,
664
+ type: flags.type,
665
+ orderId: flags['order-id'],
666
+ currency: flags.currency,
667
+ createdAfter: flags.after,
668
+ createdBefore: flags.before,
669
+ }),
504
670
  json,
505
671
  );
506
672
 
@@ -589,7 +755,25 @@ async function run(argv) {
589
755
  if (!id) throw new Error('webhook deliveries needs an endpoint id');
590
756
  return out(await client.webhookDeliveries(id), json);
591
757
  }
592
- throw new Error('webhook create --url <url> | webhook deliveries <id>');
758
+ if (sub === 'endpoints') {
759
+ return out(await client.webhookEndpoints(), json);
760
+ }
761
+ if (sub === 'attempts') {
762
+ // Separate from `deliveries` rather than replacing it: that one reads a
763
+ // sandbox registration, this one reads a registered org endpoint. Same
764
+ // question, two different stores, and quietly repointing the existing
765
+ // command would answer about the wrong one.
766
+ const id = positional[2];
767
+ if (!id) {
768
+ throw new Error(
769
+ 'webhook attempts needs an endpoint id — run: webhook endpoints',
770
+ );
771
+ }
772
+ return out(await client.webhookEndpointDeliveries(id), json);
773
+ }
774
+ throw new Error(
775
+ 'webhook <create|deliveries|endpoints|attempts>',
776
+ );
593
777
  }
594
778
 
595
779
  default: