@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/CHANGELOG.md +190 -5
- package/ERRORS.md +120 -58
- package/QUICKSTART.md +162 -54
- package/README.md +136 -37
- package/index.d.ts +801 -82
- package/package.json +11 -5
- package/src/cli.js +204 -20
- package/src/client.js +576 -58
- package/src/mcp.js +279 -29
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avvio/payments",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Pay out to your own customers from your Avvio balance. CLI, MCP server, and Node client
|
|
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://
|
|
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
|
-
"
|
|
50
|
+
"avvio",
|
|
51
51
|
"cross-border",
|
|
52
|
-
"
|
|
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=
|
|
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
|
|
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; --
|
|
56
|
-
|
|
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
|
|
71
|
-
webhook create --url <url> Register an endpoint, print its
|
|
72
|
-
(--events a,b
|
|
73
|
-
webhook deliveries <id>
|
|
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
|
|
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=
|
|
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
|
-
|
|
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
|
-
|
|
499
|
-
|
|
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
|
-
|
|
502
|
-
|
|
503
|
-
:
|
|
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
|
-
|
|
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:
|