@itpay/cli 0.1.7 → 0.1.9

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/bin/itp CHANGED
@@ -213,13 +213,14 @@ async function main() {
213
213
  "buyer payment refresh-qr <payment_intent_id> --reason order-not-found --json",
214
214
  "buyer deliveries list --checkout <checkout_id> --json",
215
215
  "buyer deliveries show <delivery_id> --checkout <checkout_id> --json",
216
+ "buyer refund create --order <order_id> --amount-minor 1000 --currency CNY --reason buyer_requested --json",
217
+ "buyer refund list --order <order_id> --json",
218
+ "buyer refund show <refund_id> --json",
219
+ "buyer refund cancel <refund_id> --reason buyer_changed_mind --json",
216
220
  "buyer vault grants list --checkout <checkout_id> --json",
217
221
  "buyer vault grants read <agent_read_grant_id> --json",
218
222
  "buyer vault read --order <order_id> --artifact <vault_artifact_id> --json",
219
223
  "account login-link --json",
220
- "ops sandbox worker run-once --json",
221
- "ops sandbox recover-alipay-once --json",
222
- "ops sandbox payment query <payment_intent_id> --json",
223
224
  "status --json",
224
225
  "docs list --role buyer --json",
225
226
  "docs show quickstart --role buyer --json",
@@ -842,6 +843,58 @@ async function buyer(command, rest, flags) {
842
843
  return;
843
844
  }
844
845
  }
