@itpay/cli 0.1.6 → 0.1.8
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 +210 -94
- package/package.json +3 -2
- package/skills/itpay-buyer/SKILL.md +13 -0
package/bin/itp
CHANGED
|
@@ -8,7 +8,6 @@ import { execFileSync } from "node:child_process";
|
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
import QRCode from "qrcode";
|
|
10
10
|
|
|
11
|
-
const VERSION = "0.1.5";
|
|
12
11
|
const DEFAULT_API_BASE = process.env.ITPAY_API_BASE || process.env.ITPAY_CORE_API_BASE || process.env.ITPAY_CORE_BASE_URL || "https://dev.api.itpay.ai";
|
|
13
12
|
const CONFIG_DIR = path.join(os.homedir(), ".itp");
|
|
14
13
|
const CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
|
|
@@ -19,6 +18,7 @@ const LOCK_PATH = path.join(CONFIG_DIR, "state.lock");
|
|
|
19
18
|
const CLI_FILE = fileURLToPath(import.meta.url);
|
|
20
19
|
const CLI_DIR = path.dirname(CLI_FILE);
|
|
21
20
|
const PACKAGE_ROOT = path.dirname(CLI_DIR);
|
|
21
|
+
const VERSION = packageVersion();
|
|
22
22
|
|
|
23
23
|
main().catch((error) => {
|
|
24
24
|
outputError(error);
|
|
@@ -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,15 +3422,6 @@ 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);
|
|
3238
|
-
if (localPath) {
|
|
3239
|
-
attachAgentGeneratedQRImage(action, localPath);
|
|
3240
|
-
annotateHumanActionPresentation(action, "");
|
|
3241
|
-
persistHumanAction(action, flags);
|
|
3242
|
-
return { rendered: false, mode: "json-local-auth-qr", outputs: [localPath] };
|
|
3243
|
-
}
|
|
3244
|
-
}
|
|
3245
3425
|
return { rendered: false, mode: flags.json ? "json" : mode };
|
|
3246
3426
|
}
|
|
3247
3427
|
const host = String(process.env.ITP_HOST || flags.host || "").toLowerCase();
|
|
@@ -3255,9 +3435,9 @@ async function renderHumanAction(action, flags = {}) {
|
|
|
3255
3435
|
persistHumanAction(action, flags);
|
|
3256
3436
|
return { rendered: false, mode: localPath ? "agent-local-image-qr" : "agent-image-qr", host, outputs: [localPath || "preferred_qr_url"] };
|
|
3257
3437
|
}
|
|
3258
|
-
|
|
3438
|
+
process.stderr.write(`No QR image available. Open payment URL: ${action.url}\n`);
|
|
3259
3439
|
persistHumanAction(action, flags);
|
|
3260
|
-
return { rendered: false, mode: "agent-
|
|
3440
|
+
return { rendered: false, mode: "agent-url-fallback", host, outputs: ["payment_url"] };
|
|
3261
3441
|
}
|
|
3262
3442
|
|
|
3263
3443
|
const renderResult = { rendered: false, mode, outputs: [] };
|
|
@@ -3276,15 +3456,13 @@ async function renderHumanAction(action, flags = {}) {
|
|
|
3276
3456
|
await downloadQRImage(qrImageURL, file, flags);
|
|
3277
3457
|
action.local_qr_path = file;
|
|
3278
3458
|
action.local_qr_mime = qrMimeType(qrImageURL, action);
|
|
3459
|
+
process.stderr.write(`Alipay QR image: ${file}\n`);
|
|
3460
|
+
renderResult.rendered = true;
|
|
3461
|
+
renderResult.outputs.push(file);
|
|
3462
|
+
if (mode === "file") return renderResult;
|
|
3279
3463
|
} else {
|
|
3280
|
-
|
|
3281
|
-
action.local_qr_path = file;
|
|
3282
|
-
action.local_qr_mime = "image/png";
|
|
3464
|
+
process.stderr.write(`No QR image available. Open payment URL: ${action.url}\n`);
|
|
3283
3465
|
}
|
|
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
3466
|
}
|
|
3289
3467
|
|
|
3290
3468
|
if (qrImageURL) {
|
|
@@ -3354,27 +3532,7 @@ function annotateHumanActionPresentation(action, qrImageURL) {
|
|
|
3354
3532
|
function shouldPrepareLocalQRForJSON(mode, flags = {}, action = {}) {
|
|
3355
3533
|
if (mode === "file" || flags.qr_file || process.env.ITP_QR_FILE) return true;
|
|
3356
3534
|
if (action?.qr_png_url || action?.preferred_qr_url || action?.qr_image_url) return true;
|
|
3357
|
-
|
|
3358
|
-
return mode === "agent" || mode === "chat";
|
|
3359
|
-
}
|
|
3360
|
-
|
|
3361
|
-
function shouldGenerateLocalQRFromActionURL(action = {}) {
|
|
3362
|
-
return action?.kind === "auth_qr" && Boolean(action?.url);
|
|
3363
|
-
}
|
|
3364
|
-
|
|
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, "");
|
|
3368
|
-
try {
|
|
3369
|
-
await QRCode.toFile(file, action.url, { errorCorrectionLevel: "M", width: 512, margin: 2 });
|
|
3370
|
-
} catch (error) {
|
|
3371
|
-
if (!optional) throw error;
|
|
3372
|
-
action.local_qr_error = safeErrorMessage(error);
|
|
3373
|
-
return "";
|
|
3374
|
-
}
|
|
3375
|
-
action.local_qr_path = file;
|
|
3376
|
-
action.local_qr_mime = "image/png";
|
|
3377
|
-
return file;
|
|
3535
|
+
return false;
|
|
3378
3536
|
}
|
|
3379
3537
|
|
|
3380
3538
|
async function prepareLocalQRFile(action, qrImageURL, flags = {}, optional = false) {
|
|
@@ -3462,57 +3620,6 @@ function attachAgentQRImage(action, qrImageURL, localPath = "") {
|
|
|
3462
3620
|
return action;
|
|
3463
3621
|
}
|
|
3464
3622
|
|
|
3465
|
-
function attachAgentGeneratedQRImage(action, localPath = "") {
|
|
3466
|
-
if (!action || !localPath) return action;
|
|
3467
|
-
if (!Array.isArray(action.display)) {
|
|
3468
|
-
action.display = [];
|
|
3469
|
-
}
|
|
3470
|
-
if (!action.display.some((item) => item?.type === "image" && item?.local_path === localPath)) {
|
|
3471
|
-
action.display.push({
|
|
3472
|
-
type: "image",
|
|
3473
|
-
format: "png",
|
|
3474
|
-
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
|
|
3511
|
-
});
|
|
3512
|
-
}
|
|
3513
|
-
return action;
|
|
3514
|
-
}
|
|
3515
|
-
|
|
3516
3623
|
async function downloadQRImage(qrImageURL, file, flags = {}) {
|
|
3517
3624
|
const controller = new AbortController();
|
|
3518
3625
|
const timer = setTimeout(() => controller.abort(), apiTimeoutMs(flags));
|
|
@@ -3648,6 +3755,7 @@ async function coreApi(pathname, options = {}, flags = {}) {
|
|
|
3648
3755
|
if (options.ops) {
|
|
3649
3756
|
headers["X-ItPay-Ops-Token"] = sandboxOpsToken(flags);
|
|
3650
3757
|
}
|
|
3758
|
+
Object.assign(headers, options.headers || {});
|
|
3651
3759
|
if (flags.request_id) headers["X-Request-ID"] = String(flags.request_id);
|
|
3652
3760
|
if (flags.correlation_id) headers["X-Correlation-ID"] = String(flags.correlation_id);
|
|
3653
3761
|
const targetURL = coreURL(pathname, flags);
|
|
@@ -3869,6 +3977,14 @@ function readConfig() {
|
|
|
3869
3977
|
return readJSON(CONFIG_PATH, {});
|
|
3870
3978
|
}
|
|
3871
3979
|
|
|
3980
|
+
function packageVersion() {
|
|
3981
|
+
try {
|
|
3982
|
+
return JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, "package.json"), "utf8")).version || "0.0.0";
|
|
3983
|
+
} catch {
|
|
3984
|
+
return "0.0.0";
|
|
3985
|
+
}
|
|
3986
|
+
}
|
|
3987
|
+
|
|
3872
3988
|
function writeConfig(value) {
|
|
3873
3989
|
writeJSON0600(CONFIG_PATH, value);
|
|
3874
3990
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@itpay/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
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.
|