@itpay/cli 2.0.21 → 2.0.22
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/dist/src/client/backend.js +3 -1
- package/dist/src/commands/buy.js +18 -5
- package/dist/src/commands/checkout.js +16 -7
- package/dist/src/commands/checkout_handoff.js +5 -7
- package/dist/src/commands/install.js +1 -1
- package/dist/src/commands/pay.js +15 -11
- package/dist/src/commands/services.js +19 -6
- package/dist/src/main.js +6 -0
- package/dist/src/render/locale.js +27 -0
- package/dist/src/render/plain_chat.js +4 -0
- package/dist/src/state/config.js +2 -2
- package/docs/agent/buyer/payment-flow.json +1 -1
- package/docs/agent/buyer/render-hosts.json +3 -3
- package/docs/cli-reference/agent-types.md +5 -5
- package/docs/cli-reference/commands/buy.md +1 -1
- package/docs/cli-reference/commands/checkout.md +1 -1
- package/docs/cli-reference/commands/install.md +1 -1
- package/docs/cli-reference/commands/pay.md +1 -1
- package/docs/cli-reference/commands/services/checkout.md +3 -3
- package/package.json +1 -1
- package/skills/itpay/SKILL.md +2 -2
|
@@ -35,8 +35,10 @@ export class BackendClient {
|
|
|
35
35
|
createCheckout(input, idempotencyKey) {
|
|
36
36
|
return this.http.post("/v1/checkouts", input, idempotencyKey ? { idempotencyKey } : undefined);
|
|
37
37
|
}
|
|
38
|
-
getCheckoutPresentation(checkoutID, displayToken) {
|
|
38
|
+
getCheckoutPresentation(checkoutID, displayToken, locale) {
|
|
39
39
|
const qs = new URLSearchParams({ display_token: displayToken });
|
|
40
|
+
if (locale)
|
|
41
|
+
qs.set("locale", locale);
|
|
40
42
|
return this.http.get(`/v1/checkouts/${encodeURIComponent(checkoutID)}/presentation?${qs}`);
|
|
41
43
|
}
|
|
42
44
|
// --- Payment intents ---
|
package/dist/src/commands/buy.js
CHANGED
|
@@ -13,6 +13,7 @@ import { validateContext } from "../state/client_context.js";
|
|
|
13
13
|
import { dispatchRender } from "../render/index.js";
|
|
14
14
|
import { platformKeyForHost } from "../render/plan.js";
|
|
15
15
|
import { ensureIdeImageAttach } from "../render/ide.js";
|
|
16
|
+
import { localizeCardURL, normalizeCardLocale } from "../render/locale.js";
|
|
16
17
|
import { buildAgentChatHandoff } from "../render/markdown.js";
|
|
17
18
|
import { formatMoney } from "../render/output.js";
|
|
18
19
|
import { CommandContractError, writeCommandEnvelope } from "./guidance.js";
|
|
@@ -68,8 +69,10 @@ export async function runBuy(backend, config, options) {
|
|
|
68
69
|
});
|
|
69
70
|
const checkoutID = checkout.checkout.checkout_id;
|
|
70
71
|
const displayToken = checkout.display_token;
|
|
72
|
+
const locale = normalizeCardLocale(options.locale);
|
|
71
73
|
const checkoutURL = tokenizedCheckoutURL(checkout.checkout_url, displayToken, checkout.qr_payload);
|
|
72
|
-
const
|
|
74
|
+
const cardURL = localizeCardURL(absolutePublicURL(config.baseURL, checkout.card_url ?? fallbackCardURL(config.baseURL, checkoutID, displayToken)), locale);
|
|
75
|
+
const qrPNGURL = localizeCardURL(absolutePublicURL(config.baseURL, checkout.card_png_url ?? checkout.qr_png_url ?? fallbackCardPNGURL(config.baseURL, checkoutID, displayToken)), locale);
|
|
73
76
|
const orderItems = cart.items.map((item) => ({
|
|
74
77
|
title: item.title,
|
|
75
78
|
quantity: item.quantity,
|
|
@@ -106,12 +109,14 @@ export async function runBuy(backend, config, options) {
|
|
|
106
109
|
host: options.host,
|
|
107
110
|
checkoutID,
|
|
108
111
|
checkoutURL,
|
|
112
|
+
cardURL,
|
|
109
113
|
displayToken,
|
|
110
114
|
qrPayload: checkout.qr_payload,
|
|
111
115
|
nextAction: checkout.checkout.next_action,
|
|
112
116
|
orderItems,
|
|
113
117
|
orderCurrency: checkout.checkout.currency,
|
|
114
118
|
...(options.agentType ? { agentType: options.agentType } : {}),
|
|
119
|
+
locale,
|
|
115
120
|
};
|
|
116
121
|
if (qrPNGURL)
|
|
117
122
|
planInput.qrPNGURL = qrPNGURL;
|
|
@@ -138,7 +143,7 @@ export async function runBuy(backend, config, options) {
|
|
|
138
143
|
displayToken,
|
|
139
144
|
plan,
|
|
140
145
|
waitStatus,
|
|
141
|
-
|
|
146
|
+
qrPNGURL,
|
|
142
147
|
...(paymentIntent ? { paymentIntent } : {}),
|
|
143
148
|
...(options.agentType ? { agentType: options.agentType } : {}),
|
|
144
149
|
...(options.target ? { target: options.target } : {}),
|
|
@@ -197,7 +202,7 @@ function buildBuyEnvelope(input) {
|
|
|
197
202
|
}
|
|
198
203
|
const presentationHandoff = buildCheckoutHandoff({
|
|
199
204
|
platform,
|
|
200
|
-
url: input.checkoutURL,
|
|
205
|
+
url: input.plan.linkOnlyURL ?? input.checkoutURL,
|
|
201
206
|
amount,
|
|
202
207
|
plan: input.plan,
|
|
203
208
|
...(input.agentType ? { agentType: input.agentType } : {}),
|
|
@@ -213,7 +218,7 @@ function buildBuyEnvelope(input) {
|
|
|
213
218
|
result,
|
|
214
219
|
handoff: presentationHandoff.handoff,
|
|
215
220
|
instruction: presentationHandoff.instruction,
|
|
216
|
-
next: { command: `itpay checkout --id ${input.checkoutID} --token ${input.displayToken} --json`, reason: "稍后查询同一笔 Checkout 状态" },
|
|
221
|
+
next: { command: input.plan.afterActionCommand ?? `itpay checkout --id ${input.checkoutID} --token ${input.displayToken} --json`, reason: "稍后查询同一笔 Checkout 状态" },
|
|
217
222
|
recovery: [],
|
|
218
223
|
};
|
|
219
224
|
}
|
|
@@ -242,7 +247,7 @@ async function waitForPaymentSSE(backend, checkoutID, displayToken, timeoutSec)
|
|
|
242
247
|
export function buildCheckoutQRPlan(input) {
|
|
243
248
|
const summary = `Scan the QR or open ${input.checkoutURL} to start the human checkout flow.`;
|
|
244
249
|
const isPayment = input.paymentIntentID != null;
|
|
245
|
-
const afterCommand = qualifyItPayCommand(`itpay checkout --id ${input.checkoutID} --token ${input.displayToken} --json`, input.agentType);
|
|
250
|
+
const afterCommand = qualifyItPayCommand(`itpay checkout --id ${input.checkoutID} --token ${input.displayToken}${input.locale === "en" ? " --locale en" : ""} --json`, input.agentType);
|
|
246
251
|
const platform = {
|
|
247
252
|
text: summary,
|
|
248
253
|
links: [
|
|
@@ -262,6 +267,7 @@ export function buildCheckoutQRPlan(input) {
|
|
|
262
267
|
host: input.host,
|
|
263
268
|
summary,
|
|
264
269
|
url: input.qrPayload,
|
|
270
|
+
...(input.agentType?.trim().toLowerCase() === "workbuddy" && input.cardURL ? { linkOnlyURL: input.cardURL } : {}),
|
|
265
271
|
preferredQRSources: [input.qrPNGURL ?? input.qrPayload],
|
|
266
272
|
checkoutID: input.checkoutID,
|
|
267
273
|
platform,
|
|
@@ -280,6 +286,13 @@ export function buildCheckoutQRPlan(input) {
|
|
|
280
286
|
plan.paymentIntentID = input.paymentIntentID;
|
|
281
287
|
return plan;
|
|
282
288
|
}
|
|
289
|
+
function fallbackCardURL(baseURL, checkoutID, displayToken) {
|
|
290
|
+
const root = baseURL.replace(/\/$/, "");
|
|
291
|
+
return `${root}/v1/checkouts/${encodeURIComponent(checkoutID)}/card?display_token=${encodeURIComponent(displayToken)}`;
|
|
292
|
+
}
|
|
293
|
+
function fallbackCardPNGURL(baseURL, checkoutID, displayToken) {
|
|
294
|
+
return `${fallbackCardURL(baseURL, checkoutID, displayToken)}.png`.replace("/card?", "/card.png?");
|
|
295
|
+
}
|
|
283
296
|
// --- contact field interaction ---
|
|
284
297
|
function findMissingContactFields(contact, fields) {
|
|
285
298
|
return fields.filter((field) => {
|
|
@@ -4,12 +4,14 @@ import { ensureIdeImageAttach } from "../render/ide.js";
|
|
|
4
4
|
import { buildAgentChatHandoff } from "../render/markdown.js";
|
|
5
5
|
import { platformKeyForHost } from "../render/plan.js";
|
|
6
6
|
import { renderTerminalQR } from "../render/qr.js";
|
|
7
|
+
import { localizeCardURL, normalizeCardLocale } from "../render/locale.js";
|
|
7
8
|
import { DEFAULT_BASE_URL } from "../state/config.js";
|
|
8
9
|
import { buildCheckoutQRPlan } from "./buy.js";
|
|
9
10
|
import { buildCheckoutHandoff, shouldPrepareLocalCheckoutImage } from "./checkout_handoff.js";
|
|
10
11
|
import { writeCommandEnvelope } from "./guidance.js";
|
|
11
12
|
export async function runCheckoutPresentation(backend, options) {
|
|
12
|
-
const
|
|
13
|
+
const locale = normalizeCardLocale(options.locale);
|
|
14
|
+
const presentation = await backend.getCheckoutPresentation(options.checkoutID, options.displayToken, locale === "en" ? locale : undefined);
|
|
13
15
|
const host = options.host ?? "terminal";
|
|
14
16
|
if (!checkoutNeedsHumanHandoff(presentation.checkout.status)) {
|
|
15
17
|
const envelope = terminalCheckoutEnvelope(presentation);
|
|
@@ -21,15 +23,18 @@ export async function runCheckoutPresentation(backend, options) {
|
|
|
21
23
|
return;
|
|
22
24
|
}
|
|
23
25
|
const checkoutURL = checkoutPageURL(options.baseURL, options.checkoutID, options.displayToken);
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
+
const cardURL = localizeCardURL(absolutePublicURL(options.baseURL, presentation.card_url ?? checkoutCardURL(options.baseURL, options.checkoutID, options.displayToken)), locale);
|
|
27
|
+
const qrPNGURL = absolutePublicURL(options.baseURL, presentation.card_png_url ?? presentation.qr_png_url ?? checkoutCardPNGURL(options.baseURL, options.checkoutID, options.displayToken));
|
|
28
|
+
const localizedPNGURL = localizeCardURL(qrPNGURL, locale);
|
|
29
|
+
const nextCommand = `itpay checkout --id ${options.checkoutID} --token ${options.displayToken}${locale === "en" ? " --locale en" : ""} --json`;
|
|
26
30
|
const plan = buildCheckoutQRPlan({
|
|
27
31
|
host,
|
|
28
32
|
checkoutID: options.checkoutID,
|
|
29
33
|
checkoutURL,
|
|
34
|
+
cardURL,
|
|
30
35
|
displayToken: options.displayToken,
|
|
31
36
|
qrPayload: checkoutURL,
|
|
32
|
-
qrPNGURL,
|
|
37
|
+
qrPNGURL: localizedPNGURL,
|
|
33
38
|
nextAction: presentation.checkout.next_action,
|
|
34
39
|
orderItems: presentation.items.map((item) => ({
|
|
35
40
|
title: item.title,
|
|
@@ -62,7 +67,7 @@ function pendingCheckoutEnvelope(presentation, checkoutURL, plan, nextCommand, a
|
|
|
62
67
|
const amount = formatMoney(presentation.checkout.amount_minor, presentation.checkout.currency);
|
|
63
68
|
const presentationHandoff = buildCheckoutHandoff({
|
|
64
69
|
platform,
|
|
65
|
-
url: checkoutURL,
|
|
70
|
+
url: plan.linkOnlyURL ?? checkoutURL,
|
|
66
71
|
amount,
|
|
67
72
|
plan,
|
|
68
73
|
...(agentType ? { agentType } : {}),
|
|
@@ -138,9 +143,13 @@ function checkoutPageURL(baseURL, checkoutID, displayToken) {
|
|
|
138
143
|
const root = publicRoot(baseURL);
|
|
139
144
|
return `${root}/checkout/${encodeURIComponent(checkoutID)}?display_token=${encodeURIComponent(displayToken)}`;
|
|
140
145
|
}
|
|
141
|
-
function
|
|
146
|
+
function checkoutCardURL(baseURL, checkoutID, displayToken) {
|
|
142
147
|
const root = publicRoot(baseURL);
|
|
143
|
-
return `${root}/v1/checkouts/${encodeURIComponent(checkoutID)}/
|
|
148
|
+
return `${root}/v1/checkouts/${encodeURIComponent(checkoutID)}/card?display_token=${encodeURIComponent(displayToken)}`;
|
|
149
|
+
}
|
|
150
|
+
function checkoutCardPNGURL(baseURL, checkoutID, displayToken) {
|
|
151
|
+
const root = publicRoot(baseURL);
|
|
152
|
+
return `${root}/v1/checkouts/${encodeURIComponent(checkoutID)}/card.png?display_token=${encodeURIComponent(displayToken)}`;
|
|
144
153
|
}
|
|
145
154
|
function publicRoot(baseURL) {
|
|
146
155
|
return (baseURL ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
@@ -7,13 +7,14 @@ export function isWorkBuddyPlainChat(agentType, platform) {
|
|
|
7
7
|
}
|
|
8
8
|
export function buildCheckoutHandoff(input) {
|
|
9
9
|
const handoff = { url: input.url };
|
|
10
|
+
const workBuddyLinkOnly = isWorkBuddyPlainChat(input.agentType, input.platform);
|
|
10
11
|
if (input.platform === "markdown") {
|
|
11
12
|
if (input.localPath)
|
|
12
13
|
handoff.qr_local_path = input.localPath;
|
|
13
14
|
if (input.markdown)
|
|
14
15
|
handoff.markdown = input.markdown;
|
|
15
16
|
}
|
|
16
|
-
else if (input.platform === "plain_chat" && input.qrImageURL) {
|
|
17
|
+
else if (input.platform === "plain_chat" && input.qrImageURL && !workBuddyLinkOnly) {
|
|
17
18
|
handoff.qr_image_url = input.qrImageURL;
|
|
18
19
|
}
|
|
19
20
|
else if (input.platform === "telegram" && input.qrImageURL) {
|
|
@@ -24,15 +25,12 @@ export function buildCheckoutHandoff(input) {
|
|
|
24
25
|
}
|
|
25
26
|
return {
|
|
26
27
|
handoff,
|
|
27
|
-
instruction: checkoutHandoffInstruction(input.agentType, input.platform, input.amount
|
|
28
|
+
instruction: checkoutHandoffInstruction(input.agentType, input.platform, input.amount),
|
|
28
29
|
};
|
|
29
30
|
}
|
|
30
|
-
function checkoutHandoffInstruction(agentType, platform, amount
|
|
31
|
+
function checkoutHandoffInstruction(agentType, platform, amount) {
|
|
31
32
|
if (isWorkBuddyPlainChat(agentType, platform)) {
|
|
32
|
-
|
|
33
|
-
return `Backend 尚未确认付款,且本次没有返回可展示的二维码。向用户说明金额 ${amount} 并发送 handoff.url,然后停止等待。不要调用 present_files,不要检查本地文件,不要下载或重建二维码,不要调用 pay,不要创建新 Checkout、Payment Intent 或 Execution。只有用户明确表示已付款或要求查询状态时,才执行 next.command;用户的话不是付款成功证明。`;
|
|
34
|
-
}
|
|
35
|
-
return `Backend 尚未确认付款。读取 handoff.qr_image_url 的完整字符串,原样作为 files 数组唯一元素调用 present_files({ files: ["<完整 qr_image_url>"] });确认右侧二维码预览已打开后,向用户说明金额 ${amount} 并发送 handoff.url,然后停止等待。如果 present_files 失败,只发送 handoff.url 并说明二维码预览未打开,然后停止。不要检查本地文件,不要下载或重建二维码,不要调用 pay,不要创建新 Checkout、Payment Intent 或 Execution。只有用户明确表示已付款或要求查询状态时,才执行 next.command;用户的话不是付款成功证明。`;
|
|
33
|
+
return `Backend 尚未确认付款。向用户说明金额 ${amount},直接发送并打开 handoff.url,然后停止等待。不要调用 present_files,不要检查本地文件,不要下载或重建二维码,不要调用 pay,不要创建新 Checkout、Payment Intent 或 Execution。只有用户明确表示已付款或要求查询状态时,才执行 next.command;用户的话不是付款成功证明。`;
|
|
36
34
|
}
|
|
37
35
|
if (platform === "markdown") {
|
|
38
36
|
return `Backend 尚未确认付款。把 handoff.markdown 原样发送到当前桌面对话,确认二维码、付款链接和金额 ${amount} 均已实际对用户可见,然后停止等待。不要创建新 Checkout、Payment Intent 或 Execution;只有用户明确表示已付款或要求查询状态时,才执行 next.command;用户的话不是付款成功证明。`;
|
|
@@ -15,7 +15,7 @@ const INSTRUCTIONS = {
|
|
|
15
15
|
"codex-cli": "在 Codex CLI 中始终传这个 Agent Type;付款交接只应显示在用户可见终端。",
|
|
16
16
|
"claude-code-desktop": "在 Claude Code Desktop 中始终传这个 Agent Type;付款时把返回的二维码和链接实际展示到当前对话。",
|
|
17
17
|
"claude-code-cli": "在 Claude Code CLI 中始终传这个 Agent Type;付款交接只应显示在用户可见终端。",
|
|
18
|
-
workbuddy: "在 WorkBuddy 中始终传这个 Agent Type。运行会持久化 ~/.itpay-v3 的 ItPay Bash 命令前,把 Shell 工具参数 dangerouslyDisableSandbox 设为 true;用户拒绝权限时停止,不要切换 Node、删除 identity 或处理 lock。Checkout
|
|
18
|
+
workbuddy: "在 WorkBuddy 中始终传这个 Agent Type。运行会持久化 ~/.itpay-v3 的 ItPay Bash 命令前,把 Shell 工具参数 dangerouslyDisableSandbox 设为 true;用户拒绝权限时停止,不要切换 Node、删除 identity 或处理 lock。Checkout 只返回已渲染的付款 Card Link;直接打开 handoff.url,不要调用 present_files,也不要检查本地二维码文件。",
|
|
19
19
|
"kimi-code": "保持 kimi-code Agent Type,使用标准 CLI 命令和终端付款交接;不要切换到其他平台身份。",
|
|
20
20
|
openclaw: "保持 openclaw Agent Type;每个展示命令都从当前可信会话上下文显式传 --host,IM 入口同时传 --target。Telegram 使用返回的原生 message action,其他入口展示标准二维码和付款链接。",
|
|
21
21
|
};
|
package/dist/src/commands/pay.js
CHANGED
|
@@ -20,10 +20,18 @@ function payEnvelope(intent, options) {
|
|
|
20
20
|
const terminal = ["failed", "expired", "refunded"].includes(intent.status);
|
|
21
21
|
const verified = intent.status === "verified" || intent.status === "partially_refunded";
|
|
22
22
|
const handoff = {};
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
const workBuddyLinkOnly = isWorkBuddyPlainChat(options.agentType, platformKeyForHost(options.host));
|
|
24
|
+
if (!terminal && !verified && workBuddyLinkOnly) {
|
|
25
|
+
const url = intent.action?.mobile_wallet_url ?? intent.action?.qr_image_url;
|
|
26
|
+
if (url)
|
|
27
|
+
handoff.url = url;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
if (!terminal && !verified && intent.action?.qr_image_url)
|
|
31
|
+
handoff.qr_image_url = intent.action.qr_image_url;
|
|
32
|
+
if (!terminal && !verified && intent.action?.mobile_wallet_url)
|
|
33
|
+
handoff.mobile_wallet_url = intent.action.mobile_wallet_url;
|
|
34
|
+
}
|
|
27
35
|
const hasAction = Object.keys(handoff).length > 0;
|
|
28
36
|
const amount = formatMoney(intent.amount_minor, intent.currency);
|
|
29
37
|
return {
|
|
@@ -35,7 +43,7 @@ function payEnvelope(intent, options) {
|
|
|
35
43
|
amount,
|
|
36
44
|
},
|
|
37
45
|
...(hasAction ? { handoff } : {}),
|
|
38
|
-
instruction: payInstruction(options, verified, terminal, hasAction,
|
|
46
|
+
instruction: payInstruction(options, verified, terminal, hasAction, amount),
|
|
39
47
|
next: {
|
|
40
48
|
command: `itpay checkout --id ${options.checkoutID} --token ${options.displayToken} --json`,
|
|
41
49
|
reason: verified ? "读取权威订单和履约状态" : "读取同一 Checkout 的权威付款状态",
|
|
@@ -43,7 +51,7 @@ function payEnvelope(intent, options) {
|
|
|
43
51
|
recovery: [],
|
|
44
52
|
};
|
|
45
53
|
}
|
|
46
|
-
function payInstruction(options, verified, terminal, hasAction,
|
|
54
|
+
function payInstruction(options, verified, terminal, hasAction, amount) {
|
|
47
55
|
if (verified)
|
|
48
56
|
return "付款已确认;不要再次展示付款动作,继续读取同一 Checkout。";
|
|
49
57
|
if (terminal)
|
|
@@ -52,11 +60,7 @@ function payInstruction(options, verified, terminal, hasAction, hasQR, hasWallet
|
|
|
52
60
|
return "Payment Intent 尚未返回可展示动作;不要猜测渠道链接,回到同一 Checkout 查询。";
|
|
53
61
|
const platform = platformKeyForHost(options.host);
|
|
54
62
|
if (isWorkBuddyPlainChat(options.agentType, platform)) {
|
|
55
|
-
|
|
56
|
-
return `这是受控逃生入口。读取 handoff.qr_image_url 的完整字符串,原样作为 files 数组唯一元素调用 present_files({ files: ["<完整 qr_image_url>"] });右侧预览打开后说明金额 ${amount}、发送 handoff.mobile_wallet_url 并停止等待。如果 present_files 失败,发送 handoff.qr_image_url 和 handoff.mobile_wallet_url 并说明二维码预览未打开,然后停止。不要立即查询、创建替代 Checkout 或 Payment Intent。`;
|
|
57
|
-
if (hasQR)
|
|
58
|
-
return `这是受控逃生入口。读取 handoff.qr_image_url 的完整字符串,原样作为 files 数组唯一元素调用 present_files({ files: ["<完整 qr_image_url>"] });右侧预览打开后说明金额 ${amount} 并停止等待。如果 present_files 失败,把 handoff.qr_image_url 作为可点击链接发送给用户并说明二维码预览未打开,然后停止。不要立即查询、创建替代 Checkout 或 Payment Intent。`;
|
|
59
|
-
return `这是受控逃生入口。说明金额 ${amount},把 handoff.mobile_wallet_url 作为可点击链接发送给用户,然后停止等待;不要立即查询、创建替代 Checkout 或 Payment Intent。`;
|
|
63
|
+
return `这是受控逃生入口。说明金额 ${amount},把 handoff.url 作为可点击链接发送给用户,然后停止等待。不要调用 present_files,不要立即查询、创建替代 Checkout 或 Payment Intent。`;
|
|
60
64
|
}
|
|
61
65
|
if (options.host === "codex" || options.host === "claude-code")
|
|
62
66
|
return "这是受控逃生入口;把 handoff 中的二维码或钱包链接实际发到当前桌面对话,然后停止等待。";
|
|
@@ -3,6 +3,7 @@ import { validateContext } from "../state/client_context.js";
|
|
|
3
3
|
import { dispatchRender } from "../render/index.js";
|
|
4
4
|
import { ensureIdeImageAttach } from "../render/ide.js";
|
|
5
5
|
import { buildCheckoutHandoff, shouldPrepareLocalCheckoutImage } from "./checkout_handoff.js";
|
|
6
|
+
import { localizeCardURL, normalizeCardLocale } from "../render/locale.js";
|
|
6
7
|
import { buildAgentChatHandoff } from "../render/markdown.js";
|
|
7
8
|
import { platformKeyForHost } from "../render/plan.js";
|
|
8
9
|
import { renderTerminalQR } from "../render/qr.js";
|
|
@@ -413,14 +414,18 @@ export async function runServicesCheckout(backend, config, serviceExecutionID, c
|
|
|
413
414
|
const checkout = response.checkout;
|
|
414
415
|
const checkoutID = checkout.checkout.checkout_id;
|
|
415
416
|
const displayToken = checkout.display_token;
|
|
417
|
+
const locale = normalizeCardLocale(options.locale);
|
|
416
418
|
const checkoutURL = tokenizedCheckoutURL(checkout.checkout_url, displayToken, checkout.qr_payload);
|
|
419
|
+
const cardURL = localizeCardURL(absolutePublicURL(config.baseURL, checkout.card_url ?? fallbackCardURL(config.baseURL, checkoutID, displayToken)), locale);
|
|
420
|
+
const cardPNGURL = localizeCardURL(absolutePublicURL(config.baseURL, checkout.card_png_url ?? checkout.qr_png_url ?? fallbackCardPNGURL(config.baseURL, checkoutID, displayToken)), locale);
|
|
417
421
|
const plan = buildCheckoutQRPlan({
|
|
418
422
|
host,
|
|
419
423
|
checkoutID,
|
|
420
424
|
checkoutURL,
|
|
425
|
+
cardURL,
|
|
421
426
|
displayToken,
|
|
422
427
|
qrPayload: checkout.qr_payload,
|
|
423
|
-
|
|
428
|
+
qrPNGURL: cardPNGURL,
|
|
424
429
|
nextAction: checkout.checkout.next_action,
|
|
425
430
|
orderItems: response.cart.items.map((item) => ({
|
|
426
431
|
title: item.title,
|
|
@@ -430,6 +435,7 @@ export async function runServicesCheckout(backend, config, serviceExecutionID, c
|
|
|
430
435
|
})),
|
|
431
436
|
orderCurrency: checkout.checkout.currency,
|
|
432
437
|
...(options.agentType ? { agentType: options.agentType } : {}),
|
|
438
|
+
locale,
|
|
433
439
|
});
|
|
434
440
|
options.persistHandoff?.({
|
|
435
441
|
serviceExecutionID,
|
|
@@ -458,7 +464,7 @@ export async function runServicesCheckout(backend, config, serviceExecutionID, c
|
|
|
458
464
|
...(options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}),
|
|
459
465
|
});
|
|
460
466
|
}
|
|
461
|
-
const envelope = buildServicesCheckoutEnvelope(response, checkoutURL, plan,
|
|
467
|
+
const envelope = buildServicesCheckoutEnvelope(response, checkoutURL, plan, options.agentType, options.target);
|
|
462
468
|
const plainResult = [
|
|
463
469
|
`service_execution_id: ${response.binding.service_execution_id}`,
|
|
464
470
|
`checkout_id: ${checkoutID}`,
|
|
@@ -939,18 +945,18 @@ function parseValue(value) {
|
|
|
939
945
|
return Number(value);
|
|
940
946
|
return value;
|
|
941
947
|
}
|
|
942
|
-
function buildServicesCheckoutEnvelope(response, checkoutURL, plan,
|
|
948
|
+
function buildServicesCheckoutEnvelope(response, checkoutURL, plan, agentType, target) {
|
|
943
949
|
const checkout = response.checkout;
|
|
944
950
|
const platform = platformKeyForHost(plan.host);
|
|
945
951
|
const amount = formatMoney(checkout.checkout.amount_minor, checkout.checkout.currency);
|
|
946
952
|
const presentationHandoff = buildCheckoutHandoff({
|
|
947
953
|
platform,
|
|
948
|
-
url: checkoutURL,
|
|
954
|
+
url: plan.linkOnlyURL ?? checkoutURL,
|
|
949
955
|
amount,
|
|
950
956
|
plan,
|
|
951
957
|
...(agentType ? { agentType } : {}),
|
|
952
958
|
...(target ? { target } : {}),
|
|
953
|
-
...(
|
|
959
|
+
...(plan.preferredQRSources[0] ? { qrImageURL: plan.preferredQRSources[0] } : {}),
|
|
954
960
|
...(plan.ideImageAttach?.status === "downloaded" && plan.ideImageAttach.localPath
|
|
955
961
|
? { localPath: plan.ideImageAttach.localPath }
|
|
956
962
|
: {}),
|
|
@@ -968,12 +974,19 @@ function buildServicesCheckoutEnvelope(response, checkoutURL, plan, baseURL, age
|
|
|
968
974
|
handoff: presentationHandoff.handoff,
|
|
969
975
|
instruction: presentationHandoff.instruction,
|
|
970
976
|
next: {
|
|
971
|
-
command: `itpay checkout --id ${checkout.checkout.checkout_id} --token ${checkout.display_token} --json`,
|
|
977
|
+
command: plan.afterActionCommand ?? `itpay checkout --id ${checkout.checkout.checkout_id} --token ${checkout.display_token} --json`,
|
|
972
978
|
reason: "仅在用户完成付款操作或要求查询后,读取同一 Checkout 的权威状态",
|
|
973
979
|
},
|
|
974
980
|
recovery: [],
|
|
975
981
|
};
|
|
976
982
|
}
|
|
983
|
+
function fallbackCardURL(baseURL, checkoutID, displayToken) {
|
|
984
|
+
const root = baseURL.replace(/\/$/, "");
|
|
985
|
+
return `${root}/v1/checkouts/${encodeURIComponent(checkoutID)}/card?display_token=${encodeURIComponent(displayToken)}`;
|
|
986
|
+
}
|
|
987
|
+
function fallbackCardPNGURL(baseURL, checkoutID, displayToken) {
|
|
988
|
+
return `${fallbackCardURL(baseURL, checkoutID, displayToken)}.png`.replace("/card?", "/card.png?");
|
|
989
|
+
}
|
|
977
990
|
function checkoutCapabilityID(response, fallback = "") {
|
|
978
991
|
return response.capability_id || fallback;
|
|
979
992
|
}
|
package/dist/src/main.js
CHANGED
|
@@ -691,6 +691,7 @@ program
|
|
|
691
691
|
.option("--require-contact <fields>", "comma-separated required contact fields: email,phone")
|
|
692
692
|
.option("--qr-format <format>", "unicode|utf8|ansi|terminal")
|
|
693
693
|
.option("--qr-file <path>", "explicit QR file path")
|
|
694
|
+
.option("--locale <locale>", "payment card language: zh-CN|en", "zh-CN")
|
|
694
695
|
.option("--pay", "also create a payment intent and optionally wait for verification")
|
|
695
696
|
.option("--method <alipay|wechatpay>", "payment method for --pay", "alipay")
|
|
696
697
|
.option("--no-wait", "do not wait for payment verification after --pay")
|
|
@@ -752,6 +753,7 @@ program
|
|
|
752
753
|
...(requiredContactFields ? { requiredContactFields } : {}),
|
|
753
754
|
...(options.qrFormat ? { qrFormat: options.qrFormat } : {}),
|
|
754
755
|
...(options.qrFile ? { qrFilePath: options.qrFile } : {}),
|
|
756
|
+
locale: options.locale,
|
|
755
757
|
...(options.pay ? { pay: true, payMethod: options.method, noWait: options.wait === false, payTimeoutSec: timeout } : {}),
|
|
756
758
|
...(jsonOutput ? { jsonOutput: true } : {}),
|
|
757
759
|
};
|
|
@@ -779,6 +781,7 @@ program
|
|
|
779
781
|
.option("--target <target>")
|
|
780
782
|
.option("--id <checkout_id>")
|
|
781
783
|
.option("--token <display_token>")
|
|
784
|
+
.option("--locale <locale>", "payment card language: zh-CN|en", "zh-CN")
|
|
782
785
|
.option("--json", "output compact JSON")
|
|
783
786
|
.action(async (options) => {
|
|
784
787
|
const config = loadConfig();
|
|
@@ -800,6 +803,7 @@ program
|
|
|
800
803
|
...(options.target ? { target: options.target } : {}),
|
|
801
804
|
...(config.agentType ? { agentType: config.agentType } : {}),
|
|
802
805
|
baseURL: config.baseURL,
|
|
806
|
+
locale: options.locale,
|
|
803
807
|
jsonOutput: Boolean(options.json),
|
|
804
808
|
});
|
|
805
809
|
}
|
|
@@ -1166,6 +1170,7 @@ services
|
|
|
1166
1170
|
.option("--target <target>", "chat id / channel id / open id for IM hosts")
|
|
1167
1171
|
.option("--qr-format <format>", "unicode|utf8|ansi|terminal")
|
|
1168
1172
|
.option("--qr-file <path>", "explicit QR file path")
|
|
1173
|
+
.option("--locale <locale>", "payment card language: zh-CN|en", "zh-CN")
|
|
1169
1174
|
.option("--json", "output JSON instead of terminal text")
|
|
1170
1175
|
.action(async (serviceExecutionID, options) => {
|
|
1171
1176
|
const config = loadConfig();
|
|
@@ -1182,6 +1187,7 @@ services
|
|
|
1182
1187
|
...(options.target ? { target: options.target } : {}),
|
|
1183
1188
|
...(options.qrFormat ? { qrFormat: options.qrFormat } : {}),
|
|
1184
1189
|
...(options.qrFile ? { qrFilePath: options.qrFile } : {}),
|
|
1190
|
+
locale: options.locale,
|
|
1185
1191
|
jsonOutput: Boolean(options.json),
|
|
1186
1192
|
persistHandoff: (handoff) => {
|
|
1187
1193
|
session.rememberCheckout({
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export function normalizeCardLocale(value) {
|
|
2
|
+
switch (value?.trim().toLowerCase()) {
|
|
3
|
+
case undefined:
|
|
4
|
+
case "":
|
|
5
|
+
case "zh":
|
|
6
|
+
case "zh-cn":
|
|
7
|
+
case "zh_cn":
|
|
8
|
+
case "cn":
|
|
9
|
+
return "zh-CN";
|
|
10
|
+
case "en":
|
|
11
|
+
case "en-us":
|
|
12
|
+
case "en_us":
|
|
13
|
+
return "en";
|
|
14
|
+
default:
|
|
15
|
+
throw new Error(`unsupported card locale: ${value}; expected zh-CN or en`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function localizeCardURL(value, locale) {
|
|
19
|
+
try {
|
|
20
|
+
const parsed = new URL(value);
|
|
21
|
+
parsed.searchParams.set("locale", locale);
|
|
22
|
+
return parsed.toString();
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
import { ideImageAttachBlock } from "./ide.js";
|
|
7
7
|
export function renderPlainChat(plan, options = {}) {
|
|
8
8
|
const out = options.output ?? ((line) => process.stdout.write(line));
|
|
9
|
+
if (plan.linkOnlyURL) {
|
|
10
|
+
out(`${plan.summary}\nopen: ${plan.linkOnlyURL}\n`);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
9
13
|
const brand = plan.preferredQRSources.find((src) => src.length > 0);
|
|
10
14
|
const lines = [];
|
|
11
15
|
lines.push(plan.summary);
|
package/dist/src/state/config.js
CHANGED
|
@@ -12,8 +12,8 @@ import { DeviceAuthority } from "./device_authority.js";
|
|
|
12
12
|
import { OperationJournal } from "./operation_journal.js";
|
|
13
13
|
export const DEFAULT_BASE_URL = "https://app.itpay.ai";
|
|
14
14
|
export const DEV_BASE_URL = "https://dev.itpay.ai";
|
|
15
|
-
export const CLI_VERSION = "2.0.
|
|
16
|
-
export const API_CONTRACT_REVISION = "sha256:
|
|
15
|
+
export const CLI_VERSION = "2.0.22";
|
|
16
|
+
export const API_CONTRACT_REVISION = "sha256:0c6470d9e3c1b4ec317d6c1b0b597acc13ec10d39312f994ece4cfd25cb0b65e";
|
|
17
17
|
const CART_SESSION_DEFAULT_DIR = ".itpay-v3";
|
|
18
18
|
const CART_SESSION_FILENAME = "cart.json";
|
|
19
19
|
const OPERATION_JOURNAL_FILENAME = "operations.json";
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"Use only handoff fields returned for the current Host and make them actually visible to the human.",
|
|
32
32
|
"Before creating a single-Service Checkout, send the exact price confirmation requested by the current instruction, stop, and wait for explicit human agreement.",
|
|
33
33
|
"After presenting a Checkout, stop. Run the returned next.command only after the human completes the payment action or asks to query the same Checkout.",
|
|
34
|
-
"For workbuddy with plain-chat,
|
|
34
|
+
"For workbuddy with plain-chat, send or open the fully rendered Card Link in handoff.url and never call present_files or create another payment resource.",
|
|
35
35
|
"For openclaw with telegram, execute the returned native message action using the current trusted target. For any other explicit OpenClaw Host, show the returned HTTPS QR image and Checkout URL.",
|
|
36
36
|
"Payment is verified only by Backend Checkout or Order state, never by QR rendering, redirect, or user claim.",
|
|
37
37
|
"A terminal payment state must never display another payment handoff."
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
{
|
|
34
34
|
"agent_type": "workbuddy",
|
|
35
35
|
"default_host": "plain-chat",
|
|
36
|
-
"responsibility": "
|
|
36
|
+
"responsibility": "show the amount and send or open handoff.url, which is the fully rendered HTML Card Link; never call present_files; then stop"
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
39
|
"agent_type": "kimi-code",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"agent_rules": [
|
|
64
64
|
"Agent Type is stable identity; Host is presentation and may be explicitly overridden.",
|
|
65
65
|
"A local QR path is not visible until a desktop Agent attaches or renders that file on the human-facing surface; WorkBuddy never receives a local QR path.",
|
|
66
|
-
"For workbuddy with plain-chat,
|
|
66
|
+
"For workbuddy with plain-chat, send or open only the rendered handoff.url and never call present_files, inspect files, or rebuild a QR.",
|
|
67
67
|
"Do not claim the handoff was shown until both a usable payment image or QR and the Checkout URL are visible.",
|
|
68
68
|
"IM Hosts that require a target must receive --target before Checkout creation. OpenClaw has no default Host; Kimi Code uses terminal.",
|
|
69
69
|
"OpenClaw Telegram uses flat url/value buttons for deployed-version compatibility. A grant_confirmed callback carries only the Checkout ID, never a display token, and never proves payment or an active grant."
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"Do not expose display tokens separately from the tokenized Checkout handoff.",
|
|
73
73
|
"Do not print base64 images, mirror lists, renderer debug state, or provider QR URLs.",
|
|
74
74
|
"Do not claim a desktop attachment exists when only a filesystem path was printed.",
|
|
75
|
-
"Do not inspect, download, or regenerate a local QR file for WorkBuddy."
|
|
75
|
+
"Do not call present_files or inspect, download, or regenerate a local QR file for WorkBuddy."
|
|
76
76
|
],
|
|
77
77
|
"next_docs": [
|
|
78
78
|
{
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
| `codex-cli` | `terminal` | 在用户可见终端渲染二维码并输出付款链接;若用户不看该终端,要求改用正确 Host。 |
|
|
15
15
|
| `claude-code-desktop` | `claude-code` | 返回桌面对话可展示的 Markdown 图片和付款链接,要求先展示再等待。 |
|
|
16
16
|
| `claude-code-cli` | `terminal` | 在用户可见终端输出二维码和链接,不声称已在桌面对话展示。 |
|
|
17
|
-
| `workbuddy` | `plain-chat` |
|
|
17
|
+
| `workbuddy` | `plain-chat` | 只返回完整渲染的 HTML Card Link;直接打开链接,不调用 `present_files`,不返回或检查本地图片路径。 |
|
|
18
18
|
| `kimi-code` | `terminal` | 使用标准 CLI 引导,在用户可见终端渲染二维码和付款链接。 |
|
|
19
19
|
| `openclaw` | 无;必须显式传入 | `--host telegram` 使用 OpenClaw 原生 `message` action;其他入口返回标准 HTTPS 二维码和付款链接。 |
|
|
20
20
|
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
"amount": "<amount> <currency>"
|
|
44
44
|
},
|
|
45
45
|
"handoff": {
|
|
46
|
-
"url": "<
|
|
46
|
+
"url": "<checkout_or_rendered_card_url>",
|
|
47
47
|
"qr_local_path": "<desktop_optional_local_path>",
|
|
48
|
-
"qr_image_url": "<
|
|
48
|
+
"qr_image_url": "<non_workbuddy_optional_absolute_https_png>",
|
|
49
49
|
"markdown": "<desktop_optional_host_ready_markdown>"
|
|
50
50
|
},
|
|
51
51
|
"instruction": "<agent-type-specific instruction>",
|
|
@@ -66,11 +66,11 @@
|
|
|
66
66
|
| `claude-code-desktop / claude-code` | `url, qr_local_path, markdown` |
|
|
67
67
|
| `codex-cli / terminal` | `url`;非 JSON 输出另外渲染终端二维码 |
|
|
68
68
|
| `claude-code-cli / terminal` | `url`;非 JSON 输出另外渲染终端二维码 |
|
|
69
|
-
| `workbuddy / plain-chat` | `url
|
|
69
|
+
| `workbuddy / plain-chat` | `url`(完整渲染的 HTML Card Link) |
|
|
70
70
|
| `kimi-code / terminal` | `url`;非 JSON 输出另外渲染终端二维码 |
|
|
71
71
|
| `openclaw / telegram` | `url, qr_image_url, agent_action` |
|
|
72
72
|
| `openclaw / other` | `url, qr_image_url` |
|
|
73
73
|
|
|
74
|
-
WorkBuddy instruction
|
|
74
|
+
WorkBuddy instruction 必须直接发送并打开 `handoff.url`,明确禁止 `present_files`。这个 URL 指向 Backend 已渲染的 HTML Card;不能检查本地文件、下载或重建二维码、调用 `pay` 或创建替代付款资源。显式 `--host` 仍覆盖默认展示方式。
|
|
75
75
|
|
|
76
76
|
OpenClaw Telegram 的 `handoff.agent_action` 是可原样执行的原生 `message` tool action。`presentation` 只包含标准 `blocks.buttons`:`📱 手机点这儿支付` 使用扁平 `url`,`📋 已授权给我读` 使用扁平 `value=itp:grant_confirmed:<checkout_id>`;二维码单独使用 action 的 `media`。CLI `instruction` 必须要求 Agent 原样执行该 action,不得改写 Presentation、换用其他消息工具或声称普通文本回复等同于已发送按钮。收到授权 callback 后立即执行 `next.command` 查询同一 Checkout,再只跟随后端返回的同一 Execution grant 流程;callback 只携带 Checkout ID,不携带 display token,也不证明付款或 grant 已生效。OpenClaw `target` 使用原生 chat target(如 `5559456744` 或 `-1001234567890:topic:42`),不添加 `telegram:` 前缀。
|
|
@@ -165,7 +165,7 @@ itpay buy \
|
|
|
165
165
|
| `codex-cli` | `terminal` | `url` | 非 JSON 模式在用户可见终端渲染二维码;始终保留付款链接。 |
|
|
166
166
|
| `claude-code-desktop` | `claude-code` | `url`、可用时 `qr_local_path` 和 `markdown` | 把 Markdown handoff 发到当前桌面对话,不能只输出本地路径。 |
|
|
167
167
|
| `claude-code-cli` | `terminal` | `url` | 在用户可见终端展示;不能声称桌面对话已收到图片。 |
|
|
168
|
-
| `workbuddy` | `plain-chat` | `url
|
|
168
|
+
| `workbuddy` | `plain-chat` | `url` | `url` 是完整渲染的 HTML Card Link;直接发送/打开,不调用 `present_files`,然后停止。 |
|
|
169
169
|
| `kimi-code` | `terminal` | `url` | 使用标准 CLI 非 JSON 终端二维码和链接。 |
|
|
170
170
|
| `openclaw` | 必须显式 | Telegram 返回 `url,qr_image_url,agent_action`;其他入口返回 `url,qr_image_url` | Telegram 执行原生 `message` action;其他入口直接展示图片和链接。 |
|
|
171
171
|
|
|
@@ -71,7 +71,7 @@ token 缺失或不匹配时使用本机句柄恢复。只有请求的 Checkout
|
|
|
71
71
|
| `codex-cli` | `url`;普通文本模式渲染终端二维码。 |
|
|
72
72
|
| `claude-code-desktop` | `url, qr_local_path, markdown`;原样发送 Markdown。 |
|
|
73
73
|
| `claude-code-cli` | `url`;普通文本模式渲染终端二维码。 |
|
|
74
|
-
| `workbuddy` | `url
|
|
74
|
+
| `workbuddy` | 只返回 `url`;它是完整渲染的 HTML Card Link。直接打开,不调用 `present_files`,不生成本地文件。 |
|
|
75
75
|
| `kimi-code` | `url`;普通文本模式渲染标准终端二维码。 |
|
|
76
76
|
| `openclaw` | Telegram 为 `url,qr_image_url,agent_action`;instruction 强制原样执行 action。`📋 已授权给我读` callback 触发同一 Checkout 查询,再由 Backend 决定是否进入 grant 读取;其他显式 Host 为 `url,qr_image_url`。 |
|
|
77
77
|
|
|
@@ -117,7 +117,7 @@ OpenClaw 额外明确没有默认入口:
|
|
|
117
117
|
| `codex-cli` | `terminal` | 只在用户可见终端展示付款交接。 |
|
|
118
118
|
| `claude-code-desktop` | `claude-code` | 桌面对话必须实际展示二维码和付款链接。 |
|
|
119
119
|
| `claude-code-cli` | `terminal` | 只在用户可见终端展示付款交接。 |
|
|
120
|
-
| `workbuddy` | `plain-chat` | 会持久化 `~/.itpay-v3` 的 Bash 调用先设 Shell 工具参数 `dangerouslyDisableSandbox=true
|
|
120
|
+
| `workbuddy` | `plain-chat` | 会持久化 `~/.itpay-v3` 的 Bash 调用先设 Shell 工具参数 `dangerouslyDisableSandbox=true`;拒绝权限时停止。Checkout 只发送/打开渲染后的 Card Link,不调用 `present_files`。 |
|
|
121
121
|
| `kimi-code` | `terminal` | 使用标准 CLI 流程和终端展示,不增加 Kimi 专属命令。 |
|
|
122
122
|
| `openclaw` | 无 | 必须显式提供当前入口;Telegram 使用原生 action,其他入口展示标准二维码和链接。 |
|
|
123
123
|
|
|
@@ -100,6 +100,6 @@ API 安全合同要求后端验证 display token 是该 Checkout 当前有效的
|
|
|
100
100
|
| `codex-cli` | `terminal` | 只在用户可见终端展示渠道动作。 |
|
|
101
101
|
| `claude-code-desktop` | `claude-code` | 把安全 handoff 发到当前桌面对话。 |
|
|
102
102
|
| `claude-code-cli` | `terminal` | 只在用户可见终端展示渠道动作。 |
|
|
103
|
-
| `workbuddy` | `plain-chat` |
|
|
103
|
+
| `workbuddy` | `plain-chat` | 受控逃生入口只返回一个可点击的 `handoff.url`;不得调用 `present_files`。展示后停止,不立即查询或创建替代付款。 |
|
|
104
104
|
|
|
105
105
|
Host 只改变 instruction;Payment Intent ID、金额、状态、重试语义和权限必须一致。
|
|
@@ -81,16 +81,16 @@ itpay services checkout <service_execution_id> --resume
|
|
|
81
81
|
| `codex-cli` | `handoff={url}`;普通文本模式在用户可见终端渲染二维码。 |
|
|
82
82
|
| `claude-code-desktop` | `handoff={url,qr_local_path,markdown}`;把 `handoff.markdown` 原样发送到当前桌面对话。 |
|
|
83
83
|
| `claude-code-cli` | `handoff={url}`;普通文本模式在用户可见终端渲染二维码。 |
|
|
84
|
-
| `workbuddy` | `handoff={url
|
|
84
|
+
| `workbuddy` | `handoff={url}`;`url` 是完整渲染的 HTML Card Link。直接发送/打开并停止,不得调用 `present_files`、检查或生成本地文件。 |
|
|
85
85
|
| `kimi-code` | `handoff={url}`;复用标准 CLI 终端展示。 |
|
|
86
86
|
| `openclaw` | 必须显式传 Host;Telegram 还必须传 OpenClaw 原生 Target,并返回必须原样执行的 `message` action;其他入口返回标准 `url,qr_image_url`。 |
|
|
87
87
|
|
|
88
88
|
WorkBuddy 的准确 instruction 语义必须完整包含:
|
|
89
89
|
|
|
90
90
|
```text
|
|
91
|
-
Backend
|
|
91
|
+
Backend 尚未确认付款。向用户说明金额,直接发送并打开 handoff.url,然后停止等待。不要调用 present_files,不要检查本地文件,不要下载或重建二维码,不要调用 pay,不要创建新 Checkout、Payment Intent 或 Execution。只有用户明确表示已付款或要求查询状态时,才执行 next.command;用户的话不是付款成功证明。
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
`--locale` 默认 `zh-CN`,可显式使用 `--locale en`。语言只影响 Card 渲染,不改变 Checkout、付款或恢复状态。
|
|
95
95
|
|
|
96
96
|
OpenClaw 的 Host/Target 校验必须发生在读取 execution 后、创建 Checkout 前;校验失败不能调用 Checkout 创建接口。Telegram `agent_action.arguments.presentation` 只使用 `blocks.buttons`,其中 `📱 手机点这儿支付` 使用扁平 `url`,`📋 已授权给我读` 使用 `value=itp:grant_confirmed:<checkout_id>`。准确 instruction 必须要求 Agent 立即用原生 `message` tool 原样执行 `handoff.agent_action`,不得改写 Presentation、换工具或用普通回复冒充按钮已发送。收到该 callback 表示用户声明已在收银台授权读取;Agent 立即执行 `next.command` 查询同一 Checkout,再只按 Backend 返回的 `next.command` 进入同一 Execution grant 流程。callback 不包含 display token,也不证明付款或 grant active;Backend 未返回 `grant_active` 前不得读取结果。
|
package/package.json
CHANGED
package/skills/itpay/SKILL.md
CHANGED
|
@@ -85,8 +85,8 @@ When `status` is `human_checkout_required`, make the amount, ItPay Checkout QR,
|
|
|
85
85
|
|
|
86
86
|
- Desktop Agents: send `handoff.markdown` unchanged; confirm QR, amount, and link are visible, then stop.
|
|
87
87
|
- CLI Agents: show the terminal QR, amount, and link in the watched terminal, then stop; never claim a desktop image was shown.
|
|
88
|
-
- WorkBuddy with `plain-chat`:
|
|
89
|
-
-
|
|
88
|
+
- WorkBuddy with `plain-chat`: `handoff.url` is the fully rendered ItPay Card Link. Show the amount, send/open that link, then stop. Never call `present_files`, inspect files, download or rebuild a QR, call `pay`, or create another Checkout.
|
|
89
|
+
- Desktop image hosts receive the PNG rendered from that same Card HTML. `--locale` defaults to `zh-CN`; use `--locale en` only when the human needs English.
|
|
90
90
|
- An explicit `--host` overrides presentation only. It never changes Agent identity or payment state.
|
|
91
91
|
|
|
92
92
|
Run `next.command` only after the human says they acted or asks for status. QR rendering, redirects, and human claims are not payment proof; only Backend Checkout or Order state is. Normal payment uses the Checkout page; `pay` and `buy --pay` are operator escape hatches, never recovery.
|