@agenzo/token-cli 0.3.4 → 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 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) {
@@ -743,6 +766,167 @@ async function collectPaymentMethodParams(type, flags) {
743
766
  return params;
744
767
  }
745
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
+
746
930
  // src/payment-methods/add.ts
747
931
  var MANUAL_POLL_INTERVAL_MS = 3e3;
748
932
  var MANUAL_POLL_TIMEOUT_MS = 15 * 60 * 1e3;
@@ -751,6 +935,13 @@ var DROPIN_POLL_TIMEOUT_MS = 30 * 60 * 1e3;
751
935
  var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["ACTIVE", "FAILED", "EXPIRED"]);
752
936
  function registerAddCommand(parent, deps) {
753
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(
754
945
  "--mode <mode>",
755
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)',
756
947
  "manual"
@@ -764,10 +955,22 @@ function registerAddCommand(parent, deps) {
764
955
  "--no-poll",
765
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)"
766
957
  );
958
+ attachSchemaHelp(cmd, pmAddSchema);
767
959
  cmd.action(async () => {
768
960
  const opts = cmd.optsWithGlobals();
769
961
  const format = resolveFormat(opts.format);
770
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
+ }
771
974
  const mode = String(opts.mode ?? "manual").toLowerCase();
772
975
  if (mode !== "manual" && mode !== "dropin") {
773
976
  throw new CliError(
@@ -891,6 +1094,96 @@ async function handleManualMode(deps, opts, format, isYes) {
891
1094
  }
892
1095
  }
893
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
+ }
894
1187
  async function handleDropinMode(deps, opts, format) {
895
1188
  const apiKey = await PromptEngine.resolveInput(opts.apiKey, {
896
1189
  message: "API Key:",
@@ -1014,6 +1307,7 @@ function sleep(ms) {
1014
1307
  // src/payment-methods/list.ts
1015
1308
  function registerListCommand(parent, deps) {
1016
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);
1017
1311
  cmd.action(async () => {
1018
1312
  const opts = cmd.optsWithGlobals();
1019
1313
  const format = resolveFormat(opts.format);
@@ -1060,6 +1354,7 @@ function registerListCommand(parent, deps) {
1060
1354
  // src/payment-methods/get.ts
1061
1355
  function registerGetCommand(parent, deps) {
1062
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);
1063
1358
  cmd.action(async (pmId) => {
1064
1359
  const opts = cmd.optsWithGlobals();
1065
1360
  const format = resolveFormat(opts.format);
@@ -1105,6 +1400,7 @@ function registerDisableCommand(parent, deps) {
1105
1400
  "--idempotency-key <key>",
1106
1401
  "Idempotency key forwarded verbatim as the Idempotency-Key header"
1107
1402
  );
1403
+ attachSchemaHelp(cmd, pmDisableSchema);
1108
1404
  cmd.action(async (pmId) => {
1109
1405
  const opts = cmd.optsWithGlobals();
1110
1406
  const format = resolveFormat(opts.format);
@@ -1234,6 +1530,7 @@ function registerDropinStatusCommand(parent, deps) {
1234
1530
  import { confirm, select as select2 } from "@inquirer/prompts";
1235
1531
  var DEFAULT_NT_FEE_CENTS = 500;
1236
1532
  var USDC_UNIT = 1e6;
1533
+ var DECIMAL_AMOUNT_RE = /^\d+(\.\d+)?$/;
1237
1534
  function mapTokenType(cliType) {
1238
1535
  if (cliType === "network-token") return "network_token";
1239
1536
  return cliType;
@@ -1311,14 +1608,23 @@ function formatPaymentToken(data) {
1311
1608
  lines.push(["Currency", String(vcn.currency || "USD")]);
1312
1609
  lines.push(["Status", String(vcn.status || data.status || "")]);
1313
1610
  } else if (type === "network_token") {
1314
- const nt = data.network_token ?? data;
1315
- lines.push(["Payment Token ID", String(data.id || nt.id || "")]);
1316
- lines.push(["Type", "Network Token"]);
1317
- lines.push(["Brand", String(nt.payment_brand || nt.brand || "")]);
1318
- lines.push(["ECI", String(nt.eci || "")]);
1319
- lines.push(["Cryptogram", String(nt.token_cryptogram || nt.cryptogram || "")]);
1320
- lines.push(["Expiry", String(nt.expiry_date || nt.expiry || "")]);
1321
- lines.push(["Value", String(nt.value || "")]);
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
+ }
1322
1628
  } else if (type === "x402") {
1323
1629
  const x402 = data.x402 ?? data;
1324
1630
  lines.push(["Payment Token ID", String(data.id || x402.id || "")]);
@@ -1338,8 +1644,13 @@ function formatCentsToUsd(cents) {
1338
1644
  const remainder = cents % 100;
1339
1645
  return `${dollars}.${String(remainder).padStart(2, "0")}`;
1340
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
+ }
1341
1652
  function registerCreateCommand(parent, deps) {
1342
- 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(
1343
1654
  "--idempotency-key <key>",
1344
1655
  "Idempotency key forwarded verbatim as the Idempotency-Key header"
1345
1656
  );
@@ -1366,6 +1677,24 @@ function registerCreateCommand(parent, deps) {
1366
1677
  card: opts.card,
1367
1678
  yes: isYes
1368
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";
1369
1698
  let member = opts.member;
1370
1699
  if (!member && !isYes) {
1371
1700
  const memberInput = await PromptEngine.resolveInput(void 0, {
@@ -1414,10 +1743,66 @@ function registerCreateCommand(parent, deps) {
1414
1743
  typeBody.currency = opts.currency;
1415
1744
  }
1416
1745
  } else if (serverType === "network_token") {
1417
- feeCents = await fetchNetworkTokenFee(deps.apiClient, apiKey);
1418
- freezeAmountCents = feeCents;
1419
- feeDisplay = `$${formatCentsToUsd(feeCents)}`;
1420
- freezeDisplay = feeDisplay;
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
+ }
1421
1806
  } else if (serverType === "x402") {
1422
1807
  const amountStr = await PromptEngine.resolveInput(opts.amount, {
1423
1808
  message: "Amount (USDC):",
@@ -1459,10 +1844,15 @@ function registerCreateCommand(parent, deps) {
1459
1844
  typeBody.deadline = deadlineNum;
1460
1845
  }
1461
1846
  if (!isYes) {
1462
- const warningLines = [`Freeze: ${freezeDisplay}`, `Fee: ${feeDisplay}`];
1463
- notify(format, "warning", warningLines.join(" | "));
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?";
1464
1854
  const confirmed = await confirm({
1465
- message: "Proceed with token creation?",
1855
+ message: confirmMessage,
1466
1856
  default: true
1467
1857
  });
1468
1858
  if (!confirmed) {
@@ -1519,6 +1909,42 @@ function registerCreateCommand(parent, deps) {
1519
1909
  }
1520
1910
  };
1521
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
+ }
1522
1948
  });
1523
1949
  }
1524
1950
  async function checkVcnFeatureEnabled(apiClient, apiKey) {