@itpay/cli 0.2.3 → 0.2.4
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/README.md +3 -3
- package/bin/itp +3 -3
- package/docs/agent/buyer/cart-checkout.json +1 -1
- package/docs/agent/buyer/payment-qr.json +8 -8
- package/docs/agent/buyer/qr-refresh.json +2 -2
- package/docs/agent/buyer/quickstart.json +4 -4
- package/lib/buyer.js +178 -8
- package/lib/runtime.js +3 -3
- package/package.json +1 -1
- package/skills/itpay-buyer/SKILL.md +8 -8
package/README.md
CHANGED
|
@@ -213,7 +213,7 @@ redacted secure delivery status:
|
|
|
213
213
|
```bash
|
|
214
214
|
itp buyer catalog search --query 企业工商 --json
|
|
215
215
|
itp buyer cart create --variant var_itpay_enterprise_fuzzy_search_cny01 --input company_name=阿里 --json
|
|
216
|
-
itp buyer checkout create --cart <cart_id> --email
|
|
216
|
+
itp buyer checkout create --cart <cart_id> --email <buyer_email> --json
|
|
217
217
|
itp buyer payment wait <payment_intent_id> --json
|
|
218
218
|
itp buyer checkout status <checkout_id> --json
|
|
219
219
|
```
|
|
@@ -221,7 +221,7 @@ itp buyer checkout status <checkout_id> --json
|
|
|
221
221
|
For the one-command buyer helper:
|
|
222
222
|
|
|
223
223
|
```bash
|
|
224
|
-
itp buy var_itpay_enterprise_fuzzy_search_cny01 --email
|
|
224
|
+
itp buy var_itpay_enterprise_fuzzy_search_cny01 --email <buyer_email> --input company_name=阿里 --display agent --no-wait-payment --json
|
|
225
225
|
```
|
|
226
226
|
|
|
227
227
|
For multi-item cart tests:
|
|
@@ -239,7 +239,7 @@ Payment QR rules:
|
|
|
239
239
|
- Otherwise render the ItPay-hosted `qr_png_url` / `preferred_qr_url`.
|
|
240
240
|
- Use `mobile_wallet_url` only as a human mobile fallback.
|
|
241
241
|
- Do not generate your own QR from payment URLs.
|
|
242
|
-
- In agent app clients, send `
|
|
242
|
+
- In agent app clients, send `human_visible_markdown` to the human first, then run `next.command`.
|
|
243
243
|
- Treat only `payment_intent.verified` as payment success.
|
|
244
244
|
|
|
245
245
|
If the human wants the agent to analyze delivered content, the human must reveal
|
package/bin/itp
CHANGED
|
@@ -93,8 +93,8 @@ async function main() {
|
|
|
93
93
|
output({
|
|
94
94
|
version: VERSION,
|
|
95
95
|
commands: [
|
|
96
|
-
"buy var_pubg_couple_skin_cny20 --
|
|
97
|
-
"buy var_pubg_couple_skin_cny20 --
|
|
96
|
+
"buy var_pubg_couple_skin_cny20 --email <buyer_email> --phone +8613800000000 --display agent --no-wait-payment --json",
|
|
97
|
+
"buy var_pubg_couple_skin_cny20 --email <buyer_email> --phone +8613800000000 --no-wait --json",
|
|
98
98
|
"buyer catalog search --query 企业工商 --category business_data_api --provider itpay_enterprise_data --json",
|
|
99
99
|
"buyer catalog get --variant var_pubg_couple_skin_cny20 --json",
|
|
100
100
|
"buyer cart create --variant var_pubg_couple_skin_cny20 --json",
|
|
@@ -105,7 +105,7 @@ async function main() {
|
|
|
105
105
|
"buyer shelf manifest --json",
|
|
106
106
|
"buyer shelf snapshot --version <catalog_version> --json",
|
|
107
107
|
"buyer shelf delta --since <catalog_version> --json",
|
|
108
|
-
"buyer checkout create --cart <cart_id> --method alipay --email
|
|
108
|
+
"buyer checkout create --cart <cart_id> --method alipay --email <buyer_email> --phone +8613800000000 --json",
|
|
109
109
|
"buyer checkout status <checkout_id> --json",
|
|
110
110
|
"buyer checkout resume <checkout_id> --json",
|
|
111
111
|
"buyer auth status --json",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
70
|
"intent": "do both steps through the high-level command",
|
|
71
|
-
"command": "itp buy <variant_id> --
|
|
71
|
+
"command": "itp buy <variant_id> --email <buyer_email> --phone <buyer_phone> --display agent --no-wait-payment --json",
|
|
72
72
|
"success_signal": "response.status is waiting_human_auth, waiting_user_payment, payment_verified, or delivery_claimable"
|
|
73
73
|
}
|
|
74
74
|
],
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"Checkout returns human_action.kind=auth_qr for the first-purchase auth-to-payment entry.",
|
|
9
9
|
"PaymentIntent has been created.",
|
|
10
10
|
"The CLI returned human_action.kind=payment_qr, qr_png_url, preferred_qr_url, or qr_image_url.",
|
|
11
|
-
"The human needs to scan with
|
|
11
|
+
"The human needs to scan with the payment provider."
|
|
12
12
|
],
|
|
13
13
|
"required_state": {
|
|
14
14
|
"needs": ["payment_intent_id", "human_action", "qr_png_url/preferred_qr_url or local_qr_path"],
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"commands": [
|
|
18
18
|
{
|
|
19
19
|
"intent": "create payment intent through the high-level buy flow",
|
|
20
|
-
"command": "itp buy <variant_id> --
|
|
21
|
-
"success_signal": "response.
|
|
20
|
+
"command": "itp buy <variant_id> --email <buyer_email> --phone <buyer_phone> --display agent --no-wait-payment --json",
|
|
21
|
+
"success_signal": "response.human_visible_markdown and response.next.command are present"
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
"intent": "recover a scanner order-not-found display problem",
|
|
@@ -33,17 +33,17 @@
|
|
|
33
33
|
"Payment QR display requires both a valid/saved buyer session and payment_intent_id. If payment_intent_id appears before buyer_session is saved, run buyer checkout resume/status instead of showing payment QR.",
|
|
34
34
|
"Do not stop after showing auth_qr unless the human explicitly asks you to pause. Keep waiting/resuming the same checkout so the post-auth payment handoff can continue.",
|
|
35
35
|
"Do not describe auth_qr as payment success or payment proof. Only payment_intent.verified proves payment.",
|
|
36
|
-
"In Codex or Claude Code app clients, the default UX is show-then-wait:
|
|
37
|
-
"For Telegram-style message clients, show response.
|
|
36
|
+
"In Codex or Claude Code app clients, the default UX is show-then-wait: send response.human_visible_markdown to the human first, then run response.next.command.",
|
|
37
|
+
"For Telegram-style message clients, show response.human_visible_markdown and stop unless the human asks you to keep waiting or returns with a payment question.",
|
|
38
38
|
"MUST show local_qr_path first when present. Many agent clients do not reliably render remote QR images; local_qr_path is the most reliable desktop/chat display artifact.",
|
|
39
39
|
"If local_qr_path is not present, show qr_png_url or preferred_qr_url as the primary scannable QR. This is an ItPay-hosted human QR image; it may render the native provider payment code for scanner reliability, but the agent must not request or decode the raw provider payload.",
|
|
40
40
|
"Use qr_image_url/SVG only as a fallback when PNG/local rendering is unavailable.",
|
|
41
41
|
"For mobile users, present mobile_wallet_url as a clickable payment button when present.",
|
|
42
42
|
"payment_entry_url is the stable ItPay status/payment page. qr_png_url/local_qr_path is usually the better desktop scanner artifact.",
|
|
43
|
-
"
|
|
43
|
+
"The provider can briefly show order not found even after precreate succeeded. Tell the human to wait 30-60 seconds and retry the same QR/page. Do not create a new checkout, do not create a new payment intent, and do not repeatedly refresh unless the ItPay page or CLI explicitly asks for recovery.",
|
|
44
44
|
"Opening the payment page must not be treated as paid.",
|
|
45
45
|
"Do not run payment wait before the QR/link has been sent to the human.",
|
|
46
|
-
"After showing QR, run
|
|
46
|
+
"After showing QR, run next.command or buyer payment wait for the same payment_intent_id."
|
|
47
47
|
],
|
|
48
48
|
"forbidden": [
|
|
49
49
|
"Do not encode payment_entry_url into a new QR yourself when qr_png_url/local_qr_path is available.",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"command": "itp docs show payment-wait --role buyer --json"
|
|
62
62
|
},
|
|
63
63
|
{
|
|
64
|
-
"condition": "
|
|
64
|
+
"condition": "the payment provider says order not found after waiting on the same QR/page",
|
|
65
65
|
"topic": "qr-refresh",
|
|
66
66
|
"command": "itp docs show qr-refresh --role buyer --json"
|
|
67
67
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"title": "QR Refresh Recovery",
|
|
6
6
|
"purpose": "Teach the agent how to recover scanner problems without changing payment truth or creating a second order.",
|
|
7
7
|
"when_to_use": [
|
|
8
|
-
"The human reports
|
|
8
|
+
"The human reports the payment provider says order not found after waiting 30-60 seconds on the same QR/page.",
|
|
9
9
|
"The QR expired or the wallet cannot load it.",
|
|
10
10
|
"The payment intent is still not verified."
|
|
11
11
|
],
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"QR refresh is display recovery only. ItPay may return the same QR again when the current provider QR is still valid.",
|
|
30
30
|
"Use the same payment_intent_id.",
|
|
31
31
|
"If QR does not render, first use the returned local_qr_path, qr_png_url, or preferred_qr_url. Do not generate a new QR from guessed provider payload.",
|
|
32
|
-
"For
|
|
32
|
+
"For provider order-not-found, first ask the human to wait and scan/open the same QR/page again; use refresh only after that wait has failed.",
|
|
33
33
|
"If refresh succeeds, show only the returned ItPay QR/display URL and continue waiting on the same payment_intent_id.",
|
|
34
34
|
"After refresh, continue waiting through the normal wait contract."
|
|
35
35
|
],
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
"intent": "buy a selected variant with the high-level cart-first flow",
|
|
39
|
-
"command": "itp buy <variant_id> --
|
|
40
|
-
"success_signal": "response.status becomes waiting_human_auth or waiting_user_payment; if waiting_user_payment, response.
|
|
39
|
+
"command": "itp buy <variant_id> --email <buyer_email> --phone <buyer_phone> --display agent --no-wait-payment --json",
|
|
40
|
+
"success_signal": "response.status becomes waiting_human_auth or waiting_user_payment; if waiting_user_payment, response.human_visible_markdown and response.next.command are present"
|
|
41
41
|
}
|
|
42
42
|
],
|
|
43
43
|
"agent_rules": [
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
"After showing auth_qr, keep running/resuming the same checkout unless the human explicitly asks you to pause. Do not stop merely because a QR was displayed.",
|
|
55
55
|
"When a buyer command returns buyer_session.status=buyer_session_saved, the CLI has stored the buyer account session for this agent device. You may continue with checkout/payment or `buyer vault` commands without asking the human for a token.",
|
|
56
56
|
"Vault grants are only for reading approved delivered artifacts. They are not buyer session credentials and do not authorize refunds or account/order management.",
|
|
57
|
-
"Use high-level itp buy when possible, but in agent app clients split payment into two visible steps: first return and show
|
|
58
|
-
"If response.
|
|
57
|
+
"Use high-level itp buy when possible, but in agent app clients split payment into two visible steps: first return and show human_visible_markdown, then run next.command.",
|
|
58
|
+
"If response.human_visible_markdown is present, send it to the human immediately in the same user-visible message. It includes the QR image and payment link.",
|
|
59
59
|
"Show the returned payment QR exactly as provided before starting payment wait.",
|
|
60
60
|
"Payment truth comes only from payment_intent.verified.",
|
|
61
61
|
"Secure delivery goes to the human first; report redacted delivery status only."
|
package/lib/buyer.js
CHANGED
|
@@ -7,9 +7,10 @@ import { coreApi, coreApiBase } from "./http.js";
|
|
|
7
7
|
import { renderHumanAction, renderItPayPaymentAction, shouldReturnAfterAgentTextQR, writeWaitHeartbeat } from "./render-human.js";
|
|
8
8
|
|
|
9
9
|
async function buyerBuy(flags) {
|
|
10
|
+
rejectBuyerSandboxFlag(flags);
|
|
10
11
|
const showThenWait = shouldReturnAfterAgentTextQR(flags);
|
|
11
12
|
const selectionID = flags.selection || flags.variant || flags.catalog_variant_id || flags.item || flags.catalog_item_id;
|
|
12
|
-
if (!selectionID) throw new Error("catalog variant id is required, for example: itp buy
|
|
13
|
+
if (!selectionID) throw new Error("catalog variant id is required, for example: itp buy <variant_id> --email <buyer_email> --json");
|
|
13
14
|
const selection = await resolveBuyerCatalogSelection(selectionID, flags);
|
|
14
15
|
const cart = await createBuyerCart(selection, flags);
|
|
15
16
|
let checkout = await createBuyerCheckoutFromCart(cart, selection, flags);
|
|
@@ -54,7 +55,7 @@ async function buyerBuy(flags) {
|
|
|
54
55
|
next: {
|
|
55
56
|
command: paymentWaitCommand(intent.payment_intent_id, { short: true }),
|
|
56
57
|
safe_for_agent: true,
|
|
57
|
-
instruction: "Send
|
|
58
|
+
instruction: "Send human_visible_markdown to the human first, then run this wait command unless the human explicitly asked you to pause."
|
|
58
59
|
}
|
|
59
60
|
}));
|
|
60
61
|
return;
|
|
@@ -81,6 +82,7 @@ async function buyerBuy(flags) {
|
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
async function buyer(command, rest, flags) {
|
|
85
|
+
rejectBuyerSandboxFlag(flags);
|
|
84
86
|
const subcommand = rest[0] && !String(rest[0]).startsWith("--") ? rest[0] : "";
|
|
85
87
|
if (command === "catalog") {
|
|
86
88
|
if (subcommand === "search") {
|
|
@@ -326,7 +328,7 @@ async function buyer(command, rest, flags) {
|
|
|
326
328
|
: {
|
|
327
329
|
command: paymentWaitCommand(intent.payment_intent_id, { short: true }),
|
|
328
330
|
safe_for_agent: true,
|
|
329
|
-
instruction: "Send
|
|
331
|
+
instruction: "Send human_visible_markdown to the human first, then run this wait command unless the human explicitly asked you to pause."
|
|
330
332
|
}
|
|
331
333
|
}));
|
|
332
334
|
return;
|
|
@@ -349,7 +351,7 @@ async function buyer(command, rest, flags) {
|
|
|
349
351
|
next: {
|
|
350
352
|
command: paymentWaitCommand(intent.payment_intent_id, { short: true }),
|
|
351
353
|
safe_for_agent: true,
|
|
352
|
-
instruction: "Send
|
|
354
|
+
instruction: "Send human_visible_markdown to the human first, then run this wait command unless the human explicitly asked you to pause."
|
|
353
355
|
}
|
|
354
356
|
}));
|
|
355
357
|
return;
|
|
@@ -400,7 +402,7 @@ async function buyer(command, rest, flags) {
|
|
|
400
402
|
: {
|
|
401
403
|
command: paymentWaitCommand(refreshed.payment_intent_id, { short: true }),
|
|
402
404
|
safe_for_agent: true,
|
|
403
|
-
instruction: "Send
|
|
405
|
+
instruction: "Send human_visible_markdown to the human first, then run this wait command unless the human explicitly asked you to pause."
|
|
404
406
|
}
|
|
405
407
|
}));
|
|
406
408
|
return;
|
|
@@ -822,7 +824,7 @@ async function createBuyerCheckoutFromCart(cart, selection = null, flags = {}) {
|
|
|
822
824
|
const cartID = typeof cart === "string" ? cart : (cart?.cart_id || cart?.id);
|
|
823
825
|
if (!cartID) throw new Error("cart_id is required");
|
|
824
826
|
const deliveryContact = {};
|
|
825
|
-
if (flags.email) deliveryContact.email = flags.email;
|
|
827
|
+
if (flags.email) deliveryContact.email = realBuyerEmail(flags.email);
|
|
826
828
|
if (flags.phone) deliveryContact.phone = flags.phone;
|
|
827
829
|
const missing = requiredDeliveryContactFields(selection).filter((field) => !deliveryContact[field]);
|
|
828
830
|
if (missing.length) {
|
|
@@ -861,6 +863,21 @@ function requiredDeliveryContactFields(selection) {
|
|
|
861
863
|
return Array.isArray(fields) ? fields.map((field) => String(field).trim()).filter(Boolean) : [];
|
|
862
864
|
}
|
|
863
865
|
|
|
866
|
+
function rejectBuyerSandboxFlag(flags = {}) {
|
|
867
|
+
if (!flags?.sandbox) return;
|
|
868
|
+
throw new Error("--sandbox is not used by buyer commands; environment is selected by the ItPay API base. Run the command without --sandbox.");
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
function realBuyerEmail(value) {
|
|
872
|
+
const email = String(value || "").trim();
|
|
873
|
+
if (!email) return "";
|
|
874
|
+
const domain = email.split("@").pop().toLowerCase();
|
|
875
|
+
if (["example.com", "example.net", "example.org"].includes(domain)) {
|
|
876
|
+
throw new Error("placeholder email rejected; ask the human for their real delivery email before checkout.");
|
|
877
|
+
}
|
|
878
|
+
return email;
|
|
879
|
+
}
|
|
880
|
+
|
|
864
881
|
async function createBuyerPaymentIntent(checkoutID, flags = {}) {
|
|
865
882
|
if (!checkoutID) throw new Error("checkout_id is required");
|
|
866
883
|
const method = String(flags.method || flags.payment_method || "alipay").toLowerCase();
|
|
@@ -1367,7 +1384,7 @@ async function refreshBuyerPaymentQR(paymentIntentID, flags = {}) {
|
|
|
1367
1384
|
if (intent.status === "verified") return intent;
|
|
1368
1385
|
const refreshURL = intent.qr_refresh_url;
|
|
1369
1386
|
if (!refreshURL) {
|
|
1370
|
-
throw new Error("payment intent does not expose qr_refresh_url; refresh is supported only for refreshable
|
|
1387
|
+
throw new Error("payment intent does not expose qr_refresh_url; refresh is supported only for refreshable provider QR intents");
|
|
1371
1388
|
}
|
|
1372
1389
|
return await coreApi(refreshURL, {
|
|
1373
1390
|
method: "POST",
|
|
@@ -1436,10 +1453,11 @@ function isBuyerDeliveryComplete(result) {
|
|
|
1436
1453
|
}
|
|
1437
1454
|
|
|
1438
1455
|
function buyerRunOutput(value = {}) {
|
|
1456
|
+
const body = compactBuyerOutput(value);
|
|
1439
1457
|
return normalizeBuyerMoneyFields(stripInternalBuyerFields({
|
|
1440
1458
|
schema_version: "itp.buyer.v1",
|
|
1459
|
+
...body,
|
|
1441
1460
|
docs: value.docs || buyerDocsFor(value),
|
|
1442
|
-
...value,
|
|
1443
1461
|
secrets: {
|
|
1444
1462
|
raw_content_included: false,
|
|
1445
1463
|
claim_token_included: false,
|
|
@@ -1448,6 +1466,158 @@ function buyerRunOutput(value = {}) {
|
|
|
1448
1466
|
}));
|
|
1449
1467
|
}
|
|
1450
1468
|
|
|
1469
|
+
function compactBuyerOutput(value = {}) {
|
|
1470
|
+
const result = {};
|
|
1471
|
+
if (value.status !== undefined) result.status = value.status;
|
|
1472
|
+
if (value.payment_handoff?.markdown) {
|
|
1473
|
+
result.must_reply_to_human_before_next_command = true;
|
|
1474
|
+
result.human_visible_markdown = value.payment_handoff.markdown;
|
|
1475
|
+
}
|
|
1476
|
+
for (const [key, item] of Object.entries(value)) {
|
|
1477
|
+
if (["docs", "status"].includes(key)) continue;
|
|
1478
|
+
result[key] = compactBuyerField(key, item);
|
|
1479
|
+
}
|
|
1480
|
+
return result;
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
function compactBuyerField(key, value) {
|
|
1484
|
+
if (key === "selection") return compactSelection(value);
|
|
1485
|
+
if (key === "selections") return Array.isArray(value) ? value.map(compactSelection) : value;
|
|
1486
|
+
if (key === "product") return compactProduct(value);
|
|
1487
|
+
if (key === "products") return Array.isArray(value) ? value.map(compactProduct) : value;
|
|
1488
|
+
if (key === "cart") return compactCart(value);
|
|
1489
|
+
if (key === "checkout") return compactCheckout(value);
|
|
1490
|
+
if (key === "payment_intent") return compactPaymentIntent(value);
|
|
1491
|
+
return value;
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
function compactSelection(selection = {}) {
|
|
1495
|
+
if (!selection || typeof selection !== "object") return selection;
|
|
1496
|
+
return compactObject({
|
|
1497
|
+
catalog_item_id: selection.catalog_item_id,
|
|
1498
|
+
catalog_variant_id: selection.catalog_variant_id,
|
|
1499
|
+
ucp_variant_id: selection.ucp_variant_id,
|
|
1500
|
+
offer_id: selection.offer_id,
|
|
1501
|
+
title: selection.title,
|
|
1502
|
+
variant_title: selection.variant_title,
|
|
1503
|
+
amount: selection.expected_amount,
|
|
1504
|
+
currency: selection.currency,
|
|
1505
|
+
required_contact_fields: selection.required_contact_fields,
|
|
1506
|
+
purchasable: selection.purchasable
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
function compactProduct(product = {}) {
|
|
1511
|
+
if (!product || typeof product !== "object") return product;
|
|
1512
|
+
return compactObject({
|
|
1513
|
+
id: product.id,
|
|
1514
|
+
title: product.title,
|
|
1515
|
+
description: product.description,
|
|
1516
|
+
selected: product.selected,
|
|
1517
|
+
variants: Array.isArray(product.variants) ? product.variants.map(compactVariant) : undefined
|
|
1518
|
+
});
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
function compactVariant(variant = {}) {
|
|
1522
|
+
const metadata = variant.metadata || {};
|
|
1523
|
+
return compactObject({
|
|
1524
|
+
id: variant.id,
|
|
1525
|
+
title: variant.title,
|
|
1526
|
+
description: variant.description,
|
|
1527
|
+
price: variant.price,
|
|
1528
|
+
availability: variant.availability,
|
|
1529
|
+
required_profile_fields: metadata["ai.itpay.required_profile_fields"],
|
|
1530
|
+
input_schema_json: metadata["ai.itpay.api.input_schema_json"],
|
|
1531
|
+
agent_explanation_zh: metadata["ai.itpay.agent_explanation_zh"]
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
function compactCart(cart = {}) {
|
|
1536
|
+
if (!cart || typeof cart !== "object") return cart;
|
|
1537
|
+
return compactObject({
|
|
1538
|
+
cart_id: cart.cart_id || cart.id,
|
|
1539
|
+
status: cart.status,
|
|
1540
|
+
amount: cart.amount,
|
|
1541
|
+
currency: cart.currency,
|
|
1542
|
+
line_items: Array.isArray(cart.line_items) ? cart.line_items.map(compactCartLine) : undefined
|
|
1543
|
+
});
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
function compactCartLine(line = {}) {
|
|
1547
|
+
return compactObject({
|
|
1548
|
+
id: line.id,
|
|
1549
|
+
quantity: line.quantity,
|
|
1550
|
+
amount: line.amount,
|
|
1551
|
+
currency: line.currency,
|
|
1552
|
+
input: line.input,
|
|
1553
|
+
item: line.item ? compactObject({
|
|
1554
|
+
id: line.item.id,
|
|
1555
|
+
title: line.item.title,
|
|
1556
|
+
catalog_item_id: line.item.catalog_item_id,
|
|
1557
|
+
catalog_variant_id: line.item.catalog_variant_id,
|
|
1558
|
+
offer_id: line.item.offer_id
|
|
1559
|
+
}) : undefined
|
|
1560
|
+
});
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
function compactCheckout(checkout = {}) {
|
|
1564
|
+
if (!checkout || typeof checkout !== "object") return checkout;
|
|
1565
|
+
return compactObject({
|
|
1566
|
+
checkout_id: checkout.checkout_id,
|
|
1567
|
+
cart_id: checkout.cart_id,
|
|
1568
|
+
order_id: checkout.order_id,
|
|
1569
|
+
status: checkout.status,
|
|
1570
|
+
delivery_status: checkout.delivery_status,
|
|
1571
|
+
identity_status: checkout.identity_status,
|
|
1572
|
+
next_required_action: checkout.next_required_action,
|
|
1573
|
+
payment_intent_id: checkout.payment_intent_id,
|
|
1574
|
+
amount: checkout.amount,
|
|
1575
|
+
currency: checkout.currency,
|
|
1576
|
+
delivery: checkout.delivery,
|
|
1577
|
+
human_action: checkout.human_action,
|
|
1578
|
+
agent_next_actions: checkout.agent_next_actions
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
function compactPaymentIntent(intent = {}) {
|
|
1583
|
+
if (!intent || typeof intent !== "object") return intent;
|
|
1584
|
+
return compactObject({
|
|
1585
|
+
payment_intent_id: intent.payment_intent_id,
|
|
1586
|
+
payment_attempt_id: intent.payment_attempt_id,
|
|
1587
|
+
checkout_id: intent.checkout_id,
|
|
1588
|
+
order_id: intent.order_id,
|
|
1589
|
+
status: intent.status,
|
|
1590
|
+
amount: intent.amount,
|
|
1591
|
+
currency: intent.currency,
|
|
1592
|
+
human_action: compactHumanAction(intent.human_action),
|
|
1593
|
+
qr_png_url: intent.qr_png_url,
|
|
1594
|
+
qr_image_url: intent.qr_image_url,
|
|
1595
|
+
mobile_wallet_url: intent.mobile_wallet_url,
|
|
1596
|
+
agent_wait: intent.agent_wait,
|
|
1597
|
+
agent_next_actions: intent.agent_next_actions
|
|
1598
|
+
});
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
function compactHumanAction(action = null) {
|
|
1602
|
+
if (!action || typeof action !== "object") return action;
|
|
1603
|
+
return compactObject({
|
|
1604
|
+
kind: action.kind,
|
|
1605
|
+
url: action.url,
|
|
1606
|
+
local_qr_path: action.local_qr_path,
|
|
1607
|
+
local_qr_mime: action.local_qr_mime,
|
|
1608
|
+
qr_png_url: action.qr_png_url,
|
|
1609
|
+
qr_image_url: action.qr_image_url,
|
|
1610
|
+
preferred_qr_url: action.preferred_qr_url,
|
|
1611
|
+
mobile_wallet_url: action.mobile_wallet_url,
|
|
1612
|
+
agent_display_hint: action.agent_display_hint,
|
|
1613
|
+
expires_at: action.expires_at
|
|
1614
|
+
});
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
function compactObject(value = {}) {
|
|
1618
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined && item !== null));
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1451
1621
|
function normalizeBuyerMoneyFields(value) {
|
|
1452
1622
|
if (Array.isArray(value)) return value.map((item) => normalizeBuyerMoneyFields(item));
|
|
1453
1623
|
if (!value || typeof value !== "object") return value;
|
package/lib/runtime.js
CHANGED
|
@@ -448,7 +448,7 @@ async function maybeMockApproveDeviceAuth(authId, flags) {
|
|
|
448
448
|
return;
|
|
449
449
|
}
|
|
450
450
|
if (!fakeTestingAllowed(flags)) {
|
|
451
|
-
throw new Error("mock approval is developer-only and disabled for agent runs; use real
|
|
451
|
+
throw new Error("mock approval is developer-only and disabled for agent runs; use real payment-provider authentication");
|
|
452
452
|
}
|
|
453
453
|
const alipayUserId = flags.alipay_user_id || process.env.ITPAY_MOCK_ALIPAY_USER_ID || `2088${crypto.randomInt(100000000000, 999999999999)}`;
|
|
454
454
|
await api(`/api/itp/auth/device/${encodeURIComponent(authId)}/mock-approve`, {
|
|
@@ -692,10 +692,10 @@ async function createCheckoutResult(flags) {
|
|
|
692
692
|
|
|
693
693
|
function validateLivePaymentFlags(method, flags = {}) {
|
|
694
694
|
if (String(method).toLowerCase() === "fake" && !fakeTestingAllowed(flags)) {
|
|
695
|
-
throw new Error("fake payment is developer-only and disabled for agent runs; use --method alipay for local
|
|
695
|
+
throw new Error("fake payment is developer-only and disabled for agent runs; use --method alipay for local and live testing");
|
|
696
696
|
}
|
|
697
697
|
if ((flags.mock_approve || process.env.ITPAY_MOCK_APPROVE === "true" || process.env.ITPAY_MOCK_APPROVE === "1") && !fakeTestingAllowed(flags)) {
|
|
698
|
-
throw new Error("mock approval is developer-only and disabled for agent runs; use real
|
|
698
|
+
throw new Error("mock approval is developer-only and disabled for agent runs; use real payment-provider authentication");
|
|
699
699
|
}
|
|
700
700
|
}
|
|
701
701
|
|
package/package.json
CHANGED
|
@@ -59,7 +59,7 @@ read this skill
|
|
|
59
59
|
The high-level command can wrap this flow:
|
|
60
60
|
|
|
61
61
|
```bash
|
|
62
|
-
itp buy <variant_id> --
|
|
62
|
+
itp buy <variant_id> --email <buyer_email> --phone <buyer_phone> --display agent --no-wait-payment --json
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
For step-by-step testing:
|
|
@@ -93,7 +93,7 @@ itp buyer cart create --variant var_itpay_enterprise_fuzzy_search_cny01 --input
|
|
|
93
93
|
itp buyer cart show <cart_id> --json
|
|
94
94
|
itp buyer cart add <cart_id> --variant var_itpay_enterprise_fuzzy_search_cny01 --input company_name=美团 --json
|
|
95
95
|
itp buyer cart create --variant var_itpay_enterprise_precise_lookup_cny05 --input company_name_or_credit_no=北京京东世纪贸易有限公司 --json
|
|
96
|
-
itp buy var_itpay_enterprise_fuzzy_search_cny01 --
|
|
96
|
+
itp buy var_itpay_enterprise_fuzzy_search_cny01 --email <buyer_email> --input company_name=京东 --display agent --no-wait-payment --json
|
|
97
97
|
```
|
|
98
98
|
|
|
99
99
|
For cart edits, always read the server cart first with `buyer cart show`.
|
|
@@ -206,14 +206,14 @@ Each docs page includes `next_docs`. Follow those links as the state changes.
|
|
|
206
206
|
For payment creation in an agent/chat client, prefer:
|
|
207
207
|
|
|
208
208
|
```bash
|
|
209
|
-
itp buy <variant_id> --
|
|
209
|
+
itp buy <variant_id> --email <buyer_email> --phone <buyer_phone> --display agent --no-wait-payment --json
|
|
210
210
|
```
|
|
211
211
|
|
|
212
212
|
This keeps JSON output machine-readable while allowing the CLI to prepare a
|
|
213
213
|
local QR image path for clients that cannot render remote SVG reliably. In
|
|
214
214
|
Codex or Claude Code app clients, prefer `--no-wait-payment`: send
|
|
215
|
-
`
|
|
216
|
-
`
|
|
215
|
+
`human_visible_markdown` to the human first, then run
|
|
216
|
+
`next.command`. If the human is on mobile, present
|
|
217
217
|
`mobile_wallet_url` as a clickable human-only fallback; do not convert it into a
|
|
218
218
|
QR.
|
|
219
219
|
|
|
@@ -221,8 +221,8 @@ For first-purchase auth, treat the returned ItPay authorization entry as a
|
|
|
221
221
|
single human orchestration entry. It may open Alipay login/registration first
|
|
222
222
|
and then payment after ItPay receives the OAuth callback. Do not call
|
|
223
223
|
`oauth_start_url` directly unless the ItPay auth page asks for fallback.
|
|
224
|
-
If the payment page says
|
|
225
|
-
or if
|
|
224
|
+
If the payment page says provider entry is stabilizing/preparing,
|
|
225
|
+
or if the payment provider says "order not found", tell the human to wait 30-60
|
|
226
226
|
seconds and use the same page/QR again. Do not ask them to refresh repeatedly,
|
|
227
227
|
and do not create another checkout or payment intent. Use
|
|
228
228
|
`itp buyer payment refresh-qr ... --reason order-not-found` only after the
|
|
@@ -238,7 +238,7 @@ I found the service and selected the matching variant.
|
|
|
238
238
|
I created the cart and checkout.
|
|
239
239
|
Please open the returned ItPay authorization link and approve Alipay login.
|
|
240
240
|
I am waiting for ItPay account authorization.
|
|
241
|
-
Please scan the returned ItPay-hosted QR image with
|
|
241
|
+
Please scan the returned ItPay-hosted QR image with the payment provider.
|
|
242
242
|
I am waiting for ItPay payment verification.
|
|
243
243
|
Payment is verified.
|
|
244
244
|
Delivery is claimable by the human buyer. Please check your email.
|