846
+ if (command === "refund") {
847
+ if (subcommand === "create") {
848
+ const orderID = flags.order || flags.order_id || positional(rest, 1);
849
+ if (!orderID) throw new Error("order_id is required");
850
+ const refund = await createBuyerRefund(orderID, flags);
851
+ if (refund.status === "policy_risk_confirmation_required") {
852
+ output(buyerRunOutput(refund));
853
+ return;
854
+ }
855
+ output(buyerRunOutput({
856
+ status: refund.status || "refund_requested",
857
+ refund,
858
+ refund_eligibility: refund.refund_eligibility || null,
859
+ agent_next_actions: ["watch_refund_status", "explain_refund_policy"]
860
+ }));
861
+ return;
862
+ }
863
+ if (subcommand === "list") {
864
+ const orderID = flags.order || flags.order_id || positional(rest, 1);
865
+ if (!orderID) throw new Error("order_id is required");
866
+ const refunds = await listBuyerRefunds(orderID, flags);
867
+ output(buyerRunOutput({
868
+ status: "refunds",
869
+ order_id: orderID,
870
+ ...refunds,
871
+ agent_next_actions: ["show_refund_status"]
872
+ }));
873
+ return;
874
+ }
875
+ if (subcommand === "show") {
876
+ const refundID = flags.refund || flags.refund_id || positional(rest, 1);
877
+ if (!refundID) throw new Error("refund_id is required");
878
+ const refund = await getBuyerRefund(refundID, flags);
879
+ output(buyerRunOutput({
880
+ status: refund.status || "refund",
881
+ refund,
882
+ agent_next_actions: ["show_refund_status"]
883
+ }));
884
+ return;
885
+ }
886
+ if (subcommand === "cancel") {
887
+ const refundID = flags.refund || flags.refund_id || positional(rest, 1);
888
+ if (!refundID) throw new Error("refund_id is required");
889
+ const refund = await cancelBuyerRefund(refundID, flags);
890
+ output(buyerRunOutput({
891
+ status: refund.status || "refund_canceled",
892
+ refund,
893
+ agent_next_actions: ["show_refund_status", "claim_delivery_if_still_needed"]
894
+ }));
895
+ return;
896
+ }
897
+ }
845
898
  if (command === "vault") {
846
899
  if (subcommand === "grants") {
847
900
  const action = rest[1] && !String(rest[1]).startsWith("--") ? rest[1] : "list";
@@ -966,6 +1019,12 @@ function booleanFlag(value) {
966
1019
  throw new Error(`invalid boolean flag value: ${value}`);
967
1020
  }
968
1021
 
1022
+ function intFlag(value, name) {
1023
+ const number = Number(value);
1024
+ if (!Number.isInteger(number)) throw new Error(`${name} must be an integer`);
1025
+ return number;
1026
+ }
1027
+
969
1028
  async function ops(command, rest, flags) {
970
1029
  if (command !== "sandbox") throw new Error(`unknown ops command: ${command || ""}`);
971
1030
  const area = rest[0];
@@ -984,6 +1043,75 @@ async function ops(command, rest, flags) {
984
1043
  output(await coreApi(`/v1/payment-intents/${encodeURIComponent(paymentIntentID)}/alipay-sandbox-query`, { method: "POST", ops: true }, flags));
985
1044
  return;
986
1045
  }
1046
+ if (area === "refund") {
1047
+ const refundID = flags.refund || flags.refund_id || positional(rest, 2);
1048
+ if (!refundID) throw new Error("refund_id is required");
1049
+ if (action === "show") {
1050
+ output(await coreApi(`/v1/sandbox/ops/refunds/${encodeURIComponent(refundID)}`, { method: "GET", ops: true }, flags));
1051
+ return;
1052
+ }
1053
+ if (action === "approve" || action === "reject") {
1054
+ output(await coreApi(`/v1/sandbox/ops/refunds/${encodeURIComponent(refundID)}/${action}`, {
1055
+ method: "POST",
1056
+ ops: true,
1057
+ idempotencyKey: flags.idempotency_key || `idem_cli_refund_${action}_${refundID}`,
1058
+ body: {
1059
+ reason_code: flags.reason || flags.reason_code || (action === "approve" ? "approved_by_ops" : "not_eligible"),
1060
+ note: flags.note || ""
1061
+ }
1062
+ }, flags));
1063
+ return;
1064
+ }
1065
+ if (action === "execute") {
1066
+ output(await coreApi(`/v1/sandbox/ops/refunds/${encodeURIComponent(refundID)}/execute`, {
1067
+ method: "POST",
1068
+ ops: true,
1069
+ idempotencyKey: flags.idempotency_key || `idem_cli_refund_execute_${refundID}`
1070
+ }, flags));
1071
+ return;
1072
+ }
1073
+ }
1074
+ if (area === "ledger" && action === "entries") {
1075
+ const params = new URLSearchParams();
1076
+ if (flags.order || flags.order_id) params.set("order_id", String(flags.order || flags.order_id));
1077
+ if (flags.refund || flags.refund_id) params.set("refund_id", String(flags.refund || flags.refund_id));
1078
+ if (flags.payment_intent || flags.payment_intent_id) params.set("payment_intent_id", String(flags.payment_intent || flags.payment_intent_id));
1079
+ if ([...params.keys()].length === 0) {
1080
+ throw new Error("ledger filter is required: use --order, --refund, or --payment-intent");
1081
+ }
1082
+ output(await coreApi(`/v1/sandbox/ops/ledger/entries${queryString(params)}`, { method: "GET", ops: true }, flags));
1083
+ return;
1084
+ }
1085
+ if (area === "reconciliation") {
1086
+ if (action === "run") {
1087
+ output(await coreApi("/v1/sandbox/ops/reconciliation-runs", {
1088
+ method: "POST",
1089
+ ops: true,
1090
+ idempotencyKey: flags.idempotency_key || `idem_cli_reconciliation_${cryptoRandom()}`,
1091
+ body: {
1092
+ reconciliation_run_id: flags.reconciliation_run_id || flags.run_id || "",
1093
+ status: flags.status || "matched",
1094
+ expected_amount_minor: intFlag(flags.expected_amount_minor || flags.expected || 0, "expected_amount_minor"),
1095
+ observed_amount_minor: intFlag(flags.observed_amount_minor || flags.observed || 0, "observed_amount_minor"),
1096
+ currency: flags.currency || "CNY",
1097
+ raw_statement_ref: flags.raw_statement_ref || flags.statement_ref || ""
1098
+ }
1099
+ }, flags));
1100
+ return;
1101
+ }
1102
+ if (action === "show") {
1103
+ const runID = flags.reconciliation_run_id || flags.run_id || positional(rest, 2);
1104
+ if (!runID) throw new Error("reconciliation_run_id is required");
1105
+ output(await coreApi(`/v1/sandbox/ops/reconciliation-runs/${encodeURIComponent(runID)}`, { method: "GET", ops: true }, flags));
1106
+ return;
1107
+ }
1108
+ }
1109
+ if (area === "settlement" && action === "show") {
1110
+ const settlementBatchID = flags.settlement_batch || flags.settlement_batch_id || positional(rest, 2);
1111
+ if (!settlementBatchID) throw new Error("settlement_batch_id is required");
1112
+ output(await coreApi(`/v1/sandbox/ops/settlement-batches/${encodeURIComponent(settlementBatchID)}`, { method: "GET", ops: true }, flags));
1113
+ return;
1114
+ }
987
1115
  throw new Error(`unknown ops sandbox command: ${rest.join(" ")}`);
988
1116
  }
989
1117
 
@@ -1462,6 +1590,67 @@ async function getBuyerCheckout(checkoutID, flags = {}) {
1462
1590
  return await coreApi(`/v1/checkouts/${encodeURIComponent(checkoutID)}`, { method: "GET" }, flags);
1463
1591
  }
1464
1592
 
1593
+ async function createBuyerRefund(orderID, flags = {}) {
1594
+ if (flags.amount !== undefined) {
1595
+ throw new Error("use --amount-minor for refund amount");
1596
+ }
1597
+ if (flags.refund_scope && flags.refund_scope !== "order") {
1598
+ throw new Error("unsupported_refund_scope");
1599
+ }
1600
+ const order = await getBuyerOrderDetail(orderID, flags);
1601
+ const eligibility = order.refund_eligibility || order.order_detail?.refund_eligibility || null;
1602
+ if (eligibility && eligibility.likely_refundable === false && !booleanFlag(flags.confirm_policy_risk || false)) {
1603
+ return {
1604
+ status: "policy_risk_confirmation_required",
1605
+ order_id: orderID,
1606
+ refund_eligibility: eligibility,
1607
+ agent_next_actions: [
1608
+ "explain_refund_policy",
1609
+ "ask_human_to_confirm_policy_risk",
1610
+ `retry_with_${cliCommand("buyer", "refund", "create", orderID, "--confirm-policy-risk", "true", "--json")}`
1611
+ ],
1612
+ submitted: false
1613
+ };
1614
+ }
1615
+ return await coreApi(`/v1/me/orders/${encodeURIComponent(orderID)}/refunds`, {
1616
+ method: "POST",
1617
+ headers: { "X-ItPay-Client-Surface": "cli" },
1618
+ idempotencyKey: flags.idempotency_key || `idem_cli_refund_create_${orderID}`,
1619
+ body: {
1620
+ refund_scope: flags.refund_scope || "order",
1621
+ order_line_item_ids: csvValues(flags.order_line_item_ids || flags.line_ids || flags.line_id),
1622
+ amount_minor: intFlag(flags.amount_minor, "amount_minor"),
1623
+ currency: flags.currency || "CNY",
1624
+ reason_code: flags.reason || flags.reason_code || "buyer_requested",
1625
+ reason_note: flags.note || flags.reason_note || ""
1626
+ }
1627
+ }, flags);
1628
+ }
1629
+
1630
+ async function getBuyerOrderDetail(orderID, flags = {}) {
1631
+ return await coreApi(`/v1/me/orders/${encodeURIComponent(orderID)}`, { method: "GET" }, flags);
1632
+ }
1633
+
1634
+ async function listBuyerRefunds(orderID, flags = {}) {
1635
+ return await coreApi(`/v1/me/orders/${encodeURIComponent(orderID)}/refunds`, { method: "GET" }, flags);
1636
+ }
1637
+
1638
+ async function getBuyerRefund(refundID, flags = {}) {
1639
+ return await coreApi(`/v1/me/refunds/${encodeURIComponent(refundID)}`, { method: "GET" }, flags);
1640
+ }
1641
+
1642
+ async function cancelBuyerRefund(refundID, flags = {}) {
1643
+ return await coreApi(`/v1/me/refunds/${encodeURIComponent(refundID)}/cancel`, {
1644
+ method: "POST",
1645
+ headers: { "X-ItPay-Client-Surface": "cli" },
1646
+ idempotencyKey: flags.idempotency_key || `idem_cli_refund_cancel_${refundID}`,
1647
+ body: {
1648
+ reason_code: flags.reason || flags.reason_code || "buyer_changed_mind",
1649
+ reason_note: flags.note || flags.reason_note || ""
1650
+ }
1651
+ }, flags);
1652
+ }
1653
+
1465
1654
  async function getBuyerPaymentIntent(paymentIntentID, flags = {}) {
1466
1655
  return await coreApi(`/v1/payment-intents/${encodeURIComponent(paymentIntentID)}`, { method: "GET" }, flags);
1467
1656
  }
@@ -3233,13 +3422,13 @@ async function renderHumanAction(action, flags = {}) {
3233
3422
  return { rendered: false, mode: "json-local-qr", outputs: [localPath] };
3234
3423
  }
3235
3424
  }
3236
- if (!qrImageURL && shouldGenerateLocalQRFromActionURL(action) && shouldPrepareLocalQRForJSON(mode, flags, action)) {
3237
- const localPath = await prepareGeneratedActionQRFile(action, flags, mode !== "file" && !flags.qr_file && !process.env.ITP_QR_FILE);
3425
+ if (!qrImageURL && shouldGenerateLocalQRFromActionURL(action, mode, flags)) {
3426
+ const localPath = await prepareLocalQRFromActionURL(action, flags, mode !== "file" && !flags.qr_file && !process.env.ITP_QR_FILE);
3238
3427
  if (localPath) {
3239
- attachAgentGeneratedQRImage(action, localPath);
3428
+ attachAgentLocalQR(action, localPath);
3240
3429
  annotateHumanActionPresentation(action, "");
3241
3430
  persistHumanAction(action, flags);
3242
- return { rendered: false, mode: "json-local-auth-qr", outputs: [localPath] };
3431
+ return { rendered: false, mode: "json-local-url-qr", outputs: [localPath] };
3243
3432
  }
3244
3433
  }
3245
3434
  return { rendered: false, mode: flags.json ? "json" : mode };
@@ -3255,9 +3444,18 @@ async function renderHumanAction(action, flags = {}) {
3255
3444
  persistHumanAction(action, flags);
3256
3445
  return { rendered: false, mode: localPath ? "agent-local-image-qr" : "agent-image-qr", host, outputs: [localPath || "preferred_qr_url"] };
3257
3446
  }
3258
- await attachAgentTextQR(action, flags);
3447
+ if (shouldGenerateLocalQRFromActionURL(action, mode, flags)) {
3448
+ const localPath = await prepareLocalQRFromActionURL(action, flags, true);
3449
+ if (localPath) {
3450
+ attachAgentLocalQR(action, localPath);
3451
+ annotateHumanActionPresentation(action, "");
3452
+ persistHumanAction(action, flags);
3453
+ return { rendered: false, mode: "agent-local-url-qr", host, outputs: [localPath] };
3454
+ }
3455
+ }
3456
+ process.stderr.write(`No QR image available. Open action URL: ${action.url}\n`);
3259
3457
  persistHumanAction(action, flags);
3260
- return { rendered: false, mode: "agent-text-qr", host, outputs: ["agent_text_qr"] };
3458
+ return { rendered: false, mode: "agent-url-fallback", host, outputs: ["action_url"] };
3261
3459
  }
3262
3460
 
3263
3461
  const renderResult = { rendered: false, mode, outputs: [] };
@@ -3276,15 +3474,25 @@ async function renderHumanAction(action, flags = {}) {
3276
3474
  await downloadQRImage(qrImageURL, file, flags);
3277
3475
  action.local_qr_path = file;
3278
3476
  action.local_qr_mime = qrMimeType(qrImageURL, action);
3477
+ process.stderr.write(`Alipay QR image: ${file}\n`);
3478
+ renderResult.rendered = true;
3479
+ renderResult.outputs.push(file);
3480
+ if (mode === "file") return renderResult;
3279
3481
  } else {
3280
- await QRCode.toFile(file, action.url, { errorCorrectionLevel: "M", width: 512, margin: 2 });
3281
- action.local_qr_path = file;
3282
- action.local_qr_mime = "image/png";
3482
+ const localPath = shouldGenerateLocalQRFromActionURL(action, mode, flags)
3483
+ ? await prepareLocalQRFromActionURL(action, flags, false)
3484
+ : "";
3485
+ if (localPath) {
3486
+ attachAgentLocalQR(action, localPath);
3487
+ annotateHumanActionPresentation(action, "");
3488
+ process.stderr.write(`Alipay action QR image: ${localPath}\n`);
3489
+ renderResult.rendered = true;
3490
+ renderResult.outputs.push(localPath);
3491
+ if (mode === "file") return renderResult;
3492
+ } else {
3493
+ process.stderr.write(`No QR image available. Open action URL: ${action.url}\n`);
3494
+ }
3283
3495
  }
3284
- process.stderr.write(`Alipay QR image: ${file}\n`);
3285
- renderResult.rendered = true;
3286
- renderResult.outputs.push(file);
3287
- if (mode === "file") return renderResult;
3288
3496
  }
