@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 +253 -67
- package/package.json +3 -2
- package/skills/itpay-buyer/SKILL.md +13 -0
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
|
|
3237
|
-
const localPath = await
|
|
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
|
-
|
|
3428
|
+
attachAgentLocalQR(action, localPath);
|
|
3240
3429
|
annotateHumanActionPresentation(action, "");
|
|
3241
3430
|
persistHumanAction(action, flags);
|
|
3242
|
-
return { rendered: false, mode: "json-local-
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
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
|
-
|
|
3358
|
-
return mode === "agent" || mode === "chat";
|
|
3565
|
+
return false;
|
|
3359
3566
|
}
|
|
3360
3567
|
|
|
3361
|
-
function shouldGenerateLocalQRFromActionURL(action = {}) {
|
|
3362
|
-
|
|
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
|
|
3366
|
-
if (!
|
|
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
|
|
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 =
|
|
3585
|
+
action.local_qr_mime = qrMimeType(qrImageURL, action);
|
|
3377
3586
|
return file;
|
|
3378
3587
|
}
|
|
3379
3588
|
|
|
3380
|
-
async function
|
|
3381
|
-
if (!
|
|
3382
|
-
const file = flags.qr_file || process.env.ITP_QR_FILE ||
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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.
|
|
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.
|