@agenzo/token-cli 0.4.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/dist/index.js +268 -359
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -18,6 +18,9 @@ var CODE_NUM = {
|
|
|
18
18
|
AUTH_INVITE_CODE_INVALID: 1004,
|
|
19
19
|
KEY_INVALID: 1101,
|
|
20
20
|
KEY_SCOPE_DENIED: 1102,
|
|
21
|
+
PAYMENT_METHOD_NOT_FOUND: 1401,
|
|
22
|
+
PAYMENT_METHOD_DISABLED: 1402,
|
|
23
|
+
INVALID_PAYMENT_METHOD: 1403,
|
|
21
24
|
RESOURCE_NOT_FOUND: 2001,
|
|
22
25
|
RESOURCE_CONFLICT: 2002,
|
|
23
26
|
RESOURCE_STATE_INVALID: 2003,
|
|
@@ -38,6 +41,23 @@ var CODE_NUM = {
|
|
|
38
41
|
QUOTE_EXPIRED: 4202,
|
|
39
42
|
BOOKING_FAILED: 4203,
|
|
40
43
|
CANCELLATION_NOT_ALLOWED: 4204,
|
|
44
|
+
PRICE_MISMATCH: 4205,
|
|
45
|
+
VEHICLE_CLASS_MISMATCH: 4206,
|
|
46
|
+
PAYMENT_METHOD_REQUIRED: 4207,
|
|
47
|
+
QUOTE_CACHE_UNAVAILABLE: 4208,
|
|
48
|
+
ARREARS_OUTSTANDING: 4209,
|
|
49
|
+
CONTACT_REQUIRED: 4210,
|
|
50
|
+
INVALID_PHONE: 4211,
|
|
51
|
+
SEAT_LIMIT_EXCEEDED: 4213,
|
|
52
|
+
RIDE_NOT_FOUND: 4212,
|
|
53
|
+
NO_AVAILABILITY: 4301,
|
|
54
|
+
PRICE_CHANGED: 4302,
|
|
55
|
+
NAME_FORMAT_INVALID: 4303,
|
|
56
|
+
HOTEL_ORDER_NOT_FOUND: 4304,
|
|
57
|
+
ALREADY_CANCELLED: 4305,
|
|
58
|
+
CHECKOUT_NOT_ALLOWED: 4306,
|
|
59
|
+
CHECKOUT_TASK_NOT_FOUND: 4307,
|
|
60
|
+
PAY_PER_CALL_NOT_AVAILABLE: 4308,
|
|
41
61
|
RATE_LIMITED: 5001,
|
|
42
62
|
UPSTREAM_ERROR: 5101,
|
|
43
63
|
INTERNAL_ERROR: 5201,
|
|
@@ -55,23 +75,29 @@ function codeNum(code) {
|
|
|
55
75
|
return CODE_NUM[code];
|
|
56
76
|
}
|
|
57
77
|
var CliError = class _CliError extends Error {
|
|
58
|
-
constructor(code, message, statusCode, requestId, upstream) {
|
|
78
|
+
constructor(code, message, statusCode, requestId, upstream, backendMessage) {
|
|
59
79
|
super(message);
|
|
60
80
|
this.code = code;
|
|
61
81
|
this.statusCode = statusCode;
|
|
62
82
|
this.requestId = requestId;
|
|
63
83
|
this.upstream = upstream;
|
|
84
|
+
this.backendMessage = backendMessage;
|
|
64
85
|
this.name = "CliError";
|
|
65
86
|
}
|
|
66
87
|
code;
|
|
67
88
|
statusCode;
|
|
68
89
|
requestId;
|
|
69
90
|
upstream;
|
|
91
|
+
backendMessage;
|
|
70
92
|
/**
|
|
71
93
|
* Map a backend ApiError to a CLI-facing code (§8.5 transitional mapping).
|
|
72
|
-
* Backend numeric codes / HTTP status are translated to §8.4 string codes
|
|
73
|
-
*
|
|
74
|
-
*
|
|
94
|
+
* Backend numeric codes / HTTP status are translated to §8.4 string codes.
|
|
95
|
+
* The top-level `message` is always the stable, catalog-owned text for the
|
|
96
|
+
* resolved code (§8.1 principle 4 — never let raw backend text become the
|
|
97
|
+
* primary/stable message). The backend's own message is NOT discarded,
|
|
98
|
+
* though: it is preserved on `backendMessage` and surfaced in the envelope
|
|
99
|
+
* as `backend_message` so the caller can see the real reason (e.g. which
|
|
100
|
+
* field failed validation) alongside the stable code/message.
|
|
75
101
|
*
|
|
76
102
|
* The optional `opts.auth` selects the auth semantics for the 401 mapping:
|
|
77
103
|
* - `'bearer'` (default, admin-cli Bearer Token): 401 → `AUTH_SESSION_EXPIRED`.
|
|
@@ -95,7 +121,8 @@ var CliError = class _CliError extends Error {
|
|
|
95
121
|
STABLE_MESSAGE[error.code] ?? "Request failed. Please check your input and retry.",
|
|
96
122
|
error.statusCode,
|
|
97
123
|
error.requestId,
|
|
98
|
-
error.upstream
|
|
124
|
+
error.upstream,
|
|
125
|
+
error.errorMessage
|
|
99
126
|
);
|
|
100
127
|
}
|
|
101
128
|
const status = error.statusCode;
|
|
@@ -112,7 +139,8 @@ var CliError = class _CliError extends Error {
|
|
|
112
139
|
STABLE_MESSAGE[code] ?? "Request failed. Please check your input and retry.",
|
|
113
140
|
status,
|
|
114
141
|
error.requestId,
|
|
115
|
-
error.upstream
|
|
142
|
+
error.upstream,
|
|
143
|
+
error.errorMessage
|
|
116
144
|
);
|
|
117
145
|
}
|
|
118
146
|
};
|
|
@@ -167,6 +195,9 @@ var STABLE_MESSAGE = {
|
|
|
167
195
|
AUTH_INVITE_CODE_INVALID: "The invitation code is invalid.",
|
|
168
196
|
KEY_INVALID: "The API key is invalid or has been revoked. Please check your --api-key and retry.",
|
|
169
197
|
KEY_SCOPE_DENIED: "This API key does not have the required scope for this command.",
|
|
198
|
+
PAYMENT_METHOD_NOT_FOUND: "Payment method not found.",
|
|
199
|
+
PAYMENT_METHOD_DISABLED: "This payment method has been disabled.",
|
|
200
|
+
INVALID_PAYMENT_METHOD: "The payment method is not available for this operation.",
|
|
170
201
|
RESOURCE_NOT_FOUND: "The resource was not found or does not belong to the current organization.",
|
|
171
202
|
RESOURCE_CONFLICT: "A resource with the same unique value already exists.",
|
|
172
203
|
RESOURCE_STATE_INVALID: "The resource is in a state that does not permit this operation.",
|
|
@@ -188,18 +219,38 @@ var STABLE_MESSAGE = {
|
|
|
188
219
|
VEHICLE_UNAVAILABLE: "No vehicle is available for the requested trip. Please try a different time or class.",
|
|
189
220
|
QUOTE_EXPIRED: "The quote has expired. Please request a new quote and retry.",
|
|
190
221
|
BOOKING_FAILED: "The booking could not be completed. Please retry.",
|
|
222
|
+
PRICE_MISMATCH: "The price does not match the quote. Re-quote and use the exact amount from the quote response.",
|
|
223
|
+
VEHICLE_CLASS_MISMATCH: "The vehicle class does not match the quote. Use the vehicle class from the quote response.",
|
|
224
|
+
PAYMENT_METHOD_REQUIRED: "Pay-per-call billing requires an active payment method. Please add a card first.",
|
|
225
|
+
QUOTE_CACHE_UNAVAILABLE: "Quote cache is temporarily unavailable. Please request a new quote and retry.",
|
|
226
|
+
ARREARS_OUTSTANDING: "You have outstanding arrears. Please settle your balance before booking a new ride.",
|
|
227
|
+
CONTACT_REQUIRED: "Passenger contact information is required. Please provide a passenger name and phone number.",
|
|
228
|
+
INVALID_PHONE: "The passenger phone number is not valid. Please provide a valid international number.",
|
|
229
|
+
SEAT_LIMIT_EXCEEDED: "The number of child/infant/toddler seats exceeds the vehicle capacity. Reduce seats or choose a larger vehicle.",
|
|
230
|
+
RIDE_NOT_FOUND: "The ride order was not found or does not belong to you.",
|
|
191
231
|
CANCELLATION_NOT_ALLOWED: "This order cannot be cancelled in its current state.",
|
|
232
|
+
NO_AVAILABILITY: "No rooms available for the selected hotel and dates.",
|
|
233
|
+
PRICE_CHANGED: "The price changed since the quote. Re-quote and confirm.",
|
|
234
|
+
NAME_FORMAT_INVALID: "The guest name format is not accepted; use Latin letters.",
|
|
235
|
+
HOTEL_ORDER_NOT_FOUND: "Hotel order not found.",
|
|
236
|
+
ALREADY_CANCELLED: "This hotel order is already cancelled.",
|
|
237
|
+
CHECKOUT_NOT_ALLOWED: "A partial check-out is not allowed in the current state.",
|
|
238
|
+
CHECKOUT_TASK_NOT_FOUND: "Check-out application not found.",
|
|
239
|
+
PAY_PER_CALL_NOT_AVAILABLE: "Pay-per-call billing is not available; use monthly_settlement.",
|
|
192
240
|
NOT_IMPLEMENTED: "This operation is not implemented yet."
|
|
193
241
|
};
|
|
194
242
|
function toErrorEnvelope(error) {
|
|
195
243
|
if (error instanceof CliError) {
|
|
244
|
+
const stableMessage = error.message || "Unknown error";
|
|
245
|
+
const backendMessage = error.backendMessage && error.backendMessage !== stableMessage ? error.backendMessage : void 0;
|
|
196
246
|
return {
|
|
197
247
|
error: {
|
|
198
248
|
code: error.code,
|
|
199
249
|
code_num: codeNum(error.code),
|
|
200
|
-
message:
|
|
250
|
+
message: stableMessage,
|
|
201
251
|
...error.requestId ? { request_id: error.requestId } : {},
|
|
202
|
-
...error.upstream ? { upstream: error.upstream } : {}
|
|
252
|
+
...error.upstream ? { upstream: error.upstream } : {},
|
|
253
|
+
...backendMessage ? { backend_message: backendMessage } : {}
|
|
203
254
|
}
|
|
204
255
|
};
|
|
205
256
|
}
|
|
@@ -262,13 +313,27 @@ function isBelow(current, minimum) {
|
|
|
262
313
|
var ApiClient = class {
|
|
263
314
|
baseUrl;
|
|
264
315
|
timeout;
|
|
316
|
+
/**
|
|
317
|
+
* Correlation id for this CLI invocation. Generated once per `ApiClient`
|
|
318
|
+
* instance (i.e. once per command execution — each `apps/*` entrypoint
|
|
319
|
+
* constructs exactly one `ApiClient` per process run) and sent as
|
|
320
|
+
* `X-Request-Id` on every request this client makes. The backend's
|
|
321
|
+
* `RequestIdMiddleware` adopts a client-supplied `X-Request-Id` verbatim as
|
|
322
|
+
* both `trace_id` and `request_id` (only generating its own when the header
|
|
323
|
+
* is absent), so every HTTP call belonging to one CLI operation — including
|
|
324
|
+
* multi-step flows like `payment-methods add`'s create + poll + get — shares
|
|
325
|
+
* a single trace_id server-side and can be followed as one chain in logs.
|
|
326
|
+
*/
|
|
327
|
+
requestId;
|
|
265
328
|
constructor(config) {
|
|
266
329
|
this.baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
267
330
|
this.timeout = config.timeout ?? 6e4;
|
|
331
|
+
this.requestId = crypto.randomUUID();
|
|
268
332
|
}
|
|
269
333
|
buildHeaders(auth) {
|
|
270
334
|
const headers = {
|
|
271
|
-
"User-Agent": `agenzo-admin-cli/${getCurrentVersion()}
|
|
335
|
+
"User-Agent": `agenzo-admin-cli/${getCurrentVersion()}`,
|
|
336
|
+
"X-Request-Id": this.requestId
|
|
272
337
|
};
|
|
273
338
|
if (auth.type === "bearer") {
|
|
274
339
|
headers["Authorization"] = `Bearer ${auth.token}`;
|
|
@@ -560,29 +625,6 @@ var STATUS_ICONS = {
|
|
|
560
625
|
warning: "\u26A0",
|
|
561
626
|
loading: "\u280B"
|
|
562
627
|
};
|
|
563
|
-
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
564
|
-
function createSpinner(message, intervalMs = 60) {
|
|
565
|
-
let frameIdx = 0;
|
|
566
|
-
let currentMessage = message;
|
|
567
|
-
const render2 = () => {
|
|
568
|
-
process.stdout.write(`\r\x1B[K${SPINNER_FRAMES[frameIdx]} ${currentMessage}`);
|
|
569
|
-
frameIdx = (frameIdx + 1) % SPINNER_FRAMES.length;
|
|
570
|
-
};
|
|
571
|
-
render2();
|
|
572
|
-
const timer = setInterval(render2, intervalMs);
|
|
573
|
-
return {
|
|
574
|
-
update(msg) {
|
|
575
|
-
currentMessage = msg;
|
|
576
|
-
},
|
|
577
|
-
stop(type, finalMessage) {
|
|
578
|
-
clearInterval(timer);
|
|
579
|
-
process.stdout.write("\r\x1B[K");
|
|
580
|
-
if (type && finalMessage) {
|
|
581
|
-
console.log(Formatter.status(type, finalMessage));
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
};
|
|
585
|
-
}
|
|
586
628
|
var Formatter = class _Formatter {
|
|
587
629
|
/** Get display width of a string (CJK characters count as 2) */
|
|
588
630
|
static displayWidth(str) {
|
|
@@ -768,6 +810,8 @@ async function collectPaymentMethodParams(type, flags) {
|
|
|
768
810
|
|
|
769
811
|
// src/verb-schema.ts
|
|
770
812
|
var CLI_NAME = "agenzo-token-cli";
|
|
813
|
+
var PAYMENT_METHODS_NOUN = "payment-methods";
|
|
814
|
+
var PAYMENT_TOKENS_NOUN = "payment-tokens";
|
|
771
815
|
function wantsJsonSchema(argv = process.argv) {
|
|
772
816
|
for (let i = 0; i < argv.length; i++) {
|
|
773
817
|
const a = argv[i];
|
|
@@ -790,140 +834,236 @@ function attachSchemaHelp(cmd, schema) {
|
|
|
790
834
|
}
|
|
791
835
|
var pmAddSchema = {
|
|
792
836
|
cli: CLI_NAME,
|
|
793
|
-
noun:
|
|
837
|
+
noun: PAYMENT_METHODS_NOUN,
|
|
794
838
|
verb: "add",
|
|
795
|
-
description: "Add a payment method
|
|
839
|
+
description: "Add a payment method (manual 3DS or Drop-in session)",
|
|
796
840
|
flags: {
|
|
797
|
-
"
|
|
798
|
-
type: "string",
|
|
799
|
-
required: false,
|
|
800
|
-
default: "evo",
|
|
801
|
-
description: 'Payment brand: "evo" (default; 3DS/Drop-in binding) or "unionpay" (UPI Agent Pay enrollment)',
|
|
802
|
-
constraints: "evo | unionpay"
|
|
803
|
-
},
|
|
804
|
-
member: {
|
|
805
|
-
type: "string",
|
|
806
|
-
required: "conditional",
|
|
807
|
-
description: "End-user member id (required when --payment-brand unionpay; identifies which user this card belongs to). Ignored for evo payment brand.",
|
|
808
|
-
source: "from_previous_step",
|
|
809
|
-
from: "user.member_id or auth context"
|
|
810
|
-
},
|
|
841
|
+
type: { type: "string", required: false, default: "card", description: "Payment method type" },
|
|
811
842
|
mode: {
|
|
812
843
|
type: "string",
|
|
813
844
|
required: false,
|
|
814
|
-
default: "
|
|
815
|
-
description: 'Add mode
|
|
845
|
+
default: "manual",
|
|
846
|
+
description: 'Add mode: "manual" (CLI collects card details and polls 3DS) or "dropin" (mint a Drop-in session; the app opens the Drop-in SDK for the user to enter card details securely \u2014 use this mode when the user wants to bind/add a card from chat)',
|
|
816
847
|
constraints: "manual | dropin"
|
|
817
848
|
},
|
|
818
849
|
email: {
|
|
850
|
+
type: "string",
|
|
851
|
+
required: "conditional",
|
|
852
|
+
description: "Manual mode: email for 3DS verification. Dropin mode: email used as the Drop-in session reference. Use the user's login profile email \u2014 never ask in chat."
|
|
853
|
+
},
|
|
854
|
+
"idempotency-key": {
|
|
819
855
|
type: "string",
|
|
820
856
|
required: false,
|
|
821
|
-
description: "
|
|
857
|
+
description: "Idempotency key forwarded verbatim as the Idempotency-Key header (manual mode only)"
|
|
822
858
|
},
|
|
823
859
|
"no-poll": {
|
|
824
860
|
type: "bool",
|
|
825
861
|
required: false,
|
|
826
862
|
default: false,
|
|
827
|
-
description: "Dropin mode: mint session and exit immediately without polling verification status"
|
|
863
|
+
description: "Dropin mode: mint the session, print it, and exit immediately without polling verification status (for server/SDK-driven flows where the front-end completes the binding). Agents integrating with a UI card flow should set this."
|
|
828
864
|
}
|
|
829
865
|
},
|
|
830
866
|
response: {
|
|
831
867
|
id: { type: "string", description: "Payment method id" },
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
}
|
|
836
|
-
"payment-brand": { type: "string", description: "evo or unionpay" },
|
|
837
|
-
session_id: {
|
|
838
|
-
type: "string|absent",
|
|
839
|
-
description: "Drop-in session id (evo/dropin mode only)"
|
|
840
|
-
},
|
|
841
|
-
enroll_url: {
|
|
842
|
-
type: "string|absent",
|
|
843
|
-
description: "UnionPay enrollment URL (unionpay payment brand only) \u2014 user opens this to bind card"
|
|
844
|
-
},
|
|
845
|
-
correlation_id: {
|
|
846
|
-
type: "string|absent",
|
|
847
|
-
description: "src_correlation_id for tracking enrollment result (unionpay payment brand only)"
|
|
848
|
-
}
|
|
868
|
+
session_id: { type: "string|absent", description: "Drop-in session id (dropin mode only) \u2014 pass to the front-end Drop-in SDK, never read aloud to the user" },
|
|
869
|
+
status: { type: "string", description: "PENDING | ACTIVE | FAILED | EXPIRED" },
|
|
870
|
+
brand: { type: "string|absent", description: "Card brand, once known" },
|
|
871
|
+
last4: { type: "string|absent", description: "Card last 4 digits, once known" }
|
|
849
872
|
},
|
|
850
873
|
example: {
|
|
851
|
-
command: "agenzo-token-cli payment-methods add --
|
|
852
|
-
output_summary: "
|
|
874
|
+
command: "agenzo-token-cli payment-methods add --mode dropin --email user@example.com --no-poll",
|
|
875
|
+
output_summary: "Dropin mode returns { id, session_id }. Never read card numbers/CVV/expiry to or from the user (PCI) \u2014 the Drop-in SDK collects them securely."
|
|
853
876
|
},
|
|
854
877
|
error_recovery: {
|
|
855
|
-
PARAM_INVALID:
|
|
856
|
-
UPSTREAM_ERROR: "UPI enrollment initiation failed. Retry after a short delay (may be transient network).",
|
|
857
|
-
MEMBER_REQUIRED: "UnionPay payment brand requires --member <id>. Supply the end-user member identifier."
|
|
878
|
+
PARAM_INVALID: 'Fix the offending flag (mode must be "manual" or "dropin"), then retry.'
|
|
858
879
|
}
|
|
859
880
|
};
|
|
860
881
|
var pmListSchema = {
|
|
861
882
|
cli: CLI_NAME,
|
|
862
|
-
noun:
|
|
883
|
+
noun: PAYMENT_METHODS_NOUN,
|
|
863
884
|
verb: "list",
|
|
864
|
-
description: "List
|
|
865
|
-
flags: {
|
|
885
|
+
description: "List payment methods",
|
|
886
|
+
flags: {
|
|
887
|
+
member: { type: "string", required: false, description: "Filter by member ID" }
|
|
888
|
+
},
|
|
866
889
|
response: {
|
|
867
890
|
payment_methods: {
|
|
868
891
|
type: "array",
|
|
869
|
-
description: "
|
|
892
|
+
description: "Payment methods for the developer (optionally scoped to --member)",
|
|
870
893
|
items: {
|
|
871
894
|
id: { type: "string", description: "Payment method id" },
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
brand: { type: "string|null", description: "Card brand
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
exp_year: { type: "int|null", description: "Expiry year" }
|
|
895
|
+
type: { type: "string", description: "Payment method type (e.g. card)" },
|
|
896
|
+
status: { type: "string", description: "PENDING | ACTIVE | FAILED | DISABLED | EXPIRED" },
|
|
897
|
+
brand: { type: "string|null", description: "Card brand" },
|
|
898
|
+
first6: { type: "string|null", description: "Card first 6 digits" },
|
|
899
|
+
last4: { type: "string|null", description: "Card last 4 digits" }
|
|
878
900
|
}
|
|
879
901
|
}
|
|
880
902
|
},
|
|
881
903
|
example: {
|
|
882
|
-
command: "agenzo-token-cli payment-methods list
|
|
883
|
-
output_summary: "Returns
|
|
904
|
+
command: "agenzo-token-cli payment-methods list",
|
|
905
|
+
output_summary: "Returns payment_methods[]. Check for status=ACTIVE before assuming the user has a usable card."
|
|
884
906
|
}
|
|
885
907
|
};
|
|
886
908
|
var pmGetSchema = {
|
|
887
909
|
cli: CLI_NAME,
|
|
888
|
-
noun:
|
|
910
|
+
noun: PAYMENT_METHODS_NOUN,
|
|
889
911
|
verb: "get",
|
|
890
912
|
description: "Get a payment method by ID",
|
|
891
913
|
flags: {
|
|
892
|
-
|
|
914
|
+
pm_id: { type: "string", required: true, description: "Payment method id (positional argument)" }
|
|
893
915
|
},
|
|
894
916
|
response: {
|
|
895
917
|
id: { type: "string", description: "Payment method id" },
|
|
896
|
-
|
|
897
|
-
|
|
918
|
+
type: { type: "string", description: "Payment method type" },
|
|
919
|
+
status: { type: "string", description: "PENDING | ACTIVE | FAILED | DISABLED | EXPIRED" },
|
|
898
920
|
brand: { type: "string|null", description: "Card brand" },
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
921
|
+
first6: { type: "string|null", description: "Card first 6 digits" },
|
|
922
|
+
last4: { type: "string|null", description: "Card last 4 digits" },
|
|
923
|
+
created_at: { type: "string", description: "Creation time" }
|
|
902
924
|
},
|
|
903
925
|
example: {
|
|
904
|
-
command: "agenzo-token-cli payment-methods get
|
|
905
|
-
output_summary: "Returns
|
|
926
|
+
command: "agenzo-token-cli payment-methods get pm_01H...",
|
|
927
|
+
output_summary: "Returns the payment method detail including status."
|
|
906
928
|
}
|
|
907
929
|
};
|
|
908
930
|
var pmDisableSchema = {
|
|
909
931
|
cli: CLI_NAME,
|
|
910
|
-
noun:
|
|
932
|
+
noun: PAYMENT_METHODS_NOUN,
|
|
911
933
|
verb: "disable",
|
|
912
|
-
description: "Disable a payment method
|
|
934
|
+
description: "Disable a payment method",
|
|
913
935
|
flags: {
|
|
914
|
-
|
|
936
|
+
pm_id: { type: "string", required: true, description: "Payment method id (positional argument)" },
|
|
937
|
+
"idempotency-key": {
|
|
938
|
+
type: "string",
|
|
939
|
+
required: true,
|
|
940
|
+
description: "Idempotency key forwarded verbatim as the Idempotency-Key header"
|
|
941
|
+
}
|
|
915
942
|
},
|
|
916
943
|
response: {
|
|
917
|
-
|
|
918
|
-
|
|
944
|
+
status: { type: "string", description: "Status after disabling (typically DISABLED)" },
|
|
945
|
+
revoked_tokens_count: { type: "int", description: "Number of payment tokens revoked as a side effect" }
|
|
946
|
+
},
|
|
947
|
+
example: {
|
|
948
|
+
command: "agenzo-token-cli payment-methods disable pm_01H... --idempotency-key disable-123",
|
|
949
|
+
output_summary: "Disables the card and revokes any payment tokens issued against it. Only use when the user explicitly requests it."
|
|
950
|
+
},
|
|
951
|
+
error_recovery: {
|
|
952
|
+
PARAM_IDEMPOTENCY_KEY_REQUIRED: "Supply --idempotency-key (1-128 chars [A-Za-z0-9_-]); the CLI never generates one under --yes."
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
var ptCreateSchema = {
|
|
956
|
+
cli: CLI_NAME,
|
|
957
|
+
noun: PAYMENT_TOKENS_NOUN,
|
|
958
|
+
verb: "create",
|
|
959
|
+
description: "Create a payment token (VCN / Network Token / X402)",
|
|
960
|
+
flags: {
|
|
961
|
+
type: { type: "string", required: true, description: "Token type", constraints: "vcn | network-token | x402" },
|
|
962
|
+
"payment-method-id": { type: "string", required: "conditional", description: "Payment method ID to use (or resolved via --card / auto-select from ACTIVE cards)" },
|
|
963
|
+
card: { type: "string", required: false, description: "Match payment method by last 4 digits" },
|
|
964
|
+
member: { type: "string", required: false, description: "Member ID" },
|
|
965
|
+
amount: { type: "string", required: "conditional", description: "Amount in USD (vcn) or USDC (x402)" },
|
|
966
|
+
currency: { type: "string", required: false, description: "Currency (vcn only; server default applies if omitted)" },
|
|
967
|
+
"pay-to": { type: "string", required: "conditional", description: "Pay-to address (x402 only)" },
|
|
968
|
+
nonce: { type: "string", required: "conditional", description: "Nonce (x402 only)" },
|
|
969
|
+
network: { type: "string", required: "conditional", description: "Network (x402 only)" },
|
|
970
|
+
deadline: { type: "string", required: "conditional", description: "Deadline as a Unix timestamp in seconds (x402 only)" },
|
|
971
|
+
"external-tx-id": { type: "string", required: false, description: "External transaction ID" },
|
|
972
|
+
"idempotency-key": {
|
|
973
|
+
type: "string",
|
|
974
|
+
required: true,
|
|
975
|
+
description: "Idempotency key forwarded verbatim as the Idempotency-Key header"
|
|
976
|
+
}
|
|
977
|
+
},
|
|
978
|
+
response: {
|
|
979
|
+
id: { type: "string", description: "Payment token id" },
|
|
980
|
+
type: { type: "string", description: "vcn | network_token | x402" },
|
|
981
|
+
status: { type: "string", description: "Token status" }
|
|
982
|
+
},
|
|
983
|
+
example: {
|
|
984
|
+
command: "agenzo-token-cli payment-tokens create --type vcn --card 1234 --amount 25.00 --idempotency-key tok-123",
|
|
985
|
+
output_summary: "Creates a token; response shape varies by type (nested under vcn / network_token / x402)."
|
|
986
|
+
},
|
|
987
|
+
error_recovery: {
|
|
988
|
+
TOKEN_FEATURE_DISABLED: "This token type is not enabled yet. Tell the user and suggest an alternative type if applicable.",
|
|
989
|
+
CLIENT_NO_PAYMENT_METHOD: "The user has no ACTIVE payment method. Guide them to add one first (payment-methods add --mode dropin).",
|
|
990
|
+
CLIENT_CARD_NOT_MATCHED: "The --card last-4 did not match any ACTIVE card. Call payment-methods list to see available cards."
|
|
991
|
+
}
|
|
992
|
+
};
|
|
993
|
+
var ptListSchema = {
|
|
994
|
+
cli: CLI_NAME,
|
|
995
|
+
noun: PAYMENT_TOKENS_NOUN,
|
|
996
|
+
verb: "list",
|
|
997
|
+
description: "List payment tokens",
|
|
998
|
+
flags: {
|
|
999
|
+
type: { type: "string", required: false, description: "Filter by token type" },
|
|
1000
|
+
member: { type: "string", required: false, description: "Filter by member ID" }
|
|
1001
|
+
},
|
|
1002
|
+
response: {
|
|
1003
|
+
payment_tokens: {
|
|
1004
|
+
type: "array",
|
|
1005
|
+
description: "Payment tokens for the developer (optionally filtered)",
|
|
1006
|
+
items: {
|
|
1007
|
+
id: { type: "string", description: "Token id" },
|
|
1008
|
+
type: { type: "string", description: "vcn | network_token | x402" },
|
|
1009
|
+
status: { type: "string", description: "Token status" }
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
919
1012
|
},
|
|
920
1013
|
example: {
|
|
921
|
-
command: "agenzo-token-cli payment-
|
|
922
|
-
output_summary: "
|
|
1014
|
+
command: "agenzo-token-cli payment-tokens list --type vcn",
|
|
1015
|
+
output_summary: "Returns payment_tokens[]."
|
|
1016
|
+
}
|
|
1017
|
+
};
|
|
1018
|
+
var ptGetSchema = {
|
|
1019
|
+
cli: CLI_NAME,
|
|
1020
|
+
noun: PAYMENT_TOKENS_NOUN,
|
|
1021
|
+
verb: "get",
|
|
1022
|
+
description: "Get a payment token by ID",
|
|
1023
|
+
flags: {
|
|
1024
|
+
payment_token_id: { type: "string", required: true, description: "Payment token id (positional argument)" },
|
|
1025
|
+
reveal: {
|
|
1026
|
+
type: "bool",
|
|
1027
|
+
required: false,
|
|
1028
|
+
default: false,
|
|
1029
|
+
description: "Reveal full VCN card number and CVC in the output. NEVER pass this or read revealed card data aloud to the user (PCI) unless explicitly required by a payment flow."
|
|
1030
|
+
}
|
|
1031
|
+
},
|
|
1032
|
+
response: {
|
|
1033
|
+
id: { type: "string", description: "Token id" },
|
|
1034
|
+
type: { type: "string", description: "vcn | network_token | x402" },
|
|
1035
|
+
status: { type: "string", description: "Token status" }
|
|
1036
|
+
},
|
|
1037
|
+
example: {
|
|
1038
|
+
command: "agenzo-token-cli payment-tokens get pt_01H...",
|
|
1039
|
+
output_summary: "Returns the token detail. VCN PAN/CVC are masked unless --reveal is set."
|
|
1040
|
+
}
|
|
1041
|
+
};
|
|
1042
|
+
var ptRevokeSchema = {
|
|
1043
|
+
cli: CLI_NAME,
|
|
1044
|
+
noun: PAYMENT_TOKENS_NOUN,
|
|
1045
|
+
verb: "revoke",
|
|
1046
|
+
description: "Revoke a payment token",
|
|
1047
|
+
flags: {
|
|
1048
|
+
payment_token_id: { type: "string", required: true, description: "Payment token id (positional argument)" },
|
|
1049
|
+
"idempotency-key": {
|
|
1050
|
+
type: "string",
|
|
1051
|
+
required: true,
|
|
1052
|
+
description: "Idempotency key forwarded verbatim as the Idempotency-Key header"
|
|
1053
|
+
}
|
|
1054
|
+
},
|
|
1055
|
+
response: {
|
|
1056
|
+
id: { type: "string", description: "Token id" },
|
|
1057
|
+
status: { type: "string", description: "Status after revocation" },
|
|
1058
|
+
revoked_at: { type: "string|absent", description: "Revocation time (immediate revoke)" },
|
|
1059
|
+
expires_at: { type: "string|absent", description: "Expiry time (delayed revoke \u2014 x402 cryptogram auto-expires)" }
|
|
1060
|
+
},
|
|
1061
|
+
example: {
|
|
1062
|
+
command: "agenzo-token-cli payment-tokens revoke pt_01H... --idempotency-key revoke-123",
|
|
1063
|
+
output_summary: "Revokes the token immediately, or schedules a delayed revoke for x402 (status stays ACTIVE until expires_at)."
|
|
923
1064
|
},
|
|
924
1065
|
error_recovery: {
|
|
925
|
-
|
|
926
|
-
ALREADY_DISABLED: "Card is already disabled. No action needed."
|
|
1066
|
+
PARAM_IDEMPOTENCY_KEY_REQUIRED: "Supply --idempotency-key (1-128 chars [A-Za-z0-9_-]); the CLI never generates one under --yes."
|
|
927
1067
|
}
|
|
928
1068
|
};
|
|
929
1069
|
|
|
@@ -935,13 +1075,6 @@ var DROPIN_POLL_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
|
935
1075
|
var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["ACTIVE", "FAILED", "EXPIRED"]);
|
|
936
1076
|
function registerAddCommand(parent, deps) {
|
|
937
1077
|
const cmd = parent.command("add").description("Add a payment method (manual 3DS or Drop-in session)").option("--api-key <key>", "API Key for authentication").option("--type <type>", "Payment method type (default: card)", "card").option(
|
|
938
|
-
"--payment-brand <brand>",
|
|
939
|
-
'Payment brand: "evo" (default; existing 3DS/Drop-in binding) or "unionpay" (UPI Agent Pay enrollment)',
|
|
940
|
-
"evo"
|
|
941
|
-
).option(
|
|
942
|
-
"--member <id>",
|
|
943
|
-
"End-user member id (required when --payment-brand unionpay; identifies which end-user this card belongs to)"
|
|
944
|
-
).option(
|
|
945
1078
|
"--mode <mode>",
|
|
946
1079
|
'Add mode: "manual" (default; CLI collects card details and polls 3DS) or "dropin" (mint a Drop-in session and poll until the user finishes adding the payment method in the browser)',
|
|
947
1080
|
"manual"
|
|
@@ -960,17 +1093,6 @@ function registerAddCommand(parent, deps) {
|
|
|
960
1093
|
const opts = cmd.optsWithGlobals();
|
|
961
1094
|
const format = resolveFormat(opts.format);
|
|
962
1095
|
const isYes = Boolean(opts.yes);
|
|
963
|
-
const paymentBrand = String(opts.paymentBrand ?? "evo").toLowerCase();
|
|
964
|
-
if (paymentBrand !== "evo" && paymentBrand !== "unionpay") {
|
|
965
|
-
throw new CliError(
|
|
966
|
-
"PARAM_INVALID",
|
|
967
|
-
`Unknown --payment-brand "${opts.paymentBrand}". Expected "evo" or "unionpay".`
|
|
968
|
-
);
|
|
969
|
-
}
|
|
970
|
-
if (paymentBrand === "unionpay") {
|
|
971
|
-
await handleUnionpayPaymentBrand(deps, opts, format);
|
|
972
|
-
return;
|
|
973
|
-
}
|
|
974
1096
|
const mode = String(opts.mode ?? "manual").toLowerCase();
|
|
975
1097
|
if (mode !== "manual" && mode !== "dropin") {
|
|
976
1098
|
throw new CliError(
|
|
@@ -1094,96 +1216,6 @@ async function handleManualMode(deps, opts, format, isYes) {
|
|
|
1094
1216
|
}
|
|
1095
1217
|
}
|
|
1096
1218
|
}
|
|
1097
|
-
async function handleUnionpayPaymentBrand(deps, opts, format) {
|
|
1098
|
-
const isYes = Boolean(opts.yes);
|
|
1099
|
-
let member;
|
|
1100
|
-
if (opts.member) {
|
|
1101
|
-
member = String(opts.member);
|
|
1102
|
-
} else if (isYes) {
|
|
1103
|
-
throw new CliError(
|
|
1104
|
-
"PARAM_INVALID",
|
|
1105
|
-
"Missing required --member <id> for --payment-brand unionpay (required in --yes mode)"
|
|
1106
|
-
);
|
|
1107
|
-
} else {
|
|
1108
|
-
member = await PromptEngine.resolveInput(void 0, {
|
|
1109
|
-
message: "Member ID (end-user identity this card belongs to):",
|
|
1110
|
-
validate: (v) => v.trim().length > 0 || "Member ID is required for --payment-brand unionpay"
|
|
1111
|
-
});
|
|
1112
|
-
}
|
|
1113
|
-
const apiKey = await PromptEngine.resolveInput(opts.apiKey, {
|
|
1114
|
-
message: "API Key:",
|
|
1115
|
-
type: "password"
|
|
1116
|
-
});
|
|
1117
|
-
const email = await PromptEngine.resolveInput(opts.email, {
|
|
1118
|
-
message: "Email:"
|
|
1119
|
-
});
|
|
1120
|
-
const result = await deps.apiClient.post(
|
|
1121
|
-
"/payment-methods/create",
|
|
1122
|
-
{ type: "api-key", key: apiKey },
|
|
1123
|
-
{ type: "card", payment_brand: "unionpay", member_id: member, email }
|
|
1124
|
-
);
|
|
1125
|
-
if (!result.success) {
|
|
1126
|
-
throw CliError.fromApi(result, { auth: "api-key" });
|
|
1127
|
-
}
|
|
1128
|
-
const pm = result.data;
|
|
1129
|
-
notify(format, "success", "Card binding initiated");
|
|
1130
|
-
const createdResult = {
|
|
1131
|
-
data: pm,
|
|
1132
|
-
text: () => Formatter.keyValue([
|
|
1133
|
-
["ID", pm.id],
|
|
1134
|
-
["Status", pm.status],
|
|
1135
|
-
["Enroll URL", pm.enroll_url ?? "-"],
|
|
1136
|
-
["Correlation ID", pm.correlation_id ?? "-"]
|
|
1137
|
-
])
|
|
1138
|
-
};
|
|
1139
|
-
const configManager = new ConfigManager();
|
|
1140
|
-
await renderWithContext(createdResult, { format }, configManager);
|
|
1141
|
-
notify(
|
|
1142
|
-
format,
|
|
1143
|
-
"info",
|
|
1144
|
-
"Open the Enroll URL in a browser to complete card binding. Waiting for result..."
|
|
1145
|
-
);
|
|
1146
|
-
const UNIONPAY_POLL_INTERVAL_MS = 5e3;
|
|
1147
|
-
const UNIONPAY_POLL_TIMEOUT_MS = 6e4;
|
|
1148
|
-
const startTime = Date.now();
|
|
1149
|
-
const spinner = format !== "json" ? createSpinner("Waiting for card binding result...") : null;
|
|
1150
|
-
while (Date.now() - startTime < UNIONPAY_POLL_TIMEOUT_MS) {
|
|
1151
|
-
await sleep(UNIONPAY_POLL_INTERVAL_MS);
|
|
1152
|
-
const pollResult = await deps.apiClient.get(
|
|
1153
|
-
`/payment-methods/${pm.id}`,
|
|
1154
|
-
{ type: "api-key", key: apiKey }
|
|
1155
|
-
);
|
|
1156
|
-
if (pollResult.success) {
|
|
1157
|
-
const status = pollResult.data.status;
|
|
1158
|
-
if (status === "ACTIVE") {
|
|
1159
|
-
spinner?.stop();
|
|
1160
|
-
notify(format, "success", "Payment method activated");
|
|
1161
|
-
const activatedPm = pollResult.data;
|
|
1162
|
-
const activatedResult = {
|
|
1163
|
-
data: activatedPm,
|
|
1164
|
-
text: () => {
|
|
1165
|
-
const lines = [
|
|
1166
|
-
["ID", activatedPm.id],
|
|
1167
|
-
["Type", activatedPm.type ?? "card"],
|
|
1168
|
-
["Status", activatedPm.status]
|
|
1169
|
-
];
|
|
1170
|
-
if (activatedPm.brand) lines.push(["Brand", activatedPm.brand]);
|
|
1171
|
-
if (activatedPm.first6) lines.push(["First 6", activatedPm.first6]);
|
|
1172
|
-
if (activatedPm.last4) lines.push(["Last 4", activatedPm.last4]);
|
|
1173
|
-
return Formatter.keyValue(lines);
|
|
1174
|
-
}
|
|
1175
|
-
};
|
|
1176
|
-
await renderWithContext(activatedResult, { format }, configManager);
|
|
1177
|
-
return;
|
|
1178
|
-
}
|
|
1179
|
-
if (status === "FAILED") {
|
|
1180
|
-
spinner?.stop("error", "Card binding failed.");
|
|
1181
|
-
return;
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
spinner?.stop("info", "Timed out waiting for card binding result. Check status later with: payment-methods get " + pm.id);
|
|
1186
|
-
}
|
|
1187
1219
|
async function handleDropinMode(deps, opts, format) {
|
|
1188
1220
|
const apiKey = await PromptEngine.resolveInput(opts.apiKey, {
|
|
1189
1221
|
message: "API Key:",
|
|
@@ -1528,9 +1560,8 @@ function registerDropinStatusCommand(parent, deps) {
|
|
|
1528
1560
|
|
|
1529
1561
|
// src/payment-tokens/create.ts
|
|
1530
1562
|
import { confirm, select as select2 } from "@inquirer/prompts";
|
|
1531
|
-
var DEFAULT_NT_FEE_CENTS =
|
|
1563
|
+
var DEFAULT_NT_FEE_CENTS = 50;
|
|
1532
1564
|
var USDC_UNIT = 1e6;
|
|
1533
|
-
var DECIMAL_AMOUNT_RE = /^\d+(\.\d+)?$/;
|
|
1534
1565
|
function mapTokenType(cliType) {
|
|
1535
1566
|
if (cliType === "network-token") return "network_token";
|
|
1536
1567
|
return cliType;
|
|
@@ -1608,23 +1639,14 @@ function formatPaymentToken(data) {
|
|
|
1608
1639
|
lines.push(["Currency", String(vcn.currency || "USD")]);
|
|
1609
1640
|
lines.push(["Status", String(vcn.status || data.status || "")]);
|
|
1610
1641
|
} else if (type === "network_token") {
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
lines.push(["Payment Token ID", String(data.id || nt.id || "")]);
|
|
1620
|
-
lines.push(["Type", "Network Token"]);
|
|
1621
|
-
lines.push(["Brand", String(nt.payment_brand || nt.brand || "")]);
|
|
1622
|
-
const eci = nt.eci || "";
|
|
1623
|
-
if (eci) lines.push(["ECI", String(eci)]);
|
|
1624
|
-
lines.push(["Cryptogram", String(nt.token_cryptogram || nt.cryptogram || "")]);
|
|
1625
|
-
lines.push(["Expiry", String(nt.expiry_date || nt.expiry || "")]);
|
|
1626
|
-
lines.push(["Token Number", String(nt.value || "")]);
|
|
1627
|
-
}
|
|
1642
|
+
const nt = data.network_token ?? data;
|
|
1643
|
+
lines.push(["Payment Token ID", String(data.id || nt.id || "")]);
|
|
1644
|
+
lines.push(["Type", "Network Token"]);
|
|
1645
|
+
lines.push(["Brand", String(nt.payment_brand || nt.brand || "")]);
|
|
1646
|
+
lines.push(["ECI", String(nt.eci || "")]);
|
|
1647
|
+
lines.push(["Cryptogram", String(nt.token_cryptogram || nt.cryptogram || "")]);
|
|
1648
|
+
lines.push(["Expiry", String(nt.expiry_date || nt.expiry || "")]);
|
|
1649
|
+
lines.push(["Value", String(nt.value || "")]);
|
|
1628
1650
|
} else if (type === "x402") {
|
|
1629
1651
|
const x402 = data.x402 ?? data;
|
|
1630
1652
|
lines.push(["Payment Token ID", String(data.id || x402.id || "")]);
|
|
@@ -1644,16 +1666,12 @@ function formatCentsToUsd(cents) {
|
|
|
1644
1666
|
const remainder = cents % 100;
|
|
1645
1667
|
return `${dollars}.${String(remainder).padStart(2, "0")}`;
|
|
1646
1668
|
}
|
|
1647
|
-
function isValidUnionpayAmount(amountStr) {
|
|
1648
|
-
const trimmed = amountStr.trim();
|
|
1649
|
-
if (!DECIMAL_AMOUNT_RE.test(trimmed)) return false;
|
|
1650
|
-
return parseFloat(trimmed) > 0;
|
|
1651
|
-
}
|
|
1652
1669
|
function registerCreateCommand(parent, deps) {
|
|
1653
|
-
const cmd = parent.command("create").description("Create a payment token (VCN / Network Token / X402)").option("--api-key <key>", "API Key for authentication").option("--type <type>", "Token type: vcn | network-token | x402").option("--payment-method-id <id>", "Payment method ID to use").option("--card <last4>", "Match payment method by last 4 digits").option("--member <member_id>", "Member ID").option("--amount <amount>", "Amount in USD (VCN / X402)").option("--currency <currency>", "Currency (default: USD)").option("--pay-to <address>", "Pay-to address (X402)").option("--nonce <nonce>", "Nonce (X402)").option("--network <network>", "Network (X402)").option("--deadline <deadline>", "Deadline (X402)").option("--external-tx-id <id>", "External transaction ID").option(
|
|
1670
|
+
const cmd = parent.command("create").description("Create a payment token (VCN / Network Token / X402)").option("--api-key <key>", "API Key for authentication").option("--type <type>", "Token type: vcn | network-token | x402").option("--payment-method-id <id>", "Payment method ID to use").option("--card <last4>", "Match payment method by last 4 digits").option("--member <member_id>", "Member ID").option("--amount <amount>", "Amount in USD (VCN / X402)").option("--currency <currency>", "Currency (default: USD)").option("--pay-to <address>", "Pay-to address (X402)").option("--nonce <nonce>", "Nonce (X402)").option("--network <network>", "Network (X402)").option("--deadline <deadline>", "Deadline (X402)").option("--external-tx-id <id>", "External transaction ID").option(
|
|
1654
1671
|
"--idempotency-key <key>",
|
|
1655
1672
|
"Idempotency key forwarded verbatim as the Idempotency-Key header"
|
|
1656
1673
|
);
|
|
1674
|
+
attachSchemaHelp(cmd, ptCreateSchema);
|
|
1657
1675
|
cmd.action(async () => {
|
|
1658
1676
|
const opts = cmd.optsWithGlobals();
|
|
1659
1677
|
const format = resolveFormat(opts.format);
|
|
@@ -1677,24 +1695,6 @@ function registerCreateCommand(parent, deps) {
|
|
|
1677
1695
|
card: opts.card,
|
|
1678
1696
|
yes: isYes
|
|
1679
1697
|
});
|
|
1680
|
-
let selectedPmPaymentBrand;
|
|
1681
|
-
if (serverType === "network_token") {
|
|
1682
|
-
const pmResult = await deps.apiClient.get(
|
|
1683
|
-
`/payment-methods/${paymentMethodId}`,
|
|
1684
|
-
{ type: "api-key", key: apiKey }
|
|
1685
|
-
);
|
|
1686
|
-
if (!pmResult.success) {
|
|
1687
|
-
throw CliError.fromApi(pmResult, { auth: "api-key" });
|
|
1688
|
-
}
|
|
1689
|
-
selectedPmPaymentBrand = pmResult.data.payment_brand;
|
|
1690
|
-
if (selectedPmPaymentBrand === "unionpay" && opts.card && !opts.paymentMethodId) {
|
|
1691
|
-
throw new CliError(
|
|
1692
|
-
"PARAM_INVALID",
|
|
1693
|
-
"UnionPay cards must be selected via --payment-method-id; --card (last4 matching) is not supported for unionpay payment brand."
|
|
1694
|
-
);
|
|
1695
|
-
}
|
|
1696
|
-
}
|
|
1697
|
-
const isUnionpayNetworkToken = serverType === "network_token" && selectedPmPaymentBrand === "unionpay";
|
|
1698
1698
|
let member = opts.member;
|
|
1699
1699
|
if (!member && !isYes) {
|
|
1700
1700
|
const memberInput = await PromptEngine.resolveInput(void 0, {
|
|
@@ -1743,66 +1743,10 @@ function registerCreateCommand(parent, deps) {
|
|
|
1743
1743
|
typeBody.currency = opts.currency;
|
|
1744
1744
|
}
|
|
1745
1745
|
} else if (serverType === "network_token") {
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
message: "UnionPay intent amount (USD, e.g. 174.58):",
|
|
1751
|
-
validate: (v) => isValidUnionpayAmount(v) || 'Amount must be a positive decimal, e.g. "174.58"'
|
|
1752
|
-
}
|
|
1753
|
-
);
|
|
1754
|
-
if (!isValidUnionpayAmount(unionpayAmountStr)) {
|
|
1755
|
-
throw new CliError(
|
|
1756
|
-
"PARAM_INVALID",
|
|
1757
|
-
`Invalid --unionpay-amount "${unionpayAmountStr}". Expected a positive decimal string, e.g. "174.58".`
|
|
1758
|
-
);
|
|
1759
|
-
}
|
|
1760
|
-
typeBody.unionpay_amount = unionpayAmountStr.trim();
|
|
1761
|
-
typeBody.recipient_first_name = await PromptEngine.resolveInput(
|
|
1762
|
-
opts.recipientFirstName,
|
|
1763
|
-
{
|
|
1764
|
-
message: "Recipient first name:",
|
|
1765
|
-
validate: (v) => v.trim().length > 0 || "Recipient first name is required"
|
|
1766
|
-
}
|
|
1767
|
-
);
|
|
1768
|
-
typeBody.recipient_last_name = await PromptEngine.resolveInput(
|
|
1769
|
-
opts.recipientLastName,
|
|
1770
|
-
{
|
|
1771
|
-
message: "Recipient last name:",
|
|
1772
|
-
validate: (v) => v.trim().length > 0 || "Recipient last name is required"
|
|
1773
|
-
}
|
|
1774
|
-
);
|
|
1775
|
-
let recipientEmail = opts.recipientEmail;
|
|
1776
|
-
let recipientPhone = opts.recipientPhone;
|
|
1777
|
-
if (!recipientEmail && !recipientPhone) {
|
|
1778
|
-
if (isYes) {
|
|
1779
|
-
throw new CliError(
|
|
1780
|
-
"PARAM_INVALID",
|
|
1781
|
-
"--recipient-email or --recipient-phone is required for unionpay network tokens."
|
|
1782
|
-
);
|
|
1783
|
-
}
|
|
1784
|
-
const emailInput = await PromptEngine.resolveInput(void 0, {
|
|
1785
|
-
message: "Recipient email (optional, press Enter to skip and enter phone instead):",
|
|
1786
|
-
validate: () => true
|
|
1787
|
-
});
|
|
1788
|
-
if (emailInput.trim()) {
|
|
1789
|
-
recipientEmail = emailInput.trim();
|
|
1790
|
-
} else {
|
|
1791
|
-
const phoneInput = await PromptEngine.resolveInput(void 0, {
|
|
1792
|
-
message: "Recipient phone (required, since no email was given):",
|
|
1793
|
-
validate: (v) => v.trim().length > 0 || "Recipient email or phone is required"
|
|
1794
|
-
});
|
|
1795
|
-
recipientPhone = phoneInput.trim();
|
|
1796
|
-
}
|
|
1797
|
-
}
|
|
1798
|
-
if (recipientEmail) typeBody.recipient_email = recipientEmail;
|
|
1799
|
-
if (recipientPhone) typeBody.recipient_phone = recipientPhone;
|
|
1800
|
-
} else {
|
|
1801
|
-
feeCents = await fetchNetworkTokenFee(deps.apiClient, apiKey);
|
|
1802
|
-
freezeAmountCents = feeCents;
|
|
1803
|
-
feeDisplay = `$${formatCentsToUsd(feeCents)}`;
|
|
1804
|
-
freezeDisplay = feeDisplay;
|
|
1805
|
-
}
|
|
1746
|
+
feeCents = await fetchNetworkTokenFee(deps.apiClient, apiKey);
|
|
1747
|
+
freezeAmountCents = feeCents;
|
|
1748
|
+
feeDisplay = `$${formatCentsToUsd(feeCents)}`;
|
|
1749
|
+
freezeDisplay = feeDisplay;
|
|
1806
1750
|
} else if (serverType === "x402") {
|
|
1807
1751
|
const amountStr = await PromptEngine.resolveInput(opts.amount, {
|
|
1808
1752
|
message: "Amount (USDC):",
|
|
@@ -1844,15 +1788,10 @@ function registerCreateCommand(parent, deps) {
|
|
|
1844
1788
|
typeBody.deadline = deadlineNum;
|
|
1845
1789
|
}
|
|
1846
1790
|
if (!isYes) {
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
notify(format, "warning", warningLines.join(" | "));
|
|
1850
|
-
} else if (isUnionpayNetworkToken) {
|
|
1851
|
-
notify(format, "info", "UnionPay: no fee (clearing network not yet enabled)");
|
|
1852
|
-
}
|
|
1853
|
-
const confirmMessage = isUnionpayNetworkToken ? "Proceed with UnionPay network token request?" : "Proceed with token creation?";
|
|
1791
|
+
const warningLines = [`Freeze: ${freezeDisplay}`, `Fee: ${feeDisplay}`];
|
|
1792
|
+
notify(format, "warning", warningLines.join(" | "));
|
|
1854
1793
|
const confirmed = await confirm({
|
|
1855
|
-
message:
|
|
1794
|
+
message: "Proceed with token creation?",
|
|
1856
1795
|
default: true
|
|
1857
1796
|
});
|
|
1858
1797
|
if (!confirmed) {
|
|
@@ -1909,42 +1848,6 @@ function registerCreateCommand(parent, deps) {
|
|
|
1909
1848
|
}
|
|
1910
1849
|
};
|
|
1911
1850
|
await renderWithContext(commandResult, { format }, configManager);
|
|
1912
|
-
if (isUnionpayNetworkToken) {
|
|
1913
|
-
notify(
|
|
1914
|
-
format,
|
|
1915
|
-
"info",
|
|
1916
|
-
"Open the Checkout URL to complete the UnionPay payment verification. Waiting for result..."
|
|
1917
|
-
);
|
|
1918
|
-
const UNIONPAY_TOKEN_POLL_INTERVAL_MS = 5e3;
|
|
1919
|
-
const UNIONPAY_TOKEN_POLL_TIMEOUT_MS = 6e4;
|
|
1920
|
-
const pollStart = Date.now();
|
|
1921
|
-
const tokenId = tokenData.id;
|
|
1922
|
-
while (Date.now() - pollStart < UNIONPAY_TOKEN_POLL_TIMEOUT_MS) {
|
|
1923
|
-
await new Promise((resolve) => setTimeout(resolve, UNIONPAY_TOKEN_POLL_INTERVAL_MS));
|
|
1924
|
-
const pollResult = await deps.apiClient.get(
|
|
1925
|
-
`/payment-tokens/${tokenId}`,
|
|
1926
|
-
{ type: "api-key", key: apiKey }
|
|
1927
|
-
);
|
|
1928
|
-
if (pollResult.success) {
|
|
1929
|
-
const status = pollResult.data.status;
|
|
1930
|
-
if (status === "ACTIVE") {
|
|
1931
|
-
notify(format, "success", "UnionPay network token activated!");
|
|
1932
|
-
if (!pollResult.data.type) pollResult.data.type = serverType;
|
|
1933
|
-
const activatedResult = {
|
|
1934
|
-
data: pollResult.data,
|
|
1935
|
-
text: () => formatPaymentToken(pollResult.data)
|
|
1936
|
-
};
|
|
1937
|
-
await renderWithContext(activatedResult, { format }, configManager);
|
|
1938
|
-
return;
|
|
1939
|
-
}
|
|
1940
|
-
if (status === "FAILED") {
|
|
1941
|
-
notify(format, "error", "UnionPay network token failed.");
|
|
1942
|
-
return;
|
|
1943
|
-
}
|
|
1944
|
-
}
|
|
1945
|
-
}
|
|
1946
|
-
notify(format, "info", `Timed out waiting for token activation. Check status later with: payment-tokens get ${tokenId}`);
|
|
1947
|
-
}
|
|
1948
1851
|
});
|
|
1949
1852
|
}
|
|
1950
1853
|
async function checkVcnFeatureEnabled(apiClient, apiKey) {
|
|
@@ -2000,6 +1903,7 @@ function getSummary(token) {
|
|
|
2000
1903
|
}
|
|
2001
1904
|
function registerListCommand2(parent, deps) {
|
|
2002
1905
|
const cmd = parent.command("list").description("List payment tokens").option("--api-key <key>", "API Key for authentication").option("--type <type>", "Filter by token type").option("--member <member_id>", "Filter by member ID");
|
|
1906
|
+
attachSchemaHelp(cmd, ptListSchema);
|
|
2003
1907
|
cmd.action(async () => {
|
|
2004
1908
|
const opts = cmd.optsWithGlobals();
|
|
2005
1909
|
const format = resolveFormat(opts.format);
|
|
@@ -2136,6 +2040,7 @@ function formatCentsPlain(cents) {
|
|
|
2136
2040
|
}
|
|
2137
2041
|
function registerGetCommand2(parent, deps) {
|
|
2138
2042
|
const cmd = parent.command("get <payment_token_id>").description("Get a payment token by ID").option("--api-key <key>", "API key for authentication").option("--reveal", "Reveal full VCN card number and CVC in the output");
|
|
2043
|
+
attachSchemaHelp(cmd, ptGetSchema);
|
|
2139
2044
|
cmd.action(async (paymentTokenId) => {
|
|
2140
2045
|
const opts = cmd.optsWithGlobals();
|
|
2141
2046
|
const format = resolveFormat(opts.format);
|
|
@@ -2167,6 +2072,7 @@ function registerRevokeCommand(parent, deps) {
|
|
|
2167
2072
|
"--idempotency-key <key>",
|
|
2168
2073
|
"Idempotency key forwarded verbatim as the Idempotency-Key header"
|
|
2169
2074
|
);
|
|
2075
|
+
attachSchemaHelp(cmd, ptRevokeSchema);
|
|
2170
2076
|
cmd.action(async (paymentTokenId) => {
|
|
2171
2077
|
const opts = cmd.optsWithGlobals();
|
|
2172
2078
|
const format = resolveFormat(opts.format);
|
|
@@ -2277,6 +2183,9 @@ function reportError(error) {
|
|
|
2277
2183
|
console.error(
|
|
2278
2184
|
Formatter.status("error", `[${envelope.error.code_num}] ${envelope.error.message}`)
|
|
2279
2185
|
);
|
|
2186
|
+
if (envelope.error.upstream) {
|
|
2187
|
+
console.error(` \u21B3 [${envelope.error.upstream.code}] ${envelope.error.upstream.message}`);
|
|
2188
|
+
}
|
|
2280
2189
|
if (!(error instanceof CliError) && process.argv.includes("--verbose")) {
|
|
2281
2190
|
console.error(error);
|
|
2282
2191
|
}
|