3289
3497
 
3290
3498
  if (qrImageURL) {
@@ -3354,41 +3562,48 @@ function annotateHumanActionPresentation(action, qrImageURL) {
3354
3562
  function shouldPrepareLocalQRForJSON(mode, flags = {}, action = {}) {
3355
3563
  if (mode === "file" || flags.qr_file || process.env.ITP_QR_FILE) return true;
3356
3564
  if (action?.qr_png_url || action?.preferred_qr_url || action?.qr_image_url) return true;
3357
- if (action?.kind === "auth_qr" && mode !== "none" && mode !== "json") return true;
3358
- return mode === "agent" || mode === "chat";
3565
+ return false;
3359
3566
  }
3360
3567
 
3361
- function shouldGenerateLocalQRFromActionURL(action = {}) {
3362
- return action?.kind === "auth_qr" && Boolean(action?.url);
3568
+ function shouldGenerateLocalQRFromActionURL(action = {}, mode = "", flags = {}) {
3569
+ if (!action?.url) return false;
3570
+ if (action.kind === "auth_qr") return true;
3571
+ return mode === "file" || Boolean(flags.qr_file || process.env.ITP_QR_FILE);
3363
3572
  }
3364
3573
 
3365
- async function prepareGeneratedActionQRFile(action, flags = {}, optional = false) {
3366
- if (!action?.url) return "";
3367
- const file = flags.qr_file || process.env.ITP_QR_FILE || defaultQRFilePath(action, "");
3574
+ async function prepareLocalQRFile(action, qrImageURL, flags = {}, optional = false) {
3575
+ if (!qrImageURL) return "";
3576
+ const file = flags.qr_file || process.env.ITP_QR_FILE || defaultQRFilePath(action, qrImageURL);
3368
3577
  try {
3369
- await QRCode.toFile(file, action.url, { errorCorrectionLevel: "M", width: 512, margin: 2 });
3578
+ await downloadQRImage(qrImageURL, file, flags);
3370
3579
  } catch (error) {
3371
3580
  if (!optional) throw error;
3372
3581
  action.local_qr_error = safeErrorMessage(error);
3373
3582
  return "";
3374
3583
  }
3375
3584
  action.local_qr_path = file;
3376
- action.local_qr_mime = "image/png";
3585
+ action.local_qr_mime = qrMimeType(qrImageURL, action);
3377
3586
  return file;
3378
3587
  }
3379
3588
 
3380
- async function prepareLocalQRFile(action, qrImageURL, flags = {}, optional = false) {
3381
- if (!qrImageURL) return "";
3382
- const file = flags.qr_file || process.env.ITP_QR_FILE || defaultQRFilePath(action, qrImageURL);
3589
+ async function prepareLocalQRFromActionURL(action, flags = {}, optional = false) {
3590
+ if (!action?.url) return "";
3591
+ const file = flags.qr_file || process.env.ITP_QR_FILE || defaultGeneratedQRFilePath(action);
3383
3592
  try {
3384
- await downloadQRImage(qrImageURL, file, flags);
3593
+ fs.mkdirSync(path.dirname(file), { recursive: true });
3594
+ await QRCode.toFile(file, action.url, {
3595
+ type: "png",
3596
+ errorCorrectionLevel: "M",
3597
+ margin: 2,
3598
+ width: 512
3599
+ });
3385
3600
  } catch (error) {
3386
3601
  if (!optional) throw error;
3387
3602
  action.local_qr_error = safeErrorMessage(error);
3388
3603
  return "";
3389
3604
  }
3390
3605
  action.local_qr_path = file;
3391
- action.local_qr_mime = qrMimeType(qrImageURL, action);
3606
+ action.local_qr_mime = "image/png";
3392
3607
  return file;
3393
3608
  }
3394
3609
 
@@ -3397,6 +3612,11 @@ function defaultQRFilePath(action, qrImageURL) {
3397
3612
  return path.join(os.tmpdir(), `itp-${id}.${qrFileExtension(qrImageURL, action)}`);
3398
3613
  }
3399
3614
 
3615
+ function defaultGeneratedQRFilePath(action) {
3616
+ const id = sanitizeFilename(action?.payment_intent_id || action?.id || "qr");
3617
+ return path.join(os.tmpdir(), `itp-${id}.png`);
3618
+ }
3619
+
3400
3620
  function qrFileExtension(qrImageURL, action = {}) {
3401
3621
  const mime = qrMimeType(qrImageURL, action);
3402
3622
  if (mime === "image/png") return "png";
@@ -3462,7 +3682,7 @@ function attachAgentQRImage(action, qrImageURL, localPath = "") {
3462
3682
  return action;
3463
3683
  }
3464
3684
 
3465
- function attachAgentGeneratedQRImage(action, localPath = "") {
3685
+ function attachAgentLocalQR(action, localPath = "") {
3466
3686
  if (!action || !localPath) return action;
3467
3687
  if (!Array.isArray(action.display)) {
3468
3688
  action.display = [];
@@ -3472,42 +3692,7 @@ function attachAgentGeneratedQRImage(action, localPath = "") {
3472
3692
  type: "image",
3473
3693
  format: "png",
3474
3694
  local_path: localPath,
3475
- instructions: "Render this local ItPay first-purchase entry QR for the human to scan. It starts account login/registration/profile authorization and should continue to payment for the same checkout after approval; it is not payment proof."
3476
- });
3477
- }
3478
- return action;
3479
- }
3480
-
3481
- async function attachAgentTextQR(action, flags = {}) {
3482
- if (!action?.url || action.agent_text_qr) return action;
3483
- const qr = await QRCode.toString(action.url, {
3484
- type: "utf8",
3485
- small: true,
3486
- errorCorrectionLevel: "M"
3487
- });
3488
- const lines = qr.replace(/\s+$/g, "").split(/\r?\n/);
3489
- const text = lines.join("\n");
3490
- const sha256 = crypto.createHash("sha256").update(text).digest("hex");
3491
- action.agent_text_qr = {
3492
- type: "terminal_text",
3493
- format: "unicode",
3494
- lines,
3495
- text,
3496
- fenced_text: `\`\`\`text\n${text}\n\`\`\``,
3497
- line_count: lines.length,
3498
- sha256,
3499
- url: action.url,
3500
- allowed_characters: "Only U+2588 FULL BLOCK, U+2580 UPPER HALF BLOCK, U+2584 LOWER HALF BLOCK, spaces, and newlines are valid inside the QR body.",
3501
- instructions: "Paste fenced_text verbatim into the normal assistant message, not the shell panel. Do not retype, translate, add line numbers, or add words inside the QR body. If any letters, digits, or Chinese characters appear inside the QR body, discard it and use the QR image URL/fallback link instead."
3502
- };
3503
- if (!Array.isArray(action.display)) {
3504
- action.display = [];
3505
- }
3506
- if (!action.display.some((item) => item?.type === "terminal_text")) {
3507
- action.display.push({
3508
- type: "terminal_text",
3509
- format: "unicode",
3510
- lines
3695
+ instructions: "Render this local QR image for the human to scan. It encodes the ItPay human action URL, not a provider raw QR payload."
3511
3696
  });
3512
3697
  }
3513
3698
  return action;
@@ -3648,6 +3833,7 @@ async function coreApi(pathname, options = {}, flags = {}) {
3648
3833
  if (options.ops) {
3649
3834
  headers["X-ItPay-Ops-Token"] = sandboxOpsToken(flags);
3650
3835
  }
3836
+ Object.assign(headers, options.headers || {});
3651
3837
  if (flags.request_id) headers["X-Request-ID"] = String(flags.request_id);
3652
3838
  if (flags.correlation_id) headers["X-Correlation-ID"] = String(flags.correlation_id);
3653
3839
  const targetURL = coreURL(pathname, flags);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itpay/cli",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "ItPay CLI, buyer skill, and agent-readable docs for agent-native commerce.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -37,6 +37,7 @@
37
37
  "node": ">=18"
38
38
  },
39
39
  "dependencies": {
40
- "qrcode": "^1.5.4"
40
+ "qrcode": "^1.5.4",
41
+ "json5": "^2.2.3"
41
42
  }
42
43
  }
@@ -73,6 +73,10 @@ itp buyer checkout create --cart <cart_id> --email <buyer_email> --phone <buyer_
73
73
  itp buyer checkout resume <checkout_id> --json
74
74
  itp buyer payment wait <payment_intent_id> --json
75
75
  itp buyer checkout status <checkout_id> --json
76
+ itp buyer refund create --order <order_id> --amount-minor <minor_units> --currency CNY --reason buyer_requested --json
77
+ itp buyer refund list --order <order_id> --json
78
+ itp buyer refund show <refund_id> --json
79
+ itp buyer refund cancel <refund_id> --reason buyer_changed_mind --json
76
80
  itp buyer vault grants list --checkout <checkout_id> --json
77
81
  itp buyer vault read --order <order_id> --artifact <vault_artifact_id> --json
78
82
  ```
@@ -100,6 +104,15 @@ registered company name or unified social credit code. If the user says
100
104
  "京东" or "那个京东商城", do not buy precise lookup until you resolve the exact
101
105
  registered name or run fuzzy search first.
102
106
 
107
+ Refund commands use ItPay shared order state. If `itp buyer refund create`
108
+ returns `policy_risk_confirmation_required`, explain the returned
109
+ `refund_eligibility.policy` and `agent_guidance` to the human first. Only retry
110
+ with `--confirm-policy-risk true` after explicit human confirmation.
111
+ Current buyer refunds are whole-order only; do not use line-item refund scope.
112
+ If the human cancels a refund before provider or money movement starts, use
113
+ `buyer refund cancel <refund_id> --json`; after cancel, the delivery claim can
114
+ be unlocked again by the ItPay backend.
115
+
103
116
  ## Non-Negotiable Rules
104
117
 
105
118
  1. Use `--json` for every ItPay command.