@itpay/cli 0.2.5 → 0.2.7
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 -2
- package/bin/itp +1 -1
- package/docs/agent/buyer/payment-qr.json +30 -8
- package/docs/agent/buyer/payment-wait.json +33 -11
- package/docs/agent/buyer/qr-refresh.json +1 -1
- package/docs/agent/buyer/quickstart.json +25 -7
- package/docs/agent/buyer/recovery.json +1 -1
- package/lib/buyer.js +57 -54
- package/lib/env.js +4 -0
- package/lib/render-human.js +130 -1
- package/package.json +1 -1
- package/skills/itpay-buyer/SKILL.md +11 -9
package/README.md
CHANGED
|
@@ -214,7 +214,7 @@ redacted secure delivery status:
|
|
|
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
216
|
itp buyer checkout create --cart <cart_id> --email <buyer_email> --json
|
|
217
|
-
itp buyer payment wait <payment_intent_id> --json
|
|
217
|
+
itp buyer payment wait <payment_intent_id> --timeout 1 --json
|
|
218
218
|
itp buyer checkout status <checkout_id> --json
|
|
219
219
|
```
|
|
220
220
|
|
|
@@ -239,7 +239,8 @@ 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,
|
|
242
|
+
- In agent app clients, execute `render_plan.selected` first. Send its media, text/markdown, real links, and native buttons when the adapter supports them.
|
|
243
|
+
- Do not rewrite `render_plan.selected` into a custom table or omit the real URL.
|
|
243
244
|
- If status is `payment_handoff_required`, `next` is the human reply step, not payment wait.
|
|
244
245
|
- Treat only `payment_intent.verified` as payment success.
|
|
245
246
|
|
package/bin/itp
CHANGED
|
@@ -109,7 +109,7 @@ async function main() {
|
|
|
109
109
|
"buyer checkout status <checkout_id> --json",
|
|
110
110
|
"buyer checkout resume <checkout_id> --json",
|
|
111
111
|
"buyer auth status --json",
|
|
112
|
-
"buyer payment wait <payment_intent_id> --json",
|
|
112
|
+
"buyer payment wait <payment_intent_id> --timeout 1 --json",
|
|
113
113
|
"buyer payment refresh-qr <payment_intent_id> --reason order-not-found --json",
|
|
114
114
|
"buyer deliveries list --checkout <checkout_id> --json",
|
|
115
115
|
"buyer deliveries show <delivery_id> --checkout <checkout_id> --json",
|
|
@@ -11,14 +11,22 @@
|
|
|
11
11
|
"The human needs to scan with the payment provider."
|
|
12
12
|
],
|
|
13
13
|
"required_state": {
|
|
14
|
-
"needs": [
|
|
15
|
-
|
|
14
|
+
"needs": [
|
|
15
|
+
"payment_intent_id",
|
|
16
|
+
"human_action",
|
|
17
|
+
"qr_png_url/preferred_qr_url or local_qr_path"
|
|
18
|
+
],
|
|
19
|
+
"must_not_need": [
|
|
20
|
+
"raw provider QR",
|
|
21
|
+
"provider sign",
|
|
22
|
+
"ops token"
|
|
23
|
+
]
|
|
16
24
|
},
|
|
17
25
|
"commands": [
|
|
18
26
|
{
|
|
19
27
|
"intent": "create payment intent through the high-level buy flow",
|
|
20
28
|
"command": "itp buy <variant_id> --email <buyer_email> --phone <buyer_phone> --display agent --no-wait-payment --json",
|
|
21
|
-
"success_signal": "response.status is payment_handoff_required; response.human_visible_markdown
|
|
29
|
+
"success_signal": "response.status is payment_handoff_required; response.human_visible_markdown and response.render_plan are present; render the QR/link to the human before any status check"
|
|
22
30
|
},
|
|
23
31
|
{
|
|
24
32
|
"intent": "recover a scanner order-not-found display problem",
|
|
@@ -33,8 +41,13 @@
|
|
|
33
41
|
"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
42
|
"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
43
|
"Do not describe auth_qr as payment success or payment proof. Only payment_intent.verified proves payment.",
|
|
36
|
-
"
|
|
37
|
-
"
|
|
44
|
+
"If response.render_plan.selected exists, treat it as the final UI render plan for the current host. Execute selected before reading other platform branches.",
|
|
45
|
+
"Do not rewrite response.render_plan.selected into a custom table. Use selected.text/markdown, selected.media, selected.links, and selected.buttons as provided.",
|
|
46
|
+
"If selected.links exists, include the real URL. Do not only show the link label.",
|
|
47
|
+
"If selected.buttons exists but the current adapter cannot render native buttons, degrade to selected.links plus short text; do not fake payment success or skip the status check.",
|
|
48
|
+
"In Codex or Claude Code app clients, send response.human_visible_markdown or response.render_plan.platforms.codex_app.markdown to the human first. Do not start a hidden long wait before the human-visible message is sent.",
|
|
49
|
+
"For Telegram-style message clients, use response.render_plan.platforms.telegram: send photo media first, include links, and expose native buttons for refresh/status when the adapter supports them.",
|
|
50
|
+
"For plain chat clients, show response.human_visible_markdown and stop unless the human asks you to check status or returns with a payment question.",
|
|
38
51
|
"When response.status is payment_handoff_required, response.next is the human reply step, not payment wait.",
|
|
39
52
|
"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.",
|
|
40
53
|
"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.",
|
|
@@ -43,8 +56,8 @@
|
|
|
43
56
|
"payment_entry_url is the stable ItPay status/payment page. qr_png_url/local_qr_path is usually the better desktop scanner artifact.",
|
|
44
57
|
"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.",
|
|
45
58
|
"Opening the payment page must not be treated as paid.",
|
|
46
|
-
"Do not run payment wait before the QR/link has been sent to the human.
|
|
47
|
-
"After
|
|
59
|
+
"Do not run payment wait before the QR/link has been sent to the human. Without an explicit short timeout, CLI returns payment_handoff_required instead of blocking.",
|
|
60
|
+
"After the human says they paid or clicks a platform status button, run response.after_human_response.check_payment_command for the same payment_intent_id."
|
|
48
61
|
],
|
|
49
62
|
"forbidden": [
|
|
50
63
|
"Do not encode payment_entry_url into a new QR yourself when qr_png_url/local_qr_path is available.",
|
|
@@ -67,5 +80,14 @@
|
|
|
67
80
|
"command": "itp docs show qr-refresh --role buyer --json"
|
|
68
81
|
}
|
|
69
82
|
],
|
|
70
|
-
"search_terms": [
|
|
83
|
+
"search_terms": [
|
|
84
|
+
"qr",
|
|
85
|
+
"payment qr",
|
|
86
|
+
"alipay",
|
|
87
|
+
"scan",
|
|
88
|
+
"order not found",
|
|
89
|
+
"二维码",
|
|
90
|
+
"扫码",
|
|
91
|
+
"付款码"
|
|
92
|
+
]
|
|
71
93
|
}
|
|
@@ -10,15 +10,23 @@
|
|
|
10
10
|
"The CLI returned agent_wait metadata."
|
|
11
11
|
],
|
|
12
12
|
"required_state": {
|
|
13
|
-
"needs": [
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
"needs": [
|
|
14
|
+
"payment_intent_id"
|
|
15
|
+
],
|
|
16
|
+
"optional": [
|
|
17
|
+
"cursor"
|
|
18
|
+
],
|
|
19
|
+
"must_not_need": [
|
|
20
|
+
"ops token",
|
|
21
|
+
"provider query permission",
|
|
22
|
+
"human text proof"
|
|
23
|
+
]
|
|
16
24
|
},
|
|
17
25
|
"commands": [
|
|
18
26
|
{
|
|
19
27
|
"intent": "wait for payment verified event",
|
|
20
|
-
"command": "itp buyer payment wait <payment_intent_id> --
|
|
21
|
-
"success_signal": "response.payment_event.event_type
|
|
28
|
+
"command": "itp buyer payment wait <payment_intent_id> --timeout 1 --json",
|
|
29
|
+
"success_signal": "response.payment_event.event_type is payment_intent.verified or wait.timeout"
|
|
22
30
|
},
|
|
23
31
|
{
|
|
24
32
|
"intent": "resume checkout after verified event",
|
|
@@ -27,12 +35,13 @@
|
|
|
27
35
|
}
|
|
28
36
|
],
|
|
29
37
|
"agent_rules": [
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
38
|
+
"If payment wait returns payment_handoff_required, render response.render_plan.selected to the human before running any later status check.",
|
|
39
|
+
"Start payment status checks only after the QR image/link has already been sent to the human or the human asks to check status.",
|
|
40
|
+
"If you run payment wait without a short timeout while payment is still pending, CLI returns payment_handoff_required and human_visible_markdown instead of long-polling.",
|
|
41
|
+
"Payment wait/status check is a recovery loop around the same payment_intent_id, not a one-shot command.",
|
|
33
42
|
"wait.timeout is a heartbeat, not a payment failure.",
|
|
34
|
-
"In Codex or Claude Code app clients, after showing QR,
|
|
35
|
-
"Continue
|
|
43
|
+
"In Codex or Claude Code app clients, after showing QR, use a short status check first. If the human still has not paid, explain that the same QR/link remains valid and pause for their reply.",
|
|
44
|
+
"Continue checking the same payment_intent_id unless the user explicitly cancels or asks you to pause.",
|
|
36
45
|
"If the network drops, the process is interrupted, or there is no response, rerun payment wait with the same payment_intent_id or run checkout status/resume with the same checkout_id.",
|
|
37
46
|
"If status is unclear, tell the human it is unclear and continue querying the same payment_intent_id/checkout_id. Do not create a new checkout.",
|
|
38
47
|
"Payment success is payment_intent.verified only.",
|
|
@@ -57,5 +66,18 @@
|
|
|
57
66
|
"command": "itp docs show recovery --role buyer --json"
|
|
58
67
|
}
|
|
59
68
|
],
|
|
60
|
-
"search_terms": [
|
|
69
|
+
"search_terms": [
|
|
70
|
+
"wait",
|
|
71
|
+
"long poll",
|
|
72
|
+
"timeout",
|
|
73
|
+
"still waiting",
|
|
74
|
+
"paid",
|
|
75
|
+
"payment",
|
|
76
|
+
"等待",
|
|
77
|
+
"付款",
|
|
78
|
+
"付款成功",
|
|
79
|
+
"没返回",
|
|
80
|
+
"一直等",
|
|
81
|
+
"超时"
|
|
82
|
+
]
|
|
61
83
|
}
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
"intent": "continue waiting after refresh",
|
|
24
|
-
"command": "itp buyer payment wait <payment_intent_id> --json",
|
|
24
|
+
"command": "itp buyer payment wait <payment_intent_id> --timeout 1 --json",
|
|
25
25
|
"success_signal": "payment_event.event_type == payment_intent.verified"
|
|
26
26
|
}
|
|
27
27
|
],
|
|
@@ -10,8 +10,16 @@
|
|
|
10
10
|
"The agent is unsure which ItPay buyer command should come first."
|
|
11
11
|
],
|
|
12
12
|
"required_state": {
|
|
13
|
-
"needs": [
|
|
14
|
-
|
|
13
|
+
"needs": [
|
|
14
|
+
"itp CLI available",
|
|
15
|
+
"buyer delivery email before checkout"
|
|
16
|
+
],
|
|
17
|
+
"must_not_need": [
|
|
18
|
+
"ops token",
|
|
19
|
+
"claim token",
|
|
20
|
+
"raw key",
|
|
21
|
+
"provider raw payload"
|
|
22
|
+
]
|
|
15
23
|
},
|
|
16
24
|
"commands": [
|
|
17
25
|
{
|
|
@@ -37,7 +45,7 @@
|
|
|
37
45
|
{
|
|
38
46
|
"intent": "buy a selected variant with the high-level cart-first flow",
|
|
39
47
|
"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 payment_handoff_required; if payment_handoff_required,
|
|
48
|
+
"success_signal": "response.status becomes waiting_human_auth or payment_handoff_required; if payment_handoff_required, render response.human_visible_markdown or response.render_plan to the human before any payment status check"
|
|
41
49
|
}
|
|
42
50
|
],
|
|
43
51
|
"agent_rules": [
|
|
@@ -54,12 +62,13 @@
|
|
|
54
62
|
"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
63
|
"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
64
|
"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 human_visible_markdown, then
|
|
65
|
+
"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/render_plan, then only check status after the human responds or uses a platform button.",
|
|
58
66
|
"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
|
-
"If response.status is payment_handoff_required, do not run payment wait from next.command; next is the human-visible reply. Only after the
|
|
67
|
+
"If response.status is payment_handoff_required, do not run payment wait from next.command; next is the human-visible reply. Only after the human asks to check payment should you run after_human_response.check_payment_command.",
|
|
60
68
|
"Show the returned payment QR exactly as provided before starting payment wait.",
|
|
61
69
|
"Payment truth comes only from payment_intent.verified.",
|
|
62
|
-
"Secure delivery goes to the human first; report redacted delivery status only."
|
|
70
|
+
"Secure delivery goes to the human first; report redacted delivery status only.",
|
|
71
|
+
"When a response includes render_plan.selected, execute that selected plan first; do not make the model choose a different platform branch or rewrite the payment/auth UI."
|
|
63
72
|
],
|
|
64
73
|
"forbidden": [
|
|
65
74
|
"Do not call ops commands from the buyer path.",
|
|
@@ -95,5 +104,14 @@
|
|
|
95
104
|
"command": "itp docs show account-portal --role buyer --json"
|
|
96
105
|
}
|
|
97
106
|
],
|
|
98
|
-
"search_terms": [
|
|
107
|
+
"search_terms": [
|
|
108
|
+
"start",
|
|
109
|
+
"quickstart",
|
|
110
|
+
"first time",
|
|
111
|
+
"buyer flow",
|
|
112
|
+
"how to use itpay",
|
|
113
|
+
"开始",
|
|
114
|
+
"第一次",
|
|
115
|
+
"怎么用"
|
|
116
|
+
]
|
|
99
117
|
}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
"intent": "wait again on the same payment intent",
|
|
25
|
-
"command": "itp buyer payment wait <payment_intent_id> --json",
|
|
25
|
+
"command": "itp buyer payment wait <payment_intent_id> --timeout 1 --json",
|
|
26
26
|
"success_signal": "response.payment_event.event_type is payment_intent.verified or still waiting"
|
|
27
27
|
},
|
|
28
28
|
{
|
package/lib/buyer.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
writeCredentials, writeSessionCredentials, writeState
|
|
5
5
|
} from "./env.js";
|
|
6
6
|
import { coreApi, coreApiBase } from "./http.js";
|
|
7
|
-
import { renderHumanAction, renderItPayPaymentAction, shouldReturnAfterAgentTextQR, writeWaitHeartbeat } from "./render-human.js";
|
|
7
|
+
import { buildHumanActionRenderPlan, renderHumanAction, renderItPayPaymentAction, shouldReturnAfterAgentTextQR, writeWaitHeartbeat } from "./render-human.js";
|
|
8
8
|
|
|
9
9
|
async function buyerBuy(flags) {
|
|
10
10
|
rejectBuyerSandboxFlag(flags);
|
|
@@ -23,6 +23,7 @@ async function buyerBuy(flags) {
|
|
|
23
23
|
cart,
|
|
24
24
|
checkout,
|
|
25
25
|
human_action: checkout.human_action,
|
|
26
|
+
render_plan: buildHumanActionRenderPlan(checkout.human_action, {}, flags),
|
|
26
27
|
agent_next_actions: checkout.agent_next_actions || ["wait_human_auth", "poll_checkout"],
|
|
27
28
|
next: {
|
|
28
29
|
command: cliCommand("buyer", "checkout", "resume", checkout.checkout_id, "--json"),
|
|
@@ -50,10 +51,9 @@ async function buyerBuy(flags) {
|
|
|
50
51
|
cart,
|
|
51
52
|
checkout,
|
|
52
53
|
payment_intent: intent,
|
|
53
|
-
|
|
54
|
+
...paymentHandoffFields(intent, flags),
|
|
54
55
|
agent_next_actions: paymentHandoffAgentNextActions(),
|
|
55
|
-
next: paymentHandoffNext()
|
|
56
|
-
after_human_visible_markdown: paymentHandoffAfter(intent)
|
|
56
|
+
next: paymentHandoffNext()
|
|
57
57
|
}));
|
|
58
58
|
return;
|
|
59
59
|
}
|
|
@@ -215,6 +215,9 @@ async function buyer(command, rest, flags) {
|
|
|
215
215
|
checkout,
|
|
216
216
|
buyer_session: buyerSessionClaimStatus(),
|
|
217
217
|
checkout_guidance: checkoutAuthorizationGuidance(checkout),
|
|
218
|
+
render_plan: checkout.next_required_action === "auth_qr" || checkout.identity_status === "waiting_human_auth"
|
|
219
|
+
? buildHumanActionRenderPlan(checkout.human_action, {}, flags)
|
|
220
|
+
: undefined,
|
|
218
221
|
agent_next_actions: checkout.agent_next_actions || checkoutAgentNextActions(checkout),
|
|
219
222
|
next: checkout.next_required_action === "auth_qr" || checkout.identity_status === "waiting_human_auth"
|
|
220
223
|
? {
|
|
@@ -240,6 +243,9 @@ async function buyer(command, rest, flags) {
|
|
|
240
243
|
checkout,
|
|
241
244
|
buyer_session: buyerSessionClaimStatus(),
|
|
242
245
|
checkout_guidance: checkoutAuthorizationGuidance(checkout),
|
|
246
|
+
render_plan: checkout.next_required_action === "auth_qr" || checkout.identity_status === "waiting_human_auth"
|
|
247
|
+
? buildHumanActionRenderPlan(checkout.human_action, {}, flags)
|
|
248
|
+
: undefined,
|
|
243
249
|
agent_next_actions: checkout.agent_next_actions || checkoutAgentNextActions(checkout),
|
|
244
250
|
next: checkout.next_required_action === "auth_qr" || checkout.identity_status === "waiting_human_auth"
|
|
245
251
|
? {
|
|
@@ -278,6 +284,7 @@ async function buyer(command, rest, flags) {
|
|
|
278
284
|
status: "waiting_human_auth",
|
|
279
285
|
checkout,
|
|
280
286
|
human_action: checkout.human_action,
|
|
287
|
+
render_plan: buildHumanActionRenderPlan(checkout.human_action, {}, flags),
|
|
281
288
|
agent_next_actions: checkout.agent_next_actions || ["wait_human_auth", "poll_checkout"],
|
|
282
289
|
next: {
|
|
283
290
|
command: cliCommand("buyer", "checkout", "resume", checkoutID, "--json"),
|
|
@@ -293,6 +300,7 @@ async function buyer(command, rest, flags) {
|
|
|
293
300
|
status: "waiting_human_auth",
|
|
294
301
|
checkout,
|
|
295
302
|
human_action: checkout.human_action,
|
|
303
|
+
render_plan: buildHumanActionRenderPlan(checkout.human_action, {}, flags),
|
|
296
304
|
agent_next_actions: checkout.agent_next_actions || ["wait_human_auth", "poll_checkout"],
|
|
297
305
|
next: {
|
|
298
306
|
command: cliCommand("buyer", "checkout", "resume", checkoutID, "--json"),
|
|
@@ -317,13 +325,12 @@ async function buyer(command, rest, flags) {
|
|
|
317
325
|
status: intent.status === "verified" ? "payment_verified" : "payment_handoff_required",
|
|
318
326
|
checkout,
|
|
319
327
|
payment_intent: intent,
|
|
320
|
-
|
|
328
|
+
...(intent.status === "verified" ? {} : paymentHandoffFields(intent, flags)),
|
|
321
329
|
buyer_session: buyerSessionClaimStatus(claimedSession),
|
|
322
330
|
agent_next_actions: intent.status === "verified" ? (intent.agent_next_actions || checkout.agent_next_actions || ["poll_checkout"]) : paymentHandoffAgentNextActions(),
|
|
323
331
|
next: intent.status === "verified"
|
|
324
332
|
? { command: cliCommand("buyer", "checkout", "status", checkout.checkout_id, "--json"), safe_for_agent: true }
|
|
325
|
-
: paymentHandoffNext()
|
|
326
|
-
after_human_visible_markdown: intent.status === "verified" ? undefined : paymentHandoffAfter(intent)
|
|
333
|
+
: paymentHandoffNext()
|
|
327
334
|
}));
|
|
328
335
|
return;
|
|
329
336
|
}
|
|
@@ -340,10 +347,9 @@ async function buyer(command, rest, flags) {
|
|
|
340
347
|
status: "payment_handoff_required",
|
|
341
348
|
checkout,
|
|
342
349
|
payment_intent: intent,
|
|
343
|
-
|
|
350
|
+
...paymentHandoffFields(intent, flags),
|
|
344
351
|
agent_next_actions: paymentHandoffAgentNextActions(),
|
|
345
|
-
next: paymentHandoffNext()
|
|
346
|
-
after_human_visible_markdown: paymentHandoffAfter(intent)
|
|
352
|
+
next: paymentHandoffNext()
|
|
347
353
|
}));
|
|
348
354
|
return;
|
|
349
355
|
}
|
|
@@ -362,16 +368,15 @@ async function buyer(command, rest, flags) {
|
|
|
362
368
|
const paymentIntentID = flags.payment_intent || flags.payment_intent_id || positional(rest, 1) || readState().last_core_payment_intent_id;
|
|
363
369
|
if (!paymentIntentID) throw new Error("payment_intent_id is required");
|
|
364
370
|
const intent = await getBuyerPaymentIntent(paymentIntentID, flags);
|
|
365
|
-
if (intent.status !== "verified" &&
|
|
371
|
+
if (intent.status !== "verified" && shouldReturnPaymentHandoffBeforeWait(flags)) {
|
|
366
372
|
await renderItPayPaymentAction(intent, flags);
|
|
367
373
|
output(buyerRunOutput({
|
|
368
374
|
status: "payment_handoff_required",
|
|
369
375
|
payment_intent: intent,
|
|
370
|
-
|
|
376
|
+
...paymentHandoffFields(intent, flags),
|
|
371
377
|
payment_guidance: paymentRecoveryGuidance(intent, { event_type: "payment_display_required" }),
|
|
372
378
|
agent_next_actions: paymentHandoffAgentNextActions(),
|
|
373
|
-
next: paymentHandoffNext()
|
|
374
|
-
after_human_visible_markdown: paymentHandoffAfter(intent)
|
|
379
|
+
next: paymentHandoffNext()
|
|
375
380
|
}));
|
|
376
381
|
return;
|
|
377
382
|
}
|
|
@@ -386,7 +391,7 @@ async function buyer(command, rest, flags) {
|
|
|
386
391
|
agent_next_actions: event.agent_next_actions || intent.agent_next_actions || paymentAgentNextActions(intent, event),
|
|
387
392
|
next: event.event_type === "payment_intent.verified"
|
|
388
393
|
? { command: intent.checkout_id ? cliCommand("buyer", "checkout", "status", intent.checkout_id, "--json") : undefined, safe_for_agent: true }
|
|
389
|
-
: { command:
|
|
394
|
+
: { command: paymentStatusCheckCommand(paymentIntentID), safe_for_agent: true }
|
|
390
395
|
}));
|
|
391
396
|
return;
|
|
392
397
|
}
|
|
@@ -398,13 +403,12 @@ async function buyer(command, rest, flags) {
|
|
|
398
403
|
output(buyerRunOutput({
|
|
399
404
|
status: refreshed.status === "verified" ? "payment_verified" : "payment_handoff_required",
|
|
400
405
|
payment_intent: refreshed,
|
|
401
|
-
|
|
406
|
+
...(refreshed.status === "verified" ? {} : paymentHandoffFields(refreshed, flags)),
|
|
402
407
|
payment_guidance: paymentRecoveryGuidance(refreshed, { event_type: refreshed.status === "verified" ? "payment_intent.verified" : "qr_refreshed" }),
|
|
403
408
|
agent_next_actions: refreshed.status === "verified" ? (refreshed.agent_next_actions || paymentAgentNextActions(refreshed, { event_type: "payment_intent.verified" })) : paymentHandoffAgentNextActions(),
|
|
404
409
|
next: refreshed.status === "verified"
|
|
405
410
|
? { command: cliCommand("buyer", "checkout", "status", refreshed.checkout_id, "--json"), safe_for_agent: true }
|
|
406
|
-
: paymentHandoffNext()
|
|
407
|
-
after_human_visible_markdown: refreshed.status === "verified" ? undefined : paymentHandoffAfter(refreshed)
|
|
411
|
+
: paymentHandoffNext()
|
|
408
412
|
}));
|
|
409
413
|
return;
|
|
410
414
|
}
|
|
@@ -900,7 +904,7 @@ async function waitBuyerCheckoutAuth(checkout, flags = {}) {
|
|
|
900
904
|
if (!checkoutID) throw new Error("checkout_id is required");
|
|
901
905
|
const timeoutMs = Number(flags.auth_timeout || flags.timeout || 900) * 1000;
|
|
902
906
|
const started = Date.now();
|
|
903
|
-
let lastHeartbeatAt =
|
|
907
|
+
let lastHeartbeatAt = Date.now();
|
|
904
908
|
let current = typeof checkout === "string" ? await getBuyerCheckout(checkoutID, flags) : checkout;
|
|
905
909
|
const authAction = current?.human_action || readCoreAuthAction(checkoutID) || null;
|
|
906
910
|
rememberCoreAuthAction(checkoutID, authAction);
|
|
@@ -1037,12 +1041,20 @@ function paymentAgentNextActions(intent = {}, event = {}) {
|
|
|
1037
1041
|
return ["poll_checkout_delivery"];
|
|
1038
1042
|
}
|
|
1039
1043
|
if (event.event_type === "wait.timeout") {
|
|
1040
|
-
return ["
|
|
1044
|
+
return ["check_same_payment_intent", "resume_checkout_status_if_needed"];
|
|
1041
1045
|
}
|
|
1042
1046
|
if (event.event_type === "qr_refreshed") {
|
|
1043
|
-
return ["show_returned_qr", "
|
|
1047
|
+
return ["show_returned_qr", "check_same_payment_intent"];
|
|
1044
1048
|
}
|
|
1045
|
-
return ["show_payment_qr_if_needed", "
|
|
1049
|
+
return ["show_payment_qr_if_needed", "check_same_payment_intent"];
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
function paymentHandoffFields(intent = {}, flags = {}) {
|
|
1053
|
+
return {
|
|
1054
|
+
payment_handoff: paymentHandoff(intent),
|
|
1055
|
+
render_plan: buildHumanActionRenderPlan(intent.human_action || {}, intent, flags),
|
|
1056
|
+
after_human_response: paymentHandoffAfterHumanResponse(intent)
|
|
1057
|
+
};
|
|
1046
1058
|
}
|
|
1047
1059
|
|
|
1048
1060
|
function paymentHandoff(intent = {}) {
|
|
@@ -1053,7 +1065,6 @@ function paymentHandoff(intent = {}) {
|
|
|
1053
1065
|
const preferredQRURL = action.preferred_qr_url || qrPNGURL || action.qr_image_url || intent.qr_image_url || intent.qr?.image_url || "";
|
|
1054
1066
|
const localQRPath = action.local_qr_path || intent.local_qr_path || "";
|
|
1055
1067
|
const mobileWalletURL = action.mobile_wallet_url || intent.mobile_wallet_url || "";
|
|
1056
|
-
const waitCommand = paymentWaitCommand(paymentIntentID, { short: true, qrShown: true });
|
|
1057
1068
|
return {
|
|
1058
1069
|
type: "payment_qr_handoff",
|
|
1059
1070
|
payment_intent_id: paymentIntentID || null,
|
|
@@ -1065,10 +1076,8 @@ function paymentHandoff(intent = {}) {
|
|
|
1065
1076
|
payment_entry_url: entryURL || null,
|
|
1066
1077
|
mobile_wallet_url: mobileWalletURL || null,
|
|
1067
1078
|
markdown: paymentHandoffMarkdown({ localQRPath, qrPNGURL, preferredQRURL, entryURL, mobileWalletURL }),
|
|
1068
|
-
wait_command: waitCommand || null,
|
|
1069
1079
|
safe_for_agent: true,
|
|
1070
|
-
instruction: "
|
|
1071
|
-
display_targets: paymentHandoffDisplayTargets()
|
|
1080
|
+
instruction: "Render the QR/link to the human first. After the human responds or uses a platform button, query the same payment_intent_id. Only payment_intent.verified proves payment."
|
|
1072
1081
|
};
|
|
1073
1082
|
}
|
|
1074
1083
|
|
|
@@ -1078,22 +1087,20 @@ function paymentHandoffMarkdown({ localQRPath = "", qrPNGURL = "", preferredQRUR
|
|
|
1078
1087
|
if (imageURL) lines.push(``);
|
|
1079
1088
|
if (entryURL) lines.push(`[打开付款页面](${entryURL})`);
|
|
1080
1089
|
if (mobileWalletURL) lines.push(`[手机钱包打开](${mobileWalletURL})`);
|
|
1081
|
-
lines.push("
|
|
1090
|
+
lines.push("付款后回复“我已付款”,我会查询真实支付状态。");
|
|
1082
1091
|
return lines.join("\n\n");
|
|
1083
1092
|
}
|
|
1084
1093
|
|
|
1085
|
-
function
|
|
1094
|
+
function paymentStatusCheckCommand(paymentIntentID) {
|
|
1086
1095
|
if (!paymentIntentID) return "";
|
|
1087
|
-
|
|
1088
|
-
if (short) args.push("--timeout", "60");
|
|
1089
|
-
if (qrShown) args.push("--qr-shown");
|
|
1090
|
-
args.push("--json");
|
|
1091
|
-
return cliCommand(...args);
|
|
1096
|
+
return cliCommand("buyer", "payment", "wait", paymentIntentID, "--timeout", "1", "--json");
|
|
1092
1097
|
}
|
|
1093
1098
|
|
|
1094
|
-
function
|
|
1095
|
-
if (flags.
|
|
1096
|
-
|
|
1099
|
+
function shouldReturnPaymentHandoffBeforeWait(flags = {}) {
|
|
1100
|
+
if (booleanFlag(flags.wait || flags.long_wait || false)) return false;
|
|
1101
|
+
if (flags.timeout === undefined) return true;
|
|
1102
|
+
const timeout = Number(flags.timeout);
|
|
1103
|
+
return !Number.isFinite(timeout) || timeout > 5;
|
|
1097
1104
|
}
|
|
1098
1105
|
|
|
1099
1106
|
function paymentHandoffAgentNextActions() {
|
|
@@ -1108,19 +1115,13 @@ function paymentHandoffNext() {
|
|
|
1108
1115
|
};
|
|
1109
1116
|
}
|
|
1110
1117
|
|
|
1111
|
-
function
|
|
1118
|
+
function paymentHandoffAfterHumanResponse(intent = {}) {
|
|
1119
|
+
const paymentIntentID = intent.payment_intent_id || intent.human_action?.id || "";
|
|
1112
1120
|
return {
|
|
1113
|
-
|
|
1121
|
+
check_payment_command: paymentStatusCheckCommand(paymentIntentID),
|
|
1122
|
+
refresh_qr_command: paymentIntentID ? cliCommand("buyer", "payment", "refresh-qr", paymentIntentID, "--reason", "order-not-found", "--json") : "",
|
|
1114
1123
|
safe_for_agent: true,
|
|
1115
|
-
instruction: "
|
|
1116
|
-
};
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
function paymentHandoffDisplayTargets() {
|
|
1120
|
-
return {
|
|
1121
|
-
desktop_app: ["local_qr_path", "qr_png_url", "payment_entry_url"],
|
|
1122
|
-
terminal: ["qr_png_url", "payment_entry_url", "mobile_wallet_url"],
|
|
1123
|
-
chat_cloud: ["qr_png_url", "payment_entry_url", "mobile_wallet_url"]
|
|
1124
|
+
instruction: "Use check_payment_command after the human says they paid or a platform button requests a status check. User text is not proof; only payment_intent.verified is."
|
|
1124
1125
|
};
|
|
1125
1126
|
}
|
|
1126
1127
|
|
|
@@ -1136,10 +1137,10 @@ function paymentRecoveryGuidance(intent = {}, event = {}) {
|
|
|
1136
1137
|
next_step: event.event_type === "payment_intent.verified" || intent.status === "verified"
|
|
1137
1138
|
? "付款已验证;进入 checkout status / 交付模块。"
|
|
1138
1139
|
: event.event_type === "wait.timeout"
|
|
1139
|
-
? "wait.timeout
|
|
1140
|
+
? "wait.timeout 不是付款失败;继续查询同一个 payment_intent_id,或用 checkout status/resume 确认状态。"
|
|
1140
1141
|
: event.event_type === "qr_refreshed"
|
|
1141
|
-
? "展示 ItPay 返回的 QR
|
|
1142
|
-
: "展示 ItPay
|
|
1142
|
+
? "展示 ItPay 返回的 QR,然后继续查询同一个 payment_intent_id。"
|
|
1143
|
+
: "展示 ItPay 返回的付款入口;用户反馈后查询 payment_intent.verified,用户口头说已付款不算证明。",
|
|
1143
1144
|
network_or_interrupt: "断网、无响应、进程中断时,重新运行 payment wait 或 checkout status/resume,使用同一个 payment_intent_id / checkout_id。",
|
|
1144
1145
|
qr_problem: "QR 未显示时不要自造二维码;先用返回的 local_qr_path/qr_png_url/preferred_qr_url。扫码提示订单不存在或过期时,先等 30-60 秒重扫同一 QR,仍失败才 refresh-qr。",
|
|
1145
1146
|
do_not: [
|
|
@@ -1444,12 +1445,13 @@ async function waitBuyerPayment(intent, flags = {}) {
|
|
|
1444
1445
|
const waitURL = flags.wait_url || intent?.agent_wait?.wait_url || `/v1/payment-intents/${encodeURIComponent(paymentIntentID)}/events/wait`;
|
|
1445
1446
|
const timeoutMs = Number(flags.timeout || 900) * 1000;
|
|
1446
1447
|
const started = Date.now();
|
|
1447
|
-
let lastHeartbeatAt =
|
|
1448
|
+
let lastHeartbeatAt = Date.now();
|
|
1448
1449
|
let lastEvent = null;
|
|
1449
1450
|
while (Date.now() - started < timeoutMs) {
|
|
1451
|
+
const remainingMs = Math.max(1000, timeoutMs - (Date.now() - started));
|
|
1450
1452
|
const params = new URLSearchParams();
|
|
1451
1453
|
if (cursor) params.set("cursor", cursor);
|
|
1452
|
-
params.set("timeout", String(flags.poll_timeout ||
|
|
1454
|
+
params.set("timeout", String(flags.poll_timeout || `${Math.ceil(Math.min(30000, remainingMs) / 1000)}s`));
|
|
1453
1455
|
const event = await coreApi(appendURLQuery(waitURL, params), { method: "GET" }, flags);
|
|
1454
1456
|
lastEvent = event;
|
|
1455
1457
|
cursor = event.cursor || cursor;
|
|
@@ -1463,10 +1465,10 @@ async function waitBuyerPayment(intent, flags = {}) {
|
|
|
1463
1465
|
action: intent?.human_action || null,
|
|
1464
1466
|
lastHeartbeatAt,
|
|
1465
1467
|
flags,
|
|
1466
|
-
command:
|
|
1468
|
+
command: paymentStatusCheckCommand(paymentIntentID)
|
|
1467
1469
|
});
|
|
1468
1470
|
}
|
|
1469
|
-
return lastEvent || { event_type: "wait.timeout", payment_intent_id: paymentIntentID, cursor, agent_next_actions: ["
|
|
1471
|
+
return lastEvent || { event_type: "wait.timeout", payment_intent_id: paymentIntentID, cursor, agent_next_actions: ["check_payment_status"] };
|
|
1470
1472
|
}
|
|
1471
1473
|
|
|
1472
1474
|
async function waitBuyerDelivery(checkoutID, flags = {}) {
|
|
@@ -1639,6 +1641,7 @@ function compactHumanAction(action = null) {
|
|
|
1639
1641
|
if (!action || typeof action !== "object") return action;
|
|
1640
1642
|
return compactObject({
|
|
1641
1643
|
kind: action.kind,
|
|
1644
|
+
payment_intent_id: action.payment_intent_id,
|
|
1642
1645
|
url: action.url,
|
|
1643
1646
|
local_qr_path: action.local_qr_path,
|
|
1644
1647
|
local_qr_mime: action.local_qr_mime,
|
package/lib/env.js
CHANGED
|
@@ -98,10 +98,14 @@ function apiTimeoutMs(flags = {}) {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
function parseFlags(args) {
|
|
101
|
+
const removedQRDisplayFlag = ["--qr", "shown"].join("-");
|
|
101
102
|
const flags = {};
|
|
102
103
|
for (let i = 0; i < args.length; i += 1) {
|
|
103
104
|
const arg = args[i];
|
|
104
105
|
if (!arg.startsWith("--")) continue;
|
|
106
|
+
if (arg === removedQRDisplayFlag) {
|
|
107
|
+
throw new Error("legacy QR display flag has been removed; show the payment QR to the human, then run buyer payment wait <payment_intent_id> --timeout 1 --json.");
|
|
108
|
+
}
|
|
105
109
|
const key = arg.slice(2).replaceAll("-", "_");
|
|
106
110
|
const next = args[i + 1];
|
|
107
111
|
if (!next || next.startsWith("--")) {
|
package/lib/render-human.js
CHANGED
|
@@ -7,12 +7,18 @@ import { apiTimeoutMs, cliCommand, commandExists, mergeRun, readRun, readState,
|
|
|
7
7
|
|
|
8
8
|
async function renderItPayPaymentAction(intent, flags = {}) {
|
|
9
9
|
const action = intent?.human_action ? { ...intent.human_action } : (intent?.payment_url ? {
|
|
10
|
+
kind: "payment_qr",
|
|
10
11
|
id: intent.payment_intent_id,
|
|
12
|
+
payment_intent_id: intent.payment_intent_id,
|
|
11
13
|
title: "Scan payment QR",
|
|
12
14
|
url: intent.payment_url,
|
|
13
15
|
expires_at: intent.qr?.expires_at
|
|
14
16
|
} : null);
|
|
15
17
|
if (action) {
|
|
18
|
+
if (!action.kind && isPaymentIntentHandoff(intent)) action.kind = "payment_qr";
|
|
19
|
+
if (!action.id && intent?.payment_intent_id) action.id = intent.payment_intent_id;
|
|
20
|
+
if (!action.payment_intent_id && intent?.payment_intent_id) action.payment_intent_id = intent.payment_intent_id;
|
|
21
|
+
if (!action.url && (intent?.payment_url || intent?.payment_entry_url)) action.url = intent.payment_url || intent.payment_entry_url;
|
|
16
22
|
if (intent?.qr_png_url || intent?.qr?.png_url) {
|
|
17
23
|
action.qr_png_url = intent.qr_png_url || intent.qr.png_url;
|
|
18
24
|
}
|
|
@@ -35,6 +41,18 @@ async function renderItPayPaymentAction(intent, flags = {}) {
|
|
|
35
41
|
return result;
|
|
36
42
|
}
|
|
37
43
|
|
|
44
|
+
function isPaymentIntentHandoff(intent = {}) {
|
|
45
|
+
return Boolean(
|
|
46
|
+
intent?.payment_intent_id ||
|
|
47
|
+
intent?.payment_url ||
|
|
48
|
+
intent?.payment_entry_url ||
|
|
49
|
+
intent?.qr_png_url ||
|
|
50
|
+
intent?.qr_image_url ||
|
|
51
|
+
intent?.qr?.png_url ||
|
|
52
|
+
intent?.qr?.image_url
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
38
56
|
function humanActionSummaryLines(action) {
|
|
39
57
|
if (!action?.url) return [];
|
|
40
58
|
const lines = [
|
|
@@ -265,6 +283,117 @@ function annotateHumanActionPresentation(action, qrImageURL) {
|
|
|
265
283
|
return action;
|
|
266
284
|
}
|
|
267
285
|
|
|
286
|
+
function buildHumanActionRenderPlan(action = {}, intent = {}, flags = {}) {
|
|
287
|
+
if (!action || typeof action !== "object") return null;
|
|
288
|
+
const kind = action.kind || (isPaymentIntentHandoff(intent) ? "payment_qr" : "human_action");
|
|
289
|
+
const planAction = action.kind === kind ? action : { ...action, kind };
|
|
290
|
+
const entryURL = intent.payment_entry_url || intent.payment_url || action.url || action.web_url || "";
|
|
291
|
+
const qrPNGURL = action.qr_png_url || intent.qr_png_url || intent.qr?.png_url || "";
|
|
292
|
+
const preferredQRURL = action.preferred_qr_url || qrPNGURL || action.qr_image_url || intent.qr_image_url || intent.qr?.image_url || "";
|
|
293
|
+
const localQRPath = action.local_qr_path || intent.local_qr_path || "";
|
|
294
|
+
const mobileWalletURL = action.mobile_wallet_url || intent.mobile_wallet_url || humanActionPresentationURL(action, "mobile_wallet_url") || "";
|
|
295
|
+
const imageSource = localQRPath || qrPNGURL || preferredQRURL;
|
|
296
|
+
const requiredOutputs = [];
|
|
297
|
+
if (imageSource) {
|
|
298
|
+
requiredOutputs.push(compactObject({
|
|
299
|
+
type: "image",
|
|
300
|
+
local_path: localQRPath || undefined,
|
|
301
|
+
fallback_url: preferredQRURL || qrPNGURL || undefined,
|
|
302
|
+
must_be_user_visible: true
|
|
303
|
+
}));
|
|
304
|
+
}
|
|
305
|
+
if (entryURL) requiredOutputs.push({ type: "link", label: kind === "auth_qr" ? "打开授权页面" : "打开付款页面", url: entryURL });
|
|
306
|
+
if (mobileWalletURL) requiredOutputs.push({ type: "link", label: "手机钱包打开", url: mobileWalletURL });
|
|
307
|
+
const markdown = humanActionMarkdown(planAction, { localQRPath, qrPNGURL, preferredQRURL, entryURL, mobileWalletURL });
|
|
308
|
+
const telegram = telegramRenderPlan(planAction, { localQRPath, preferredQRURL, entryURL, mobileWalletURL });
|
|
309
|
+
const host = agentHost(flags);
|
|
310
|
+
const platforms = compactObject({
|
|
311
|
+
codex_app: { format: "markdown_image_and_links", markdown },
|
|
312
|
+
claude_code: { format: "markdown_image_and_links", markdown },
|
|
313
|
+
telegram,
|
|
314
|
+
plain_chat: {
|
|
315
|
+
format: "image_or_link_then_human_reply",
|
|
316
|
+
text: kind === "payment_qr"
|
|
317
|
+
? "请扫码或点击链接完成支付。付完后回复“我已付款”,我再查询真实状态。"
|
|
318
|
+
: "请打开上面的授权入口。完成后告诉我,我再继续查询状态。",
|
|
319
|
+
links: requiredOutputs.filter((item) => item.type === "link")
|
|
320
|
+
},
|
|
321
|
+
terminal: {
|
|
322
|
+
format: "cli_prints_qr_then_wait",
|
|
323
|
+
print_terminal_qr: true,
|
|
324
|
+
print_links: true
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
const selectedPlatform = selectedRenderPlatform(host);
|
|
328
|
+
const selected = {
|
|
329
|
+
platform: selectedPlatform,
|
|
330
|
+
...platforms[selectedPlatform]
|
|
331
|
+
};
|
|
332
|
+
return compactObject({
|
|
333
|
+
kind,
|
|
334
|
+
proof_rule: kind === "payment_qr"
|
|
335
|
+
? "Only payment_intent.verified proves payment."
|
|
336
|
+
: "This human action is not payment proof.",
|
|
337
|
+
host: host || undefined,
|
|
338
|
+
required_outputs: requiredOutputs,
|
|
339
|
+
selected,
|
|
340
|
+
platforms,
|
|
341
|
+
forbidden: [
|
|
342
|
+
"Do not say 'scan the QR above' unless an image or scannable URL is actually attached.",
|
|
343
|
+
"Do not read a local image file as model input and treat that as sent to the human.",
|
|
344
|
+
"Do not treat page open, QR display, button click, or human text as payment proof.",
|
|
345
|
+
"Do not create a new checkout because payment is still pending."
|
|
346
|
+
]
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function humanActionMarkdown(action = {}, { localQRPath = "", qrPNGURL = "", preferredQRURL = "", entryURL = "", mobileWalletURL = "" } = {}) {
|
|
351
|
+
const imageURL = localQRPath || qrPNGURL || preferredQRURL;
|
|
352
|
+
const lines = [action.kind === "auth_qr" ? "请打开 ItPay 授权入口:" : "请扫码付款:"];
|
|
353
|
+
if (imageURL) lines.push(``);
|
|
354
|
+
if (entryURL) lines.push(`[${action.kind === "auth_qr" ? "打开授权页面" : "打开付款页面"}](${entryURL})`);
|
|
355
|
+
if (mobileWalletURL) lines.push(`[手机钱包打开](${mobileWalletURL})`);
|
|
356
|
+
lines.push(action.kind === "payment_qr" ? "付款后回复“我已付款”,我会查询真实支付状态。" : "完成授权后回复,我会继续同一个 checkout。");
|
|
357
|
+
return lines.join("\n\n");
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function telegramRenderPlan(action = {}, { localQRPath = "", preferredQRURL = "", entryURL = "", mobileWalletURL = "" } = {}) {
|
|
361
|
+
const media = localQRPath || preferredQRURL
|
|
362
|
+
? [compactObject({ type: "photo", local_path: localQRPath || undefined, fallback_url: preferredQRURL || undefined })]
|
|
363
|
+
: [];
|
|
364
|
+
const links = [];
|
|
365
|
+
if (entryURL) links.push({ label: action.kind === "auth_qr" ? "打开授权页面" : "打开付款页面", url: entryURL });
|
|
366
|
+
if (mobileWalletURL) links.push({ label: "手机钱包打开", url: mobileWalletURL });
|
|
367
|
+
const buttons = action.kind === "payment_qr"
|
|
368
|
+
? [
|
|
369
|
+
{ text: "支付遇到问题 / 刷新", intent: "refresh_payment_qr" },
|
|
370
|
+
{ text: "我已付款,查询状态", intent: "check_payment_status" }
|
|
371
|
+
]
|
|
372
|
+
: entryURL ? [{ text: "打开授权页面", url: entryURL }] : [];
|
|
373
|
+
return {
|
|
374
|
+
format: action.kind === "payment_qr" ? "photo_text_inline_buttons" : "text_inline_buttons",
|
|
375
|
+
media,
|
|
376
|
+
text: action.kind === "payment_qr"
|
|
377
|
+
? "请扫码或点击链接完成支付。付款后点“我已付款,查询状态”。"
|
|
378
|
+
: "请打开 ItPay 授权入口,完成后回到当前对话。",
|
|
379
|
+
links,
|
|
380
|
+
buttons
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function selectedRenderPlatform(host = "") {
|
|
385
|
+
const normalized = String(host || "").toLowerCase().replaceAll("-", "_");
|
|
386
|
+
if (["codex", "codex_app", "codex_cli"].includes(normalized)) return "codex_app";
|
|
387
|
+
if (["claude", "claude_code", "claudecode"].includes(normalized)) return "claude_code";
|
|
388
|
+
if (["telegram", "tg"].includes(normalized)) return "telegram";
|
|
389
|
+
if (terminalQRHostAliases().has(normalized)) return "terminal";
|
|
390
|
+
return "plain_chat";
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function compactObject(value = {}) {
|
|
394
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined && item !== null && item !== ""));
|
|
395
|
+
}
|
|
396
|
+
|
|
268
397
|
function shouldPrepareLocalQRForJSON(mode, flags = {}, action = {}) {
|
|
269
398
|
if (mode === "file" || flags.qr_file || process.env.ITP_QR_FILE) return true;
|
|
270
399
|
if (action?.qr_png_url || action?.preferred_qr_url || action?.qr_image_url) return true;
|
|
@@ -496,4 +625,4 @@ function openBrowser(targetURL) {
|
|
|
496
625
|
return false;
|
|
497
626
|
}
|
|
498
627
|
|
|
499
|
-
export { renderItPayPaymentAction, humanActionSummaryLines, writeHumanActionSummary, waitHeartbeatMs, writeWaitHeartbeat, renderHumanAction, preferredHumanActionQRURL, humanActionPresentationURL, annotateHumanActionPresentation, shouldPrepareLocalQRForJSON, shouldGenerateLocalQRFromActionURL, prepareLocalQRFile, prepareLocalQRFromActionURL, defaultQRFilePath, defaultGeneratedQRFilePath, qrFileExtension, qrMimeType, sanitizeFilename, formatActionTime, shouldUseAgentTextQR, shouldReturnAfterAgentTextQR, attachAgentQRImage, attachAgentLocalQR, downloadQRImage, persistHumanAction, shouldRenderTerminalQR, terminalQRType, shouldOpenBrowser, openBrowser };
|
|
628
|
+
export { renderItPayPaymentAction, humanActionSummaryLines, writeHumanActionSummary, waitHeartbeatMs, writeWaitHeartbeat, renderHumanAction, buildHumanActionRenderPlan, preferredHumanActionQRURL, humanActionPresentationURL, annotateHumanActionPresentation, shouldPrepareLocalQRForJSON, shouldGenerateLocalQRFromActionURL, prepareLocalQRFile, prepareLocalQRFromActionURL, defaultQRFilePath, defaultGeneratedQRFilePath, qrFileExtension, qrMimeType, sanitizeFilename, formatActionTime, shouldUseAgentTextQR, shouldReturnAfterAgentTextQR, attachAgentQRImage, attachAgentLocalQR, downloadQRImage, persistHumanAction, shouldRenderTerminalQR, terminalQRType, shouldOpenBrowser, openBrowser };
|
package/package.json
CHANGED
|
@@ -75,7 +75,7 @@ itp buyer cart add <cart_id> --variant <variant_id> --input key=value --quantity
|
|
|
75
75
|
itp buyer cart remove <cart_id> --line <cart_line_item_id> --json
|
|
76
76
|
itp buyer checkout create --cart <cart_id> --email <buyer_email> --phone <buyer_phone> --json
|
|
77
77
|
itp buyer checkout resume <checkout_id> --json
|
|
78
|
-
itp buyer payment wait <payment_intent_id> --json
|
|
78
|
+
itp buyer payment wait <payment_intent_id> --timeout 1 --json
|
|
79
79
|
itp buyer checkout status <checkout_id> --json
|
|
80
80
|
itp buyer refund create --order <order_id> --amount-minor <minor_units> --currency CNY --reason buyer_requested --json
|
|
81
81
|
itp buyer refund list --order <order_id> --json
|
|
@@ -211,16 +211,18 @@ itp buy <variant_id> --email <buyer_email> --phone <buyer_phone> --display agent
|
|
|
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
|
-
Codex or Claude Code app clients, prefer `--no-wait-payment`:
|
|
215
|
-
`
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
214
|
+
Codex or Claude Code app clients, prefer `--no-wait-payment`: execute
|
|
215
|
+
`render_plan.selected` first. If `selected.markdown` exists, send it exactly to
|
|
216
|
+
the human. If `selected.media` exists, attach the listed image before saying the
|
|
217
|
+
human can scan it. If `selected.links` exists, include the real URL, not just
|
|
218
|
+
the label. If `selected.buttons` exists and the current adapter supports native
|
|
219
|
+
buttons, render them; otherwise fall back to the real URL plus short text. Do
|
|
220
|
+
not invent a table or rewrite the UI plan.
|
|
219
221
|
|
|
220
222
|
If a response has `status=payment_handoff_required`, `next` is the user-visible
|
|
221
|
-
reply step, not payment wait. Do not run `buyer payment wait` until the
|
|
222
|
-
has
|
|
223
|
-
`
|
|
223
|
+
reply step, not payment wait. Do not run `buyer payment wait` until the human
|
|
224
|
+
has seen the QR/link and asks to check status; then use
|
|
225
|
+
`after_human_response.check_payment_command`.
|
|
224
226
|
|
|
225
227
|
For first-purchase auth, treat the returned ItPay authorization entry as a
|
|
226
228
|
single human orchestration entry. It may open Alipay login/registration first
|