@itpay/cli 0.2.17 → 2.0.0-rc.2
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 +148 -447
- package/bin/itp +1 -150
- package/dist/src/client/backend.js +154 -0
- package/dist/src/client/http.js +76 -0
- package/dist/src/client/types.js +4 -0
- package/dist/src/commands/buy.js +351 -0
- package/dist/src/commands/cart.js +264 -0
- package/dist/src/commands/catalog.js +26 -0
- package/dist/src/commands/checkout.js +106 -0
- package/dist/src/commands/docs.js +61 -0
- package/dist/src/commands/guidance.js +422 -0
- package/dist/src/commands/install.js +95 -0
- package/dist/src/commands/order.js +78 -0
- package/dist/src/commands/orders.js +22 -0
- package/dist/src/commands/pay.js +26 -0
- package/dist/src/commands/readyz.js +8 -0
- package/dist/src/commands/refund.js +20 -0
- package/dist/src/commands/services.js +317 -0
- package/dist/src/main.js +606 -0
- package/dist/src/render/feishu.js +201 -0
- package/dist/src/render/ide.js +321 -0
- package/dist/src/render/index.js +57 -0
- package/dist/src/render/interaction.js +49 -0
- package/dist/src/render/markdown.js +83 -0
- package/dist/src/render/output.js +42 -0
- package/dist/src/render/plain_chat.js +60 -0
- package/dist/src/render/plan.js +31 -0
- package/dist/src/render/qr.js +32 -0
- package/dist/src/render/sink.js +6 -0
- package/dist/src/render/status.js +37 -0
- package/dist/src/render/telegram.js +172 -0
- package/dist/src/render/terminal.js +148 -0
- package/dist/src/render/terminal_image.js +19 -0
- package/dist/src/state/cart_session.js +151 -0
- package/dist/src/state/client_context.js +73 -0
- package/dist/src/state/config.js +82 -0
- package/dist/src/state/device_authority.js +217 -0
- package/dist/src/state/operation_journal.js +80 -0
- package/docs/agent/buyer/cart-checkout.json +56 -94
- package/docs/agent/buyer/catalog-list.json +47 -0
- package/docs/agent/buyer/install-and-setup.json +82 -0
- package/docs/agent/buyer/orders-refunds.json +76 -0
- package/docs/agent/buyer/payment-flow.json +77 -0
- package/docs/agent/buyer/quickstart.json +143 -75
- package/docs/agent/buyer/render-hosts.json +79 -0
- package/package.json +32 -13
- package/skills/itpay-buyer/SKILL.md +107 -238
- package/docs/agent/buyer/account-portal.json +0 -81
- package/docs/agent/buyer/catalog-search.json +0 -106
- package/docs/agent/buyer/human-claim-ui.json +0 -77
- package/docs/agent/buyer/payment-qr.json +0 -97
- package/docs/agent/buyer/payment-wait.json +0 -84
- package/docs/agent/buyer/product-recommendation.json +0 -80
- package/docs/agent/buyer/qr-refresh.json +0 -67
- package/docs/agent/buyer/recovery.json +0 -85
- package/docs/agent/buyer/safety-policy.json +0 -70
- package/docs/agent/buyer/secure-delivery.json +0 -90
- package/docs/agent/buyer/vault-agent-read.json +0 -95
- package/install.ps1 +0 -65
- package/install.sh +0 -66
- package/lib/account-status.js +0 -157
- package/lib/buyer.js +0 -2332
- package/lib/client-context.js +0 -126
- package/lib/docs.js +0 -200
- package/lib/env.js +0 -723
- package/lib/http.js +0 -151
- package/lib/ops.js +0 -135
- package/lib/render-human.js +0 -718
- package/lib/runtime.js +0 -1456
package/lib/buyer.js
DELETED
|
@@ -1,2332 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
appendURLQuery, booleanFlag, cliCommand, cryptoRandom, csvValues, deleteSessionCredential, intFlag, output, positional, readConfig, readCredentials,
|
|
3
|
-
readSessionToken, readState, safeErrorMessage, sleep, splitCSV, stripInternalBuyerFields, updateRun, writeConfig,
|
|
4
|
-
writeCredentials, writeSessionCredentials, writeState, shellQuote
|
|
5
|
-
} from "./env.js";
|
|
6
|
-
import { clientCommandArgs, clientHost, clientTarget } from "./client-context.js";
|
|
7
|
-
import { coreApi, coreApiBase } from "./http.js";
|
|
8
|
-
import { buildHumanActionRenderPlan, renderHumanAction, renderItPayPaymentAction, shouldReturnAfterAgentTextQR, writeWaitHeartbeat } from "./render-human.js";
|
|
9
|
-
|
|
10
|
-
async function buyerBuy(flags) {
|
|
11
|
-
rejectBuyerSandboxFlag(flags);
|
|
12
|
-
const showThenWait = shouldReturnAfterAgentTextQR(flags);
|
|
13
|
-
const selectionID = flags.selection || flags.variant || flags.catalog_variant_id || flags.item || flags.catalog_item_id;
|
|
14
|
-
if (!selectionID) throw new Error("catalog variant id is required, for example: itp buy <variant_id> --email <buyer_email> --json");
|
|
15
|
-
const selection = await resolveBuyerCatalogSelection(selectionID, flags);
|
|
16
|
-
const cart = await createBuyerCart(selection, flags);
|
|
17
|
-
let checkout = await createBuyerCheckoutFromCart(cart, selection, flags);
|
|
18
|
-
if (checkout.next_required_action === "auth_qr" || checkout.identity_status === "waiting_human_auth") {
|
|
19
|
-
await renderHumanAction(checkout.human_action, flags);
|
|
20
|
-
if (flags.no_wait || flags.no_wait_auth || showThenWait) {
|
|
21
|
-
output(buyerRunOutput({
|
|
22
|
-
status: "waiting_human_auth",
|
|
23
|
-
selection,
|
|
24
|
-
cart,
|
|
25
|
-
checkout,
|
|
26
|
-
human_action: checkout.human_action,
|
|
27
|
-
render_plan: buildHumanActionRenderPlan(checkout.human_action, {}, flags),
|
|
28
|
-
agent_next_actions: checkout.agent_next_actions || ["wait_human_auth", "poll_checkout"],
|
|
29
|
-
next: {
|
|
30
|
-
command: cliCommand("buyer", "checkout", "resume", checkout.checkout_id, "--json"),
|
|
31
|
-
safe_for_agent: true,
|
|
32
|
-
instruction: "After presenting the first-purchase auth-to-payment QR, keep this resume command running/waiting. Do not stop at QR display unless the human explicitly asks you to pause."
|
|
33
|
-
}
|
|
34
|
-
}));
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
checkout = await waitBuyerCheckoutAuth(checkout, flags);
|
|
38
|
-
}
|
|
39
|
-
if (!buyerSessionReadyForPayment()) {
|
|
40
|
-
output(buyerRunOutput(buyerSessionRequiredBeforePayment(checkout, flags, { selection, cart })));
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
const intent = checkout.payment_intent_id
|
|
44
|
-
? await getBuyerPaymentIntent(checkout.payment_intent_id, flags)
|
|
45
|
-
: await createBuyerPaymentIntent(checkout.checkout_id, flags);
|
|
46
|
-
await renderItPayPaymentAction(intent, flags);
|
|
47
|
-
|
|
48
|
-
if (flags.no_wait || flags.no_wait_payment || showThenWait) {
|
|
49
|
-
output(buyerRunOutput({
|
|
50
|
-
status: "payment_handoff_required",
|
|
51
|
-
selection,
|
|
52
|
-
cart,
|
|
53
|
-
checkout,
|
|
54
|
-
payment_intent: intent,
|
|
55
|
-
...paymentHandoffFields(intent, flags),
|
|
56
|
-
agent_next_actions: paymentHandoffAgentNextActions(),
|
|
57
|
-
next: paymentHandoffNext()
|
|
58
|
-
}));
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const event = await waitBuyerPayment(intent, flags);
|
|
63
|
-
const delivery = await waitBuyerDelivery(checkout.checkout_id, flags);
|
|
64
|
-
const finalCheckout = delivery.checkout || checkout;
|
|
65
|
-
const delivered = isBuyerDeliveryComplete(finalCheckout);
|
|
66
|
-
output(buyerRunOutput({
|
|
67
|
-
status: delivered ? "delivery_claimable" : event.event_type === "payment_intent.verified" ? "payment_verified" : "waiting_user_payment",
|
|
68
|
-
selection,
|
|
69
|
-
cart,
|
|
70
|
-
checkout: finalCheckout,
|
|
71
|
-
payment_intent: intent,
|
|
72
|
-
payment_event: event,
|
|
73
|
-
delivery: delivery.delivery || finalCheckout.delivery || null,
|
|
74
|
-
agent_next_actions: delivered ? deliveryAwareAgentNextActions(finalCheckout) : (finalCheckout.agent_next_actions || event.agent_next_actions || intent.agent_next_actions || ["poll_checkout"]),
|
|
75
|
-
optional_agent_read_grant: optionalAgentReadGrantHint(finalCheckout.checkout_id || checkout.checkout_id, finalCheckout),
|
|
76
|
-
next: delivered
|
|
77
|
-
? { type: "human_check_email", safe_for_agent: true }
|
|
78
|
-
: { command: cliCommand("buyer", "checkout", "status", checkout.checkout_id, "--json"), safe_for_agent: true }
|
|
79
|
-
}));
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async function buyer(command, rest, flags) {
|
|
83
|
-
rejectBuyerSandboxFlag(flags);
|
|
84
|
-
const subcommand = rest[0] && !String(rest[0]).startsWith("--") ? rest[0] : "";
|
|
85
|
-
if (command === "catalog") {
|
|
86
|
-
if (subcommand === "search") {
|
|
87
|
-
const query = flags.query || flags.q || "";
|
|
88
|
-
const body = {
|
|
89
|
-
query,
|
|
90
|
-
filters: buyerCatalogSearchFilters(flags),
|
|
91
|
-
context: {},
|
|
92
|
-
pagination: {}
|
|
93
|
-
};
|
|
94
|
-
if (flags.currency) body.context.currency = String(flags.currency);
|
|
95
|
-
if (flags.page_size || flags.limit) body.pagination.limit = Number(flags.page_size || flags.limit);
|
|
96
|
-
if (flags.cursor) body.pagination.cursor = String(flags.cursor);
|
|
97
|
-
const catalog = await coreApi("/v1/catalog/search", { method: "POST", body }, flags);
|
|
98
|
-
output(buyerRunOutput({
|
|
99
|
-
status: "catalog_search_results",
|
|
100
|
-
catalog,
|
|
101
|
-
products: catalog.products || [],
|
|
102
|
-
catalog_guidance: catalogSearchGuidance(catalog, body),
|
|
103
|
-
agent_next_actions: ["compare_services", "explain_options_to_human", "ask_human_to_confirm_purchase_option"]
|
|
104
|
-
}));
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
if (subcommand === "get") {
|
|
108
|
-
const selectionID = flags.variant || flags.catalog_variant_id || flags.item || flags.catalog_item_id || rest[1];
|
|
109
|
-
const detail = await getBuyerUCPProduct(selectionID, flags);
|
|
110
|
-
const selection = selectionFromUCPProduct(detail, selectionID, flags);
|
|
111
|
-
output(buyerRunOutput({
|
|
112
|
-
status: "catalog_product",
|
|
113
|
-
product: detail.product,
|
|
114
|
-
messages: detail.messages || [],
|
|
115
|
-
selection,
|
|
116
|
-
catalog_guidance: catalogSelectionGuidance(selection),
|
|
117
|
-
agent_next_actions: ["explain_selected_service", "ask_human_to_confirm_purchase", "create_cart_after_confirmation"]
|
|
118
|
-
}));
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
if (command === "cart") {
|
|
123
|
-
if (subcommand === "create") {
|
|
124
|
-
const selectionIDs = buyerCartSelectionIDs(rest, flags);
|
|
125
|
-
const selections = await resolveBuyerCatalogSelections(selectionIDs, flags);
|
|
126
|
-
const cart = await createBuyerCartFromSelections(selections, flags);
|
|
127
|
-
output(buyerRunOutput({
|
|
128
|
-
status: "cart_created",
|
|
129
|
-
selection: selections.length === 1 ? selections[0] : undefined,
|
|
130
|
-
selections,
|
|
131
|
-
cart,
|
|
132
|
-
cart_id: cart.cart_id || cart.id,
|
|
133
|
-
cart_guidance: cartConfirmationGuidance(cart, selections),
|
|
134
|
-
agent_next_actions: cart.agent_next_actions || ["show_cart_to_human", "confirm_cart_before_checkout"]
|
|
135
|
-
}));
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
if (subcommand === "add") {
|
|
139
|
-
const cartID = flags.cart || flags.cart_id || positional(rest, 1) || readState().last_core_cart_id;
|
|
140
|
-
if (!cartID) throw new Error("cart_id is required");
|
|
141
|
-
const selectionIDs = buyerCartSelectionIDs(["add"], flags);
|
|
142
|
-
const selections = await resolveBuyerCatalogSelections(selectionIDs, flags);
|
|
143
|
-
if (selections.length !== 1) throw new Error("buyer cart add requires exactly one --variant");
|
|
144
|
-
const cart = await addBuyerCartLineItem(cartID, selections[0], flags);
|
|
145
|
-
output(buyerRunOutput({
|
|
146
|
-
status: "cart_updated",
|
|
147
|
-
selection: selections[0],
|
|
148
|
-
cart,
|
|
149
|
-
cart_id: cart.cart_id || cart.id,
|
|
150
|
-
cart_guidance: cartConfirmationGuidance(cart, selections),
|
|
151
|
-
agent_next_actions: cart.agent_next_actions || ["show_cart_to_human", "confirm_cart_before_checkout"]
|
|
152
|
-
}));
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
if (subcommand === "remove" || subcommand === "delete") {
|
|
156
|
-
const cartID = flags.cart || flags.cart_id || positional(rest, 1) || readState().last_core_cart_id;
|
|
157
|
-
const lineID = flags.line || flags.line_id || flags.cart_line_item_id || positional(rest, 2);
|
|
158
|
-
if (!cartID) throw new Error("cart_id is required");
|
|
159
|
-
if (!lineID) throw new Error("cart_line_item_id is required; run buyer cart show <cart_id> --json first");
|
|
160
|
-
const cart = await removeBuyerCartLineItem(cartID, lineID, flags);
|
|
161
|
-
output(buyerRunOutput({
|
|
162
|
-
status: "cart_updated",
|
|
163
|
-
cart,
|
|
164
|
-
cart_id: cart.cart_id || cart.id,
|
|
165
|
-
removed_line_item_id: lineID,
|
|
166
|
-
cart_guidance: cartConfirmationGuidance(cart),
|
|
167
|
-
agent_next_actions: cart.agent_next_actions || ["show_cart_to_human", "confirm_cart_before_checkout"]
|
|
168
|
-
}));
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
if (subcommand === "show" || subcommand === "status") {
|
|
172
|
-
const cartID = flags.cart || flags.cart_id || positional(rest, 1) || readState().last_core_cart_id;
|
|
173
|
-
if (!cartID) throw new Error("cart_id is required");
|
|
174
|
-
const cart = await getBuyerCart(cartID, flags);
|
|
175
|
-
output(buyerRunOutput({
|
|
176
|
-
status: cart.status || "cart_ready",
|
|
177
|
-
cart,
|
|
178
|
-
cart_id: cart.cart_id || cart.id,
|
|
179
|
-
cart_guidance: cartConfirmationGuidance(cart),
|
|
180
|
-
agent_next_actions: cart.agent_next_actions || ["show_cart_to_human", "confirm_cart_before_checkout"]
|
|
181
|
-
}));
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
if (command === "shelf") {
|
|
186
|
-
if (subcommand === "manifest") {
|
|
187
|
-
output(await coreApi("/v1/catalog/manifests/current", { method: "GET" }, flags));
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
if (subcommand === "snapshot") {
|
|
191
|
-
const version = flags.version || positional(rest, 1);
|
|
192
|
-
if (!version) throw new Error("snapshot version is required");
|
|
193
|
-
output(await coreApi(`/v1/catalog/snapshots/${encodeURIComponent(version)}`, { method: "GET" }, flags));
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
if (subcommand === "delta") {
|
|
197
|
-
const since = flags.since || positional(rest, 1);
|
|
198
|
-
if (!since) throw new Error("delta --since version is required");
|
|
199
|
-
const params = new URLSearchParams({ since });
|
|
200
|
-
output(await coreApi(`/v1/catalog/delta?${params.toString()}`, { method: "GET" }, flags));
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
if (command === "checkout") {
|
|
205
|
-
if (subcommand === "create") {
|
|
206
|
-
const cartID = flags.cart || flags.cart_id;
|
|
207
|
-
if (cartID) {
|
|
208
|
-
const cart = await getBuyerCart(cartID, flags);
|
|
209
|
-
const checkout = await createBuyerCheckoutFromCart(cart, null, flags);
|
|
210
|
-
if (checkout.next_required_action === "auth_qr" || checkout.identity_status === "waiting_human_auth") {
|
|
211
|
-
await renderHumanAction(checkout.human_action, flags);
|
|
212
|
-
}
|
|
213
|
-
output(buyerRunOutput({
|
|
214
|
-
status: "checkout_created",
|
|
215
|
-
cart,
|
|
216
|
-
checkout,
|
|
217
|
-
buyer_session: buyerSessionClaimStatus(),
|
|
218
|
-
checkout_guidance: checkoutAuthorizationGuidance(checkout),
|
|
219
|
-
render_plan: checkout.next_required_action === "auth_qr" || checkout.identity_status === "waiting_human_auth"
|
|
220
|
-
? buildHumanActionRenderPlan(checkout.human_action, {}, flags)
|
|
221
|
-
: undefined,
|
|
222
|
-
agent_next_actions: checkout.agent_next_actions || checkoutAgentNextActions(checkout),
|
|
223
|
-
next: checkout.next_required_action === "auth_qr" || checkout.identity_status === "waiting_human_auth"
|
|
224
|
-
? {
|
|
225
|
-
command: cliCommand("buyer", "checkout", "resume", checkout.checkout_id, "--json"),
|
|
226
|
-
safe_for_agent: true,
|
|
227
|
-
instruction: "After presenting the first-purchase auth-to-payment QR, keep this resume command running/waiting. Do not stop at QR display unless the human explicitly asks you to pause."
|
|
228
|
-
}
|
|
229
|
-
: undefined
|
|
230
|
-
}));
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
const selectionID = flags.variant || flags.catalog_variant_id || flags.item || flags.catalog_item_id;
|
|
234
|
-
const selection = await resolveBuyerCatalogSelection(selectionID, flags);
|
|
235
|
-
const cart = await createBuyerCart(selection, flags);
|
|
236
|
-
const checkout = await createBuyerCheckoutFromCart(cart, selection, flags);
|
|
237
|
-
if (checkout.next_required_action === "auth_qr" || checkout.identity_status === "waiting_human_auth") {
|
|
238
|
-
await renderHumanAction(checkout.human_action, flags);
|
|
239
|
-
}
|
|
240
|
-
output(buyerRunOutput({
|
|
241
|
-
status: "checkout_created",
|
|
242
|
-
selection,
|
|
243
|
-
cart,
|
|
244
|
-
checkout,
|
|
245
|
-
buyer_session: buyerSessionClaimStatus(),
|
|
246
|
-
checkout_guidance: checkoutAuthorizationGuidance(checkout),
|
|
247
|
-
render_plan: checkout.next_required_action === "auth_qr" || checkout.identity_status === "waiting_human_auth"
|
|
248
|
-
? buildHumanActionRenderPlan(checkout.human_action, {}, flags)
|
|
249
|
-
: undefined,
|
|
250
|
-
agent_next_actions: checkout.agent_next_actions || checkoutAgentNextActions(checkout),
|
|
251
|
-
next: checkout.next_required_action === "auth_qr" || checkout.identity_status === "waiting_human_auth"
|
|
252
|
-
? {
|
|
253
|
-
command: cliCommand("buyer", "checkout", "resume", checkout.checkout_id, "--json"),
|
|
254
|
-
safe_for_agent: true,
|
|
255
|
-
instruction: "After presenting the first-purchase auth-to-payment QR, keep this resume command running/waiting. Do not stop at QR display unless the human explicitly asks you to pause."
|
|
256
|
-
}
|
|
257
|
-
: undefined
|
|
258
|
-
}));
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
if (subcommand === "status") {
|
|
262
|
-
const checkoutID = flags.checkout || flags.checkout_id || positional(rest, 1) || readState().last_core_checkout_id;
|
|
263
|
-
if (!checkoutID) throw new Error("checkout_id is required");
|
|
264
|
-
const checkout = await getBuyerCheckout(checkoutID, flags);
|
|
265
|
-
const claimedSession = await maybeClaimBuyerSessionForCheckout(checkout, flags);
|
|
266
|
-
output(buyerRunOutput({
|
|
267
|
-
status: checkout.delivery_status || checkout.status,
|
|
268
|
-
checkout,
|
|
269
|
-
delivery: checkout.delivery,
|
|
270
|
-
buyer_session: buyerSessionClaimStatus(claimedSession),
|
|
271
|
-
delivery_guidance: deliveryAccessGuidance(checkout),
|
|
272
|
-
agent_next_actions: deliveryAwareAgentNextActions(checkout),
|
|
273
|
-
optional_agent_read_grant: optionalAgentReadGrantHint(checkout.checkout_id, checkout)
|
|
274
|
-
}));
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
if (subcommand === "resume") {
|
|
278
|
-
const checkoutID = flags.checkout || flags.checkout_id || positional(rest, 1) || readState().last_core_checkout_id;
|
|
279
|
-
if (!checkoutID) throw new Error("checkout_id is required");
|
|
280
|
-
let checkout = await getBuyerCheckout(checkoutID, flags);
|
|
281
|
-
if (checkout.next_required_action === "auth_qr" || checkout.identity_status === "waiting_human_auth") {
|
|
282
|
-
await renderHumanAction(checkout.human_action, flags);
|
|
283
|
-
if (flags.no_wait || flags.no_wait_auth) {
|
|
284
|
-
output(buyerRunOutput({
|
|
285
|
-
status: "waiting_human_auth",
|
|
286
|
-
checkout,
|
|
287
|
-
human_action: checkout.human_action,
|
|
288
|
-
render_plan: buildHumanActionRenderPlan(checkout.human_action, {}, flags),
|
|
289
|
-
agent_next_actions: checkout.agent_next_actions || ["wait_human_auth", "poll_checkout"],
|
|
290
|
-
next: {
|
|
291
|
-
command: cliCommand("buyer", "checkout", "resume", checkoutID, "--json"),
|
|
292
|
-
safe_for_agent: true,
|
|
293
|
-
instruction: "Run this resume command and keep it active; do not stop after showing the auth-to-payment QR unless the human explicitly asks you to pause."
|
|
294
|
-
}
|
|
295
|
-
}));
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
checkout = await waitBuyerCheckoutAuth(checkout, flags);
|
|
299
|
-
if (checkout.next_required_action === "auth_qr" || checkout.identity_status === "waiting_human_auth") {
|
|
300
|
-
output(buyerRunOutput({
|
|
301
|
-
status: "waiting_human_auth",
|
|
302
|
-
checkout,
|
|
303
|
-
human_action: checkout.human_action,
|
|
304
|
-
render_plan: buildHumanActionRenderPlan(checkout.human_action, {}, flags),
|
|
305
|
-
agent_next_actions: checkout.agent_next_actions || ["wait_human_auth", "poll_checkout"],
|
|
306
|
-
next: {
|
|
307
|
-
command: cliCommand("buyer", "checkout", "resume", checkoutID, "--json"),
|
|
308
|
-
safe_for_agent: true,
|
|
309
|
-
instruction: "Auth is still pending. Keep waiting/resuming the same checkout; do not create a new checkout."
|
|
310
|
-
}
|
|
311
|
-
}));
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
const claimedSession = await maybeClaimBuyerSessionForCheckout(checkout, flags);
|
|
316
|
-
if (checkout.payment_intent_id) {
|
|
317
|
-
if (!buyerSessionReadyForPayment()) {
|
|
318
|
-
output(buyerRunOutput(buyerSessionRequiredBeforePayment(checkout, flags, {
|
|
319
|
-
buyer_session: buyerSessionClaimStatus(claimedSession)
|
|
320
|
-
})));
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
let intent = await getBuyerPaymentIntent(checkout.payment_intent_id, flags);
|
|
324
|
-
intent = await ensureTelegramPaymentDisplay(intent, flags);
|
|
325
|
-
await renderItPayPaymentAction(intent, flags);
|
|
326
|
-
output(buyerRunOutput({
|
|
327
|
-
status: intent.status === "verified" ? "payment_verified" : "payment_handoff_required",
|
|
328
|
-
checkout,
|
|
329
|
-
payment_intent: intent,
|
|
330
|
-
...(intent.status === "verified" ? {} : paymentHandoffFields(intent, flags)),
|
|
331
|
-
buyer_session: buyerSessionClaimStatus(claimedSession),
|
|
332
|
-
agent_next_actions: intent.status === "verified" ? (intent.agent_next_actions || checkout.agent_next_actions || ["poll_checkout"]) : paymentHandoffAgentNextActions(),
|
|
333
|
-
next: intent.status === "verified"
|
|
334
|
-
? { command: cliCommand("buyer", "checkout", "status", checkout.checkout_id, "--json"), safe_for_agent: true }
|
|
335
|
-
: paymentHandoffNext()
|
|
336
|
-
}));
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
if (checkout.agent_next_actions?.includes("create_payment_intent") || checkout.next_required_action === "create_payment_intent") {
|
|
340
|
-
if (!buyerSessionReadyForPayment()) {
|
|
341
|
-
output(buyerRunOutput(buyerSessionRequiredBeforePayment(checkout, flags, {
|
|
342
|
-
buyer_session: buyerSessionClaimStatus(claimedSession)
|
|
343
|
-
})));
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
const intent = await createBuyerPaymentIntent(checkout.checkout_id, flags);
|
|
347
|
-
await renderItPayPaymentAction(intent, flags);
|
|
348
|
-
output(buyerRunOutput({
|
|
349
|
-
status: "payment_handoff_required",
|
|
350
|
-
checkout,
|
|
351
|
-
payment_intent: intent,
|
|
352
|
-
...paymentHandoffFields(intent, flags),
|
|
353
|
-
agent_next_actions: paymentHandoffAgentNextActions(),
|
|
354
|
-
next: paymentHandoffNext()
|
|
355
|
-
}));
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
output(buyerRunOutput({
|
|
359
|
-
status: checkout.delivery_status || checkout.status,
|
|
360
|
-
checkout,
|
|
361
|
-
delivery: checkout.delivery,
|
|
362
|
-
buyer_session: buyerSessionClaimStatus(claimedSession),
|
|
363
|
-
agent_next_actions: deliveryAwareAgentNextActions(checkout),
|
|
364
|
-
optional_agent_read_grant: optionalAgentReadGrantHint(checkout.checkout_id, checkout)
|
|
365
|
-
}));
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
if (command === "payment" && subcommand === "wait") {
|
|
370
|
-
const paymentIntentID = flags.payment_intent || flags.payment_intent_id || positional(rest, 1) || readState().last_core_payment_intent_id;
|
|
371
|
-
if (!paymentIntentID) throw new Error("payment_intent_id is required");
|
|
372
|
-
let intent = await getBuyerPaymentIntent(paymentIntentID, flags);
|
|
373
|
-
if (intent.status !== "verified" && shouldReturnPaymentHandoffBeforeWait(flags)) {
|
|
374
|
-
intent = await ensureTelegramPaymentDisplay(intent, flags);
|
|
375
|
-
await renderItPayPaymentAction(intent, flags);
|
|
376
|
-
const context = await optionalBuyerPaymentContext(intent, flags);
|
|
377
|
-
output(buyerRunOutput({
|
|
378
|
-
status: "payment_handoff_required",
|
|
379
|
-
...context,
|
|
380
|
-
payment_intent: intent,
|
|
381
|
-
...paymentHandoffFields(intent, flags),
|
|
382
|
-
payment_guidance: paymentRecoveryGuidance(intent, { event_type: "payment_display_required" }),
|
|
383
|
-
agent_next_actions: paymentHandoffAgentNextActions(),
|
|
384
|
-
next: paymentHandoffNext()
|
|
385
|
-
}));
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
const event = intent.status === "verified"
|
|
389
|
-
? { event_type: "payment_intent.verified", payment_intent_id: paymentIntentID, agent_next_actions: intent.agent_next_actions || ["poll_checkout"] }
|
|
390
|
-
: await waitBuyerPayment(intent, flags);
|
|
391
|
-
output(buyerRunOutput({
|
|
392
|
-
status: event.event_type === "payment_intent.verified" ? "payment_verified" : "waiting_user_payment",
|
|
393
|
-
payment_intent: intent,
|
|
394
|
-
payment_event: event,
|
|
395
|
-
payment_guidance: paymentRecoveryGuidance(intent, event),
|
|
396
|
-
agent_next_actions: event.agent_next_actions || intent.agent_next_actions || paymentAgentNextActions(intent, event),
|
|
397
|
-
next: event.event_type === "payment_intent.verified"
|
|
398
|
-
? { command: intent.checkout_id ? cliCommand("buyer", "checkout", "status", intent.checkout_id, "--json") : undefined, safe_for_agent: true }
|
|
399
|
-
: { command: paymentStatusCheckCommand(paymentIntentID, flags), safe_for_agent: true }
|
|
400
|
-
}));
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
if (command === "payment" && subcommand === "refresh-qr") {
|
|
404
|
-
const paymentIntentID = flags.payment_intent || flags.payment_intent_id || positional(rest, 1) || readState().last_core_payment_intent_id;
|
|
405
|
-
if (!paymentIntentID) throw new Error("payment_intent_id is required");
|
|
406
|
-
const refreshed = await refreshBuyerPaymentQR(paymentIntentID, flags);
|
|
407
|
-
await renderItPayPaymentAction(refreshed, flags);
|
|
408
|
-
const context = refreshed.status === "verified" ? {} : await optionalBuyerPaymentContext(refreshed, flags);
|
|
409
|
-
output(buyerRunOutput({
|
|
410
|
-
status: refreshed.status === "verified" ? "payment_verified" : "payment_handoff_required",
|
|
411
|
-
...context,
|
|
412
|
-
payment_intent: refreshed,
|
|
413
|
-
...(refreshed.status === "verified" ? {} : paymentHandoffFields(refreshed, flags)),
|
|
414
|
-
payment_guidance: paymentRecoveryGuidance(refreshed, { event_type: refreshed.status === "verified" ? "payment_intent.verified" : "qr_refreshed" }),
|
|
415
|
-
agent_next_actions: refreshed.status === "verified" ? (refreshed.agent_next_actions || paymentAgentNextActions(refreshed, { event_type: "payment_intent.verified" })) : paymentHandoffAgentNextActions(),
|
|
416
|
-
next: refreshed.status === "verified"
|
|
417
|
-
? { command: cliCommand("buyer", "checkout", "status", refreshed.checkout_id, "--json"), safe_for_agent: true }
|
|
418
|
-
: paymentHandoffNext()
|
|
419
|
-
}));
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
if (command === "deliveries") {
|
|
423
|
-
if (subcommand === "list") {
|
|
424
|
-
const checkoutID = flags.checkout || flags.checkout_id || readState().last_core_checkout_id;
|
|
425
|
-
if (!checkoutID) throw new Error("--checkout is required");
|
|
426
|
-
const checkout = await getBuyerCheckout(checkoutID, flags);
|
|
427
|
-
output(buyerDeliveryListOutput(checkout));
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
if (subcommand === "show") {
|
|
431
|
-
const checkoutID = flags.checkout || flags.checkout_id || readState().last_core_checkout_id;
|
|
432
|
-
if (!checkoutID) throw new Error("--checkout is required for agent-safe delivery status");
|
|
433
|
-
const checkout = await getBuyerCheckout(checkoutID, flags);
|
|
434
|
-
output({
|
|
435
|
-
schema_version: "itp.buyer.v1",
|
|
436
|
-
status: checkout.delivery?.status || checkout.delivery_status,
|
|
437
|
-
delivery_id: positional(rest, 1) || flags.delivery || flags.delivery_id || null,
|
|
438
|
-
checkout_id: checkout.checkout_id,
|
|
439
|
-
delivery: checkout.delivery || null,
|
|
440
|
-
agent_next_actions: deliveryAwareAgentNextActions(checkout),
|
|
441
|
-
optional_agent_read_grant: optionalAgentReadGrantHint(checkout.checkout_id, checkout),
|
|
442
|
-
secrets: { raw_content_included: false, claim_token_included: false }
|
|
443
|
-
});
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
if (command === "refund") {
|
|
448
|
-
if (subcommand === "create") {
|
|
449
|
-
const orderID = flags.order || flags.order_id || positional(rest, 1);
|
|
450
|
-
if (!orderID) throw new Error("order_id is required");
|
|
451
|
-
const refund = await createBuyerRefund(orderID, flags);
|
|
452
|
-
if (refund.status === "policy_risk_confirmation_required") {
|
|
453
|
-
output(buyerRunOutput({
|
|
454
|
-
...refund,
|
|
455
|
-
refund_guidance: refundGuidance({ order_id: orderID, refund, phase: "policy_risk" })
|
|
456
|
-
}));
|
|
457
|
-
return;
|
|
458
|
-
}
|
|
459
|
-
output(buyerRunOutput({
|
|
460
|
-
status: refund.status || "refund_requested",
|
|
461
|
-
refund,
|
|
462
|
-
refund_eligibility: refund.refund_eligibility || null,
|
|
463
|
-
refund_guidance: refundGuidance({ order_id: orderID, refund, phase: "created" }),
|
|
464
|
-
agent_next_actions: ["watch_refund_status", "explain_refund_policy"]
|
|
465
|
-
}));
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
if (subcommand === "list") {
|
|
469
|
-
const orderID = flags.order || flags.order_id || positional(rest, 1);
|
|
470
|
-
if (!orderID) throw new Error("order_id is required");
|
|
471
|
-
const refunds = await listBuyerRefunds(orderID, flags);
|
|
472
|
-
output(buyerRunOutput({
|
|
473
|
-
status: "refunds",
|
|
474
|
-
order_id: orderID,
|
|
475
|
-
...refunds,
|
|
476
|
-
refund_guidance: refundGuidance({ order_id: orderID, refunds, phase: "list" }),
|
|
477
|
-
agent_next_actions: ["show_refund_status"]
|
|
478
|
-
}));
|
|
479
|
-
return;
|
|
480
|
-
}
|
|
481
|
-
if (subcommand === "show") {
|
|
482
|
-
const refundID = flags.refund || flags.refund_id || positional(rest, 1);
|
|
483
|
-
if (!refundID) throw new Error("refund_id is required");
|
|
484
|
-
const refund = await getBuyerRefund(refundID, flags);
|
|
485
|
-
output(buyerRunOutput({
|
|
486
|
-
status: refund.status || "refund",
|
|
487
|
-
refund,
|
|
488
|
-
refund_guidance: refundGuidance({ refund, phase: "show" }),
|
|
489
|
-
agent_next_actions: ["show_refund_status"]
|
|
490
|
-
}));
|
|
491
|
-
return;
|
|
492
|
-
}
|
|
493
|
-
if (subcommand === "cancel") {
|
|
494
|
-
const refundID = flags.refund || flags.refund_id || positional(rest, 1);
|
|
495
|
-
if (!refundID) throw new Error("refund_id is required");
|
|
496
|
-
const refund = await cancelBuyerRefund(refundID, flags);
|
|
497
|
-
output(buyerRunOutput({
|
|
498
|
-
status: refund.status || "refund_canceled",
|
|
499
|
-
refund,
|
|
500
|
-
refund_guidance: refundGuidance({ refund, phase: "cancel" }),
|
|
501
|
-
agent_next_actions: ["show_refund_status", "claim_delivery_if_still_needed"]
|
|
502
|
-
}));
|
|
503
|
-
return;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
if (command === "vault") {
|
|
507
|
-
if (subcommand === "grants") {
|
|
508
|
-
const action = rest[1] && !String(rest[1]).startsWith("--") ? rest[1] : "list";
|
|
509
|
-
if (action === "list") {
|
|
510
|
-
const claimedSession = await ensureBuyerSessionForVaultAccess(flags);
|
|
511
|
-
const grants = await listBuyerAgentReadGrants(flags);
|
|
512
|
-
output(buyerRunOutput({
|
|
513
|
-
status: "agent_read_grants",
|
|
514
|
-
...grants,
|
|
515
|
-
buyer_session: buyerSessionClaimStatus(claimedSession),
|
|
516
|
-
vault_guidance: vaultAccessGuidance(grants),
|
|
517
|
-
agent_next_actions: grants.agent_readable_grants?.length ? ["read_agent_grant_view"] : ["wait_for_human_agent_read_grant"]
|
|
518
|
-
}));
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
if (action === "read" || action === "show") {
|
|
522
|
-
const grantID = flags.grant || flags.grant_id || flags.agent_read_grant_id || positional(rest, 2);
|
|
523
|
-
if (!grantID) throw new Error("agent_read_grant_id is required");
|
|
524
|
-
await ensureBuyerSessionForVaultAccess(flags);
|
|
525
|
-
const view = await readBuyerAgentReadGrant(grantID, flags);
|
|
526
|
-
output(buyerRunOutput({
|
|
527
|
-
status: "agent_read_grant_view",
|
|
528
|
-
grant: view,
|
|
529
|
-
vault_guidance: vaultAccessGuidance({ agent_readable_grants: [view] }, view),
|
|
530
|
-
agent_next_actions: ["use_human_approved_fields_only"]
|
|
531
|
-
}));
|
|
532
|
-
return;
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
if (subcommand === "read") {
|
|
536
|
-
await ensureBuyerSessionForVaultAccess(flags);
|
|
537
|
-
const view = await readBuyerVaultArtifactGrant(flags);
|
|
538
|
-
output(buyerRunOutput({
|
|
539
|
-
status: "agent_read_grant_view",
|
|
540
|
-
grant: view,
|
|
541
|
-
vault_guidance: vaultAccessGuidance({ agent_readable_grants: [view.discovered_grant || view] }, view),
|
|
542
|
-
agent_next_actions: ["use_human_approved_fields_only"]
|
|
543
|
-
}));
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
if (command === "account" && subcommand === "login-link") {
|
|
548
|
-
await accountLoginLink(flags);
|
|
549
|
-
return;
|
|
550
|
-
}
|
|
551
|
-
if (command === "auth" && subcommand === "status") {
|
|
552
|
-
output(await buyerAuthStatusOutput(flags));
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
555
|
-
throw new Error(`unknown buyer command: ${[command, subcommand].filter(Boolean).join(" ") || ""}`);
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
function buyerCatalogSearchFilters(flags = {}) {
|
|
559
|
-
const filters = {};
|
|
560
|
-
const categories = csvValues(flags.category || flags.categories);
|
|
561
|
-
if (categories.length) filters.categories = categories;
|
|
562
|
-
|
|
563
|
-
const mappings = [
|
|
564
|
-
["service_type", "ai.itpay.service_type"],
|
|
565
|
-
["delivery_method", "ai.itpay.delivery_method"],
|
|
566
|
-
["provider", "ai.itpay.provider"],
|
|
567
|
-
["provider_product_id", "ai.itpay.provider_product_id"],
|
|
568
|
-
["provider_product", "ai.itpay.provider_product_id"],
|
|
569
|
-
["sensitivity_level", "ai.itpay.sensitivity_level"],
|
|
570
|
-
["sensitivity", "ai.itpay.sensitivity_level"],
|
|
571
|
-
["delivery_mode", "ai.itpay.delivery_mode"],
|
|
572
|
-
["settlement_group", "ai.itpay.settlement_group"]
|
|
573
|
-
];
|
|
574
|
-
for (const [flagName, filterName] of mappings) {
|
|
575
|
-
if (flags[flagName]) filters[filterName] = String(flags[flagName]);
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
const listMappings = [
|
|
579
|
-
["use_case", "ai.itpay.taxonomy.use_cases"],
|
|
580
|
-
["use_cases", "ai.itpay.taxonomy.use_cases"],
|
|
581
|
-
["input_facet", "ai.itpay.taxonomy.input_facets"],
|
|
582
|
-
["input_facets", "ai.itpay.taxonomy.input_facets"],
|
|
583
|
-
["output_facet", "ai.itpay.taxonomy.output_facets"],
|
|
584
|
-
["output_facets", "ai.itpay.taxonomy.output_facets"],
|
|
585
|
-
["required_profile_field", "ai.itpay.required_profile_fields"],
|
|
586
|
-
["required_profile_fields", "ai.itpay.required_profile_fields"],
|
|
587
|
-
["agent_runtime", "ai.itpay.agent_runtimes"],
|
|
588
|
-
["agent_runtimes", "ai.itpay.agent_runtimes"]
|
|
589
|
-
];
|
|
590
|
-
for (const [flagName, filterName] of listMappings) {
|
|
591
|
-
const values = csvValues(flags[flagName]);
|
|
592
|
-
if (values.length) filters[filterName] = values;
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
const boolMappings = [
|
|
596
|
-
["payment_qr_mpm", "ai.itpay.payment.qr_mpm"],
|
|
597
|
-
["merchant_verified", "ai.itpay.merchant_verified"],
|
|
598
|
-
["requires_human_input", "ai.itpay.requires_human_input"],
|
|
599
|
-
["requires_webauthn_reveal", "ai.itpay.requires_webauthn_reveal"],
|
|
600
|
-
["agent_may_execute_query", "ai.itpay.agent_may_execute_query"],
|
|
601
|
-
["agent_may_view_raw_result", "ai.itpay.agent_may_view_raw_result"]
|
|
602
|
-
];
|
|
603
|
-
for (const [flagName, filterName] of boolMappings) {
|
|
604
|
-
if (flags[flagName] !== undefined) filters[filterName] = booleanFlag(flags[flagName]);
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
const hasMin = flags.price_min !== undefined || flags.min_price !== undefined;
|
|
608
|
-
const hasMax = flags.price_max !== undefined || flags.max_price !== undefined;
|
|
609
|
-
const min = hasMin ? Number(flags.price_min ?? flags.min_price) : NaN;
|
|
610
|
-
const max = hasMax ? Number(flags.price_max ?? flags.max_price) : NaN;
|
|
611
|
-
if (Number.isFinite(min) || Number.isFinite(max)) {
|
|
612
|
-
filters.price = {};
|
|
613
|
-
if (Number.isFinite(min)) filters.price.min = min;
|
|
614
|
-
if (Number.isFinite(max)) filters.price.max = max;
|
|
615
|
-
}
|
|
616
|
-
return filters;
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
async function resolveBuyerCatalogSelection(selectionID, flags = {}) {
|
|
620
|
-
if (!selectionID) throw new Error("catalog selection id is required");
|
|
621
|
-
const detail = await getBuyerUCPProduct(selectionID, flags);
|
|
622
|
-
return selectionFromUCPProduct(detail, selectionID, flags);
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
async function resolveBuyerCatalogSelections(selectionIDs, flags = {}) {
|
|
626
|
-
const ids = selectionIDs.map((id) => String(id).trim()).filter(Boolean);
|
|
627
|
-
if (!ids.length) throw new Error("at least one catalog variant id is required");
|
|
628
|
-
const selections = [];
|
|
629
|
-
for (const id of ids) {
|
|
630
|
-
selections.push(await resolveBuyerCatalogSelection(id, flags));
|
|
631
|
-
}
|
|
632
|
-
return selections;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
async function getBuyerUCPProduct(selectionID, flags = {}) {
|
|
636
|
-
if (!selectionID) throw new Error("catalog selection id is required");
|
|
637
|
-
const body = {
|
|
638
|
-
id: String(selectionID),
|
|
639
|
-
filters: {},
|
|
640
|
-
context: {}
|
|
641
|
-
};
|
|
642
|
-
if (flags.currency) body.context.currency = String(flags.currency);
|
|
643
|
-
return await coreApi("/v1/catalog/selections/resolve", { method: "POST", body }, flags);
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
function selectionFromUCPProduct(detail, selectionID, flags = {}) {
|
|
647
|
-
const product = detail?.product;
|
|
648
|
-
if (!product) throw new Error(`catalog selection not found: ${selectionID}`);
|
|
649
|
-
const variants = Array.isArray(product.variants) ? product.variants : [];
|
|
650
|
-
const selectedVariantID = product.selected?.variant_id || selectionID;
|
|
651
|
-
const variant = variants.find((candidate) => candidate.id === selectionID) ||
|
|
652
|
-
variants.find((candidate) => candidate.id === selectedVariantID) ||
|
|
653
|
-
variants[0];
|
|
654
|
-
if (!variant) throw new Error(`catalog product has no variants: ${selectionID}`);
|
|
655
|
-
const metadata = {
|
|
656
|
-
...(product.metadata || {}),
|
|
657
|
-
...(variant.metadata || {})
|
|
658
|
-
};
|
|
659
|
-
const requiredFields = Array.isArray(metadata["ai.itpay.required_profile_fields"])
|
|
660
|
-
? metadata["ai.itpay.required_profile_fields"].map((field) => String(field)).filter(Boolean)
|
|
661
|
-
: [];
|
|
662
|
-
return {
|
|
663
|
-
catalog_item_id: product.id,
|
|
664
|
-
catalog_variant_id: variant.id,
|
|
665
|
-
ucp_variant_id: variant.id,
|
|
666
|
-
offer_id: flags.offer || flags.offer_id || metadata["ai.itpay.offer_id"] || "",
|
|
667
|
-
catalog_version: metadata["ai.itpay.catalog_version"] || product.selected?.catalog_version || "",
|
|
668
|
-
expected_amount: Number(flags.expected_amount || variant.price?.amount || 0),
|
|
669
|
-
currency: flags.currency || variant.price?.currency || metadata.currency || "",
|
|
670
|
-
title: product.title,
|
|
671
|
-
description: product.description || variant.description || "",
|
|
672
|
-
variant_title: variant.title,
|
|
673
|
-
required_contact_fields: requiredFields,
|
|
674
|
-
product,
|
|
675
|
-
variant,
|
|
676
|
-
metadata,
|
|
677
|
-
purchasable: variant.availability?.available !== false
|
|
678
|
-
};
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
async function createBuyerCart(selection, flags = {}) {
|
|
682
|
-
return await createBuyerCartFromSelections([selection], flags);
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
async function createBuyerCartFromSelections(selections, flags = {}) {
|
|
686
|
-
if (!Array.isArray(selections) || !selections.length) throw new Error("at least one catalog selection is required");
|
|
687
|
-
const quantities = buyerCartQuantities(selections.length, flags);
|
|
688
|
-
const lineItems = selections.map((selection, index) => {
|
|
689
|
-
if (!selection?.purchasable) throw new Error(`catalog variant is not purchasable: ${selection?.catalog_variant_id || selection?.ucp_variant_id || index}`);
|
|
690
|
-
return {
|
|
691
|
-
item: { id: selection.catalog_variant_id || selection.ucp_variant_id },
|
|
692
|
-
quantity: quantities[index],
|
|
693
|
-
input: buyerLineInputForSelection(selection, flags, index)
|
|
694
|
-
};
|
|
695
|
-
});
|
|
696
|
-
const currencies = new Set(selections.map((selection) => String(flags.currency || selection.currency || "")).filter(Boolean));
|
|
697
|
-
if (currencies.size > 1) {
|
|
698
|
-
throw new Error(`selected variants have different currencies: ${Array.from(currencies).join(", ")}`);
|
|
699
|
-
}
|
|
700
|
-
const currency = flags.currency || selections[0]?.currency || "";
|
|
701
|
-
const body = {
|
|
702
|
-
line_items: lineItems,
|
|
703
|
-
context: {},
|
|
704
|
-
client_reference_id: flags.cart_client_reference_id || flags.client_reference_id || `cli_cart_${Date.now()}`
|
|
705
|
-
};
|
|
706
|
-
if (currency) body.context.currency = String(currency);
|
|
707
|
-
const cart = await coreApi("/v1/carts", {
|
|
708
|
-
method: "POST",
|
|
709
|
-
idempotencyKey: flags.cart_idempotency_key || `idem_cli_cart_${cryptoRandom()}`,
|
|
710
|
-
body
|
|
711
|
-
}, flags);
|
|
712
|
-
rememberBuyerCartDisplayContext(cart, selections, lineItems);
|
|
713
|
-
writeState({ ...readState(), last_core_cart_id: cart.cart_id || cart.id });
|
|
714
|
-
return mergeBuyerCartDisplayContext(cart);
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
function rememberBuyerCartDisplayContext(cart = {}, selections = [], lineItems = []) {
|
|
718
|
-
const cartID = cart.cart_id || cart.id;
|
|
719
|
-
if (!cartID) return;
|
|
720
|
-
const state = readState();
|
|
721
|
-
const contexts = state.cart_display_contexts && typeof state.cart_display_contexts === "object" ? state.cart_display_contexts : {};
|
|
722
|
-
const entries = Object.entries({
|
|
723
|
-
...contexts,
|
|
724
|
-
[cartID]: {
|
|
725
|
-
line_items: lineItems.map((line, index) => compactObject({
|
|
726
|
-
quantity: line.quantity,
|
|
727
|
-
input: line.input,
|
|
728
|
-
title: selections[index]?.title || selections[index]?.variant_title
|
|
729
|
-
}))
|
|
730
|
-
}
|
|
731
|
-
}).slice(-20);
|
|
732
|
-
writeState({ ...state, cart_display_contexts: Object.fromEntries(entries) });
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
function mergeBuyerCartDisplayContext(cart = {}) {
|
|
736
|
-
if (!cart || typeof cart !== "object") return cart;
|
|
737
|
-
const context = readState().cart_display_contexts?.[cart.cart_id || cart.id];
|
|
738
|
-
if (!context?.line_items?.length || !Array.isArray(cart.line_items)) return cart;
|
|
739
|
-
return {
|
|
740
|
-
...cart,
|
|
741
|
-
line_items: cart.line_items.map((line, index) => ({
|
|
742
|
-
...line,
|
|
743
|
-
input: line.input || context.line_items[index]?.input,
|
|
744
|
-
item: {
|
|
745
|
-
...(line.item || {}),
|
|
746
|
-
title: line.item?.title || context.line_items[index]?.title
|
|
747
|
-
}
|
|
748
|
-
}))
|
|
749
|
-
};
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
function buyerCartSelectionIDs(rest, flags = {}) {
|
|
753
|
-
const raw = flags.variants || flags.variant_ids || flags.variant || flags.catalog_variant_id || flags.item || flags.catalog_item_id || positional(rest, 1);
|
|
754
|
-
if (!raw) return [];
|
|
755
|
-
if (Array.isArray(raw)) return raw.flatMap((value) => splitCSV(value));
|
|
756
|
-
return splitCSV(raw);
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
function buyerCartQuantities(count, flags = {}) {
|
|
760
|
-
const raw = flags.quantities || flags.quantity || flags.qty;
|
|
761
|
-
const values = raw === undefined || raw === null || raw === true
|
|
762
|
-
? []
|
|
763
|
-
: splitCSV(raw).map((value) => Number(value));
|
|
764
|
-
if (values.length && values.length !== count) {
|
|
765
|
-
throw new Error(`--quantities must provide ${count} value(s), got ${values.length}`);
|
|
766
|
-
}
|
|
767
|
-
const quantities = values.length ? values : Array(count).fill(1);
|
|
768
|
-
for (const quantity of quantities) {
|
|
769
|
-
if (!Number.isInteger(quantity) || quantity <= 0) {
|
|
770
|
-
throw new Error(`cart quantity must be a positive integer, got ${quantity}`);
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
return quantities;
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
function buyerLineInputForSelection(selection, flags = {}, index = 0) {
|
|
777
|
-
const input = parseBuyerInputs(flags, index);
|
|
778
|
-
const providerProductID = String(selection?.metadata?.["ai.itpay.provider_product_id"] || selection?.variant?.metadata?.["ai.itpay.provider_product_id"] || "");
|
|
779
|
-
if (providerProductID === "81api_company_fuzzy_search") {
|
|
780
|
-
if (!input.company_name && flags.company_name) input.company_name = String(flags.company_name).trim();
|
|
781
|
-
if (!input.company_name && flags.keyword) input.company_name = String(flags.keyword).trim();
|
|
782
|
-
if (!input.PageNum && flags.page_num) input.PageNum = String(flags.page_num).trim();
|
|
783
|
-
if (!input.PageNum) input.PageNum = "1";
|
|
784
|
-
if (!input.company_name) {
|
|
785
|
-
throw new Error("企业工商数据模糊查询 requires --input company_name=<关键词> or --company-name <关键词>. Ask the user for a company keyword/short name before checkout.");
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
if (providerProductID === "81api_company_base_info") {
|
|
789
|
-
if (!input.company_name_or_credit_no && flags.company_name_or_credit_no) input.company_name_or_credit_no = String(flags.company_name_or_credit_no).trim();
|
|
790
|
-
if (!input.company_name_or_credit_no && flags.company_name) input.company_name_or_credit_no = String(flags.company_name).trim();
|
|
791
|
-
if (!input.isRaiseErrorCode && flags.is_raise_error_code !== undefined) input.isRaiseErrorCode = String(flags.is_raise_error_code).trim();
|
|
792
|
-
if (!input.isRaiseErrorCode) input.isRaiseErrorCode = "0";
|
|
793
|
-
if (!input.company_name_or_credit_no) {
|
|
794
|
-
throw new Error("企业工商数据精准查询 requires --input company_name_or_credit_no=<完整企业名称或统一社会信用代码>. If the user only gave a brand/short name, run fuzzy search first or resolve the exact registered company name before checkout.");
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
return input;
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
function parseBuyerInputs(flags = {}, index = 0) {
|
|
801
|
-
const input = {};
|
|
802
|
-
const rawValues = [];
|
|
803
|
-
for (const key of ["input", "inputs"]) {
|
|
804
|
-
const raw = flags[key];
|
|
805
|
-
if (Array.isArray(raw)) rawValues.push(...raw);
|
|
806
|
-
else if (raw !== undefined && raw !== true) rawValues.push(raw);
|
|
807
|
-
}
|
|
808
|
-
for (const raw of rawValues) {
|
|
809
|
-
for (const part of splitInputParts(raw)) {
|
|
810
|
-
const eq = part.indexOf("=");
|
|
811
|
-
if (eq <= 0) throw new Error(`invalid --input ${part}; expected key=value`);
|
|
812
|
-
const key = part.slice(0, eq).trim();
|
|
813
|
-
const value = part.slice(eq + 1).trim();
|
|
814
|
-
if (key) input[key] = value;
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
const indexed = flags[`input_${index + 1}`] || flags[`inputs_${index + 1}`];
|
|
818
|
-
if (indexed && indexed !== true) {
|
|
819
|
-
for (const part of splitInputParts(indexed)) {
|
|
820
|
-
const eq = part.indexOf("=");
|
|
821
|
-
if (eq <= 0) throw new Error(`invalid indexed input ${part}; expected key=value`);
|
|
822
|
-
input[part.slice(0, eq).trim()] = part.slice(eq + 1).trim();
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
return input;
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
function splitInputParts(raw) {
|
|
829
|
-
const text = String(raw || "").trim();
|
|
830
|
-
if (!text) return [];
|
|
831
|
-
if (text.startsWith("{")) {
|
|
832
|
-
const parsed = JSON.parse(text);
|
|
833
|
-
return Object.entries(parsed).map(([key, value]) => `${key}=${value}`);
|
|
834
|
-
}
|
|
835
|
-
return text.split(",").map((part) => part.trim()).filter(Boolean);
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
async function getBuyerCart(cartID, flags = {}) {
|
|
839
|
-
if (!cartID) throw new Error("cart_id is required");
|
|
840
|
-
return mergeBuyerCartDisplayContext(await coreApi(`/v1/carts/${encodeURIComponent(cartID)}`, { method: "GET" }, flags));
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
async function addBuyerCartLineItem(cartID, selection, flags = {}) {
|
|
844
|
-
if (!cartID) throw new Error("cart_id is required");
|
|
845
|
-
if (!selection?.purchasable) throw new Error(`catalog variant is not purchasable: ${selection?.catalog_variant_id || selection?.ucp_variant_id || ""}`);
|
|
846
|
-
const quantity = buyerCartQuantities(1, flags)[0];
|
|
847
|
-
const body = {
|
|
848
|
-
item: { id: selection.catalog_variant_id || selection.ucp_variant_id },
|
|
849
|
-
quantity,
|
|
850
|
-
input: buyerLineInputForSelection(selection, flags, 0)
|
|
851
|
-
};
|
|
852
|
-
const cart = await coreApi(`/v1/carts/${encodeURIComponent(cartID)}/line-items`, {
|
|
853
|
-
method: "POST",
|
|
854
|
-
idempotencyKey: flags.cart_idempotency_key || `idem_cli_cart_add_${cryptoRandom()}`,
|
|
855
|
-
body
|
|
856
|
-
}, flags);
|
|
857
|
-
writeState({ ...readState(), last_core_cart_id: cart.cart_id || cart.id });
|
|
858
|
-
return cart;
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
async function removeBuyerCartLineItem(cartID, lineID, flags = {}) {
|
|
862
|
-
if (!cartID) throw new Error("cart_id is required");
|
|
863
|
-
if (!lineID) throw new Error("cart_line_item_id is required");
|
|
864
|
-
const cart = await coreApi(`/v1/carts/${encodeURIComponent(cartID)}/line-items/${encodeURIComponent(lineID)}`, {
|
|
865
|
-
method: "DELETE"
|
|
866
|
-
}, flags);
|
|
867
|
-
writeState({ ...readState(), last_core_cart_id: cart.cart_id || cart.id });
|
|
868
|
-
return cart;
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
async function createBuyerCheckoutFromCart(cart, selection = null, flags = {}) {
|
|
872
|
-
const cartID = typeof cart === "string" ? cart : (cart?.cart_id || cart?.id);
|
|
873
|
-
if (!cartID) throw new Error("cart_id is required");
|
|
874
|
-
const deliveryContact = {};
|
|
875
|
-
if (flags.email) deliveryContact.email = realBuyerEmail(flags.email);
|
|
876
|
-
if (flags.phone) deliveryContact.phone = flags.phone;
|
|
877
|
-
const missing = requiredDeliveryContactFields(selection).filter((field) => !deliveryContact[field]);
|
|
878
|
-
if (missing.length) {
|
|
879
|
-
throw new Error(`missing required delivery contact: ${missing.join(", ")}; provide ${missing.map((field) => `--${field} <value>`).join(" ")}`);
|
|
880
|
-
}
|
|
881
|
-
const request = {
|
|
882
|
-
method: "POST",
|
|
883
|
-
idempotencyKey: flags.checkout_idempotency_key || flags.idempotency_key || `idem_cli_checkout_${cartID}`,
|
|
884
|
-
body: {
|
|
885
|
-
cart_id: cartID,
|
|
886
|
-
client_reference_id: flags.checkout_client_reference_id || flags.client_reference_id || `cli_checkout_${cartID}`,
|
|
887
|
-
delivery_contact: deliveryContact
|
|
888
|
-
}
|
|
889
|
-
};
|
|
890
|
-
let checkout;
|
|
891
|
-
try {
|
|
892
|
-
checkout = await coreApi("/v1/checkouts", request, flags);
|
|
893
|
-
} catch (error) {
|
|
894
|
-
if (error?.status === 401 && readSessionToken(readCredentials()) && !flags.access_token) {
|
|
895
|
-
writeCredentials(deleteSessionCredential(readCredentials()));
|
|
896
|
-
checkout = await coreApi("/v1/checkouts", request, flags);
|
|
897
|
-
} else {
|
|
898
|
-
throw error;
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
writeState({ ...readState(), last_core_cart_id: cartID, last_core_checkout_id: checkout.checkout_id });
|
|
902
|
-
rememberCoreAuthAction(checkout.checkout_id, checkout.human_action);
|
|
903
|
-
return checkout;
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
function requiredDeliveryContactFields(selection) {
|
|
907
|
-
if (Array.isArray(selection?.required_contact_fields)) {
|
|
908
|
-
return selection.required_contact_fields.map((field) => String(field).trim()).filter(Boolean);
|
|
909
|
-
}
|
|
910
|
-
const fields = selection?.delivery?.requires_contact_fields;
|
|
911
|
-
return Array.isArray(fields) ? fields.map((field) => String(field).trim()).filter(Boolean) : [];
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
function rejectBuyerSandboxFlag(flags = {}) {
|
|
915
|
-
if (!flags?.sandbox) return;
|
|
916
|
-
throw new Error("--sandbox is not used by buyer commands; environment is selected by the ItPay API base. Run the command without --sandbox.");
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
function realBuyerEmail(value) {
|
|
920
|
-
const email = String(value || "").trim();
|
|
921
|
-
if (!email) return "";
|
|
922
|
-
const domain = email.split("@").pop().toLowerCase();
|
|
923
|
-
if (["example.com", "example.net", "example.org"].includes(domain)) {
|
|
924
|
-
throw new Error("placeholder email rejected; ask the human for their real delivery email before checkout.");
|
|
925
|
-
}
|
|
926
|
-
return email;
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
async function createBuyerPaymentIntent(checkoutID, flags = {}) {
|
|
930
|
-
if (!checkoutID) throw new Error("checkout_id is required");
|
|
931
|
-
const method = String(flags.method || flags.payment_method || "alipay").toLowerCase();
|
|
932
|
-
const provider = String(flags.provider || flags.preferred_provider || method).toLowerCase();
|
|
933
|
-
const intent = await coreApi(`/v1/checkouts/${encodeURIComponent(checkoutID)}/payment-intents`, {
|
|
934
|
-
method: "POST",
|
|
935
|
-
idempotencyKey: flags.payment_idempotency_key || flags.idempotency_key || `idem_cli_payment_${cryptoRandom()}`,
|
|
936
|
-
body: {
|
|
937
|
-
payment_method_type: method,
|
|
938
|
-
preferred_provider: provider
|
|
939
|
-
}
|
|
940
|
-
}, flags);
|
|
941
|
-
writeState({ ...readState(), last_core_checkout_id: checkoutID, last_core_payment_intent_id: intent.payment_intent_id });
|
|
942
|
-
return intent;
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
async function waitBuyerCheckoutAuth(checkout, flags = {}) {
|
|
946
|
-
const checkoutID = checkout?.checkout_id || checkout;
|
|
947
|
-
if (!checkoutID) throw new Error("checkout_id is required");
|
|
948
|
-
const timeoutMs = Number(flags.auth_timeout || flags.timeout || 900) * 1000;
|
|
949
|
-
const started = Date.now();
|
|
950
|
-
let lastHeartbeatAt = Date.now();
|
|
951
|
-
let current = typeof checkout === "string" ? await getBuyerCheckout(checkoutID, flags) : checkout;
|
|
952
|
-
const authAction = current?.human_action || readCoreAuthAction(checkoutID) || null;
|
|
953
|
-
rememberCoreAuthAction(checkoutID, authAction);
|
|
954
|
-
while (Date.now() - started < timeoutMs) {
|
|
955
|
-
if (current.payment_intent_id || current.identity_status === "identity_resolved" || current.next_required_action !== "auth_qr") {
|
|
956
|
-
await maybeClaimBuyerSessionFromAuthAction(authAction, flags);
|
|
957
|
-
return current;
|
|
958
|
-
}
|
|
959
|
-
lastHeartbeatAt = writeWaitHeartbeat({
|
|
960
|
-
kind: "ItPay buyer auth",
|
|
961
|
-
idName: "checkout_id",
|
|
962
|
-
idValue: checkoutID,
|
|
963
|
-
status: current.identity_status || current.next_required_action || "waiting_human_auth",
|
|
964
|
-
action: current.human_action || null,
|
|
965
|
-
lastHeartbeatAt,
|
|
966
|
-
flags,
|
|
967
|
-
command: cliCommand("buyer", "checkout", "resume", checkoutID, "--json")
|
|
968
|
-
});
|
|
969
|
-
await sleep(Number(flags.auth_poll_ms || flags.poll_ms || 2000));
|
|
970
|
-
current = await getBuyerCheckout(checkoutID, flags);
|
|
971
|
-
}
|
|
972
|
-
await maybeClaimBuyerSessionFromAuthAction(authAction, flags);
|
|
973
|
-
return current;
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
async function maybeClaimBuyerSessionFromAuthAction(action, flags = {}) {
|
|
977
|
-
const parsed = parseBuyerAuthActionURL(action);
|
|
978
|
-
if (!parsed.authSessionID || !parsed.displayToken) return null;
|
|
979
|
-
try {
|
|
980
|
-
const response = await coreApi(`/v1/session-exchanges/auth-sessions/${encodeURIComponent(parsed.authSessionID)}/agent-session?display_token=${encodeURIComponent(parsed.displayToken)}`, {
|
|
981
|
-
method: "POST"
|
|
982
|
-
}, flags);
|
|
983
|
-
const rawToken = response.raw_session_token || response.session_token || response.session?.raw_session_token;
|
|
984
|
-
if (!rawToken) return response;
|
|
985
|
-
writeSessionCredentials({
|
|
986
|
-
account_id: response.buyer_account_id,
|
|
987
|
-
device_id: response.agent_device_id,
|
|
988
|
-
session_token: rawToken
|
|
989
|
-
});
|
|
990
|
-
writeConfig({
|
|
991
|
-
api_base: coreApiBase(flags),
|
|
992
|
-
account_id: response.buyer_account_id,
|
|
993
|
-
device_id: response.agent_device_id
|
|
994
|
-
});
|
|
995
|
-
forgetCoreAuthAction(response.checkout_id);
|
|
996
|
-
return response;
|
|
997
|
-
} catch (error) {
|
|
998
|
-
if (!flags.quiet) {
|
|
999
|
-
process.stderr.write(`ItPay buyer session claim skipped: ${safeErrorMessage(error)}\n`);
|
|
1000
|
-
}
|
|
1001
|
-
return null;
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
function buyerSessionClaimStatus(claimed = null) {
|
|
1006
|
-
const config = readConfig();
|
|
1007
|
-
const hasSession = Boolean(readSessionToken());
|
|
1008
|
-
if (!claimed && !hasSession) return undefined;
|
|
1009
|
-
if (!hasSession) {
|
|
1010
|
-
return {
|
|
1011
|
-
status: "buyer_session_missing",
|
|
1012
|
-
session_stored: false,
|
|
1013
|
-
buyer_account_id: claimed?.buyer_account_id || config.account_id || null,
|
|
1014
|
-
agent_device_id: claimed?.agent_device_id || config.device_id || null,
|
|
1015
|
-
token_included: false,
|
|
1016
|
-
agent_next_actions: ["resume_checkout_to_save_buyer_session"]
|
|
1017
|
-
};
|
|
1018
|
-
}
|
|
1019
|
-
return {
|
|
1020
|
-
status: "buyer_session_saved",
|
|
1021
|
-
session_stored: true,
|
|
1022
|
-
buyer_account_id: claimed?.buyer_account_id || config.account_id || null,
|
|
1023
|
-
agent_device_id: claimed?.agent_device_id || config.device_id || null,
|
|
1024
|
-
token_included: false,
|
|
1025
|
-
agent_next_actions: ["reuse_buyer_session", "list_agent_read_grants"]
|
|
1026
|
-
};
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
function buyerSessionReadyForPayment() {
|
|
1030
|
-
return Boolean(readSessionToken());
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
function checkoutAgentNextActions(checkout = {}) {
|
|
1034
|
-
if (checkout.next_required_action === "auth_qr" || checkout.identity_status === "waiting_human_auth") {
|
|
1035
|
-
return ["show_auth_qr", "resume_same_checkout"];
|
|
1036
|
-
}
|
|
1037
|
-
if (checkout.payment_intent_id || checkout.agent_next_actions?.includes("create_payment_intent") || checkout.next_required_action === "create_payment_intent") {
|
|
1038
|
-
return buyerSessionReadyForPayment()
|
|
1039
|
-
? ["continue_to_payment"]
|
|
1040
|
-
: ["resume_checkout_to_save_buyer_session"];
|
|
1041
|
-
}
|
|
1042
|
-
return ["resume_checkout"];
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
function checkoutAuthorizationGuidance(checkout = {}) {
|
|
1046
|
-
const hasSession = buyerSessionReadyForPayment();
|
|
1047
|
-
return {
|
|
1048
|
-
buyer_session_ready: hasSession,
|
|
1049
|
-
payment_intent_id: checkout.payment_intent_id || null,
|
|
1050
|
-
auth_required: checkout.next_required_action === "auth_qr" || checkout.identity_status === "waiting_human_auth",
|
|
1051
|
-
next_step: hasSession && checkout.payment_intent_id
|
|
1052
|
-
? "已有有效 buyer session 且 payment_intent_id 已就绪,可以进入付款模块。"
|
|
1053
|
-
: hasSession
|
|
1054
|
-
? "已有有效 buyer session;继续同一个 checkout 直到 payment_intent_id 就绪。"
|
|
1055
|
-
: checkout.next_required_action === "auth_qr" || checkout.identity_status === "waiting_human_auth"
|
|
1056
|
-
? "展示首次购买授权入口,然后继续 resume 同一个 checkout;这是授权,不是付款。"
|
|
1057
|
-
: "没有有效 buyer session 时不能进入付款;先 resume/status 同一个 checkout,领取并保存 buyer session。",
|
|
1058
|
-
do_not: [
|
|
1059
|
-
"不要把 auth_qr 说成付款二维码或付款证明。",
|
|
1060
|
-
"不要在 buyer session 未保存时展示付款 QR。",
|
|
1061
|
-
"不要为了修复授权状态新建 checkout。"
|
|
1062
|
-
]
|
|
1063
|
-
};
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
function buyerSessionRequiredBeforePayment(checkout = {}, flags = {}, extra = {}) {
|
|
1067
|
-
return {
|
|
1068
|
-
status: "buyer_session_required_before_payment",
|
|
1069
|
-
...extra,
|
|
1070
|
-
checkout,
|
|
1071
|
-
buyer_session: extra.buyer_session || buyerSessionClaimStatus(),
|
|
1072
|
-
checkout_guidance: checkoutAuthorizationGuidance(checkout),
|
|
1073
|
-
agent_next_actions: ["resume_checkout_to_save_buyer_session"],
|
|
1074
|
-
next: {
|
|
1075
|
-
command: cliCommand("buyer", "checkout", "resume", checkout.checkout_id || flags.checkout || flags.checkout_id || readState().last_core_checkout_id, "--json"),
|
|
1076
|
-
safe_for_agent: true,
|
|
1077
|
-
instruction: "payment_intent_id is not enough. Save or verify the buyer session before showing a payment QR."
|
|
1078
|
-
}
|
|
1079
|
-
};
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
function paymentAgentNextActions(intent = {}, event = {}) {
|
|
1083
|
-
if (event.event_type === "payment_intent.verified" || intent.status === "verified") {
|
|
1084
|
-
return ["poll_checkout_delivery"];
|
|
1085
|
-
}
|
|
1086
|
-
if (event.event_type === "wait.timeout") {
|
|
1087
|
-
return ["check_same_payment_intent", "resume_checkout_status_if_needed"];
|
|
1088
|
-
}
|
|
1089
|
-
if (event.event_type === "qr_refreshed") {
|
|
1090
|
-
return ["show_returned_qr", "check_same_payment_intent"];
|
|
1091
|
-
}
|
|
1092
|
-
return ["show_payment_qr_if_needed", "check_same_payment_intent"];
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
function paymentHandoffFields(intent = {}, flags = {}) {
|
|
1096
|
-
return {
|
|
1097
|
-
render_plan: buildHumanActionRenderPlan(intent.human_action || {}, intent, flags),
|
|
1098
|
-
after_human_response: paymentHandoffAfterHumanResponse(intent, flags)
|
|
1099
|
-
};
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
function paymentStatusCheckCommand(paymentIntentID, flags = {}) {
|
|
1103
|
-
if (!paymentIntentID) return "";
|
|
1104
|
-
return cliCommand("buyer", "payment", "wait", paymentIntentID, "--timeout", "1", ...clientCommandArgs(flags), "--json");
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
function shouldReturnPaymentHandoffBeforeWait(flags = {}) {
|
|
1108
|
-
if (booleanFlag(flags.wait || flags.long_wait || false)) return false;
|
|
1109
|
-
if (flags.timeout === undefined) return true;
|
|
1110
|
-
const timeout = Number(flags.timeout);
|
|
1111
|
-
return !Number.isFinite(timeout) || timeout > 5;
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
async function ensureTelegramPaymentDisplay(intent = {}, flags = {}) {
|
|
1115
|
-
if (!isTelegramBuyerHost(flags) || paymentDisplayAvailable(intent) || !intent.qr_refresh_url) return intent;
|
|
1116
|
-
try {
|
|
1117
|
-
return await refreshBuyerPaymentQR(intent.payment_intent_id, flags);
|
|
1118
|
-
} catch {
|
|
1119
|
-
return intent;
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
function paymentDisplayAvailable(intent = {}) {
|
|
1124
|
-
const action = intent.human_action || {};
|
|
1125
|
-
return Boolean(
|
|
1126
|
-
action.url ||
|
|
1127
|
-
action.local_qr_path ||
|
|
1128
|
-
action.qr_png_url ||
|
|
1129
|
-
action.qr_image_url ||
|
|
1130
|
-
action.preferred_qr_url ||
|
|
1131
|
-
action.mobile_wallet_url ||
|
|
1132
|
-
intent.payment_url ||
|
|
1133
|
-
intent.payment_entry_url ||
|
|
1134
|
-
intent.qr_png_url ||
|
|
1135
|
-
intent.qr_image_url ||
|
|
1136
|
-
intent.mobile_wallet_url
|
|
1137
|
-
);
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
async function optionalBuyerPaymentContext(intent = {}, flags = {}) {
|
|
1141
|
-
if (!isTelegramBuyerHost(flags) || !intent.checkout_id) return {};
|
|
1142
|
-
try {
|
|
1143
|
-
const checkout = await getBuyerCheckout(intent.checkout_id, flags);
|
|
1144
|
-
return compactObject({
|
|
1145
|
-
checkout,
|
|
1146
|
-
cart: checkout.cart_id ? await getBuyerCart(checkout.cart_id, flags) : undefined
|
|
1147
|
-
});
|
|
1148
|
-
} catch {
|
|
1149
|
-
return {};
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
function isTelegramBuyerHost(flags = {}) {
|
|
1154
|
-
return clientHost(flags) === "telegram";
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
function paymentHandoffAgentNextActions() {
|
|
1158
|
-
return ["send_agent_instruction_to_human", "run_after_visible_action_once_if_visible"];
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
function paymentHandoffNext() {
|
|
1162
|
-
return {
|
|
1163
|
-
type: "send_to_human_then_wait_once_if_visible",
|
|
1164
|
-
safe_for_agent: true,
|
|
1165
|
-
instruction: "Send agent_instruction to the human first. If it is visible, run after_visible_action once; otherwise stop and wait for the human."
|
|
1166
|
-
};
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
function paymentHandoffAfterHumanResponse(intent = {}, flags = {}) {
|
|
1170
|
-
const paymentIntentID = intent.payment_intent_id || intent.human_action?.id || "";
|
|
1171
|
-
return {
|
|
1172
|
-
check_payment_command: paymentStatusCheckCommand(paymentIntentID, flags),
|
|
1173
|
-
refresh_qr_command: paymentIntentID ? cliCommand("buyer", "payment", "refresh-qr", paymentIntentID, "--reason", "order-not-found", ...clientCommandArgs(flags), "--json") : "",
|
|
1174
|
-
safe_for_agent: true,
|
|
1175
|
-
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."
|
|
1176
|
-
};
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
function paymentRecoveryGuidance(intent = {}, event = {}) {
|
|
1180
|
-
const paymentIntentID = intent.payment_intent_id || event.payment_intent_id || null;
|
|
1181
|
-
return {
|
|
1182
|
-
invariant: "所有付款异常都优先回到同一个 payment_intent_id / checkout_id;不要重复创建 checkout。",
|
|
1183
|
-
payment_intent_id: paymentIntentID,
|
|
1184
|
-
checkout_id: intent.checkout_id || null,
|
|
1185
|
-
status: intent.status || null,
|
|
1186
|
-
event_type: event.event_type || null,
|
|
1187
|
-
qr_display_order: ["local_qr_path", "qr_png_url", "preferred_qr_url", "qr_image_url_fallback", "mobile_wallet_url_as_link"],
|
|
1188
|
-
next_step: event.event_type === "payment_intent.verified" || intent.status === "verified"
|
|
1189
|
-
? "付款已验证;进入 checkout status / 交付模块。"
|
|
1190
|
-
: event.event_type === "wait.timeout"
|
|
1191
|
-
? "wait.timeout 不是付款失败;继续查询同一个 payment_intent_id,或用 checkout status/resume 确认状态。"
|
|
1192
|
-
: event.event_type === "qr_refreshed"
|
|
1193
|
-
? "展示 ItPay 返回的 QR,然后继续查询同一个 payment_intent_id。"
|
|
1194
|
-
: "展示 ItPay 返回的付款入口;用户反馈后查询 payment_intent.verified,用户口头说已付款不算证明。",
|
|
1195
|
-
network_or_interrupt: "断网、无响应、进程中断时,重新运行 payment wait 或 checkout status/resume,使用同一个 payment_intent_id / checkout_id。",
|
|
1196
|
-
qr_problem: "QR 未显示时不要自造二维码;先用返回的 local_qr_path/qr_png_url/preferred_qr_url。扫码提示订单不存在或过期时,先等 30-60 秒重扫同一 QR,仍失败才 refresh-qr。",
|
|
1197
|
-
do_not: [
|
|
1198
|
-
"不要把用户说“已付款”当证明。",
|
|
1199
|
-
"不要因为 timeout、断网或没回应创建新 checkout。",
|
|
1200
|
-
"不要改写、缩短、重新编码或自造付款 QR。",
|
|
1201
|
-
"不要从买家流程调用 provider query 或 ops recovery。"
|
|
1202
|
-
]
|
|
1203
|
-
};
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
function deliveryAccessGuidance(checkout = {}) {
|
|
1207
|
-
return {
|
|
1208
|
-
boundary: "交付是人类优先;agent 只能看到脱敏状态,不能读取 claim link、claim token 或原始内容。",
|
|
1209
|
-
next_step: "如果 delivery_claimable/check_email/claim_link_sent,请让用户打开邮箱或 ItPay 领取/订单页面。",
|
|
1210
|
-
if_user_wants_agent_to_use_result: [
|
|
1211
|
-
"请用户在 ItPay 页面找到 Give to Agent / 一键给 Agent。",
|
|
1212
|
-
"让用户选择愿意授权给 agent 读取的字段。",
|
|
1213
|
-
"让用户用 Passkey/WebAuthn 确认。",
|
|
1214
|
-
"完成后 agent 运行 buyer vault grants list --checkout <checkout_id> --json 自动发现授权;不要让用户复制链接、token 或 grant_id。"
|
|
1215
|
-
],
|
|
1216
|
-
checkout_id: checkout.checkout_id || null,
|
|
1217
|
-
order_id: checkout.order_id || null,
|
|
1218
|
-
do_not: [
|
|
1219
|
-
"不要让用户把 claim link、claim token、portal text、session token、grant id 或原始交付内容贴进聊天。",
|
|
1220
|
-
"不要用浏览器/自动化工具代替人类打开领取页。",
|
|
1221
|
-
"不要猜交付内容;读不到就解释原因并继续同一个 checkout/grant 流程。"
|
|
1222
|
-
]
|
|
1223
|
-
};
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
function vaultAccessGuidance(grants = {}, view = null) {
|
|
1227
|
-
const list = Array.isArray(grants.agent_readable_grants) ? grants.agent_readable_grants : [];
|
|
1228
|
-
const expiresAt = view?.expires_at || view?.grant?.expires_at || view?.valid_until || view?.grant?.valid_until || null;
|
|
1229
|
-
const selectedFields = view?.selected_fields || view?.grant?.selected_fields || view?.structured_view || view?.grant?.structured_view || null;
|
|
1230
|
-
if (!view && !list.length) {
|
|
1231
|
-
return {
|
|
1232
|
-
status: "no_agent_read_grant_found",
|
|
1233
|
-
likely_reasons: [
|
|
1234
|
-
"用户还没有点击 Give to Agent / 一键给 Agent。",
|
|
1235
|
-
"用户选错了订单或领取页。",
|
|
1236
|
-
"授权还没同步,等几秒后用同一个 checkout 再查。",
|
|
1237
|
-
"checkout/order/artifact 不匹配。",
|
|
1238
|
-
"buyer session 过期,需要先运行 buyer checkout status <checkout_id> --json。",
|
|
1239
|
-
"授权已过期或被撤销。"
|
|
1240
|
-
],
|
|
1241
|
-
next_step: "指导用户在 ItPay 领取/订单页面选择字段并用 Passkey 确认,然后重新运行 grants list;不要让用户复制 grant_id 或 token。"
|
|
1242
|
-
};
|
|
1243
|
-
}
|
|
1244
|
-
return {
|
|
1245
|
-
status: "agent_read_grant_available",
|
|
1246
|
-
readable_scope: "只能使用本次返回的已授权字段;未返回字段不可读取、不可推断。",
|
|
1247
|
-
selected_fields_present: Boolean(selectedFields),
|
|
1248
|
-
expires_at: expiresAt,
|
|
1249
|
-
expires_at_note: expiresAt ? "把授权有效期告诉用户;过期后需要重新授权。" : "返回里没有明确过期时间;不要编造有效期。",
|
|
1250
|
-
if_fields_insufficient: "如果当前字段不足以完成任务,请用户回到 ItPay 页面重新 Give to Agent,增加需要的字段后确认。",
|
|
1251
|
-
do_not: [
|
|
1252
|
-
"不要要求用户复制原文、claim link、session token、grant id 或 Passkey/WebAuthn token。",
|
|
1253
|
-
"不要声称能看到未授权字段。",
|
|
1254
|
-
"不要把授权字段长期保存到本地,除非用户明确要求创建本地产物。"
|
|
1255
|
-
]
|
|
1256
|
-
};
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
function refundGuidance({ order_id: orderID = null, refund = null, refunds = null, phase = "" } = {}) {
|
|
1260
|
-
const refundID = refund?.refund_id || refund?.refund?.refund_id || null;
|
|
1261
|
-
return {
|
|
1262
|
-
boundary: "退款支线必须基于已确认 order_id;不要猜订单、金额或退款范围。",
|
|
1263
|
-
order_id: orderID || refund?.order_id || refund?.refund?.order_id || null,
|
|
1264
|
-
refund_id: refundID,
|
|
1265
|
-
amount_rule: "金额必须使用 --amount-minor;CNY 1000 表示 CNY 10.00。不要使用 --amount。",
|
|
1266
|
-
scope_rule: "当前 buyer refund 走整单退款思路;不要发明 line-item refund scope。",
|
|
1267
|
-
next_step: phase === "policy_risk"
|
|
1268
|
-
? "先解释 refund_eligibility.policy / agent_guidance,并等待用户明确确认;只有确认后才用 --confirm-policy-risk true 重试。"
|
|
1269
|
-
: phase === "created"
|
|
1270
|
-
? "退款请求已提交;用 refund list/show 查看状态,不要重复提交。"
|
|
1271
|
-
: phase === "cancel"
|
|
1272
|
-
? "取消后用 refund show/list 确认状态;交付是否重新可领取以服务端返回为准。"
|
|
1273
|
-
: "向用户展示退款状态;如需取消,先确认是否仍在供应商/资金动作前。",
|
|
1274
|
-
cancel_rule: "只有供应商或资金动作前才适合取消;如果已进入资金/供应商动作,不能保证取消。",
|
|
1275
|
-
session_rule: "退款需要 buyer session,不是 vault grant。session 过期时先运行 status --refresh 或 checkout status 恢复。",
|
|
1276
|
-
do_not: [
|
|
1277
|
-
"不要猜 order_id;没有 order_id 先查 checkout status。",
|
|
1278
|
-
"不要把 CNY minor units 当成人民币元。",
|
|
1279
|
-
"不要自动越过 policy_risk_confirmation_required。",
|
|
1280
|
-
"不要因为没立刻成功就重复提交退款。",
|
|
1281
|
-
"不要用 vault grant 代替 buyer session。"
|
|
1282
|
-
],
|
|
1283
|
-
refunds_count: Array.isArray(refunds?.refunds) ? refunds.refunds.length : undefined
|
|
1284
|
-
};
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
async function maybeClaimBuyerSessionForCheckout(checkout, flags = {}) {
|
|
1288
|
-
if (!checkout?.checkout_id) return null;
|
|
1289
|
-
if (readSessionToken()) return null;
|
|
1290
|
-
if (checkout.identity_status !== "identity_resolved" && !checkout.payment_intent_id) return null;
|
|
1291
|
-
const action = checkout.human_action || readCoreAuthAction(checkout.checkout_id);
|
|
1292
|
-
return await maybeClaimBuyerSessionFromAuthAction(action, flags);
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
|
-
async function ensureBuyerSessionForVaultAccess(flags = {}) {
|
|
1296
|
-
if (readSessionToken()) return null;
|
|
1297
|
-
const checkoutID =
|
|
1298
|
-
flags.checkout ||
|
|
1299
|
-
flags.checkout_id ||
|
|
1300
|
-
readState().last_core_checkout_id ||
|
|
1301
|
-
readState().last_core_auth_checkout_id;
|
|
1302
|
-
if (checkoutID) {
|
|
1303
|
-
try {
|
|
1304
|
-
const checkout = await getBuyerCheckout(checkoutID, flags);
|
|
1305
|
-
rememberCoreAuthAction(checkout.checkout_id || checkoutID, checkout.human_action);
|
|
1306
|
-
const claimed = await maybeClaimBuyerSessionForCheckout(checkout, { ...flags, quiet: true });
|
|
1307
|
-
if (claimed || readSessionToken()) return claimed;
|
|
1308
|
-
} catch {
|
|
1309
|
-
// Continue with the locally remembered auth action below.
|
|
1310
|
-
}
|
|
1311
|
-
const action = readCoreAuthAction(checkoutID);
|
|
1312
|
-
const claimed = await maybeClaimBuyerSessionFromAuthAction(action, { ...flags, quiet: true });
|
|
1313
|
-
if (claimed || readSessionToken()) return claimed;
|
|
1314
|
-
}
|
|
1315
|
-
const state = readState();
|
|
1316
|
-
const entries = Object.entries(state.core_auth_actions || {});
|
|
1317
|
-
for (const [, action] of entries.reverse()) {
|
|
1318
|
-
const claimed = await maybeClaimBuyerSessionFromAuthAction(action, { ...flags, quiet: true });
|
|
1319
|
-
if (claimed || readSessionToken()) return claimed;
|
|
1320
|
-
}
|
|
1321
|
-
return null;
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
function parseBuyerAuthActionURL(action) {
|
|
1325
|
-
if (!action || typeof action !== "object") return {};
|
|
1326
|
-
let authSessionID = String(action.auth_session_id || "").trim();
|
|
1327
|
-
if (!authSessionID && String(action.id || "").startsWith("auth_")) {
|
|
1328
|
-
authSessionID = String(action.id).trim();
|
|
1329
|
-
}
|
|
1330
|
-
let displayToken = "";
|
|
1331
|
-
let sourceURL = "";
|
|
1332
|
-
for (const rawURL of buyerAuthActionCandidateURLs(action)) {
|
|
1333
|
-
try {
|
|
1334
|
-
const parsed = new URL(rawURL);
|
|
1335
|
-
const token = parsed.searchParams.get("display_token") || "";
|
|
1336
|
-
if (token && !displayToken) displayToken = token;
|
|
1337
|
-
const match = parsed.pathname.match(/(?:^|\/)v1\/session-exchanges\/auth-sessions\/([^/?#]+)(?:\/|$)/);
|
|
1338
|
-
if (match && !authSessionID) authSessionID = decodeURIComponent(match[1]);
|
|
1339
|
-
if (!sourceURL && (match || token)) sourceURL = rawURL;
|
|
1340
|
-
if (authSessionID && displayToken) break;
|
|
1341
|
-
} catch {
|
|
1342
|
-
// Ignore non-URL display entries.
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1345
|
-
return { authSessionID, displayToken, sourceURL };
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
function buyerAuthActionCandidateURLs(action) {
|
|
1349
|
-
const urls = [];
|
|
1350
|
-
for (const key of ["url", "web_url", "auth_url", "oauth_start_url", "mobile_wallet_url"]) {
|
|
1351
|
-
if (action?.[key]) urls.push(String(action[key]));
|
|
1352
|
-
}
|
|
1353
|
-
const presentationDisplay = action?.presentation?.display;
|
|
1354
|
-
if (Array.isArray(presentationDisplay)) {
|
|
1355
|
-
for (const entry of presentationDisplay) {
|
|
1356
|
-
if (entry?.url) urls.push(String(entry.url));
|
|
1357
|
-
}
|
|
1358
|
-
}
|
|
1359
|
-
if (Array.isArray(action?.display)) {
|
|
1360
|
-
for (const entry of action.display) {
|
|
1361
|
-
if (entry?.url) urls.push(String(entry.url));
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
return urls;
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
function rememberCoreAuthAction(checkoutID, action) {
|
|
1368
|
-
if (!checkoutID || action?.kind !== "auth_qr") return;
|
|
1369
|
-
const parsed = parseBuyerAuthActionURL(action);
|
|
1370
|
-
if (!parsed.authSessionID || !parsed.displayToken) return;
|
|
1371
|
-
const state = readState();
|
|
1372
|
-
const existing = state.core_auth_actions && typeof state.core_auth_actions === "object" ? state.core_auth_actions : {};
|
|
1373
|
-
const entries = Object.entries(existing).slice(-19);
|
|
1374
|
-
const next = Object.fromEntries(entries);
|
|
1375
|
-
next[checkoutID] = {
|
|
1376
|
-
kind: "auth_qr",
|
|
1377
|
-
id: action.id || action.auth_session_id || parsed.authSessionID,
|
|
1378
|
-
auth_session_id: parsed.authSessionID,
|
|
1379
|
-
url: action.url || parsed.sourceURL,
|
|
1380
|
-
web_url: action.web_url || action.url || parsed.sourceURL,
|
|
1381
|
-
expires_at: action.expires_at || null,
|
|
1382
|
-
saved_at: new Date().toISOString()
|
|
1383
|
-
};
|
|
1384
|
-
writeState({ ...state, core_auth_actions: next, last_core_auth_checkout_id: checkoutID });
|
|
1385
|
-
}
|
|
1386
|
-
|
|
1387
|
-
function readCoreAuthAction(checkoutID) {
|
|
1388
|
-
if (!checkoutID) return null;
|
|
1389
|
-
const state = readState();
|
|
1390
|
-
return state.core_auth_actions?.[checkoutID] || null;
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
function forgetCoreAuthAction(checkoutID) {
|
|
1394
|
-
if (!checkoutID) return;
|
|
1395
|
-
const state = readState();
|
|
1396
|
-
if (!state.core_auth_actions?.[checkoutID]) return;
|
|
1397
|
-
const next = { ...state.core_auth_actions };
|
|
1398
|
-
delete next[checkoutID];
|
|
1399
|
-
writeState({ ...state, core_auth_actions: next });
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
|
-
async function getBuyerCheckout(checkoutID, flags = {}) {
|
|
1403
|
-
return await coreApi(`/v1/checkouts/${encodeURIComponent(checkoutID)}`, { method: "GET" }, flags);
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
async function createBuyerRefund(orderID, flags = {}) {
|
|
1407
|
-
if (flags.amount !== undefined) {
|
|
1408
|
-
throw new Error("use --amount-minor for refund amount");
|
|
1409
|
-
}
|
|
1410
|
-
if (flags.refund_scope && flags.refund_scope !== "order") {
|
|
1411
|
-
throw new Error("unsupported_refund_scope");
|
|
1412
|
-
}
|
|
1413
|
-
const order = await getBuyerOrderDetail(orderID, flags);
|
|
1414
|
-
const eligibility = order.refund_eligibility || order.order_detail?.refund_eligibility || null;
|
|
1415
|
-
if (eligibility && eligibility.likely_refundable === false && !booleanFlag(flags.confirm_policy_risk || false)) {
|
|
1416
|
-
return {
|
|
1417
|
-
status: "policy_risk_confirmation_required",
|
|
1418
|
-
order_id: orderID,
|
|
1419
|
-
refund_eligibility: eligibility,
|
|
1420
|
-
agent_next_actions: [
|
|
1421
|
-
"explain_refund_policy",
|
|
1422
|
-
"ask_human_to_confirm_policy_risk",
|
|
1423
|
-
`retry_with_${cliCommand("buyer", "refund", "create", orderID, "--confirm-policy-risk", "true", "--json")}`
|
|
1424
|
-
],
|
|
1425
|
-
submitted: false
|
|
1426
|
-
};
|
|
1427
|
-
}
|
|
1428
|
-
return await coreApi(`/v1/me/orders/${encodeURIComponent(orderID)}/refunds`, {
|
|
1429
|
-
method: "POST",
|
|
1430
|
-
headers: { "X-ItPay-Client-Surface": "cli" },
|
|
1431
|
-
idempotencyKey: flags.idempotency_key || `idem_cli_refund_create_${orderID}`,
|
|
1432
|
-
body: {
|
|
1433
|
-
refund_scope: flags.refund_scope || "order",
|
|
1434
|
-
order_line_item_ids: csvValues(flags.order_line_item_ids || flags.line_ids || flags.line_id),
|
|
1435
|
-
amount_minor: intFlag(flags.amount_minor, "amount_minor"),
|
|
1436
|
-
currency: flags.currency || "CNY",
|
|
1437
|
-
reason_code: flags.reason || flags.reason_code || "buyer_requested",
|
|
1438
|
-
reason_note: flags.note || flags.reason_note || ""
|
|
1439
|
-
}
|
|
1440
|
-
}, flags);
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
async function getBuyerOrderDetail(orderID, flags = {}) {
|
|
1444
|
-
return await coreApi(`/v1/me/orders/${encodeURIComponent(orderID)}`, { method: "GET" }, flags);
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
async function listBuyerRefunds(orderID, flags = {}) {
|
|
1448
|
-
return await coreApi(`/v1/me/orders/${encodeURIComponent(orderID)}/refunds`, { method: "GET" }, flags);
|
|
1449
|
-
}
|
|
1450
|
-
|
|
1451
|
-
async function getBuyerRefund(refundID, flags = {}) {
|
|
1452
|
-
return await coreApi(`/v1/me/refunds/${encodeURIComponent(refundID)}`, { method: "GET" }, flags);
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
|
-
async function cancelBuyerRefund(refundID, flags = {}) {
|
|
1456
|
-
return await coreApi(`/v1/me/refunds/${encodeURIComponent(refundID)}/cancel`, {
|
|
1457
|
-
method: "POST",
|
|
1458
|
-
headers: { "X-ItPay-Client-Surface": "cli" },
|
|
1459
|
-
idempotencyKey: flags.idempotency_key || `idem_cli_refund_cancel_${refundID}`,
|
|
1460
|
-
body: {
|
|
1461
|
-
reason_code: flags.reason || flags.reason_code || "buyer_changed_mind",
|
|
1462
|
-
reason_note: flags.note || flags.reason_note || ""
|
|
1463
|
-
}
|
|
1464
|
-
}, flags);
|
|
1465
|
-
}
|
|
1466
|
-
|
|
1467
|
-
async function getBuyerPaymentIntent(paymentIntentID, flags = {}) {
|
|
1468
|
-
return await coreApi(`/v1/payment-intents/${encodeURIComponent(paymentIntentID)}`, { method: "GET" }, flags);
|
|
1469
|
-
}
|
|
1470
|
-
|
|
1471
|
-
async function refreshBuyerPaymentQR(paymentIntentID, flags = {}) {
|
|
1472
|
-
const intent = await getBuyerPaymentIntent(paymentIntentID, flags);
|
|
1473
|
-
if (intent.status === "verified") return intent;
|
|
1474
|
-
const refreshURL = intent.qr_refresh_url;
|
|
1475
|
-
if (!refreshURL) {
|
|
1476
|
-
throw new Error("payment intent does not expose qr_refresh_url; refresh is supported only for refreshable provider QR intents");
|
|
1477
|
-
}
|
|
1478
|
-
return await coreApi(refreshURL, {
|
|
1479
|
-
method: "POST",
|
|
1480
|
-
body: {
|
|
1481
|
-
reason: normalizeQRRefreshReasonForCLI(flags.reason || flags.refresh_reason || "order_not_found")
|
|
1482
|
-
}
|
|
1483
|
-
}, flags);
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
function normalizeQRRefreshReasonForCLI(reason) {
|
|
1487
|
-
const normalized = String(reason || "").trim().toLowerCase().replaceAll("-", "_");
|
|
1488
|
-
if (["order_not_found", "qr_unavailable", "manual_refresh", "human_open"].includes(normalized)) return normalized;
|
|
1489
|
-
return "manual_refresh";
|
|
1490
|
-
}
|
|
1491
|
-
|
|
1492
|
-
async function waitBuyerPayment(intent, flags = {}) {
|
|
1493
|
-
const paymentIntentID = intent?.payment_intent_id || intent;
|
|
1494
|
-
if (!paymentIntentID) throw new Error("payment_intent_id is required");
|
|
1495
|
-
let cursor = flags.cursor || intent?.agent_wait?.cursor || "";
|
|
1496
|
-
const waitURL = flags.wait_url || intent?.agent_wait?.wait_url || `/v1/payment-intents/${encodeURIComponent(paymentIntentID)}/events/wait`;
|
|
1497
|
-
const timeoutMs = Number(flags.timeout || 900) * 1000;
|
|
1498
|
-
const started = Date.now();
|
|
1499
|
-
let lastHeartbeatAt = Date.now();
|
|
1500
|
-
let lastEvent = null;
|
|
1501
|
-
while (Date.now() - started < timeoutMs) {
|
|
1502
|
-
const remainingMs = Math.max(1000, timeoutMs - (Date.now() - started));
|
|
1503
|
-
const params = new URLSearchParams();
|
|
1504
|
-
if (cursor) params.set("cursor", cursor);
|
|
1505
|
-
params.set("timeout", String(flags.poll_timeout || `${Math.ceil(Math.min(30000, remainingMs) / 1000)}s`));
|
|
1506
|
-
const event = await coreApi(appendURLQuery(waitURL, params), { method: "GET" }, flags);
|
|
1507
|
-
lastEvent = event;
|
|
1508
|
-
cursor = event.cursor || cursor;
|
|
1509
|
-
if (event.event_type === "payment_intent.verified") return event;
|
|
1510
|
-
if (event.event_type && event.event_type !== "wait.timeout") return event;
|
|
1511
|
-
lastHeartbeatAt = writeWaitHeartbeat({
|
|
1512
|
-
kind: "ItPay payment notify",
|
|
1513
|
-
idName: "payment_intent_id",
|
|
1514
|
-
idValue: paymentIntentID,
|
|
1515
|
-
status: event.event_type === "wait.timeout" ? "still_waiting" : (event.event_type || "still_waiting"),
|
|
1516
|
-
action: intent?.human_action || null,
|
|
1517
|
-
lastHeartbeatAt,
|
|
1518
|
-
flags,
|
|
1519
|
-
command: paymentStatusCheckCommand(paymentIntentID)
|
|
1520
|
-
});
|
|
1521
|
-
}
|
|
1522
|
-
return lastEvent || { event_type: "wait.timeout", payment_intent_id: paymentIntentID, cursor, agent_next_actions: ["check_payment_status"] };
|
|
1523
|
-
}
|
|
1524
|
-
|
|
1525
|
-
async function waitBuyerDelivery(checkoutID, flags = {}) {
|
|
1526
|
-
const timeoutMs = Number(flags.delivery_timeout || 30) * 1000;
|
|
1527
|
-
const started = Date.now();
|
|
1528
|
-
let checkout = await getBuyerCheckout(checkoutID, flags);
|
|
1529
|
-
while (!isBuyerDeliveryComplete({ checkout }) && Date.now() - started < timeoutMs) {
|
|
1530
|
-
await sleep(Number(flags.delivery_poll_ms || 2000));
|
|
1531
|
-
checkout = await getBuyerCheckout(checkoutID, flags);
|
|
1532
|
-
}
|
|
1533
|
-
return { checkout, delivery: checkout.delivery || null };
|
|
1534
|
-
}
|
|
1535
|
-
|
|
1536
|
-
function isBuyerDeliveryComplete(result) {
|
|
1537
|
-
const checkout = result?.checkout || result || {};
|
|
1538
|
-
const delivery = checkout.delivery || result?.delivery || {};
|
|
1539
|
-
return checkout.delivery_status === "delivered" ||
|
|
1540
|
-
delivery.status === "delivery_claimable" ||
|
|
1541
|
-
delivery.next_required_action === "check_email" ||
|
|
1542
|
-
checkout.agent_next_actions?.includes("stop_check_email");
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
|
-
function buyerRunOutput(value = {}, flags = {}) {
|
|
1546
|
-
const body = compactBuyerOutput(value, flags);
|
|
1547
|
-
return normalizeBuyerMoneyFields(stripInternalBuyerFields({
|
|
1548
|
-
schema_version: "itp.buyer.v1",
|
|
1549
|
-
...body,
|
|
1550
|
-
docs: value.docs || buyerDocsFor(value),
|
|
1551
|
-
secrets: {
|
|
1552
|
-
raw_content_included: false,
|
|
1553
|
-
claim_token_included: false,
|
|
1554
|
-
provider_raw_payload_included: false
|
|
1555
|
-
}
|
|
1556
|
-
}));
|
|
1557
|
-
}
|
|
1558
|
-
|
|
1559
|
-
function compactBuyerOutput(value = {}, flags = {}) {
|
|
1560
|
-
const clientInstruction = clientBuyerInstruction(value, flags);
|
|
1561
|
-
if (clientInstruction) return clientInstruction;
|
|
1562
|
-
const result = {};
|
|
1563
|
-
if (value.status !== undefined) result.status = value.status;
|
|
1564
|
-
for (const [key, item] of Object.entries(value)) {
|
|
1565
|
-
if (["docs", "status"].includes(key)) continue;
|
|
1566
|
-
result[key] = compactBuyerField(key, item);
|
|
1567
|
-
}
|
|
1568
|
-
return result;
|
|
1569
|
-
}
|
|
1570
|
-
|
|
1571
|
-
function clientBuyerInstruction(value = {}, flags = {}) {
|
|
1572
|
-
const plan = value.render_plan || {};
|
|
1573
|
-
const selected = plan.selected || {};
|
|
1574
|
-
if (!plan.kind || !selected.platform) return null;
|
|
1575
|
-
if (selected.platform === "telegram" && selected.openclaw_message) return telegramBuyerInstruction(value, flags);
|
|
1576
|
-
if ((selected.platform === "codex_app" || selected.platform === "claude_code") && selected.markdown) {
|
|
1577
|
-
return markdownBuyerInstruction(value, flags, selected.platform, selected.markdown);
|
|
1578
|
-
}
|
|
1579
|
-
if (selected.platform === "plain_chat") return plainChatBuyerInstruction(value, flags, selected);
|
|
1580
|
-
if (selected.platform === "terminal") return terminalBuyerInstruction(value, flags);
|
|
1581
|
-
return null;
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
|
-
function telegramBuyerInstruction(value = {}, flags = {}) {
|
|
1585
|
-
const plan = value.render_plan || {};
|
|
1586
|
-
const selected = plan.selected || {};
|
|
1587
|
-
if (selected.platform !== "telegram" || !selected.openclaw_message) return null;
|
|
1588
|
-
const summary = buyerOrderSummary(value, plan.kind);
|
|
1589
|
-
const rawMessage = selected.openclaw_message;
|
|
1590
|
-
const presentation = telegramBuyerPresentation(plan.kind, rawMessage);
|
|
1591
|
-
const media = rawMessage.command_args?.media || rawMessage.media || selected.media?.[0]?.local_path || selected.media?.[0]?.fallback_url || "";
|
|
1592
|
-
const hasHumanEntry = Boolean(media || presentation.blocks?.some((block) => block.type === "buttons" && block.buttons?.some((button) => button.url)));
|
|
1593
|
-
const text = telegramBuyerInstructionText(plan.kind, summary, hasHumanEntry);
|
|
1594
|
-
const callbacks = rawMessage.callbacks || {};
|
|
1595
|
-
const commandArgs = compactObject({
|
|
1596
|
-
channel: "telegram",
|
|
1597
|
-
target: clientTarget(flags) || undefined,
|
|
1598
|
-
message: text,
|
|
1599
|
-
media: media || undefined,
|
|
1600
|
-
presentation
|
|
1601
|
-
});
|
|
1602
|
-
const action = telegramAgentAction(commandArgs);
|
|
1603
|
-
const instruction = "必须先执行顶层 agent_action.command 或 agent_action.argv,把二维码、说明和 Telegram 原生按钮发给用户。不要手写 Markdown 表格,不要只贴链接,不要先运行 wait/status;如果不能执行 openclaw message send,就停止并说明工具缺失。";
|
|
1604
|
-
return compactObject({
|
|
1605
|
-
status: value.status,
|
|
1606
|
-
instruction,
|
|
1607
|
-
must_send_to_human_before_next_command: true,
|
|
1608
|
-
must_execute_agent_action_before_reply: true,
|
|
1609
|
-
order_summary: summary,
|
|
1610
|
-
agent_action: action,
|
|
1611
|
-
agent_instruction: {
|
|
1612
|
-
type: plan.kind === "auth_qr" ? "send_telegram_auth_handoff" : "send_telegram_payment_handoff",
|
|
1613
|
-
platform: "telegram",
|
|
1614
|
-
proof_rule: plan.proof_rule,
|
|
1615
|
-
instruction,
|
|
1616
|
-
openclaw_message: {
|
|
1617
|
-
command: "openclaw message send",
|
|
1618
|
-
command_args: commandArgs,
|
|
1619
|
-
telegram_rule: "Telegram 原生按钮必须通过 openclaw message send --presentation 发送;presentation.blocks[].type=\"buttons\" 才会生成 Inline Keyboard。",
|
|
1620
|
-
callbacks
|
|
1621
|
-
}
|
|
1622
|
-
},
|
|
1623
|
-
after_human_response: telegramAfterHumanResponse(value, callbacks),
|
|
1624
|
-
agent_next_actions: ["execute_agent_action"],
|
|
1625
|
-
next: {
|
|
1626
|
-
type: "reply_to_human",
|
|
1627
|
-
safe_for_agent: false,
|
|
1628
|
-
instruction: "先执行 agent_action.command/argv;用户点击按钮或回复后,再运行 after_human_response 里的命令。"
|
|
1629
|
-
}
|
|
1630
|
-
});
|
|
1631
|
-
}
|
|
1632
|
-
|
|
1633
|
-
function telegramAgentAction(commandArgs = {}) {
|
|
1634
|
-
const argv = ["openclaw", "message", "send"];
|
|
1635
|
-
if (commandArgs.channel) argv.push("--channel", commandArgs.channel);
|
|
1636
|
-
if (commandArgs.target) argv.push("--target", commandArgs.target);
|
|
1637
|
-
if (commandArgs.message) argv.push("--message", commandArgs.message);
|
|
1638
|
-
if (commandArgs.media) argv.push("--media", commandArgs.media);
|
|
1639
|
-
if (commandArgs.presentation) argv.push("--presentation", JSON.stringify(commandArgs.presentation));
|
|
1640
|
-
return {
|
|
1641
|
-
type: "openclaw_message_send",
|
|
1642
|
-
tool: "shell",
|
|
1643
|
-
instruction: "现在立刻执行这个 shell 命令发送 Telegram 原生消息;不要改写为普通聊天回复。执行成功后再简短告诉用户已发出。",
|
|
1644
|
-
command: argv.map((item) => shellQuote(String(item))).join(" "),
|
|
1645
|
-
argv,
|
|
1646
|
-
if_unavailable: "如果当前 agent 不能执行 openclaw message send,停止并告诉用户:当前 agent 缺少 Telegram 原生按钮发送工具;不要降级成 Markdown 表格或普通链接。"
|
|
1647
|
-
};
|
|
1648
|
-
}
|
|
1649
|
-
|
|
1650
|
-
function markdownBuyerInstruction(value = {}, flags = {}, platform = "codex_app", markdown = "") {
|
|
1651
|
-
const plan = value.render_plan || {};
|
|
1652
|
-
const summary = buyerOrderSummary(value, plan.kind);
|
|
1653
|
-
const instruction = markdownBuyerInstructionText(platform);
|
|
1654
|
-
return handoffInstructionOutput(value, flags, {
|
|
1655
|
-
type: plan.kind === "auth_qr" ? "send_markdown_auth_handoff" : "send_markdown_payment_handoff",
|
|
1656
|
-
platform,
|
|
1657
|
-
proof_rule: plan.proof_rule,
|
|
1658
|
-
instruction,
|
|
1659
|
-
markdown
|
|
1660
|
-
}, summary, {
|
|
1661
|
-
instruction,
|
|
1662
|
-
preferred_flow: "send_agent_action_then_wait_once",
|
|
1663
|
-
agent_action: markdownAgentAction(platform, markdown),
|
|
1664
|
-
after_visible_action: afterVisibleAction(value, flags, summary),
|
|
1665
|
-
after_human_response_fallback: true,
|
|
1666
|
-
agent_next_actions: ["send_agent_action_to_human", "run_after_visible_action_once_if_visible"],
|
|
1667
|
-
next: {
|
|
1668
|
-
type: "send_to_human_then_wait_once_if_visible",
|
|
1669
|
-
safe_for_agent: true,
|
|
1670
|
-
instruction: "先把 agent_action.markdown 原样发到当前聊天;确认二维码、摘要和链接可见后,立刻执行 after_visible_action.command 等一次。只有无法确认可见时才停下等人类。"
|
|
1671
|
-
}
|
|
1672
|
-
});
|
|
1673
|
-
}
|
|
1674
|
-
|
|
1675
|
-
function plainChatBuyerInstruction(value = {}, flags = {}, selected = {}) {
|
|
1676
|
-
const plan = value.render_plan || {};
|
|
1677
|
-
const summary = buyerOrderSummary(value, plan.kind);
|
|
1678
|
-
return handoffInstructionOutput(value, flags, {
|
|
1679
|
-
type: plan.kind === "auth_qr" ? "send_plain_auth_handoff" : "send_plain_payment_handoff",
|
|
1680
|
-
platform: "plain_chat",
|
|
1681
|
-
proof_rule: plan.proof_rule,
|
|
1682
|
-
instruction: "把 message 和 links 发给用户后停下,等用户回复再查状态。",
|
|
1683
|
-
message: selected.text,
|
|
1684
|
-
links: selected.links || []
|
|
1685
|
-
}, summary);
|
|
1686
|
-
}
|
|
1687
|
-
|
|
1688
|
-
function terminalBuyerInstruction(value = {}, flags = {}) {
|
|
1689
|
-
const plan = value.render_plan || {};
|
|
1690
|
-
const summary = buyerOrderSummary(value, plan.kind);
|
|
1691
|
-
const instruction = "必须先执行 agent_action.command,让 CLI 在人类正在看的终端里打印二维码、链接和摘要;不要把 terminal 当成 Codex 桌面端。如果人类不是直接看这个终端,改用 --host codex 重跑。";
|
|
1692
|
-
return handoffInstructionOutput(value, flags, {
|
|
1693
|
-
type: plan.kind === "auth_qr" ? "terminal_auth_handoff" : "terminal_payment_handoff",
|
|
1694
|
-
platform: "terminal",
|
|
1695
|
-
proof_rule: plan.proof_rule,
|
|
1696
|
-
instruction,
|
|
1697
|
-
print_terminal_qr: true
|
|
1698
|
-
}, summary, {
|
|
1699
|
-
instruction,
|
|
1700
|
-
preferred_flow: "execute_agent_action_then_wait_once",
|
|
1701
|
-
agent_action: terminalAgentAction(summary, flags, plan.kind),
|
|
1702
|
-
after_visible_action: afterVisibleAction(value, flags, summary),
|
|
1703
|
-
after_human_response_fallback: true,
|
|
1704
|
-
agent_next_actions: ["execute_agent_action", "run_after_visible_action_once_if_visible"],
|
|
1705
|
-
next: {
|
|
1706
|
-
type: "execute_terminal_handoff_then_wait_once_if_visible",
|
|
1707
|
-
safe_for_agent: true,
|
|
1708
|
-
instruction: "先执行 agent_action.command,让人类正在看的终端显示二维码和链接;确认可见后,立刻执行 after_visible_action.command 等一次。若人类不是直接看终端,停止并用正确 --host 重跑。"
|
|
1709
|
-
}
|
|
1710
|
-
});
|
|
1711
|
-
}
|
|
1712
|
-
|
|
1713
|
-
function handoffInstructionOutput(value = {}, flags = {}, agentInstruction = {}, summary = {}, extra = {}) {
|
|
1714
|
-
const afterHumanResponse = genericAfterHumanResponse(value, flags, summary);
|
|
1715
|
-
if (extra.after_human_response_fallback) {
|
|
1716
|
-
afterHumanResponse.fallback = true;
|
|
1717
|
-
afterHumanResponse.instruction = "Fallback only: use check_status_command when after_visible_action was not run, or when the human later replies/clicks status. User text is not proof.";
|
|
1718
|
-
}
|
|
1719
|
-
return compactObject({
|
|
1720
|
-
status: value.status,
|
|
1721
|
-
instruction: extra.instruction,
|
|
1722
|
-
must_send_to_human_before_next_command: true,
|
|
1723
|
-
preferred_flow: extra.preferred_flow,
|
|
1724
|
-
order_summary: summary,
|
|
1725
|
-
agent_action: extra.agent_action,
|
|
1726
|
-
agent_instruction: agentInstruction,
|
|
1727
|
-
after_visible_action: extra.after_visible_action,
|
|
1728
|
-
after_human_response: afterHumanResponse,
|
|
1729
|
-
agent_next_actions: extra.agent_next_actions || ["send_agent_instruction_to_human"],
|
|
1730
|
-
next: extra.next || {
|
|
1731
|
-
type: "reply_to_human",
|
|
1732
|
-
safe_for_agent: false,
|
|
1733
|
-
instruction: extra.next_instruction || "先把 agent_instruction 发送给用户;用户点击按钮或回复后,再运行 after_human_response 里的命令。"
|
|
1734
|
-
}
|
|
1735
|
-
});
|
|
1736
|
-
}
|
|
1737
|
-
|
|
1738
|
-
function markdownBuyerInstructionText(platform = "codex_app") {
|
|
1739
|
-
const label = platform === "claude_code" ? "Claude Code app" : "Codex desktop app";
|
|
1740
|
-
return `你在 ${label}。必须先把顶层 agent_action.markdown 原样发到当前聊天,让人类真正看到二维码、订单摘要和链接;不要因为你用 shell 跑 itp 就改用 --host terminal。发出后,如果你能确认消息已经对人类可见,默认立刻执行 after_visible_action.command 等 30 秒;只有不能确认可见时才停止等人类回复。`;
|
|
1741
|
-
}
|
|
1742
|
-
|
|
1743
|
-
function markdownAgentAction(platform = "codex_app", markdown = "") {
|
|
1744
|
-
const label = platform === "claude_code" ? "Claude Code app" : "Codex desktop app";
|
|
1745
|
-
return {
|
|
1746
|
-
type: "markdown_reply",
|
|
1747
|
-
platform,
|
|
1748
|
-
instruction: `现在立刻把 markdown 原样发送到当前聊天。${label} 可以用绝对路径 Markdown 图片显示本地二维码。不要只贴文件路径,不要先运行 wait/status。`,
|
|
1749
|
-
markdown,
|
|
1750
|
-
if_unavailable: "如果当前客户端不能让这段 Markdown 对人类可见,停止并说明无法展示二维码;不要先运行 wait/status。"
|
|
1751
|
-
};
|
|
1752
|
-
}
|
|
1753
|
-
|
|
1754
|
-
function terminalAgentAction(summary = {}, flags = {}, kind = "") {
|
|
1755
|
-
const args = kind === "auth_qr" && summary.checkout_id
|
|
1756
|
-
? ["buyer", "checkout", "resume", summary.checkout_id, ...clientCommandArgs(flags), "--no-wait-auth"]
|
|
1757
|
-
: summary.payment_intent_id
|
|
1758
|
-
? ["buyer", "payment", "wait", summary.payment_intent_id, ...clientCommandArgs(flags)]
|
|
1759
|
-
: [];
|
|
1760
|
-
if (!args.length) return undefined;
|
|
1761
|
-
return {
|
|
1762
|
-
type: "terminal_handoff_command",
|
|
1763
|
-
tool: "shell",
|
|
1764
|
-
instruction: "只有当人类直接看这个终端窗口时才执行。它会让 CLI 打印二维码和链接。若你在 Codex 桌面聊天里,请改用 --host codex 重跑原命令。",
|
|
1765
|
-
command: cliCommand(...args),
|
|
1766
|
-
if_unavailable: "如果不能确认人类正在看该终端,停止并用正确的 --host 重跑;不要把终端输出当成 Codex 桌面端回复。"
|
|
1767
|
-
};
|
|
1768
|
-
}
|
|
1769
|
-
|
|
1770
|
-
function afterVisibleAction(value = {}, flags = {}, summary = {}) {
|
|
1771
|
-
if (summary.payment_intent_id) {
|
|
1772
|
-
return {
|
|
1773
|
-
recommended: true,
|
|
1774
|
-
command: cliCommand("buyer", "payment", "wait", summary.payment_intent_id, "--wait", "--timeout", "30", ...clientCommandArgs(flags), "--json"),
|
|
1775
|
-
instruction: "只有在二维码、订单摘要和链接已经对人类可见后才能执行。用户说已付款不算证明,只有 payment_intent.verified 算付款成功。"
|
|
1776
|
-
};
|
|
1777
|
-
}
|
|
1778
|
-
if (summary.checkout_id) {
|
|
1779
|
-
return {
|
|
1780
|
-
recommended: true,
|
|
1781
|
-
command: cliCommand("buyer", "checkout", "resume", summary.checkout_id, ...clientCommandArgs(flags), "--json"),
|
|
1782
|
-
instruction: "只有在授权入口已经对人类可见后才能执行;继续同一个 checkout,不要新建 checkout。"
|
|
1783
|
-
};
|
|
1784
|
-
}
|
|
1785
|
-
return undefined;
|
|
1786
|
-
}
|
|
1787
|
-
|
|
1788
|
-
function buyerOrderSummary(value = {}, kind = "") {
|
|
1789
|
-
const cart = value.cart || {};
|
|
1790
|
-
const checkout = value.checkout || {};
|
|
1791
|
-
const intent = value.payment_intent || {};
|
|
1792
|
-
const context = checkout.cart_id ? readState().cart_display_contexts?.[checkout.cart_id] : null;
|
|
1793
|
-
const line = cart.line_items?.[0] || context?.line_items?.[0] || {};
|
|
1794
|
-
const title = value.selection?.title || line.item?.title || line.title || "ItPay 服务";
|
|
1795
|
-
const quantity = line.quantity || 1;
|
|
1796
|
-
const amount = intent.amount ?? checkout.amount ?? cart.amount ?? value.selection?.expected_amount ?? value.selection?.amount;
|
|
1797
|
-
const currency = intent.currency || checkout.currency || cart.currency || value.selection?.currency || "";
|
|
1798
|
-
return compactObject({
|
|
1799
|
-
kind,
|
|
1800
|
-
title,
|
|
1801
|
-
quantity,
|
|
1802
|
-
input_summary: buyerInputSummary(line.input),
|
|
1803
|
-
amount_display: buyerHumanAmount(amount, currency),
|
|
1804
|
-
order_id: checkout.order_id || intent.order_id,
|
|
1805
|
-
checkout_id: checkout.checkout_id || intent.checkout_id,
|
|
1806
|
-
payment_intent_id: intent.payment_intent_id,
|
|
1807
|
-
status: intent.status || checkout.status || value.status
|
|
1808
|
-
});
|
|
1809
|
-
}
|
|
1810
|
-
|
|
1811
|
-
function buyerInputSummary(input = {}) {
|
|
1812
|
-
if (!input || typeof input !== "object") return "";
|
|
1813
|
-
return input.company_name || input.company_name_or_credit_no || Object.values(input).find((value) => value && value !== "1" && value !== "0") || "";
|
|
1814
|
-
}
|
|
1815
|
-
|
|
1816
|
-
function buyerHumanAmount(amount, currency = "") {
|
|
1817
|
-
if (!Number.isFinite(Number(amount))) return "";
|
|
1818
|
-
if (String(currency).toUpperCase() === "CNY") return `¥${(Number(amount) / 100).toFixed(2)}`;
|
|
1819
|
-
return `${String(currency).toUpperCase()} ${(Number(amount) / 100).toFixed(2)}`.trim();
|
|
1820
|
-
}
|
|
1821
|
-
|
|
1822
|
-
function telegramBuyerInstructionText(kind, summary = {}, hasHumanEntry = true) {
|
|
1823
|
-
const amount = summary.amount_display ? `,金额 ${summary.amount_display}` : "";
|
|
1824
|
-
if (!hasHumanEntry) {
|
|
1825
|
-
return [
|
|
1826
|
-
`购物车和结算已创建,但当前付款入口需要刷新${amount}。`,
|
|
1827
|
-
"请点“支付遇到问题 / 刷新”,或运行 after_human_response.refresh_qr_command 后重新发送付款入口。",
|
|
1828
|
-
"",
|
|
1829
|
-
`摘要:${telegramOrderSummaryText(summary)}`
|
|
1830
|
-
].join("\n");
|
|
1831
|
-
}
|
|
1832
|
-
const action = kind === "auth_qr" ? "完成首次授权/付款" : "完成付款";
|
|
1833
|
-
const open = kind === "auth_qr" ? "打开授权" : "打开付款页面";
|
|
1834
|
-
const done = kind === "auth_qr" ? "我已完成,查询状态" : "我已付款,查询状态";
|
|
1835
|
-
return [
|
|
1836
|
-
`购物车和结算已创建。现在需要你${action}${amount}。`,
|
|
1837
|
-
`可扫二维码,或点“${open}”进入页面。`,
|
|
1838
|
-
`完成后点“${done}”,我会继续查支付和交付状态。`,
|
|
1839
|
-
"",
|
|
1840
|
-
`摘要:${telegramOrderSummaryText(summary)}`
|
|
1841
|
-
].join("\n");
|
|
1842
|
-
}
|
|
1843
|
-
|
|
1844
|
-
function telegramOrderSummaryText(summary = {}) {
|
|
1845
|
-
const input = summary.input_summary ? `,关键词「${summary.input_summary}」` : "";
|
|
1846
|
-
const amount = summary.amount_display ? `,${summary.amount_display}` : "";
|
|
1847
|
-
return `${summary.title} × ${summary.quantity || 1} 次${input}${amount}。`;
|
|
1848
|
-
}
|
|
1849
|
-
|
|
1850
|
-
function telegramBuyerPresentation(kind, rawMessage = {}) {
|
|
1851
|
-
const rawBlocks = rawMessage.command_args?.presentation?.blocks || rawMessage.presentation?.blocks || [];
|
|
1852
|
-
const buttons = rawBlocks.find((block) => block.type === "buttons")?.buttons || [];
|
|
1853
|
-
return {
|
|
1854
|
-
blocks: [{
|
|
1855
|
-
type: "buttons",
|
|
1856
|
-
buttons: buttons.map((button) => telegramBuyerButton(kind, button)).filter(Boolean)
|
|
1857
|
-
}]
|
|
1858
|
-
};
|
|
1859
|
-
}
|
|
1860
|
-
|
|
1861
|
-
function telegramBuyerButton(kind, button = {}) {
|
|
1862
|
-
if (button.url) {
|
|
1863
|
-
if (kind === "auth_qr") return { text: "🔓 打开授权", url: button.url };
|
|
1864
|
-
if (button.text === "手机钱包打开") return { text: "📱 手机钱包打开", url: button.url };
|
|
1865
|
-
return { text: "💳 打开付款页面", url: button.url };
|
|
1866
|
-
}
|
|
1867
|
-
if (button.callback_data?.includes("refresh_payment_qr")) return { text: "🔄 支付遇到问题 / 刷新", callback_data: button.callback_data };
|
|
1868
|
-
if (button.callback_data?.includes("check_payment_status")) return { text: "✅ 我已付款,查询状态", callback_data: button.callback_data };
|
|
1869
|
-
if (button.callback_data?.includes("check_checkout_status")) return { text: "✅ 我已完成,查询状态", callback_data: button.callback_data };
|
|
1870
|
-
return button.text && button.callback_data ? { text: button.text, callback_data: button.callback_data } : null;
|
|
1871
|
-
}
|
|
1872
|
-
|
|
1873
|
-
function telegramAfterHumanResponse(value = {}, callbacks = {}) {
|
|
1874
|
-
return compactObject({
|
|
1875
|
-
check_status_command: callbacks.check_payment_status || callbacks.check_checkout_status || value.after_human_response?.check_payment_command,
|
|
1876
|
-
refresh_qr_command: callbacks.refresh_payment_qr || value.after_human_response?.refresh_qr_command,
|
|
1877
|
-
help_command: cliCommand("docs", "show", "payment-qr", "--role", "buyer", "--json"),
|
|
1878
|
-
instruction: "用户点击查询按钮或回复已完成后,运行 check_status_command。用户口头说已付款不是证明。"
|
|
1879
|
-
});
|
|
1880
|
-
}
|
|
1881
|
-
|
|
1882
|
-
function genericAfterHumanResponse(value = {}, flags = {}, summary = {}) {
|
|
1883
|
-
const paymentIntentID = summary.payment_intent_id;
|
|
1884
|
-
const checkoutID = summary.checkout_id;
|
|
1885
|
-
return compactObject({
|
|
1886
|
-
check_status_command: value.after_human_response?.check_payment_command ||
|
|
1887
|
-
(paymentIntentID ? paymentStatusCheckCommand(paymentIntentID, flags) : undefined) ||
|
|
1888
|
-
(checkoutID ? cliCommand("buyer", "checkout", "resume", checkoutID, ...clientCommandArgs(flags), "--json") : undefined),
|
|
1889
|
-
refresh_qr_command: value.after_human_response?.refresh_qr_command,
|
|
1890
|
-
help_command: cliCommand("docs", "show", "payment-qr", "--role", "buyer", "--json"),
|
|
1891
|
-
instruction: "用户点击查询按钮或回复已完成后,运行 check_status_command。用户口头说已付款不是证明。"
|
|
1892
|
-
});
|
|
1893
|
-
}
|
|
1894
|
-
|
|
1895
|
-
function compactBuyerField(key, value) {
|
|
1896
|
-
if (key === "selection") return compactSelection(value);
|
|
1897
|
-
if (key === "selections") return Array.isArray(value) ? value.map(compactSelection) : value;
|
|
1898
|
-
if (key === "product") return compactProduct(value);
|
|
1899
|
-
if (key === "products") return Array.isArray(value) ? value.map(compactProduct) : value;
|
|
1900
|
-
if (key === "cart") return compactCart(value);
|
|
1901
|
-
if (key === "checkout") return compactCheckout(value);
|
|
1902
|
-
if (key === "payment_intent") return compactPaymentIntent(value);
|
|
1903
|
-
return value;
|
|
1904
|
-
}
|
|
1905
|
-
|
|
1906
|
-
function compactSelection(selection = {}) {
|
|
1907
|
-
if (!selection || typeof selection !== "object") return selection;
|
|
1908
|
-
return compactObject({
|
|
1909
|
-
catalog_item_id: selection.catalog_item_id,
|
|
1910
|
-
catalog_variant_id: selection.catalog_variant_id,
|
|
1911
|
-
ucp_variant_id: selection.ucp_variant_id,
|
|
1912
|
-
offer_id: selection.offer_id,
|
|
1913
|
-
title: selection.title,
|
|
1914
|
-
variant_title: selection.variant_title,
|
|
1915
|
-
amount: selection.expected_amount,
|
|
1916
|
-
currency: selection.currency,
|
|
1917
|
-
required_contact_fields: selection.required_contact_fields,
|
|
1918
|
-
purchasable: selection.purchasable
|
|
1919
|
-
});
|
|
1920
|
-
}
|
|
1921
|
-
|
|
1922
|
-
function compactProduct(product = {}) {
|
|
1923
|
-
if (!product || typeof product !== "object") return product;
|
|
1924
|
-
return compactObject({
|
|
1925
|
-
id: product.id,
|
|
1926
|
-
title: product.title,
|
|
1927
|
-
description: product.description,
|
|
1928
|
-
selected: product.selected,
|
|
1929
|
-
variants: Array.isArray(product.variants) ? product.variants.map(compactVariant) : undefined
|
|
1930
|
-
});
|
|
1931
|
-
}
|
|
1932
|
-
|
|
1933
|
-
function compactVariant(variant = {}) {
|
|
1934
|
-
const metadata = variant.metadata || {};
|
|
1935
|
-
return compactObject({
|
|
1936
|
-
id: variant.id,
|
|
1937
|
-
title: variant.title,
|
|
1938
|
-
description: variant.description,
|
|
1939
|
-
price: variant.price,
|
|
1940
|
-
availability: variant.availability,
|
|
1941
|
-
required_profile_fields: metadata["ai.itpay.required_profile_fields"],
|
|
1942
|
-
input_schema_json: metadata["ai.itpay.api.input_schema_json"],
|
|
1943
|
-
agent_explanation_zh: metadata["ai.itpay.agent_explanation_zh"]
|
|
1944
|
-
});
|
|
1945
|
-
}
|
|
1946
|
-
|
|
1947
|
-
function compactCart(cart = {}) {
|
|
1948
|
-
if (!cart || typeof cart !== "object") return cart;
|
|
1949
|
-
return compactObject({
|
|
1950
|
-
cart_id: cart.cart_id || cart.id,
|
|
1951
|
-
status: cart.status,
|
|
1952
|
-
amount: cart.amount,
|
|
1953
|
-
currency: cart.currency,
|
|
1954
|
-
line_items: Array.isArray(cart.line_items) ? cart.line_items.map(compactCartLine) : undefined
|
|
1955
|
-
});
|
|
1956
|
-
}
|
|
1957
|
-
|
|
1958
|
-
function compactCartLine(line = {}) {
|
|
1959
|
-
return compactObject({
|
|
1960
|
-
id: line.id,
|
|
1961
|
-
quantity: line.quantity,
|
|
1962
|
-
amount: line.amount,
|
|
1963
|
-
currency: line.currency,
|
|
1964
|
-
input: line.input,
|
|
1965
|
-
item: line.item ? compactObject({
|
|
1966
|
-
id: line.item.id,
|
|
1967
|
-
title: line.item.title,
|
|
1968
|
-
catalog_item_id: line.item.catalog_item_id,
|
|
1969
|
-
catalog_variant_id: line.item.catalog_variant_id,
|
|
1970
|
-
offer_id: line.item.offer_id
|
|
1971
|
-
}) : undefined
|
|
1972
|
-
});
|
|
1973
|
-
}
|
|
1974
|
-
|
|
1975
|
-
function compactCheckout(checkout = {}) {
|
|
1976
|
-
if (!checkout || typeof checkout !== "object") return checkout;
|
|
1977
|
-
return compactObject({
|
|
1978
|
-
checkout_id: checkout.checkout_id,
|
|
1979
|
-
cart_id: checkout.cart_id,
|
|
1980
|
-
order_id: checkout.order_id,
|
|
1981
|
-
status: checkout.status,
|
|
1982
|
-
delivery_status: checkout.delivery_status,
|
|
1983
|
-
identity_status: checkout.identity_status,
|
|
1984
|
-
next_required_action: checkout.next_required_action,
|
|
1985
|
-
payment_intent_id: checkout.payment_intent_id,
|
|
1986
|
-
amount: checkout.amount,
|
|
1987
|
-
currency: checkout.currency,
|
|
1988
|
-
delivery: checkout.delivery,
|
|
1989
|
-
human_action: checkout.human_action,
|
|
1990
|
-
agent_next_actions: checkout.agent_next_actions
|
|
1991
|
-
});
|
|
1992
|
-
}
|
|
1993
|
-
|
|
1994
|
-
function compactPaymentIntent(intent = {}) {
|
|
1995
|
-
if (!intent || typeof intent !== "object") return intent;
|
|
1996
|
-
return compactObject({
|
|
1997
|
-
payment_intent_id: intent.payment_intent_id,
|
|
1998
|
-
payment_attempt_id: intent.payment_attempt_id,
|
|
1999
|
-
checkout_id: intent.checkout_id,
|
|
2000
|
-
order_id: intent.order_id,
|
|
2001
|
-
status: intent.status,
|
|
2002
|
-
amount: intent.amount,
|
|
2003
|
-
currency: intent.currency,
|
|
2004
|
-
human_action: compactHumanAction(intent.human_action),
|
|
2005
|
-
qr_png_url: intent.qr_png_url,
|
|
2006
|
-
qr_image_url: intent.qr_image_url,
|
|
2007
|
-
mobile_wallet_url: intent.mobile_wallet_url,
|
|
2008
|
-
agent_wait: intent.agent_wait,
|
|
2009
|
-
agent_next_actions: intent.agent_next_actions
|
|
2010
|
-
});
|
|
2011
|
-
}
|
|
2012
|
-
|
|
2013
|
-
function compactHumanAction(action = null) {
|
|
2014
|
-
if (!action || typeof action !== "object") return action;
|
|
2015
|
-
return compactObject({
|
|
2016
|
-
kind: action.kind,
|
|
2017
|
-
payment_intent_id: action.payment_intent_id,
|
|
2018
|
-
url: action.url,
|
|
2019
|
-
local_qr_path: action.local_qr_path,
|
|
2020
|
-
local_qr_mime: action.local_qr_mime,
|
|
2021
|
-
qr_png_url: action.qr_png_url,
|
|
2022
|
-
qr_image_url: action.qr_image_url,
|
|
2023
|
-
preferred_qr_url: action.preferred_qr_url,
|
|
2024
|
-
mobile_wallet_url: action.mobile_wallet_url,
|
|
2025
|
-
presentation: action.presentation,
|
|
2026
|
-
agent_display_hint: action.agent_display_hint,
|
|
2027
|
-
expires_at: action.expires_at
|
|
2028
|
-
});
|
|
2029
|
-
}
|
|
2030
|
-
|
|
2031
|
-
function compactObject(value = {}) {
|
|
2032
|
-
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined && item !== null));
|
|
2033
|
-
}
|
|
2034
|
-
|
|
2035
|
-
function normalizeBuyerMoneyFields(value) {
|
|
2036
|
-
if (Array.isArray(value)) return value.map((item) => normalizeBuyerMoneyFields(item));
|
|
2037
|
-
if (!value || typeof value !== "object") return value;
|
|
2038
|
-
const next = {};
|
|
2039
|
-
for (const [key, item] of Object.entries(value)) {
|
|
2040
|
-
next[key] = normalizeBuyerMoneyFields(item);
|
|
2041
|
-
}
|
|
2042
|
-
if (
|
|
2043
|
-
typeof next.amount === "number" &&
|
|
2044
|
-
Number.isFinite(next.amount) &&
|
|
2045
|
-
typeof next.currency === "string" &&
|
|
2046
|
-
next.currency.trim()
|
|
2047
|
-
) {
|
|
2048
|
-
const currency = next.currency.trim().toUpperCase();
|
|
2049
|
-
next.amount_minor = Number.isInteger(next.amount) ? next.amount : Math.round(next.amount);
|
|
2050
|
-
next.amount_major = Number((next.amount_minor / 100).toFixed(2));
|
|
2051
|
-
next.display_amount = `${currency} ${(next.amount_minor / 100).toFixed(2)}`;
|
|
2052
|
-
next.amount_unit = "minor";
|
|
2053
|
-
}
|
|
2054
|
-
return next;
|
|
2055
|
-
}
|
|
2056
|
-
|
|
2057
|
-
function buyerDocsFor(value = {}) {
|
|
2058
|
-
const topics = new Set();
|
|
2059
|
-
const status = String(value.status || "").toLowerCase();
|
|
2060
|
-
const actions = Array.isArray(value.agent_next_actions) ? value.agent_next_actions : [];
|
|
2061
|
-
if (status.includes("catalog")) topics.add("catalog-search");
|
|
2062
|
-
if (status.includes("cart") || actions.includes("create_checkout_from_cart")) topics.add("cart-checkout");
|
|
2063
|
-
if (status.includes("checkout") || actions.includes("create_payment_intent")) topics.add("cart-checkout");
|
|
2064
|
-
if (status.includes("human_auth") || actions.includes("wait_human_auth") || value.human_action?.kind === "auth_qr" || value.checkout?.human_action?.kind === "auth_qr") {
|
|
2065
|
-
topics.add("cart-checkout");
|
|
2066
|
-
topics.add("payment-qr");
|
|
2067
|
-
}
|
|
2068
|
-
if (status.includes("payment_handoff") || status.includes("waiting_user_payment") || actions.includes("send_agent_instruction_to_human") || actions.includes("wait_payment") || value.payment_intent?.human_action || value.payment_intent?.qr_image_url) {
|
|
2069
|
-
topics.add("payment-qr");
|
|
2070
|
-
topics.add("payment-wait");
|
|
2071
|
-
}
|
|
2072
|
-
if (status.includes("payment_verified") || value.payment_event?.event_type === "payment_intent.verified") topics.add("secure-delivery");
|
|
2073
|
-
if (status.includes("delivery") || value.delivery || actions.includes("stop_check_email")) {
|
|
2074
|
-
topics.add("secure-delivery");
|
|
2075
|
-
topics.add("human-claim-ui");
|
|
2076
|
-
topics.add("vault-agent-read");
|
|
2077
|
-
}
|
|
2078
|
-
if (status.includes("agent_read_grant") || value.agent_readable_grants || value.grant || actions.includes("read_agent_grant_view") || actions.includes("list_agent_read_grants")) {
|
|
2079
|
-
topics.add("vault-agent-read");
|
|
2080
|
-
}
|
|
2081
|
-
if (topics.size === 0) topics.add("quickstart");
|
|
2082
|
-
return Array.from(topics).map((topic) => buyerDocRef(topic));
|
|
2083
|
-
}
|
|
2084
|
-
|
|
2085
|
-
function buyerDocRef(topic) {
|
|
2086
|
-
return {
|
|
2087
|
-
topic,
|
|
2088
|
-
command: cliCommand("docs", "show", topic, "--role", "buyer", "--json")
|
|
2089
|
-
};
|
|
2090
|
-
}
|
|
2091
|
-
|
|
2092
|
-
function buyerDeliveryListOutput(checkout) {
|
|
2093
|
-
const delivery = checkout.delivery || null;
|
|
2094
|
-
const checkoutID = checkout.checkout_id;
|
|
2095
|
-
return buyerRunOutput({
|
|
2096
|
-
status: delivery?.status || checkout.delivery_status,
|
|
2097
|
-
checkout_id: checkoutID,
|
|
2098
|
-
deliveries: delivery ? [{
|
|
2099
|
-
checkout_id: checkoutID,
|
|
2100
|
-
status: delivery.status,
|
|
2101
|
-
next_required_action: delivery.next_required_action,
|
|
2102
|
-
channels: delivery.channels || [],
|
|
2103
|
-
artifact_status: delivery.artifact_status,
|
|
2104
|
-
sensitive_content_redacted: delivery.sensitive_content_redacted !== false
|
|
2105
|
-
}] : [],
|
|
2106
|
-
agent_next_actions: deliveryAwareAgentNextActions(checkout),
|
|
2107
|
-
optional_agent_read_grant: optionalAgentReadGrantHint(checkoutID, checkout)
|
|
2108
|
-
});
|
|
2109
|
-
}
|
|
2110
|
-
|
|
2111
|
-
function deliveryAwareAgentNextActions(checkout = {}) {
|
|
2112
|
-
const actions = Array.isArray(checkout.agent_next_actions) ? [...checkout.agent_next_actions] : [];
|
|
2113
|
-
if (isBuyerDeliveryComplete(checkout) && !actions.includes("optionally_request_agent_read_grant")) {
|
|
2114
|
-
actions.push("optionally_request_agent_read_grant");
|
|
2115
|
-
}
|
|
2116
|
-
return actions;
|
|
2117
|
-
}
|
|
2118
|
-
|
|
2119
|
-
function optionalAgentReadGrantHint(checkoutID, checkout = {}) {
|
|
2120
|
-
if (!checkoutID || !isBuyerDeliveryComplete(checkout)) return undefined;
|
|
2121
|
-
return {
|
|
2122
|
-
type: "optional_human_passkey_agent_read_grant",
|
|
2123
|
-
safe_for_agent: true,
|
|
2124
|
-
requires_human: true,
|
|
2125
|
-
instruction: "If the human wants you to analyze or use the delivered result, ask them to open the ItPay claim/account page, reveal with Passkey, choose 'Give to Agent / 一键给 Agent', select fields, and confirm. After they approve, do not ask for a grant id; run the probe command.",
|
|
2126
|
-
docs_command: cliCommand("docs", "show", "vault-agent-read", "--role", "buyer", "--json"),
|
|
2127
|
-
probe_command: cliCommand("buyer", "vault", "grants", "list", "--checkout", checkoutID, "--json"),
|
|
2128
|
-
read_pattern: cliCommand("buyer", "vault", "read", "--order", "<order_id>", "--artifact", "<vault_artifact_id>", "--json")
|
|
2129
|
-
};
|
|
2130
|
-
}
|
|
2131
|
-
|
|
2132
|
-
async function buyerAuthStatusOutput(flags = {}) {
|
|
2133
|
-
const config = readConfig();
|
|
2134
|
-
const credentials = readCredentials();
|
|
2135
|
-
const token = readSessionToken(credentials);
|
|
2136
|
-
if (token && config.account_id) {
|
|
2137
|
-
try {
|
|
2138
|
-
const status = await coreApi("/v1/me/auth/status", { method: "GET" }, flags);
|
|
2139
|
-
return buyerRunOutput({
|
|
2140
|
-
status: "authenticated_buyer_session",
|
|
2141
|
-
authenticated: true,
|
|
2142
|
-
buyer_account_id: status.buyer_account_id || config.account_id,
|
|
2143
|
-
agent_device_id: config.device_id || null,
|
|
2144
|
-
account_status: status.account_status,
|
|
2145
|
-
sensitive_redacted: true,
|
|
2146
|
-
agent_next_actions: ["search_catalog", "view_orders", "list_agent_read_grants"]
|
|
2147
|
-
});
|
|
2148
|
-
} catch (error) {
|
|
2149
|
-
return buyerRunOutput({
|
|
2150
|
-
status: "buyer_session_invalid",
|
|
2151
|
-
authenticated: false,
|
|
2152
|
-
buyer_account_id: config.account_id || null,
|
|
2153
|
-
agent_device_id: config.device_id || null,
|
|
2154
|
-
error: safeErrorMessage(error),
|
|
2155
|
-
agent_next_actions: ["create_checkout_for_human_auth"]
|
|
2156
|
-
});
|
|
2157
|
-
}
|
|
2158
|
-
}
|
|
2159
|
-
return buyerRunOutput({
|
|
2160
|
-
status: "public_purchase_mode",
|
|
2161
|
-
authenticated: false,
|
|
2162
|
-
auth_required_for_discovery: false,
|
|
2163
|
-
agent_next_actions: ["search_catalog", "create_checkout_for_human_auth"],
|
|
2164
|
-
note: "Catalog discovery is public. Checkout creates a human auth-to-payment QR and saves a buyer session after the human authorizes."
|
|
2165
|
-
});
|
|
2166
|
-
}
|
|
2167
|
-
|
|
2168
|
-
async function listBuyerAgentReadGrants(flags = {}) {
|
|
2169
|
-
const params = new URLSearchParams();
|
|
2170
|
-
const mappings = [
|
|
2171
|
-
["checkout", "checkout_id"],
|
|
2172
|
-
["checkout_id", "checkout_id"],
|
|
2173
|
-
["order", "order_id"],
|
|
2174
|
-
["order_id", "order_id"],
|
|
2175
|
-
["artifact", "vault_artifact_id"],
|
|
2176
|
-
["vault_artifact", "vault_artifact_id"],
|
|
2177
|
-
["vault_artifact_id", "vault_artifact_id"],
|
|
2178
|
-
["line", "order_line_item_id"],
|
|
2179
|
-
["line_id", "order_line_item_id"],
|
|
2180
|
-
["order_line_item_id", "order_line_item_id"]
|
|
2181
|
-
];
|
|
2182
|
-
for (const [flagName, paramName] of mappings) {
|
|
2183
|
-
if (flags[flagName] !== undefined && flags[flagName] !== null && flags[flagName] !== false) {
|
|
2184
|
-
params.set(paramName, String(flags[flagName]));
|
|
2185
|
-
}
|
|
2186
|
-
}
|
|
2187
|
-
const query = params.toString();
|
|
2188
|
-
return await coreApi(`/v1/me/agent-grants${query ? `?${query}` : ""}`, { method: "GET" }, flags);
|
|
2189
|
-
}
|
|
2190
|
-
|
|
2191
|
-
async function readBuyerAgentReadGrant(grantID, flags = {}) {
|
|
2192
|
-
if (!grantID) throw new Error("agent_read_grant_id is required");
|
|
2193
|
-
return await coreApi(`/v1/me/agent-grants/${encodeURIComponent(grantID)}/view`, { method: "GET" }, flags);
|
|
2194
|
-
}
|
|
2195
|
-
|
|
2196
|
-
async function readBuyerVaultArtifactGrant(flags = {}) {
|
|
2197
|
-
const grants = await listBuyerAgentReadGrants(flags);
|
|
2198
|
-
const list = Array.isArray(grants.agent_readable_grants) ? grants.agent_readable_grants : [];
|
|
2199
|
-
if (!list.length) {
|
|
2200
|
-
throw new Error("no active agent-readable grant found; ask the human to open the account portal and confirm one-key agent authorization with Passkey");
|
|
2201
|
-
}
|
|
2202
|
-
const grant = list[0];
|
|
2203
|
-
const grantID = grant.agent_read_grant_id || grant.agent_readGrantID;
|
|
2204
|
-
if (!grantID) throw new Error("active grant did not include agent_read_grant_id");
|
|
2205
|
-
const view = await readBuyerAgentReadGrant(grantID, flags);
|
|
2206
|
-
return {
|
|
2207
|
-
...view,
|
|
2208
|
-
discovered_grant: grant
|
|
2209
|
-
};
|
|
2210
|
-
}
|
|
2211
|
-
|
|
2212
|
-
function catalogSearchGuidance(catalog = {}, request = {}) {
|
|
2213
|
-
const products = Array.isArray(catalog.products) ? catalog.products : [];
|
|
2214
|
-
return {
|
|
2215
|
-
terminology: {
|
|
2216
|
-
product: "服务",
|
|
2217
|
-
variant: "购买选项",
|
|
2218
|
-
variant_id: "购买选项 ID",
|
|
2219
|
-
catalog: "服务目录",
|
|
2220
|
-
shelf: "目录清单"
|
|
2221
|
-
},
|
|
2222
|
-
search_model: "这是 ItPay 服务目录的结构化搜索,不是通用网页搜索。先把用户需求转成 query、category、provider、use-case、input-facet 等条件。",
|
|
2223
|
-
before_search: "如果不清楚目录里有什么,先读 itp docs show catalog-search --role buyer --json,并用 buyer shelf manifest/snapshot 查看目录清单。",
|
|
2224
|
-
result_count: products.length,
|
|
2225
|
-
next_step: products.length === 0
|
|
2226
|
-
? "不要硬猜或直接说没有服务;放宽过滤、换关键词,或回到目录清单重新选择 category/facet。"
|
|
2227
|
-
: products.length > 3
|
|
2228
|
-
? "候选较多;继续用 category/provider/use-case/input-facet 收窄,再比较服务内容和购买选项。"
|
|
2229
|
-
: "比较候选服务的描述、价格、输入要求、敏感等级和交付方式,向用户解释后再确认购买选项。",
|
|
2230
|
-
do_not: [
|
|
2231
|
-
"不要把搜索结果排序当成人类购买确认。",
|
|
2232
|
-
"不要发明服务 ID 或购买选项 ID。",
|
|
2233
|
-
"不要在用户确认前创建购物车或 checkout。"
|
|
2234
|
-
],
|
|
2235
|
-
request: {
|
|
2236
|
-
query: request.query || "",
|
|
2237
|
-
filters: request.filters || {}
|
|
2238
|
-
}
|
|
2239
|
-
};
|
|
2240
|
-
}
|
|
2241
|
-
|
|
2242
|
-
function catalogSelectionGuidance(selection = {}) {
|
|
2243
|
-
return {
|
|
2244
|
-
selected_purchase_option_id: selection.catalog_variant_id || selection.ucp_variant_id || null,
|
|
2245
|
-
next_step: "先向用户解释这个服务/购买选项的内容、价格、必填输入、交付方式和限制。用户确认购买后,才创建购物车。",
|
|
2246
|
-
explain_before_cart: [
|
|
2247
|
-
"服务名称和购买选项",
|
|
2248
|
-
"价格;金额字段是 minor units,CNY 100 表示 CNY 1.00",
|
|
2249
|
-
"需要用户提供或授权的输入",
|
|
2250
|
-
"交付是否人类优先、是否需要 Passkey/WebAuthn reveal"
|
|
2251
|
-
],
|
|
2252
|
-
do_not: [
|
|
2253
|
-
"不要把 catalog get 当成购买。",
|
|
2254
|
-
"不要跳过用户确认直接 cart create。",
|
|
2255
|
-
"不要选择更贵购买选项,除非用户明确要求。"
|
|
2256
|
-
]
|
|
2257
|
-
};
|
|
2258
|
-
}
|
|
2259
|
-
|
|
2260
|
-
function cartConfirmationGuidance(cart = {}, selections = []) {
|
|
2261
|
-
const lines = Array.isArray(cart.line_items) ? cart.line_items : [];
|
|
2262
|
-
return {
|
|
2263
|
-
next_step: "把购物车内容完整展示给用户确认;用户确认后才进入 checkout create。",
|
|
2264
|
-
input_source: "服务输入类型来自服务商在 product/selection metadata 或 input schema 中的声明;agent 不要自己猜精确/模糊/敏感类型。",
|
|
2265
|
-
confirm_fields: ["服务", "购买选项", "每条输入", "数量", "价格", "交付方式"],
|
|
2266
|
-
line_count: lines.length,
|
|
2267
|
-
double_check_required: lines.length > 1 || selections.length > 1,
|
|
2268
|
-
multi_line_guidance: lines.length > 1 || selections.length > 1
|
|
2269
|
-
? "购物车有多条内容;逐条向用户确认每个服务、输入、数量和价格。"
|
|
2270
|
-
: "单条购物车也要展示给用户确认;不要只说已创建。",
|
|
2271
|
-
do_not: [
|
|
2272
|
-
"不要在聊天中收集服务商声明为敏感/人类授权的输入。",
|
|
2273
|
-
"不要用模糊简称填入服务商声明为精确输入的字段。",
|
|
2274
|
-
"不要在购物车未确认前创建 checkout。"
|
|
2275
|
-
]
|
|
2276
|
-
};
|
|
2277
|
-
}
|
|
2278
|
-
|
|
2279
|
-
function noRecoverableContext() {
|
|
2280
|
-
return {
|
|
2281
|
-
found: false,
|
|
2282
|
-
guidance: "未发现可恢复的旧任务;可以按当前用户意图开始新流程。"
|
|
2283
|
-
};
|
|
2284
|
-
}
|
|
2285
|
-
|
|
2286
|
-
function recoverableBuyerContextForStatus(run = {}) {
|
|
2287
|
-
const status = String(run.status || run.phase || "").toLowerCase();
|
|
2288
|
-
const checkoutID = run.checkout?.checkout_id || run.checkout_id || null;
|
|
2289
|
-
const paymentIntentID = run.payment_intent?.payment_intent_id || run.payment_intent_id || null;
|
|
2290
|
-
const orderID = run.order?.order_id || run.order_id || run.checkout?.order_id || null;
|
|
2291
|
-
const waitingPayment = status.includes("payment") || run.checkout?.status === "waiting_user_payment" || run.payment_intent?.status === "requires_action";
|
|
2292
|
-
const waitingAuth = status.includes("auth") || run.auth?.status === "pending";
|
|
2293
|
-
const resumable = Boolean(run.run_id || checkoutID || paymentIntentID || orderID);
|
|
2294
|
-
if (!resumable && !waitingPayment && !waitingAuth) return noRecoverableContext();
|
|
2295
|
-
return recoverableContextPayload({
|
|
2296
|
-
kind: waitingAuth ? "human_auth" : waitingPayment ? "payment" : "buyer_run",
|
|
2297
|
-
run_id: run.run_id || null,
|
|
2298
|
-
checkout_id: checkoutID,
|
|
2299
|
-
payment_intent_id: paymentIntentID,
|
|
2300
|
-
order_id: orderID,
|
|
2301
|
-
status: run.status || run.phase || null,
|
|
2302
|
-
phase: run.phase || null,
|
|
2303
|
-
resume_command: run.run_id ? cliCommand("resume", "--run-id", run.run_id, "--json") : null,
|
|
2304
|
-
status_command: run.run_id ? cliCommand("status", "--refresh", "--run-id", run.run_id, "--json") : cliCommand("status", "--refresh", "--json")
|
|
2305
|
-
});
|
|
2306
|
-
}
|
|
2307
|
-
|
|
2308
|
-
function recoverableContextPayload(context = {}) {
|
|
2309
|
-
return {
|
|
2310
|
-
found: true,
|
|
2311
|
-
...context,
|
|
2312
|
-
intent_check: recoverableIntentCheckGuidance(context),
|
|
2313
|
-
safe_choices: [
|
|
2314
|
-
{ choice: "continue_old_task", when: "旧任务与当前用户意图相关,例如用户说刚刚卡住、继续刚才那单、查看刚才付款状态。", action: context.resume_command || context.status_command },
|
|
2315
|
-
{ choice: "ignore_old_task", when: "当前用户明确要做新的、不相关的任务,例如重新开一单或查询另一件事。", action: "继续当前用户的新意图,但不要把旧任务当成当前上下文。" },
|
|
2316
|
-
{ choice: "ask_human", when: "旧任务和当前意图是否相关无法判断,尤其涉及付款、退款、订单、授权。", action: "先向用户确认是否继续旧任务。" }
|
|
2317
|
-
],
|
|
2318
|
-
guidance: "发现可恢复状态后,不要自动恢复也不要自动忽略。先判断旧任务是否与当前用户意图相关:相关就继续;明确不相关就忽略;不确定就问人类。"
|
|
2319
|
-
};
|
|
2320
|
-
}
|
|
2321
|
-
|
|
2322
|
-
function recoverableIntentCheckGuidance(context = {}) {
|
|
2323
|
-
return {
|
|
2324
|
-
question: "这个可恢复状态是否属于用户当前正在要求处理的同一件事?",
|
|
2325
|
-
related_signals: ["用户提到刚刚/上一单/卡住/继续/付款状态/退款", "checkout_id、order_id、payment_intent_id 与用户提供的信息一致"],
|
|
2326
|
-
unrelated_signals: ["用户明确要求开新单或处理另一件商品/订单", "旧状态只是本机遗留,当前请求不依赖它"],
|
|
2327
|
-
default_when_unsure: "ask_human",
|
|
2328
|
-
context_keys_to_compare: Object.fromEntries(Object.entries(context).filter(([, value]) => value !== null && value !== undefined && value !== ""))
|
|
2329
|
-
};
|
|
2330
|
-
}
|
|
2331
|
-
|
|
2332
|
-
export { buyerBuy, buyer, buyerCatalogSearchFilters, resolveBuyerCatalogSelection, resolveBuyerCatalogSelections, getBuyerUCPProduct, selectionFromUCPProduct, createBuyerCart, createBuyerCartFromSelections, buyerCartSelectionIDs, buyerCartQuantities, buyerLineInputForSelection, parseBuyerInputs, splitInputParts, getBuyerCart, addBuyerCartLineItem, removeBuyerCartLineItem, createBuyerCheckoutFromCart, requiredDeliveryContactFields, createBuyerPaymentIntent, waitBuyerCheckoutAuth, maybeClaimBuyerSessionFromAuthAction, buyerSessionClaimStatus, maybeClaimBuyerSessionForCheckout, ensureBuyerSessionForVaultAccess, parseBuyerAuthActionURL, buyerAuthActionCandidateURLs, rememberCoreAuthAction, readCoreAuthAction, forgetCoreAuthAction, getBuyerCheckout, createBuyerRefund, getBuyerOrderDetail, listBuyerRefunds, getBuyerRefund, cancelBuyerRefund, getBuyerPaymentIntent, refreshBuyerPaymentQR, normalizeQRRefreshReasonForCLI, waitBuyerPayment, waitBuyerDelivery, isBuyerDeliveryComplete, buyerRunOutput, normalizeBuyerMoneyFields, buyerDocsFor, buyerDocRef, buyerDeliveryListOutput, deliveryAwareAgentNextActions, optionalAgentReadGrantHint, buyerAuthStatusOutput, listBuyerAgentReadGrants, readBuyerAgentReadGrant, readBuyerVaultArtifactGrant, noRecoverableContext, recoverableBuyerContextForStatus, recoverableContextPayload, recoverableIntentCheckGuidance };
|