@itpay/cli 2.1.9 → 2.1.10

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.
@@ -203,6 +203,7 @@ function buildBuyEnvelope(input) {
203
203
  const presentationHandoff = buildCheckoutHandoff({
204
204
  platform,
205
205
  url: input.plan.linkOnlyURL ?? input.checkoutURL,
206
+ mobileUrl: input.checkoutURL,
206
207
  amount,
207
208
  plan: input.plan,
208
209
  ...(input.agentType ? { agentType: input.agentType } : {}),
@@ -22,7 +22,7 @@ export async function runCheckoutPresentation(backend, options) {
22
22
  });
23
23
  return;
24
24
  }
25
- const checkoutURL = checkoutPageURL(options.baseURL, options.checkoutID, options.displayToken);
25
+ const checkoutURL = savedCheckoutURLOrFallback(options.savedCheckoutURL, options.checkoutID, options.displayToken, checkoutPageURL(options.baseURL, options.checkoutID, options.displayToken));
26
26
  const cardURL = localizeCardURL(absolutePublicURL(options.baseURL, presentation.card_url ?? checkoutCardURL(options.baseURL, options.checkoutID, options.displayToken)), locale);
27
27
  const qrPNGURL = absolutePublicURL(options.baseURL, presentation.card_png_url ?? presentation.qr_png_url ?? checkoutCardPNGURL(options.baseURL, options.checkoutID, options.displayToken));
28
28
  const localizedPNGURL = localizeCardURL(qrPNGURL, locale);
@@ -68,6 +68,7 @@ function pendingCheckoutEnvelope(presentation, checkoutURL, plan, nextCommand, a
68
68
  const presentationHandoff = buildCheckoutHandoff({
69
69
  platform,
70
70
  url: plan.linkOnlyURL ?? checkoutURL,
71
+ mobileUrl: checkoutURL,
71
72
  amount,
72
73
  plan,
73
74
  ...(agentType ? { agentType } : {}),
@@ -164,6 +165,20 @@ function checkoutPageURL(baseURL, checkoutID, displayToken) {
164
165
  const root = publicRoot(baseURL);
165
166
  return `${root}/checkout/${encodeURIComponent(checkoutID)}?display_token=${encodeURIComponent(displayToken)}`;
166
167
  }
168
+ function savedCheckoutURLOrFallback(savedURL, checkoutID, displayToken, fallback) {
169
+ if (!savedURL) {
170
+ return fallback;
171
+ }
172
+ try {
173
+ const parsed = new URL(savedURL);
174
+ const belongsToCheckout = parsed.pathname === `/checkout/${checkoutID}`;
175
+ const sameToken = parsed.searchParams.get("display_token") === displayToken;
176
+ return belongsToCheckout && sameToken ? savedURL : fallback;
177
+ }
178
+ catch {
179
+ return fallback;
180
+ }
181
+ }
167
182
  function checkoutCardURL(baseURL, checkoutID, displayToken) {
168
183
  const root = publicRoot(baseURL);
169
184
  return `${root}/v1/checkouts/${encodeURIComponent(checkoutID)}/card?display_token=${encodeURIComponent(displayToken)}`;
@@ -17,8 +17,26 @@ export function buildWorkBuddyPresentFilesAction(url) {
17
17
  arguments: { files: [url] },
18
18
  };
19
19
  }
20
+ function hasCompleteCheckoutTokens(url) {
21
+ if (!url) {
22
+ return false;
23
+ }
24
+ try {
25
+ const parsed = new URL(url);
26
+ return Boolean(parsed.searchParams.get("display_token") && parsed.searchParams.get("complete_exchange_token"));
27
+ }
28
+ catch {
29
+ return false;
30
+ }
31
+ }
20
32
  export function buildCheckoutHandoff(input) {
21
33
  const handoff = { url: input.url };
34
+ // mobile_url is only emitted when the link carries the complete token pair;
35
+ // a display-token-only link cannot finish the same-device exchange after payment.
36
+ const mobileUrl = hasCompleteCheckoutTokens(input.mobileUrl) ? input.mobileUrl : undefined;
37
+ if (mobileUrl) {
38
+ handoff.mobile_url = mobileUrl;
39
+ }
22
40
  const workBuddyAction = isWorkBuddyPlainChat(input.agentType, input.platform);
23
41
  if (workBuddyAction) {
24
42
  handoff.agent_action = buildWorkBuddyPresentFilesAction(input.url);
@@ -40,27 +58,41 @@ export function buildCheckoutHandoff(input) {
40
58
  }
41
59
  return {
42
60
  handoff,
43
- instruction: checkoutHandoffInstruction(input.agentType, input.platform, input.amount),
61
+ instruction: checkoutHandoffInstruction(input.agentType, input.platform, input.amount, Boolean(mobileUrl)),
44
62
  };
45
63
  }
46
- function checkoutHandoffInstruction(agentType, platform, amount) {
64
+ function checkoutHandoffInstruction(agentType, platform, amount, hasMobileLink) {
47
65
  if (isWorkBuddyPlainChat(agentType, platform)) {
48
- return `Backend 尚未确认付款。立即严格按 handoff.agent_action.tool 和 handoff.agent_action.arguments 原样执行一次,在右侧打开 handoff.url;确认工具调用成功后说明金额 ${amount},然后停止等待。若工具失败,只发送原始 handoff.url,报告未自动打开并停止。不要用 present_files 打开本地文件或二维码 PNG,不要下载或重建二维码,不要调用 pay,不要创建新 Checkout、Payment Intent 或 Execution。只有用户明确表示已付款或要求查询状态时,才执行 next.command;用户的话不是付款成功证明。`;
66
+ const mobileSentence = hasMobileLink
67
+ ? ",然后把 handoff.mobile_url 作为可点击链接发给用户,说明“手机端点开可直接跳转支付宝或微信支付;小程序内打不开时复制到手机浏览器打开即可”"
68
+ : "";
69
+ const failLinks = hasMobileLink ? "发送 handoff.url 与 handoff.mobile_url" : "发送原始 handoff.url";
70
+ return `Backend 尚未确认付款。立即严格按 handoff.agent_action.tool 和 handoff.agent_action.arguments 原样执行一次,在右侧打开 handoff.url;确认工具调用成功后说明金额 ${amount}${mobileSentence},然后停止等待。若工具失败,只${failLinks},报告未自动打开并停止。不要用 present_files 打开本地文件或二维码 PNG,不要下载或重建二维码,不要调用 pay,不要创建新 Checkout、Payment Intent 或 Execution。只有用户明确表示已付款或要求查询状态时,才执行 next.command;用户的话不是付款成功证明。`;
49
71
  }
50
72
  if (isZCodePlainChat(agentType, platform)) {
51
- return `Backend 尚未确认付款。立即用 ZCode 内置浏览器打开 handoff.url,让用户完成付款;确认已发起打开后说明金额 ${amount},然后停止等待。不要只粘贴文字链接,不要下载、解析或重建二维码,不要创建新 Checkout、Payment Intent 或 Execution。只有内置浏览器明确不可用时,才展示同一个可点击 handoff.url。只有用户明确表示已付款或要求查询状态时,才执行 next.command;用户的话不是付款成功证明。`;
73
+ const mobileSentence = hasMobileLink
74
+ ? ",并把 handoff.mobile_url 发给用户备用(手机端点开可直接跳转支付宝或微信支付)"
75
+ : "";
76
+ const fallbackLinks = hasMobileLink ? "同一个可点击 handoff.url 与 handoff.mobile_url" : "同一个可点击 handoff.url";
77
+ return `Backend 尚未确认付款。立即用 ZCode 内置浏览器打开 handoff.url,让用户完成付款;确认已发起打开后说明金额 ${amount}${mobileSentence},然后停止等待。不要只粘贴文字链接,不要下载、解析或重建二维码,不要创建新 Checkout、Payment Intent 或 Execution。只有内置浏览器明确不可用时,才展示${fallbackLinks}。只有用户明确表示已付款或要求查询状态时,才执行 next.command;用户的话不是付款成功证明。`;
52
78
  }
53
79
  if (agentType?.trim().toLowerCase() === "doubao-work" && platform === "plain_chat") {
54
- return `Backend 尚未确认付款。在当前豆包工作对话中展示两个有标签的官方入口:将 handoff.url 标为“手机直接打开收银台”,将 handoff.qr_image_url 标为“二维码图片(保存或用另一台设备扫码)”。说明金额 ${amount} 后停止等待。不要解析或单独输出 URL credential,不要下载或重建二维码,不要调用 pay,不要创建新 Checkout、Payment Intent 或 Execution。只有用户明确表示已付款或要求查询状态时,才执行 next.command;用户的话不是付款成功证明。`;
80
+ const directLink = hasMobileLink ? "handoff.mobile_url" : "handoff.url";
81
+ const directLabel = hasMobileLink ? "手机直接打开收银台(可跳转支付宝/微信)" : "手机直接打开收银台";
82
+ return `Backend 尚未确认付款。在当前豆包工作对话中展示两个有标签的官方入口:将 ${directLink} 标为“${directLabel}”,将 handoff.qr_image_url 标为“二维码图片(保存或用另一台设备扫码)”。说明金额 ${amount} 后停止等待。不要解析或单独输出 URL credential,不要下载或重建二维码,不要调用 pay,不要创建新 Checkout、Payment Intent 或 Execution。只有用户明确表示已付款或要求查询状态时,才执行 next.command;用户的话不是付款成功证明。`;
55
83
  }
56
84
  if (platform === "markdown") {
57
- return `Backend 尚未确认付款。把 handoff.markdown 原样发送到当前桌面对话,确认二维码、付款链接和金额 ${amount} 均已实际对用户可见,然后停止等待。不要创建新 Checkout、Payment Intent 或 Execution;只有用户明确表示已付款或要求查询状态时,才执行 next.command;用户的话不是付款成功证明。`;
85
+ const mobileSentence = hasMobileLink ? ",并附 handoff.mobile_url 供手机端点击直达支付" : "";
86
+ return `Backend 尚未确认付款。把 handoff.markdown 原样发送到当前桌面对话,确认二维码、付款链接和金额 ${amount} 均已实际对用户可见${mobileSentence},然后停止等待。不要创建新 Checkout、Payment Intent 或 Execution;只有用户明确表示已付款或要求查询状态时,才执行 next.command;用户的话不是付款成功证明。`;
58
87
  }
59
88
  if (platform === "terminal") {
60
- return `Backend 尚未确认付款。在用户可见终端展示当前 Checkout 的二维码、handoff.url 和金额 ${amount},然后停止等待。不要创建新 Checkout、Payment Intent 或 Execution;只有用户明确表示已付款或要求查询状态时,才执行 next.command;用户的话不是付款成功证明。`;
89
+ const mobileSentence = hasMobileLink ? ",并展示 handoff.mobile_url 供手机端点击直达支付" : "";
90
+ return `Backend 尚未确认付款。在用户可见终端展示当前 Checkout 的二维码、handoff.url 和金额 ${amount}${mobileSentence},然后停止等待。不要创建新 Checkout、Payment Intent 或 Execution;只有用户明确表示已付款或要求查询状态时,才执行 next.command;用户的话不是付款成功证明。`;
61
91
  }
62
92
  if (agentType?.trim().toLowerCase() === "openclaw" && platform === "telegram") {
63
- return `Backend 尚未确认付款或授权状态。立即使用 OpenClaw 原生 message tool,严格按 handoff.agent_action.tool 和 handoff.agent_action.arguments 原样执行;它会发送金额 ${amount}、二维码、📱 手机点这儿支付和 📋 已授权给我读。不得改写 presentation、换用其他消息工具、拆开按钮发送或用普通回复声称按钮已经发送。确认 message tool 调用成功后停止等待。收到 callback_data "itp:grant_confirmed:<checkout_id>" 时,表示用户声明已在收银台授权读取;立即执行 next.command 查询同一 Checkout,再只执行 Backend 返回的 next.command 进入同一 Execution 的 grant 流程。该 callback 不证明付款成功或 grant 已生效;Backend 未返回 grant_active 前不得读取或猜测结果。若原生 message tool 明确失败或当前 Telegram 未启用 inline buttons,只发送现有 handoff.qr_image_url、金额和 handoff.url,报告按钮不可用并停止;不要创建新的 Checkout、Payment Intent 或 Execution。`;
93
+ const failLinks = hasMobileLink ? "handoff.qr_image_url、金额、handoff.url 与 handoff.mobile_url" : "handoff.qr_image_url、金额和 handoff.url";
94
+ return `Backend 尚未确认付款或授权状态。立即使用 OpenClaw 原生 message tool,严格按 handoff.agent_action.tool 和 handoff.agent_action.arguments 原样执行;它会发送金额 ${amount}、二维码、📱 手机点这儿支付和 📋 已授权给我读。不得改写 presentation、换用其他消息工具、拆开按钮发送或用普通回复声称按钮已经发送。确认 message tool 调用成功后停止等待。收到 callback_data "itp:grant_confirmed:<checkout_id>" 时,表示用户声明已在收银台授权读取;立即执行 next.command 查询同一 Checkout,再只执行 Backend 返回的 next.command 进入同一 Execution 的 grant 流程。该 callback 不证明付款成功或 grant 已生效;Backend 未返回 grant_active 前不得读取或猜测结果。若原生 message tool 明确失败或当前 Telegram 未启用 inline buttons,只发送现有 ${failLinks},报告按钮不可用并停止;不要创建新的 Checkout、Payment Intent 或 Execution。`;
64
95
  }
65
- return `Backend 尚未确认付款。把 handoff.url 和可用的 handoff.qr_image_url 实际发送到当前会话,说明金额 ${amount},然后停止等待。不要创建新 Checkout、Payment Intent 或 Execution;只有用户明确表示已付款或要求查询状态时,才执行 next.command;用户的话不是付款成功证明。`;
96
+ const urlList = hasMobileLink ? "handoff.url、handoff.mobile_url(手机端点击直达支付)和可用的 handoff.qr_image_url" : "handoff.url 和可用的 handoff.qr_image_url";
97
+ return `Backend 尚未确认付款。把 ${urlList} 实际发送到当前会话,说明金额 ${amount},然后停止等待。不要创建新 Checkout、Payment Intent 或 Execution;只有用户明确表示已付款或要求查询状态时,才执行 next.command;用户的话不是付款成功证明。`;
66
98
  }
@@ -1500,6 +1500,7 @@ function buildServicesCheckoutEnvelope(response, checkoutURL, plan, agentType, t
1500
1500
  const presentationHandoff = buildCheckoutHandoff({
1501
1501
  platform,
1502
1502
  url: plan.linkOnlyURL ?? checkoutURL,
1503
+ mobileUrl: checkoutURL,
1503
1504
  amount,
1504
1505
  plan,
1505
1506
  ...(agentType ? { agentType } : {}),
package/dist/src/main.js CHANGED
@@ -910,6 +910,9 @@ program
910
910
  await runCheckoutPresentation(backend, {
911
911
  checkoutID,
912
912
  displayToken,
913
+ ...(snap.lastCheckoutID === checkoutID && snap.lastCheckoutURL
914
+ ? { savedCheckoutURL: snap.lastCheckoutURL }
915
+ : {}),
913
916
  host,
914
917
  ...(options.target ? { target: options.target } : {}),
915
918
  ...(config.agentType ? { agentType: config.agentType } : {}),
@@ -15,7 +15,7 @@ import { OperationJournal } from "./operation_journal.js";
15
15
  export const DEFAULT_BASE_URL = "https://app.itpay.ai";
16
16
  export const DEV_BASE_URL = "https://dev.itpay.ai";
17
17
  export const SANDBOX_BASE_URL = "https://sandbox.itpay.ai";
18
- export const CLI_VERSION = "2.1.9";
18
+ export const CLI_VERSION = "2.1.10";
19
19
  export const API_CONTRACT_REVISION = "sha256:66a95bbc5ec9d7b35747cf87206c63dd45983f26de21afab582d8dd63bf198f7";
20
20
  const CART_SESSION_DEFAULT_DIR = ".itpay-v3";
21
21
  const CART_SESSION_FILENAME = "cart.json";
@@ -33,17 +33,17 @@
33
33
  {
34
34
  "agent_type": "workbuddy",
35
35
  "default_host": "plain-chat",
36
- "responsibility": "execute handoff.agent_action exactly once to open the fully rendered handoff.url with present_files; show the amount and stop"
36
+ "responsibility": "execute handoff.agent_action exactly once to open the fully rendered handoff.url with present_files, then send handoff.mobile_url as a clickable link that jumps straight into Alipay or WeChat Pay on a phone; show the amount and stop"
37
37
  },
38
38
  {
39
39
  "agent_type": "zcode",
40
40
  "default_host": "plain-chat",
41
- "responsibility": "open handoff.url immediately in the ZCode built-in browser and stop; only show the same clickable URL when the built-in browser is unavailable"
41
+ "responsibility": "open handoff.url immediately in the ZCode built-in browser, also send handoff.mobile_url as the phone-friendly entry, then stop; when the built-in browser is unavailable show both clickable links"
42
42
  },
43
43
  {
44
44
  "agent_type": "doubao-work",
45
45
  "default_host": "plain-chat",
46
- "responsibility": "show handoff.url as the preferred same-phone checkout or authorization link and handoff.qr_image_url as the labeled save-or-scan fallback; state the amount for Checkout and stop"
46
+ "responsibility": "show handoff.mobile_url as the preferred same-phone checkout or authorization link and handoff.qr_image_url as the labeled save-or-scan fallback; state the amount for Checkout and stop"
47
47
  },
48
48
  {
49
49
  "agent_type": "kimi-code",
@@ -73,10 +73,11 @@
73
73
  "agent_rules": [
74
74
  "Agent Type is stable identity; Host is presentation and may be explicitly overridden.",
75
75
  "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.",
76
- "For workbuddy with plain-chat, execute handoff.agent_action exactly once; it calls present_files with handoff.url as the only files element and opens the rendered Card Link in the right-side browser.",
77
- "For zcode with plain-chat, open handoff.url in the built-in browser. Do not only paste text, download or rebuild a QR, or invent a private browser-tool payload; use the same clickable URL only as fallback.",
78
- "For doubao-work with plain-chat, show two labeled official links: handoff.url for direct same-phone opening and handoff.qr_image_url for saving or scanning on another device; do not parse credentials, download or rebuild the QR, or create an alternative resource.",
79
- "If the WorkBuddy action fails, send the original handoff.url, report that it did not auto-open, and stop without creating another payment resource.",
76
+ "For workbuddy with plain-chat, execute handoff.agent_action exactly once; it calls present_files with handoff.url as the only files element and opens the rendered Card Link in the right-side browser, then send handoff.mobile_url so phone users can tap straight into Alipay or WeChat Pay.",
77
+ "For zcode with plain-chat, open handoff.url in the built-in browser and also send handoff.mobile_url as the phone entry; do not only paste text, download or rebuild a QR, or invent a private browser-tool payload; when the built-in browser is unavailable show both clickable links.",
78
+ "For doubao-work with plain-chat, show two labeled official links: handoff.mobile_url for direct same-phone opening and handoff.qr_image_url for saving or scanning on another device; do not parse credentials, download or rebuild the QR, or create an alternative resource.",
79
+ "If the WorkBuddy action fails, send the original handoff.url and handoff.mobile_url, report that it did not auto-open, and stop without creating another payment resource.",
80
+ "handoff.mobile_url is the phone-friendly Checkout URL; when the current client cannot open links in place (for example a mobile mini program), send it and tell the human to copy it into the phone browser where it can jump into Alipay or WeChat Pay. It is only returned when the handoff carries the complete token pair; when absent, handoff.url remains the entry.",
80
81
  "Do not claim the handoff was shown until both a usable payment image or QR and the Checkout URL are visible.",
81
82
  "IM Hosts that require a target must receive --target before Checkout creation. OpenClaw has no default Host; Kimi Code uses terminal.",
82
83
  "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."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itpay/cli",
3
- "version": "2.1.9",
3
+ "version": "2.1.10",
4
4
  "description": "The ItPay CLI for services, orders, and human-authorized purchased content.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -76,6 +76,13 @@ platform whenever possible. Only show the same clickable URL when no browser
76
76
  or native action is available; never ask the human to run a command or rebuild
77
77
  a QR code.
78
78
 
79
+ Checkout handoffs also carry `handoff.mobile_url`: send it alongside the main
80
+ link and tell the human that on a phone it opens the cashier and can jump
81
+ straight into Alipay or WeChat Pay. When the current client cannot open links
82
+ itself (for example a mobile mini program without a side panel), send
83
+ `mobile_url` and tell the human to copy it into the phone's browser if it does
84
+ not open in place.
85
+
79
86
  ## Serve The Human
80
87
 
81
88
  - Ask the human only to choose, authorize, pay, provide required contact