@agenzo/token-cli 0.3.3 → 0.4.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 +457 -22
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -560,6 +560,29 @@ var STATUS_ICONS = {
|
|
|
560
560
|
warning: "\u26A0",
|
|
561
561
|
loading: "\u280B"
|
|
562
562
|
};
|
|
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
|
+
}
|
|
563
586
|
var Formatter = class _Formatter {
|
|
564
587
|
/** Get display width of a string (CJK characters count as 2) */
|
|
565
588
|
static displayWidth(str) {
|
|
@@ -697,10 +720,6 @@ var PromptEngine = class {
|
|
|
697
720
|
if (flagValue !== void 0) {
|
|
698
721
|
return flagValue;
|
|
699
722
|
}
|
|
700
|
-
const envKey = process.env.AGENZO_API_KEY;
|
|
701
|
-
if (envKey) {
|
|
702
|
-
return envKey;
|
|
703
|
-
}
|
|
704
723
|
if (config.type === "password") {
|
|
705
724
|
return password({ message: config.message, mask: "*" });
|
|
706
725
|
}
|
|
@@ -747,6 +766,167 @@ async function collectPaymentMethodParams(type, flags) {
|
|
|
747
766
|
return params;
|
|
748
767
|
}
|
|
749
768
|
|
|
769
|
+
// src/verb-schema.ts
|
|
770
|
+
var CLI_NAME = "agenzo-token-cli";
|
|
771
|
+
function wantsJsonSchema(argv = process.argv) {
|
|
772
|
+
for (let i = 0; i < argv.length; i++) {
|
|
773
|
+
const a = argv[i];
|
|
774
|
+
if (a === "--format=json") return true;
|
|
775
|
+
if (a === "--format" && argv[i + 1] === "json") return true;
|
|
776
|
+
}
|
|
777
|
+
return false;
|
|
778
|
+
}
|
|
779
|
+
function emitSchema(schema) {
|
|
780
|
+
console.log(JSON.stringify(schema, null, 2));
|
|
781
|
+
}
|
|
782
|
+
function attachSchemaHelp(cmd, schema) {
|
|
783
|
+
const baseHelp = cmd.helpInformation.bind(cmd);
|
|
784
|
+
cmd.helpInformation = (context) => {
|
|
785
|
+
if (!wantsJsonSchema()) return baseHelp(context);
|
|
786
|
+
emitSchema(schema);
|
|
787
|
+
return "";
|
|
788
|
+
};
|
|
789
|
+
return cmd;
|
|
790
|
+
}
|
|
791
|
+
var pmAddSchema = {
|
|
792
|
+
cli: CLI_NAME,
|
|
793
|
+
noun: "payment-methods",
|
|
794
|
+
verb: "add",
|
|
795
|
+
description: "Add a payment method. --payment-brand evo (default): card binding + 3DS via Drop-in session. --payment-brand unionpay: UPI Agent Pay enrollment (requires --member); returns enroll_url, result arrives asynchronously via webhook.",
|
|
796
|
+
flags: {
|
|
797
|
+
"payment-brand": {
|
|
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
|
+
},
|
|
811
|
+
mode: {
|
|
812
|
+
type: "string",
|
|
813
|
+
required: false,
|
|
814
|
+
default: "dropin",
|
|
815
|
+
description: 'Add mode (evo payment brand only): "manual" (CLI collects card details) or "dropin" (mint a Drop-in session)',
|
|
816
|
+
constraints: "manual | dropin"
|
|
817
|
+
},
|
|
818
|
+
email: {
|
|
819
|
+
type: "string",
|
|
820
|
+
required: false,
|
|
821
|
+
description: "Email for 3DS verification (evo/manual) or Drop-in session reference (evo/dropin)"
|
|
822
|
+
},
|
|
823
|
+
"no-poll": {
|
|
824
|
+
type: "bool",
|
|
825
|
+
required: false,
|
|
826
|
+
default: false,
|
|
827
|
+
description: "Dropin mode: mint session and exit immediately without polling verification status"
|
|
828
|
+
}
|
|
829
|
+
},
|
|
830
|
+
response: {
|
|
831
|
+
id: { type: "string", description: "Payment method id" },
|
|
832
|
+
status: {
|
|
833
|
+
type: "string",
|
|
834
|
+
description: "PENDING (awaiting enrollment/3DS) / ACTIVE / FAILED / DISABLED"
|
|
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
|
+
}
|
|
849
|
+
},
|
|
850
|
+
example: {
|
|
851
|
+
command: "agenzo-token-cli payment-methods add --payment-brand unionpay --member usr_abc123 --format json",
|
|
852
|
+
output_summary: "Returns enroll_url. User opens the URL in a browser to complete card binding. Result arrives async via webhook; poll with `payment-methods list` or `payment-methods get`."
|
|
853
|
+
},
|
|
854
|
+
error_recovery: {
|
|
855
|
+
PARAM_INVALID: "Fix the offending flag (--payment-brand must be evo|unionpay; --member required for unionpay).",
|
|
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."
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
var pmListSchema = {
|
|
861
|
+
cli: CLI_NAME,
|
|
862
|
+
noun: "payment-methods",
|
|
863
|
+
verb: "list",
|
|
864
|
+
description: "List all payment methods for the authenticated developer",
|
|
865
|
+
flags: {},
|
|
866
|
+
response: {
|
|
867
|
+
payment_methods: {
|
|
868
|
+
type: "array",
|
|
869
|
+
description: "List of payment methods",
|
|
870
|
+
items: {
|
|
871
|
+
id: { type: "string", description: "Payment method id" },
|
|
872
|
+
status: { type: "string", description: "PENDING / ACTIVE / FAILED / DISABLED" },
|
|
873
|
+
payment_brand: { type: "string", description: "evo or unionpay" },
|
|
874
|
+
brand: { type: "string|null", description: "Card brand (Visa, Mastercard, UnionPay, etc.)" },
|
|
875
|
+
last4: { type: "string|null", description: "Last 4 digits of card" },
|
|
876
|
+
exp_month: { type: "int|null", description: "Expiry month" },
|
|
877
|
+
exp_year: { type: "int|null", description: "Expiry year" }
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
},
|
|
881
|
+
example: {
|
|
882
|
+
command: "agenzo-token-cli payment-methods list --format json",
|
|
883
|
+
output_summary: "Returns array of payment methods with status and card info."
|
|
884
|
+
}
|
|
885
|
+
};
|
|
886
|
+
var pmGetSchema = {
|
|
887
|
+
cli: CLI_NAME,
|
|
888
|
+
noun: "payment-methods",
|
|
889
|
+
verb: "get",
|
|
890
|
+
description: "Get a payment method by ID",
|
|
891
|
+
flags: {
|
|
892
|
+
id: { type: "string", required: true, description: "Payment method id", source: "from_previous_step", from: "payment_methods[].id" }
|
|
893
|
+
},
|
|
894
|
+
response: {
|
|
895
|
+
id: { type: "string", description: "Payment method id" },
|
|
896
|
+
status: { type: "string", description: "PENDING / ACTIVE / FAILED / DISABLED" },
|
|
897
|
+
payment_brand: { type: "string", description: "evo or unionpay" },
|
|
898
|
+
brand: { type: "string|null", description: "Card brand" },
|
|
899
|
+
last4: { type: "string|null", description: "Last 4 digits" },
|
|
900
|
+
exp_month: { type: "int|null", description: "Expiry month" },
|
|
901
|
+
exp_year: { type: "int|null", description: "Expiry year" }
|
|
902
|
+
},
|
|
903
|
+
example: {
|
|
904
|
+
command: "agenzo-token-cli payment-methods get --id pm_abc123 --format json",
|
|
905
|
+
output_summary: "Returns full payment method details including card info and status."
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
var pmDisableSchema = {
|
|
909
|
+
cli: CLI_NAME,
|
|
910
|
+
noun: "payment-methods",
|
|
911
|
+
verb: "disable",
|
|
912
|
+
description: "Disable a payment method (cascades revoke on active tokens)",
|
|
913
|
+
flags: {
|
|
914
|
+
id: { type: "string", required: true, description: "Payment method id to disable", source: "from_previous_step", from: "payment_methods[].id" }
|
|
915
|
+
},
|
|
916
|
+
response: {
|
|
917
|
+
id: { type: "string", description: "Payment method id" },
|
|
918
|
+
status: { type: "string", description: "DISABLED" }
|
|
919
|
+
},
|
|
920
|
+
example: {
|
|
921
|
+
command: "agenzo-token-cli payment-methods disable --id pm_abc123 --format json",
|
|
922
|
+
output_summary: "Disables the payment method and revokes any active tokens."
|
|
923
|
+
},
|
|
924
|
+
error_recovery: {
|
|
925
|
+
NOT_FOUND: "Payment method id does not exist. Check the id with `payment-methods list`.",
|
|
926
|
+
ALREADY_DISABLED: "Card is already disabled. No action needed."
|
|
927
|
+
}
|
|
928
|
+
};
|
|
929
|
+
|
|
750
930
|
// src/payment-methods/add.ts
|
|
751
931
|
var MANUAL_POLL_INTERVAL_MS = 3e3;
|
|
752
932
|
var MANUAL_POLL_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
@@ -755,6 +935,13 @@ var DROPIN_POLL_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
|
755
935
|
var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["ACTIVE", "FAILED", "EXPIRED"]);
|
|
756
936
|
function registerAddCommand(parent, deps) {
|
|
757
937
|
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(
|
|
758
945
|
"--mode <mode>",
|
|
759
946
|
'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)',
|
|
760
947
|
"manual"
|
|
@@ -764,11 +951,26 @@ function registerAddCommand(parent, deps) {
|
|
|
764
951
|
).option("--card-number <number>", "Card number (manual mode only)").option("--expiry <mmyy>", "Expiry date (MMYY format) (manual mode only)").option("--cvv <cvv>", "Card CVV (manual mode only)").option(
|
|
765
952
|
"--idempotency-key <key>",
|
|
766
953
|
"Idempotency key forwarded verbatim as the Idempotency-Key header (manual mode only)"
|
|
954
|
+
).option(
|
|
955
|
+
"--no-poll",
|
|
956
|
+
"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)"
|
|
767
957
|
);
|
|
958
|
+
attachSchemaHelp(cmd, pmAddSchema);
|
|
768
959
|
cmd.action(async () => {
|
|
769
960
|
const opts = cmd.optsWithGlobals();
|
|
770
961
|
const format = resolveFormat(opts.format);
|
|
771
962
|
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
|
+
}
|
|
772
974
|
const mode = String(opts.mode ?? "manual").toLowerCase();
|
|
773
975
|
if (mode !== "manual" && mode !== "dropin") {
|
|
774
976
|
throw new CliError(
|
|
@@ -892,6 +1094,96 @@ async function handleManualMode(deps, opts, format, isYes) {
|
|
|
892
1094
|
}
|
|
893
1095
|
}
|
|
894
1096
|
}
|
|
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
|
+
}
|
|
895
1187
|
async function handleDropinMode(deps, opts, format) {
|
|
896
1188
|
const apiKey = await PromptEngine.resolveInput(opts.apiKey, {
|
|
897
1189
|
message: "API Key:",
|
|
@@ -922,6 +1214,9 @@ async function handleDropinMode(deps, opts, format) {
|
|
|
922
1214
|
"info",
|
|
923
1215
|
"Use the Session ID to add the payment method in the browser via the Drop-in SDK"
|
|
924
1216
|
);
|
|
1217
|
+
if (opts.poll === false) {
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
925
1220
|
const finalPm = await pollVerificationStatus(deps.apiClient, apiKey, pmId, {
|
|
926
1221
|
intervalMs: DROPIN_POLL_INTERVAL_MS,
|
|
927
1222
|
timeoutMs: DROPIN_POLL_TIMEOUT_MS
|
|
@@ -1012,6 +1307,7 @@ function sleep(ms) {
|
|
|
1012
1307
|
// src/payment-methods/list.ts
|
|
1013
1308
|
function registerListCommand(parent, deps) {
|
|
1014
1309
|
const cmd = parent.command("list").description("List payment methods").option("--api-key <key>", "API Key for authentication").option("--member <member_id>", "Filter by member ID");
|
|
1310
|
+
attachSchemaHelp(cmd, pmListSchema);
|
|
1015
1311
|
cmd.action(async () => {
|
|
1016
1312
|
const opts = cmd.optsWithGlobals();
|
|
1017
1313
|
const format = resolveFormat(opts.format);
|
|
@@ -1058,6 +1354,7 @@ function registerListCommand(parent, deps) {
|
|
|
1058
1354
|
// src/payment-methods/get.ts
|
|
1059
1355
|
function registerGetCommand(parent, deps) {
|
|
1060
1356
|
const cmd = parent.command("get <pm_id>").description("Get a payment method by ID").option("--api-key <key>", "API key for authentication");
|
|
1357
|
+
attachSchemaHelp(cmd, pmGetSchema);
|
|
1061
1358
|
cmd.action(async (pmId) => {
|
|
1062
1359
|
const opts = cmd.optsWithGlobals();
|
|
1063
1360
|
const format = resolveFormat(opts.format);
|
|
@@ -1103,6 +1400,7 @@ function registerDisableCommand(parent, deps) {
|
|
|
1103
1400
|
"--idempotency-key <key>",
|
|
1104
1401
|
"Idempotency key forwarded verbatim as the Idempotency-Key header"
|
|
1105
1402
|
);
|
|
1403
|
+
attachSchemaHelp(cmd, pmDisableSchema);
|
|
1106
1404
|
cmd.action(async (pmId) => {
|
|
1107
1405
|
const opts = cmd.optsWithGlobals();
|
|
1108
1406
|
const format = resolveFormat(opts.format);
|
|
@@ -1190,10 +1488,17 @@ function registerDropinCreateCommand(parent, deps) {
|
|
|
1190
1488
|
|
|
1191
1489
|
// src/payment-methods/dropin-status.ts
|
|
1192
1490
|
function registerDropinStatusCommand(parent, deps) {
|
|
1193
|
-
const cmd = parent.command("dropin-status
|
|
1194
|
-
|
|
1491
|
+
const cmd = parent.command("dropin-status [pm_id]").description("Query the current Drop-in binding status for a payment method (single check)").option("--api-key <key>", "API Key for authentication").option(
|
|
1492
|
+
"--payment-method-id <id>",
|
|
1493
|
+
"Payment method id to query (alternative to the positional <pm_id>, for programmatic callers that pass flags only)"
|
|
1494
|
+
);
|
|
1495
|
+
cmd.action(async (pmIdArg) => {
|
|
1195
1496
|
const opts = cmd.optsWithGlobals();
|
|
1196
1497
|
const format = resolveFormat(opts.format);
|
|
1498
|
+
const pmId = await PromptEngine.resolveInput(
|
|
1499
|
+
pmIdArg ?? opts.paymentMethodId,
|
|
1500
|
+
{ message: "Payment method id:" }
|
|
1501
|
+
);
|
|
1197
1502
|
const apiKey = await PromptEngine.resolveInput(opts.apiKey, {
|
|
1198
1503
|
message: "API Key:",
|
|
1199
1504
|
type: "password"
|
|
@@ -1225,6 +1530,7 @@ function registerDropinStatusCommand(parent, deps) {
|
|
|
1225
1530
|
import { confirm, select as select2 } from "@inquirer/prompts";
|
|
1226
1531
|
var DEFAULT_NT_FEE_CENTS = 500;
|
|
1227
1532
|
var USDC_UNIT = 1e6;
|
|
1533
|
+
var DECIMAL_AMOUNT_RE = /^\d+(\.\d+)?$/;
|
|
1228
1534
|
function mapTokenType(cliType) {
|
|
1229
1535
|
if (cliType === "network-token") return "network_token";
|
|
1230
1536
|
return cliType;
|
|
@@ -1302,14 +1608,23 @@ function formatPaymentToken(data) {
|
|
|
1302
1608
|
lines.push(["Currency", String(vcn.currency || "USD")]);
|
|
1303
1609
|
lines.push(["Status", String(vcn.status || data.status || "")]);
|
|
1304
1610
|
} else if (type === "network_token") {
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1611
|
+
if (data.status === "PENDING" && data.checkout_url) {
|
|
1612
|
+
lines.push(["Payment Token ID", String(data.id || "")]);
|
|
1613
|
+
lines.push(["Type", "Network Token"]);
|
|
1614
|
+
lines.push(["Status", "PENDING"]);
|
|
1615
|
+
lines.push(["Checkout URL", String(data.checkout_url || "")]);
|
|
1616
|
+
lines.push(["Correlation ID", String(data.correlation_id || "")]);
|
|
1617
|
+
} else {
|
|
1618
|
+
const nt = data.network_token ?? data;
|
|
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
|
+
}
|
|
1313
1628
|
} else if (type === "x402") {
|
|
1314
1629
|
const x402 = data.x402 ?? data;
|
|
1315
1630
|
lines.push(["Payment Token ID", String(data.id || x402.id || "")]);
|
|
@@ -1329,8 +1644,13 @@ function formatCentsToUsd(cents) {
|
|
|
1329
1644
|
const remainder = cents % 100;
|
|
1330
1645
|
return `${dollars}.${String(remainder).padStart(2, "0")}`;
|
|
1331
1646
|
}
|
|
1647
|
+
function isValidUnionpayAmount(amountStr) {
|
|
1648
|
+
const trimmed = amountStr.trim();
|
|
1649
|
+
if (!DECIMAL_AMOUNT_RE.test(trimmed)) return false;
|
|
1650
|
+
return parseFloat(trimmed) > 0;
|
|
1651
|
+
}
|
|
1332
1652
|
function registerCreateCommand(parent, deps) {
|
|
1333
|
-
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(
|
|
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("--recipient-first-name <name>", "UnionPay network token: recipient first name (order delivery details)").option("--recipient-last-name <name>", "UnionPay network token: recipient last name (order delivery details)").option("--recipient-email <email>", "UnionPay network token: recipient email (recipient-email or recipient-phone required)").option("--recipient-phone <phone>", "UnionPay network token: recipient phone (recipient-email or recipient-phone required)").option("--unionpay-amount <amount>", 'UnionPay network token: intent amount as a decimal string, e.g. "174.58"').option(
|
|
1334
1654
|
"--idempotency-key <key>",
|
|
1335
1655
|
"Idempotency key forwarded verbatim as the Idempotency-Key header"
|
|
1336
1656
|
);
|
|
@@ -1357,6 +1677,24 @@ function registerCreateCommand(parent, deps) {
|
|
|
1357
1677
|
card: opts.card,
|
|
1358
1678
|
yes: isYes
|
|
1359
1679
|
});
|
|
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";
|
|
1360
1698
|
let member = opts.member;
|
|
1361
1699
|
if (!member && !isYes) {
|
|
1362
1700
|
const memberInput = await PromptEngine.resolveInput(void 0, {
|
|
@@ -1405,10 +1743,66 @@ function registerCreateCommand(parent, deps) {
|
|
|
1405
1743
|
typeBody.currency = opts.currency;
|
|
1406
1744
|
}
|
|
1407
1745
|
} else if (serverType === "network_token") {
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1746
|
+
if (isUnionpayNetworkToken) {
|
|
1747
|
+
const unionpayAmountStr = await PromptEngine.resolveInput(
|
|
1748
|
+
opts.unionpayAmount,
|
|
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
|
+
}
|
|
1412
1806
|
} else if (serverType === "x402") {
|
|
1413
1807
|
const amountStr = await PromptEngine.resolveInput(opts.amount, {
|
|
1414
1808
|
message: "Amount (USDC):",
|
|
@@ -1450,10 +1844,15 @@ function registerCreateCommand(parent, deps) {
|
|
|
1450
1844
|
typeBody.deadline = deadlineNum;
|
|
1451
1845
|
}
|
|
1452
1846
|
if (!isYes) {
|
|
1453
|
-
|
|
1454
|
-
|
|
1847
|
+
if (freezeDisplay !== void 0 && feeDisplay !== void 0) {
|
|
1848
|
+
const warningLines = [`Freeze: ${freezeDisplay}`, `Fee: ${feeDisplay}`];
|
|
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?";
|
|
1455
1854
|
const confirmed = await confirm({
|
|
1456
|
-
message:
|
|
1855
|
+
message: confirmMessage,
|
|
1457
1856
|
default: true
|
|
1458
1857
|
});
|
|
1459
1858
|
if (!confirmed) {
|
|
@@ -1510,6 +1909,42 @@ function registerCreateCommand(parent, deps) {
|
|
|
1510
1909
|
}
|
|
1511
1910
|
};
|
|
1512
1911
|
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
|
+
}
|
|
1513
1948
|
});
|
|
1514
1949
|
}
|
|
1515
1950
|
async function checkVcnFeatureEnabled(apiClient, apiKey) {
|