@itpay/cli 2.1.34 → 2.1.41
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 +5 -12
- package/dist/src/client/backend.js +2 -33
- package/dist/src/client/http.js +0 -2
- package/dist/src/commands/buy.js +4 -7
- package/dist/src/commands/cart.js +15 -15
- package/dist/src/commands/catalog.js +7 -17
- package/dist/src/commands/checkout.js +5 -146
- package/dist/src/commands/checkout_handoff.js +10 -48
- package/dist/src/commands/docs.js +18 -7
- package/dist/src/commands/guidance.js +19 -69
- package/dist/src/commands/install.js +3 -6
- package/dist/src/commands/order.js +1 -14
- package/dist/src/commands/orders.js +9 -4
- package/dist/src/commands/pay.js +1 -1
- package/dist/src/commands/readyz.js +2 -2
- package/dist/src/commands/refund.js +5 -5
- package/dist/src/commands/services.js +167 -1712
- package/dist/src/commands/skill.js +5 -8
- package/dist/src/commands/vault.js +20 -27
- package/dist/src/commands/vault_handoff.js +0 -18
- package/dist/src/main.js +58 -294
- package/dist/src/render/markdown.js +3 -0
- package/dist/src/render/qr.js +1 -1
- package/dist/src/state/cart_session.js +1 -4
- package/dist/src/state/client_context.js +0 -72
- package/dist/src/state/config.js +14 -44
- package/dist/src/state/device_authority.js +41 -141
- package/docs/agent/buyer/cart-checkout.json +5 -6
- package/docs/agent/buyer/catalog-list.json +6 -24
- package/docs/agent/buyer/identity-and-sessions.json +9 -25
- package/docs/agent/buyer/install-and-setup.json +15 -11
- package/docs/agent/buyer/orders-refunds.json +5 -12
- package/docs/agent/buyer/payment-flow.json +3 -4
- package/docs/agent/buyer/purchased-content.json +4 -18
- package/docs/agent/buyer/quickstart.json +16 -24
- package/docs/agent/buyer/render-hosts.json +12 -23
- package/docs/agent/buyer/service-feedback.json +1 -2
- package/docs/cli-reference/agent-types.md +2 -7
- package/docs/cli-reference/commands/buy.md +1 -2
- package/docs/cli-reference/commands/cart/add.md +1 -1
- package/docs/cli-reference/commands/cart/clear.md +1 -1
- package/docs/cli-reference/commands/cart/index.md +1 -1
- package/docs/cli-reference/commands/cart/next.md +1 -1
- package/docs/cli-reference/commands/cart/remove.md +1 -1
- package/docs/cli-reference/commands/cart/show.md +1 -1
- package/docs/cli-reference/commands/catalog/index.md +1 -1
- package/docs/cli-reference/commands/catalog/list.md +2 -2
- package/docs/cli-reference/commands/checkout.md +2 -12
- package/docs/cli-reference/commands/device.md +11 -18
- package/docs/cli-reference/commands/docs/index.md +1 -1
- package/docs/cli-reference/commands/docs/list.md +1 -1
- package/docs/cli-reference/commands/docs/search.md +1 -1
- package/docs/cli-reference/commands/docs/show.md +1 -1
- package/docs/cli-reference/commands/install.md +6 -8
- package/docs/cli-reference/commands/next.md +1 -1
- package/docs/cli-reference/commands/order.md +1 -1
- package/docs/cli-reference/commands/pay.md +1 -1
- package/docs/cli-reference/commands/readyz.md +10 -58
- package/docs/cli-reference/commands/refund/cancel.md +1 -1
- package/docs/cli-reference/commands/refund/create.md +1 -1
- package/docs/cli-reference/commands/refund/get.md +2 -2
- package/docs/cli-reference/commands/refund/index.md +1 -1
- package/docs/cli-reference/commands/refund/list.md +1 -1
- package/docs/cli-reference/commands/refund/watch.md +1 -1
- package/docs/cli-reference/commands/services/action.md +3 -24
- package/docs/cli-reference/commands/services/checkout.md +2 -13
- package/docs/cli-reference/commands/services/events.md +1 -1
- package/docs/cli-reference/commands/services/get.md +2 -4
- package/docs/cli-reference/commands/services/index.md +3 -4
- package/docs/cli-reference/commands/services/invoke.md +4 -6
- package/docs/cli-reference/commands/services/list.md +1 -1
- package/docs/cli-reference/commands/services/next.md +12 -105
- package/docs/cli-reference/commands/services/quote.md +3 -3
- package/docs/cli-reference/commands/services/read-result.md +6 -17
- package/docs/cli-reference/commands/services/start.md +2 -2
- package/docs/cli-reference/commands/vault/access.md +1 -5
- package/docs/cli-reference/conventions.md +6 -4
- package/docs/cli-reference/index.md +1 -8
- package/docs/skill-bundle-rollout/01-mcp-authentication.md +134 -0
- package/docs/skill-bundle-rollout/02-platform-bundle-repositories.md +210 -0
- package/docs/skill-bundle-rollout/03-platform-publishing.md +161 -0
- package/docs/skill-bundle-rollout/04-first-wave-platforms.md +69 -0
- package/docs/skill-bundle-rollout/05-sync-operations.md +62 -0
- package/docs/skill-bundle-rollout/README.md +151 -0
- package/package.json +13 -20
- package/skills/itpay/SKILL.md +105 -101
- package/assets/sell-preview/index.html +0 -6
- package/assets/sell-preview/preview.js +0 -9
- package/bin/darwin-amd64/itpay-sell +0 -0
- package/bin/darwin-arm64/itpay-sell +0 -0
- package/bin/linux-amd64/itpay-sell +0 -0
- package/bin/linux-arm64/itpay-sell +0 -0
- package/bin/windows-amd64/itpay-sell.exe +0 -0
- package/dist/src/commands/presentation.js +0 -153
- package/dist/src/commands/rail_catalog.js +0 -261
- package/dist/src/render/browser.js +0 -65
- package/dist/src/sell/commands.js +0 -80
- package/dist/src/sell/contract.js +0 -86
- package/dist/src/sell/local.js +0 -260
- package/dist/src/sell/mcp.js +0 -49
- package/dist/src/sell/preview.js +0 -70
- package/dist/src/sell/sync.js +0 -145
- package/dist/src/state/account_auth.js +0 -332
- package/dist/src/state/task_journal.js +0 -90
- package/docs/agent/buyer/rail-booking.json +0 -162
- package/docs/agent/buyer/rail-fast-checkout.json +0 -48
- package/docs/cli-reference/commands/auth.md +0 -96
- package/docs/cli-reference/commands/sell.md +0 -1364
- package/docs/cli-reference/commands/services/page.md +0 -71
- package/docs/cli-reference/commands/services/run.md +0 -156
- package/docs/sell.md +0 -84
- /package/bin/{itp.mjs → itp} +0 -0
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ The official V3 CLI and the single ItPay entry point for services, purchases, ac
|
|
|
4
4
|
|
|
5
5
|
## One Entry Point, Several Human Intents
|
|
6
6
|
|
|
7
|
-
`itpay` is the only public CLI entry point, and `$itpay` is its user-facing Skill invocation. The Agent first distinguishes a new service request, previously purchased content, order history, or a refund. Seller workflows use the
|
|
7
|
+
`itpay` is the only public CLI entry point, and `$itpay` is its user-facing Skill invocation. The Agent first distinguishes a new service request, previously purchased content, order history, or a refund. Seller workflows will use the same entry point later and are not implemented yet.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npm install -g @itpay/cli
|
|
@@ -25,7 +25,7 @@ Official Agent install prompt(可直接复制给 Agent):
|
|
|
25
25
|
Use a separate test prompt to set the exact official Dev Backend. Never put a
|
|
26
26
|
Dev override in the public install prompt.
|
|
27
27
|
|
|
28
|
-
The CLI defaults to the production Backend `https://app.itpay.ai`. Explicit
|
|
28
|
+
The CLI defaults to the production Backend `https://app.itpay.ai`. Explicit tests may set `ITPAY_BACKEND_URL=https://dev.itpay.ai`; every other Backend URL is rejected before network or local state access.
|
|
29
29
|
|
|
30
30
|
## Output Contract
|
|
31
31
|
|
|
@@ -54,7 +54,6 @@ Normative per-command contracts: [CLI Command Reference](docs/cli-reference/inde
|
|
|
54
54
|
| `claude-code-cli` | `terminal` |
|
|
55
55
|
| `workbuddy` | `plain-chat` |
|
|
56
56
|
| `zcode` | `plain-chat` |
|
|
57
|
-
| `doubao-work` | `plain-chat` |
|
|
58
57
|
| `kimi-code` | `terminal` |
|
|
59
58
|
| `openclaw` | 必须显式提供 |
|
|
60
59
|
|
|
@@ -68,7 +67,7 @@ The local installation keeps one Ed25519 private key and a separate registration
|
|
|
68
67
|
- `services start/invoke/action/checkout/next`: generic Service Execution flow.
|
|
69
68
|
- `cart add/show/remove/clear/next`, `buy`: canonical Cart and ordinary Checkout flow.
|
|
70
69
|
- `checkout`: authoritative payment and fulfillment recovery.
|
|
71
|
-
- `services read-result`: read
|
|
70
|
+
- `services read-result`: read one human-granted protected result.
|
|
72
71
|
- `order`, `orders`: exact order and account order views.
|
|
73
72
|
- `vault list/access/read`: find and read previously purchased content after time-limited human authorization.
|
|
74
73
|
- `refund create/list/get/watch/cancel`: Refund Owner flow.
|
|
@@ -91,12 +90,12 @@ itpay --agent-type <agent_type> services checkout <service_execution_id> --resum
|
|
|
91
90
|
itpay checkout --id <checkout_id> --token <display_token> --json
|
|
92
91
|
```
|
|
93
92
|
|
|
94
|
-
The local `~/.itpay-v3` directory stores one owner-only signing key, Backend-scoped Device registrations and Agent instances, idempotency operations, and recovery handles. Production uses `cart.json` / `operations.json`;
|
|
93
|
+
The local `~/.itpay-v3` directory stores one owner-only signing key, Backend-scoped Device registrations and Agent instances, idempotency operations, and recovery handles. Production uses `cart.json` / `operations.json`; dev uses `cart.dev.json` / `operations.dev.json`. Backend state remains authoritative. Do not delete or rotate this identity to recover quota.
|
|
95
94
|
|
|
96
95
|
## Environment
|
|
97
96
|
|
|
98
97
|
- `ITPAY_AGENT_TYPE`: stable alternative to global `--agent-type`.
|
|
99
|
-
- `ITPAY_BACKEND_URL`: optional
|
|
98
|
+
- `ITPAY_BACKEND_URL`: optional test override; only the exact official URL `https://dev.itpay.ai` is accepted. Unset it for production.
|
|
100
99
|
- `ITPAY_BEARER_TOKEN`: optional account-scoped browser session for legacy account reads. Local Agents normally use signed Device Authority plus the time-limited human authorization returned by `vault access`.
|
|
101
100
|
- `ITPAY_CART_SESSION_PATH`: local recovery-state path override.
|
|
102
101
|
- `ITPAY_CURRENCY`: ordinary Cart currency, default `CNY`.
|
|
@@ -128,9 +127,3 @@ Source boundaries:
|
|
|
128
127
|
- `src/state`: local Device Authority, idempotency journal, and recovery handles.
|
|
129
128
|
- `docs/cli-reference`: normative command contracts.
|
|
130
129
|
- `docs/agent/buyer`: packaged progressive workflow guidance.
|
|
131
|
-
|
|
132
|
-
### Run a published service
|
|
133
|
-
|
|
134
|
-
Use `itpay services run <service_id> --input-json ./input.json --json` for a published workflow. The input file must contain a JSON object matching the service input schema. With no input file, the CLI reports required fields and a command to continue the same execution.
|
|
135
|
-
|
|
136
|
-
Use `itpay services run <service_id> --execution <execution_id> --json` to resume. At payment, the CLI uses the existing Checkout QR/card handoff. The backend continues after verified payment even if the CLI exits. `--timeout <seconds>` controls initial status polling (default 120, maximum 600). Use existing order/refund commands for refund requests; an application is not a successful refund until its final status confirms success.
|
|
@@ -5,19 +5,6 @@ export class BackendClient {
|
|
|
5
5
|
constructor(http) {
|
|
6
6
|
this.http = http;
|
|
7
7
|
}
|
|
8
|
-
get localSimulation() { return this.http.localSimulation; }
|
|
9
|
-
// Called only through the fixed Sell operation contract, never arbitrary Agent URLs.
|
|
10
|
-
sellRequest(request) {
|
|
11
|
-
if (!request.path.startsWith("/v1/seller/organizations") && !request.path.startsWith("/v1/library/"))
|
|
12
|
-
throw new Error("Invalid Sell route");
|
|
13
|
-
return this.http.request(request.path, { method: request.method, ...(request.body !== undefined ? { body: request.body } : {}) });
|
|
14
|
-
}
|
|
15
|
-
agentAccountStatus() {
|
|
16
|
-
return this.http.get('/v1/agent-device-account-bindings');
|
|
17
|
-
}
|
|
18
|
-
bindAgentAccount(input) {
|
|
19
|
-
return this.http.post('/v1/agent-device-account-bindings', input);
|
|
20
|
-
}
|
|
21
8
|
readyz() {
|
|
22
9
|
return this.http.get("/v1/readyz");
|
|
23
10
|
}
|
|
@@ -121,15 +108,6 @@ export class BackendClient {
|
|
|
121
108
|
startServiceExecution(input) {
|
|
122
109
|
return this.http.post("/v1/service-executions", input);
|
|
123
110
|
}
|
|
124
|
-
advanceServiceExecution(id, input, idempotencyKey) {
|
|
125
|
-
return this.http.post(`/v1/service-executions/${encodeURIComponent(id)}/advance`, { input, idempotency_key: idempotencyKey });
|
|
126
|
-
}
|
|
127
|
-
getServiceExecutionResultItemPage(serviceExecutionID, resultItemID, offset, limit, view) {
|
|
128
|
-
const qs = new URLSearchParams({ offset: String(offset), limit: String(limit) });
|
|
129
|
-
if (view)
|
|
130
|
-
qs.set("view", view);
|
|
131
|
-
return this.http.get(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/result-items/${encodeURIComponent(resultItemID)}?${qs}`);
|
|
132
|
-
}
|
|
133
111
|
invokeServiceCapability(serviceExecutionID, capabilityID, input) {
|
|
134
112
|
return this.http.post(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/capabilities/${encodeURIComponent(capabilityID)}/invoke`, input, { replaySafe: Boolean(input.idempotency_key) });
|
|
135
113
|
}
|
|
@@ -142,9 +120,8 @@ export class BackendClient {
|
|
|
142
120
|
prepareServiceQuote(serviceExecutionID, input) {
|
|
143
121
|
return this.http.post(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/quotes`, input, { replaySafe: true });
|
|
144
122
|
}
|
|
145
|
-
getServiceExecution(serviceExecutionID
|
|
146
|
-
|
|
147
|
-
return this.http.get(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}${query}`);
|
|
123
|
+
getServiceExecution(serviceExecutionID) {
|
|
124
|
+
return this.http.get(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}`);
|
|
148
125
|
}
|
|
149
126
|
listServiceExecutions(limit = 50) {
|
|
150
127
|
return this.http.get(`/v1/service-executions?limit=${limit}`);
|
|
@@ -159,14 +136,6 @@ export class BackendClient {
|
|
|
159
136
|
getGrantedServiceResult(serviceExecutionID) {
|
|
160
137
|
return this.http.get(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/granted-result`);
|
|
161
138
|
}
|
|
162
|
-
getRailPlanningCatalog(serviceExecutionID, snapshotID) {
|
|
163
|
-
const query = snapshotID ? `?snapshot=${encodeURIComponent(snapshotID)}` : "";
|
|
164
|
-
return this.http.get(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/rail-planning/catalog${query}`);
|
|
165
|
-
}
|
|
166
|
-
getRailJourneyDetail(serviceExecutionID, journeyID, snapshotID) {
|
|
167
|
-
const query = snapshotID ? `?snapshot=${encodeURIComponent(snapshotID)}` : "";
|
|
168
|
-
return this.http.get(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/rail-planning/journeys/${encodeURIComponent(journeyID)}${query}`);
|
|
169
|
-
}
|
|
170
139
|
}
|
|
171
140
|
// --- SSE streaming helper ---
|
|
172
141
|
async function streamSSE(url, onEvent, signal) {
|
package/dist/src/client/http.js
CHANGED
|
@@ -14,7 +14,6 @@ export class HttpError extends Error {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
export class HttpClient {
|
|
17
|
-
localSimulation = false;
|
|
18
17
|
static MAX_TRANSPORT_RETRIES = 2;
|
|
19
18
|
baseURL;
|
|
20
19
|
fetchImpl;
|
|
@@ -71,7 +70,6 @@ export class HttpClient {
|
|
|
71
70
|
...(options.signal ? { signal: options.signal } : {}),
|
|
72
71
|
});
|
|
73
72
|
text = await response.text();
|
|
74
|
-
this.localSimulation = response.headers.get("X-ItPay-Environment") === "local_simulation";
|
|
75
73
|
}
|
|
76
74
|
catch (error) {
|
|
77
75
|
if (options.signal?.aborted)
|
package/dist/src/commands/buy.js
CHANGED
|
@@ -106,7 +106,6 @@ export async function runBuy(backend, config, options) {
|
|
|
106
106
|
}
|
|
107
107
|
// --- Build render plan (after payment if --pay) ---
|
|
108
108
|
const planInput = {
|
|
109
|
-
localSimulation: backend.localSimulation,
|
|
110
109
|
host: options.host,
|
|
111
110
|
checkoutID,
|
|
112
111
|
checkoutURL,
|
|
@@ -136,7 +135,7 @@ export async function runBuy(backend, config, options) {
|
|
|
136
135
|
});
|
|
137
136
|
}
|
|
138
137
|
// --- Output ---
|
|
139
|
-
if (options.jsonOutput
|
|
138
|
+
if (options.jsonOutput) {
|
|
140
139
|
const envelope = buildBuyEnvelope({
|
|
141
140
|
cart,
|
|
142
141
|
checkoutID,
|
|
@@ -149,7 +148,7 @@ export async function runBuy(backend, config, options) {
|
|
|
149
148
|
...(options.agentType ? { agentType: options.agentType } : {}),
|
|
150
149
|
...(options.target ? { target: options.target } : {}),
|
|
151
150
|
});
|
|
152
|
-
writeCommandEnvelope(envelope, { jsonOutput:
|
|
151
|
+
writeCommandEnvelope(envelope, { jsonOutput: true, ...(options.output ? { output: options.output } : {}) });
|
|
153
152
|
return {
|
|
154
153
|
kind: "checkout_rendered",
|
|
155
154
|
plan,
|
|
@@ -204,7 +203,6 @@ function buildBuyEnvelope(input) {
|
|
|
204
203
|
const presentationHandoff = buildCheckoutHandoff({
|
|
205
204
|
platform,
|
|
206
205
|
url: input.plan.linkOnlyURL ?? input.checkoutURL,
|
|
207
|
-
mobileUrl: input.checkoutURL,
|
|
208
206
|
amount,
|
|
209
207
|
plan: input.plan,
|
|
210
208
|
...(input.agentType ? { agentType: input.agentType } : {}),
|
|
@@ -220,7 +218,7 @@ function buildBuyEnvelope(input) {
|
|
|
220
218
|
result,
|
|
221
219
|
handoff: presentationHandoff.handoff,
|
|
222
220
|
instruction: presentationHandoff.instruction,
|
|
223
|
-
next: { command: input.plan.afterActionCommand ?? `itpay checkout --id ${input.checkoutID} --token ${input.displayToken} --json`, reason: "
|
|
221
|
+
next: { command: input.plan.afterActionCommand ?? `itpay checkout --id ${input.checkoutID} --token ${input.displayToken} --json`, reason: "稍后查询同一笔 Checkout 状态" },
|
|
224
222
|
recovery: [],
|
|
225
223
|
};
|
|
226
224
|
}
|
|
@@ -247,7 +245,7 @@ async function waitForPaymentSSE(backend, checkoutID, displayToken, timeoutSec)
|
|
|
247
245
|
}
|
|
248
246
|
// --- checkout QR plan ---
|
|
249
247
|
export function buildCheckoutQRPlan(input) {
|
|
250
|
-
const summary =
|
|
248
|
+
const summary = `Scan the QR or open ${input.checkoutURL} to start the human checkout flow.`;
|
|
251
249
|
const isPayment = input.paymentIntentID != null;
|
|
252
250
|
const afterCommand = qualifyItPayCommand(`itpay checkout --id ${input.checkoutID} --token ${input.displayToken}${input.locale === "en" ? " --locale en" : ""} --json`, input.agentType);
|
|
253
251
|
const platform = {
|
|
@@ -266,7 +264,6 @@ export function buildCheckoutQRPlan(input) {
|
|
|
266
264
|
};
|
|
267
265
|
const plan = {
|
|
268
266
|
kind: isPayment ? "payment_qr" : "checkout_qr",
|
|
269
|
-
...(input.localSimulation ? { localSimulation: true } : {}),
|
|
270
267
|
host: input.host,
|
|
271
268
|
summary,
|
|
272
269
|
url: input.qrPayload,
|
|
@@ -21,7 +21,7 @@ export function runCartAdd(session, options) {
|
|
|
21
21
|
offer_id: item.offerID,
|
|
22
22
|
quantity: item.quantity,
|
|
23
23
|
},
|
|
24
|
-
instruction: "仅写入本地兼容草稿,未验证目录、价格或服务合同;不要把它当作
|
|
24
|
+
instruction: "仅写入本地兼容草稿,未验证目录、价格或服务合同;不要把它当作 canonical Cart。",
|
|
25
25
|
next: { command: "itpay cart show --local", reason: "检查本地草稿" },
|
|
26
26
|
recovery: [],
|
|
27
27
|
};
|
|
@@ -74,10 +74,10 @@ export async function runCartAddServer(options) {
|
|
|
74
74
|
},
|
|
75
75
|
instruction: serviceExecutionID
|
|
76
76
|
? "服务型项目已创建 Service Execution;先读取其当前步骤,不要直接进入普通 buy。"
|
|
77
|
-
: "普通项目已加入
|
|
77
|
+
: "普通项目已加入 canonical Cart;先检查购物车,再创建 Checkout。",
|
|
78
78
|
next: serviceExecutionID
|
|
79
79
|
? { command: `itpay services next ${serviceExecutionID} --json`, reason: "读取服务执行的当前步骤" }
|
|
80
|
-
: { command: "itpay cart next --json", reason: "检查
|
|
80
|
+
: { command: "itpay cart next --json", reason: "检查 canonical Cart" },
|
|
81
81
|
recovery: [],
|
|
82
82
|
};
|
|
83
83
|
writeCommandEnvelope(envelope, {
|
|
@@ -121,7 +121,7 @@ export async function runCartAddQuoteServer(options) {
|
|
|
121
121
|
total: formatMoney(cart.amount_minor, cart.currency),
|
|
122
122
|
},
|
|
123
123
|
instruction: "付费服务报价已加入同一 Cart;每个项目仍保持独立 Execution 和交付。",
|
|
124
|
-
next: { command: `itpay buy --cart ${cart.cart_id} --json`, reason: "
|
|
124
|
+
next: { command: `itpay buy --cart ${cart.cart_id} --json`, reason: "确认项目齐全后创建一次合并付款" },
|
|
125
125
|
recovery: [{ command: "itpay cart show --json", reason: "检查当前合并 Cart" }],
|
|
126
126
|
};
|
|
127
127
|
writeCommandEnvelope(envelope, {
|
|
@@ -144,7 +144,7 @@ export function runCartRemove(session, options) {
|
|
|
144
144
|
catalog_variant_id: options.catalogVariantID,
|
|
145
145
|
offer_id: options.offerID,
|
|
146
146
|
},
|
|
147
|
-
instruction: "本地兼容草稿已更新;这不代表任何
|
|
147
|
+
instruction: "本地兼容草稿已更新;这不代表任何 canonical Cart 或 Service Execution 已变更。",
|
|
148
148
|
next: { command: "itpay cart show --local --json", reason: "检查剩余本地草稿" },
|
|
149
149
|
recovery: [],
|
|
150
150
|
}, {
|
|
@@ -176,7 +176,7 @@ export async function runCartRemoveServer(backend, session, cartItemID, options
|
|
|
176
176
|
cart_item_id: lineID,
|
|
177
177
|
remaining_item_count: cart.items.length,
|
|
178
178
|
},
|
|
179
|
-
instruction: "
|
|
179
|
+
instruction: "canonical Cart 已更新;被删除的最后一个 service-backed line 对应执行会由服务端一致性事务取消。",
|
|
180
180
|
next: { command: "itpay cart next --json", reason: "检查剩余内容" },
|
|
181
181
|
recovery: [],
|
|
182
182
|
};
|
|
@@ -198,10 +198,10 @@ export function runCartShow(session, options = {}) {
|
|
|
198
198
|
status: items.length > 0 ? "shown_local" : "local_empty",
|
|
199
199
|
result: { currency: snap.currency, items },
|
|
200
200
|
instruction: items.length > 0
|
|
201
|
-
? "这是未验证的本地兼容草稿,只能用于明确的普通商品流程;不要把它当作
|
|
201
|
+
? "这是未验证的本地兼容草稿,只能用于明确的普通商品流程;不要把它当作 canonical Cart。"
|
|
202
202
|
: "本地兼容草稿为空;从已发布目录重新选择项目。",
|
|
203
203
|
next: items.length > 0
|
|
204
|
-
? { command: "itpay buy --json", reason: "将普通本地草稿提交为
|
|
204
|
+
? { command: "itpay buy --json", reason: "将普通本地草稿提交为 canonical Cart" }
|
|
205
205
|
: { command: "itpay catalog list --json", reason: "读取已发布目录" },
|
|
206
206
|
recovery: [],
|
|
207
207
|
};
|
|
@@ -215,7 +215,7 @@ export async function runCartShowServer(backend, session, options = {}) {
|
|
|
215
215
|
writeCommandEnvelope({
|
|
216
216
|
status: "cart_handle_missing",
|
|
217
217
|
result: {},
|
|
218
|
-
instruction: "本地没有
|
|
218
|
+
instruction: "本地没有 canonical Cart 句柄;不要把本地草稿默认为服务端 Cart。",
|
|
219
219
|
next: { command: "itpay catalog list --json", reason: "读取已发布目录" },
|
|
220
220
|
recovery: [{ command: "itpay cart show --local --json", reason: "仅在明确需要时检查本地兼容草稿" }],
|
|
221
221
|
}, {
|
|
@@ -241,7 +241,7 @@ export async function runCartShowServer(backend, session, options = {}) {
|
|
|
241
241
|
},
|
|
242
242
|
instruction: items.length > 0
|
|
243
243
|
? "使用 line 或 execution 句柄继续;不要使用内部 quote lock ID。"
|
|
244
|
-
: "
|
|
244
|
+
: "canonical Cart 当前为空;从已发布目录选择项目。",
|
|
245
245
|
next: items.length > 0
|
|
246
246
|
? { command: "itpay cart next --json", reason: "取得当前首选动作" }
|
|
247
247
|
: { command: "itpay catalog list --json", reason: "读取已发布目录" },
|
|
@@ -278,7 +278,7 @@ export async function runCartAbandonServer(backend, session, options = {}) {
|
|
|
278
278
|
writeCommandEnvelope({
|
|
279
279
|
status: "cart_handle_missing",
|
|
280
280
|
result: {},
|
|
281
|
-
instruction: "本地没有
|
|
281
|
+
instruction: "本地没有 canonical Cart 句柄;未修改任何 Backend 或本地资源。",
|
|
282
282
|
next: { command: "itpay next --json", reason: "检查其他可恢复句柄" },
|
|
283
283
|
recovery: [{ command: "itpay cart clear --local --json", reason: "仅在明确放弃本地草稿和句柄时执行" }],
|
|
284
284
|
}, {
|
|
@@ -292,7 +292,7 @@ export async function runCartAbandonServer(backend, session, options = {}) {
|
|
|
292
292
|
const envelope = {
|
|
293
293
|
status: "abandoned",
|
|
294
294
|
result: { cart_id: cart.cart_id, server_abandoned: true },
|
|
295
|
-
instruction: "
|
|
295
|
+
instruction: "canonical Cart 已放弃;Backend 已在同一事务中软删除 active lines,并取消其未付款 Service Execution。",
|
|
296
296
|
next: { command: "itpay catalog list --json", reason: "仅在用户提出新需求时重新选择" },
|
|
297
297
|
recovery: [],
|
|
298
298
|
};
|
|
@@ -308,7 +308,7 @@ export async function runCartNext(backend, session, options = {}) {
|
|
|
308
308
|
writeCartNextEnvelope({
|
|
309
309
|
status: "cart_handle_missing",
|
|
310
310
|
result: {},
|
|
311
|
-
instruction: "本地没有
|
|
311
|
+
instruction: "本地没有 canonical Cart 句柄;先恢复已有资源,不要创建重复 Cart。",
|
|
312
312
|
next: { command: "itpay next --json", reason: "检查其他可恢复句柄" },
|
|
313
313
|
recovery: [{ command: "itpay services list --json", reason: "从服务端恢复当前设备的执行" }],
|
|
314
314
|
}, options);
|
|
@@ -325,7 +325,7 @@ export async function runCartNext(backend, session, options = {}) {
|
|
|
325
325
|
cart_status: cart.status,
|
|
326
326
|
service_execution_id: unquotedServiceLine.service_execution_id,
|
|
327
327
|
},
|
|
328
|
-
instruction: "该 Cart 包含
|
|
328
|
+
instruction: "该 Cart 包含 service-backed line;继续 Service Execution,不要从 Cart 猜 capability。",
|
|
329
329
|
next: {
|
|
330
330
|
command: `itpay services next ${unquotedServiceLine.service_execution_id} --json`,
|
|
331
331
|
reason: "读取服务端最新执行状态",
|
|
@@ -355,7 +355,7 @@ export async function runCartNext(backend, session, options = {}) {
|
|
|
355
355
|
instruction: cart.items.some((item) => item.service_quote_lock_id)
|
|
356
356
|
? "服务报价已锁定输入和价格;确认项目齐全后使用同一 Cart 创建一次 Checkout。"
|
|
357
357
|
: "该 Cart 是普通购买流程;使用同一 Cart 创建 Checkout,不要重复添加商品。",
|
|
358
|
-
next: { command: `itpay buy --cart ${cart.cart_id} --json`, reason: "
|
|
358
|
+
next: { command: `itpay buy --cart ${cart.cart_id} --json`, reason: "继续 canonical Cart 结算" },
|
|
359
359
|
recovery: [],
|
|
360
360
|
};
|
|
361
361
|
}
|
|
@@ -9,11 +9,14 @@ export async function runCatalogList(backend, options = {}) {
|
|
|
9
9
|
status: empty ? "catalog_empty" : "listed",
|
|
10
10
|
result: { catalog_version: manifest.version, services },
|
|
11
11
|
instruction: empty
|
|
12
|
-
? "
|
|
12
|
+
? "当前没有已发布服务;稍后重试,不要猜测 service_id。"
|
|
13
13
|
: "向用户解释主服务、辅助步骤和价格;得到用户意图后再启动对应 service_id。",
|
|
14
|
-
next:
|
|
15
|
-
? { command: `itpay
|
|
16
|
-
:
|
|
14
|
+
next: empty
|
|
15
|
+
? { command: `itpay catalog list${jsonFlag}`, reason: "稍后重新读取已发布目录" }
|
|
16
|
+
: {
|
|
17
|
+
command: `itpay services start ${services.length === 1 && firstServiceID ? firstServiceID : "<service_id>"}${jsonFlag}`,
|
|
18
|
+
reason: "启动用户选择的服务",
|
|
19
|
+
},
|
|
17
20
|
recovery: [],
|
|
18
21
|
}, {
|
|
19
22
|
...options,
|
|
@@ -22,13 +25,10 @@ export async function runCatalogList(backend, options = {}) {
|
|
|
22
25
|
}
|
|
23
26
|
function summarizeService(item) {
|
|
24
27
|
const flow = item.service_flow;
|
|
25
|
-
const offer = item.variants?.[0];
|
|
26
28
|
return {
|
|
27
29
|
service_id: item.service_id ?? null,
|
|
28
|
-
...(item.service_id ? { entry: `itpay services start ${item.service_id} --json` } : {}),
|
|
29
30
|
title: item.title,
|
|
30
31
|
description: item.description ?? "",
|
|
31
|
-
...(item.service_id?.startsWith("itpay-rail-") ? { guide: "itpay docs show rail-booking" } : {}),
|
|
32
32
|
...(flow ? {
|
|
33
33
|
discovery: {
|
|
34
34
|
title: flow.discovery.title,
|
|
@@ -43,12 +43,6 @@ function summarizeService(item) {
|
|
|
43
43
|
description: flow.primary_service.description,
|
|
44
44
|
price: formatProductMoney(flow.primary_service.amount_minor, flow.primary_service.currency),
|
|
45
45
|
},
|
|
46
|
-
} : offer ? {
|
|
47
|
-
primary_offer: {
|
|
48
|
-
title: offer.title || item.title,
|
|
49
|
-
description: item.description ?? "",
|
|
50
|
-
price: formatProductMoney(offer.amount_minor, offer.currency),
|
|
51
|
-
},
|
|
52
46
|
} : {}),
|
|
53
47
|
};
|
|
54
48
|
}
|
|
@@ -57,10 +51,6 @@ function catalogPlainLines(version, services) {
|
|
|
57
51
|
for (const service of services) {
|
|
58
52
|
lines.push(`service: ${String(service.title)}`);
|
|
59
53
|
lines.push(` service_id: ${String(service.service_id ?? "unavailable")}`);
|
|
60
|
-
if (service.entry)
|
|
61
|
-
lines.push(` entry: ${String(service.entry)}`);
|
|
62
|
-
if (service.guide)
|
|
63
|
-
lines.push(` entry: ${String(service.guide)}`);
|
|
64
54
|
if (service.description)
|
|
65
55
|
lines.push(` description: ${String(service.description)}`);
|
|
66
56
|
const discovery = service.discovery;
|
|
@@ -5,40 +5,11 @@ import { buildAgentChatHandoff } from "../render/markdown.js";
|
|
|
5
5
|
import { platformKeyForHost } from "../render/plan.js";
|
|
6
6
|
import { renderTerminalQR } from "../render/qr.js";
|
|
7
7
|
import { localizeCardURL, normalizeCardLocale } from "../render/locale.js";
|
|
8
|
-
import { openInSystemBrowser } from "../render/browser.js";
|
|
9
|
-
import { hostCapabilities, normalizeViewer } from "../state/client_context.js";
|
|
10
8
|
import { DEFAULT_BASE_URL } from "../state/config.js";
|
|
11
9
|
import { buildCheckoutQRPlan } from "./buy.js";
|
|
12
10
|
import { buildCheckoutHandoff, shouldPrepareLocalCheckoutImage } from "./checkout_handoff.js";
|
|
13
|
-
import {
|
|
14
|
-
import { resolvePresentation, resolveRelay } from "./presentation.js";
|
|
11
|
+
import { writeCommandEnvelope } from "./guidance.js";
|
|
15
12
|
export async function runCheckoutPresentation(backend, options) {
|
|
16
|
-
const present = options.present;
|
|
17
|
-
if (present !== undefined && !["auto", "browser", "image", "link", "none"].includes(present)) {
|
|
18
|
-
throw new CommandContractError("present_invalid", `--present must be one of auto|browser|image|link|none`, "使用支持的展示方式;未知参数已被拒绝。", []);
|
|
19
|
-
}
|
|
20
|
-
const relayRequested = Boolean(options.relayOption || options.confirmRelay || options.requestKey || options.relayStatus);
|
|
21
|
-
if (relayRequested && options.present && options.present !== "none") {
|
|
22
|
-
throw new CommandContractError("present_relay_conflict", "--present and --relay-* options are mutually exclusive", "展示和消息转发二选一;不要在同一次调用中同时开页面和发消息。", []);
|
|
23
|
-
}
|
|
24
|
-
if (options.noOpen && options.present === "browser") {
|
|
25
|
-
throw new CommandContractError("present_conflict", "--present browser conflicts with --no-open", "--no-open 与浏览器展示冲突;改用 --present link 或 image。", []);
|
|
26
|
-
}
|
|
27
|
-
if (options.relayStatus) {
|
|
28
|
-
// Read-only relay status. The backend exposes no relay routes yet, so the
|
|
29
|
-
// honest answer is unavailable — never a resend, never a guessed state.
|
|
30
|
-
writeCommandEnvelope({
|
|
31
|
-
status: "relay_unavailable",
|
|
32
|
-
result: { relay_id: options.relayStatus },
|
|
33
|
-
instruction: "当前 Backend 未提供消息转发状态查询;不要重发。读取原订单/Checkout 状态核对。",
|
|
34
|
-
next: { command: `itpay checkout --id ${options.checkoutID} --token ${options.displayToken} --json`, reason: "读取原 Checkout 当前状态" },
|
|
35
|
-
recovery: [],
|
|
36
|
-
}, {
|
|
37
|
-
...(options.jsonOutput !== undefined ? { jsonOutput: options.jsonOutput } : {}),
|
|
38
|
-
...(options.output ? { output: options.output } : {}),
|
|
39
|
-
});
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
13
|
const locale = normalizeCardLocale(options.locale);
|
|
43
14
|
const presentation = await backend.getCheckoutPresentation(options.checkoutID, options.displayToken, locale === "en" ? locale : undefined);
|
|
44
15
|
const host = options.host ?? "terminal";
|
|
@@ -51,13 +22,12 @@ export async function runCheckoutPresentation(backend, options) {
|
|
|
51
22
|
});
|
|
52
23
|
return;
|
|
53
24
|
}
|
|
54
|
-
const checkoutURL =
|
|
25
|
+
const checkoutURL = checkoutPageURL(options.baseURL, options.checkoutID, options.displayToken);
|
|
55
26
|
const cardURL = localizeCardURL(absolutePublicURL(options.baseURL, presentation.card_url ?? checkoutCardURL(options.baseURL, options.checkoutID, options.displayToken)), locale);
|
|
56
27
|
const qrPNGURL = absolutePublicURL(options.baseURL, presentation.card_png_url ?? presentation.qr_png_url ?? checkoutCardPNGURL(options.baseURL, options.checkoutID, options.displayToken));
|
|
57
28
|
const localizedPNGURL = localizeCardURL(qrPNGURL, locale);
|
|
58
29
|
const nextCommand = `itpay checkout --id ${options.checkoutID} --token ${options.displayToken}${locale === "en" ? " --locale en" : ""} --json`;
|
|
59
30
|
const plan = buildCheckoutQRPlan({
|
|
60
|
-
localSimulation: backend.localSimulation,
|
|
61
31
|
host,
|
|
62
32
|
checkoutID: options.checkoutID,
|
|
63
33
|
checkoutURL,
|
|
@@ -82,64 +52,6 @@ export async function runCheckoutPresentation(backend, options) {
|
|
|
82
52
|
});
|
|
83
53
|
}
|
|
84
54
|
const envelope = pendingCheckoutEnvelope(presentation, checkoutURL, plan, nextCommand, options.agentType, options.target);
|
|
85
|
-
// W5: deterministic presentation plan — business state first, then declared
|
|
86
|
-
// capabilities. `present` is the explicit display request; without it (or
|
|
87
|
-
// under --json alone) nothing opens.
|
|
88
|
-
// An explicit --viewer (including "unknown") wins over the host default.
|
|
89
|
-
const viewer = options.viewer ? normalizeViewer(options.viewer) : (host === "terminal" ? "desktop" : "unknown");
|
|
90
|
-
const locality = ["terminal", "codex", "claude-code"].includes(host) ? "same_device" : "unknown";
|
|
91
|
-
const decision = resolvePresentation({
|
|
92
|
-
business: {
|
|
93
|
-
commerce_policy: "allowed",
|
|
94
|
-
payment_state: "unpaid",
|
|
95
|
-
quote_valid: presentation.rail_quote?.expires_at
|
|
96
|
-
? Date.parse(presentation.rail_quote.expires_at) > Date.now()
|
|
97
|
-
: true,
|
|
98
|
-
amount_minor: presentation.checkout.amount_minor,
|
|
99
|
-
},
|
|
100
|
-
viewer,
|
|
101
|
-
executor_locality: locality,
|
|
102
|
-
capabilities: hostCapabilities(host),
|
|
103
|
-
...(explicitChoiceFor(options.present) ? { explicit_choice: explicitChoiceFor(options.present) } : {}),
|
|
104
|
-
});
|
|
105
|
-
const presentationResult = {
|
|
106
|
-
recommended: decision.recommended,
|
|
107
|
-
alternatives: decision.alternatives,
|
|
108
|
-
...(decision.blocker ? { blocker: decision.blocker } : {}),
|
|
109
|
-
};
|
|
110
|
-
let dispatch = { status: "not_attempted" };
|
|
111
|
-
// Explicit --present browser may open even under --json (the flag IS the
|
|
112
|
-
// display request); --present auto under --json stays a plan and never opens.
|
|
113
|
-
const browserRequested = options.present === "browser"
|
|
114
|
-
|| (options.present === "auto" && !options.jsonOutput && !options.noOpen && decision.recommended === "open_system_browser");
|
|
115
|
-
if (browserRequested && !options.noOpen && process.env.ITPAY_NO_BROWSER !== "1") {
|
|
116
|
-
const target = decision.recommended === "open_embedded_browser" ? decision.recommended : "open_system_browser";
|
|
117
|
-
dispatch = await openInSystemBrowser(checkoutURL, options.baseURL);
|
|
118
|
-
presentationResult.dispatched = { route: target, observation: dispatch.status };
|
|
119
|
-
// Dispatch proves the request was accepted — nothing about visibility.
|
|
120
|
-
if (dispatch.status === "dispatch_accepted") {
|
|
121
|
-
envelope.instruction += " 已发起在本机浏览器打开官方确认页;只有用户确认看到页面才视为已显示,没看到时改发链接。";
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
envelope.instruction += " 浏览器打开未成功;按 handoff 链接展示,不要重复尝试同一方式。";
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
if (relayRequested) {
|
|
128
|
-
presentationResult.relay = resolveRelay({
|
|
129
|
-
...(options.relayOption ? { relayOption: options.relayOption } : {}),
|
|
130
|
-
...(options.confirmRelay ? { confirmRelay: options.confirmRelay } : {}),
|
|
131
|
-
...(options.requestKey ? { requestKey: options.requestKey } : {}),
|
|
132
|
-
backendCapability: false,
|
|
133
|
-
});
|
|
134
|
-
envelope.instruction += " 消息转发当前不可用;不要声称已发送,改用现有官方入口展示。";
|
|
135
|
-
}
|
|
136
|
-
envelope.result = { ...envelope.result, presentation: presentationResult };
|
|
137
|
-
envelope.communication = {
|
|
138
|
-
schema_version: "itpay.communication.v1",
|
|
139
|
-
tell: decision.communication.tell,
|
|
140
|
-
wait_for: decision.communication.wait_for,
|
|
141
|
-
must_convey: decision.communication.must_convey,
|
|
142
|
-
};
|
|
143
55
|
const plainResult = checkoutPlainResult(envelope.result);
|
|
144
56
|
if (!options.jsonOutput && platformKeyForHost(host) === "terminal") {
|
|
145
57
|
plainResult.push("qr:", await renderTerminalQR(checkoutURL, "terminal"));
|
|
@@ -150,21 +62,12 @@ export async function runCheckoutPresentation(backend, options) {
|
|
|
150
62
|
plainResult,
|
|
151
63
|
});
|
|
152
64
|
}
|
|
153
|
-
function explicitChoiceFor(present) {
|
|
154
|
-
switch (present) {
|
|
155
|
-
case "browser": return "open_system_browser";
|
|
156
|
-
case "link": return "show_link";
|
|
157
|
-
case "image": return "show_official_image";
|
|
158
|
-
default: return undefined;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
65
|
function pendingCheckoutEnvelope(presentation, checkoutURL, plan, nextCommand, agentType, target) {
|
|
162
66
|
const platform = platformKeyForHost(plan.host);
|
|
163
67
|
const amount = formatMoney(presentation.checkout.amount_minor, presentation.checkout.currency);
|
|
164
68
|
const presentationHandoff = buildCheckoutHandoff({
|
|
165
69
|
platform,
|
|
166
70
|
url: plan.linkOnlyURL ?? checkoutURL,
|
|
167
|
-
mobileUrl: checkoutURL,
|
|
168
71
|
amount,
|
|
169
72
|
plan,
|
|
170
73
|
...(agentType ? { agentType } : {}),
|
|
@@ -175,46 +78,16 @@ function pendingCheckoutEnvelope(presentation, checkoutURL, plan, nextCommand, a
|
|
|
175
78
|
: {}),
|
|
176
79
|
...(platform === "markdown" ? { markdown: buildAgentChatHandoff(plan).markdown } : {}),
|
|
177
80
|
});
|
|
178
|
-
const railQuote = presentation.rail_quote;
|
|
179
|
-
const railPassengersPending = presentation.checkout_details === "rail_passengers" && !presentation.rail_passengers_confirmed;
|
|
180
81
|
return {
|
|
181
82
|
status: "human_checkout_required",
|
|
182
83
|
result: {
|
|
183
84
|
checkout_id: presentation.checkout.checkout_id,
|
|
184
85
|
payment: "pending",
|
|
185
86
|
amount,
|
|
186
|
-
...(railQuote ? { rail_quote: {
|
|
187
|
-
passengers: railQuote.passengers,
|
|
188
|
-
expires_at: railQuote.expires_at,
|
|
189
|
-
legs: railQuote.legs.map((leg) => ({
|
|
190
|
-
train_code: leg.train_code,
|
|
191
|
-
travel_date: leg.travel_date,
|
|
192
|
-
route: `${leg.from} → ${leg.to}`,
|
|
193
|
-
time: `${leg.departure}–${leg.arrival}`,
|
|
194
|
-
seat_name: leg.seat_name,
|
|
195
|
-
...(leg.seat_preferences?.length ? { seat_preferences: leg.seat_preferences } : {}),
|
|
196
|
-
...(leg.seat_request_planned ? {
|
|
197
|
-
seat_request_planned: leg.seat_request_planned,
|
|
198
|
-
auto_reason: leg.auto_reason ?? "none",
|
|
199
|
-
} : {}),
|
|
200
|
-
})),
|
|
201
|
-
} } : {}),
|
|
202
|
-
...(railPassengersPending ? { rail_passengers_confirmed: false } : {}),
|
|
203
|
-
...(presentation.payment_deadline_at ? { payment_deadline_at: presentation.payment_deadline_at } : {}),
|
|
204
|
-
...(typeof presentation.payment_remaining_seconds === "number"
|
|
205
|
-
? { payment_remaining_seconds: presentation.payment_remaining_seconds }
|
|
206
|
-
: {}),
|
|
207
|
-
...(presentation.server_now ? { server_now: presentation.server_now } : {}),
|
|
208
|
-
...(presentation.verified_sms_contact ? { verified_sms_contact: presentation.verified_sms_contact } : {}),
|
|
209
|
-
...(presentation.account_phone_option?.masked_recipient
|
|
210
|
-
? { account_phone_option: { masked_recipient: presentation.account_phone_option.masked_recipient } }
|
|
211
|
-
: {}),
|
|
212
87
|
},
|
|
213
88
|
handoff: presentationHandoff.handoff,
|
|
214
|
-
instruction:
|
|
215
|
-
|
|
216
|
-
: presentationHandoff.instruction,
|
|
217
|
-
next: { command: nextCommand, reason: "展示后停止;仅用户完成操作或要求核对时读取同一 Checkout" },
|
|
89
|
+
instruction: presentationHandoff.instruction,
|
|
90
|
+
next: { command: nextCommand, reason: "稍后只查询同一 Checkout" },
|
|
218
91
|
recovery: [],
|
|
219
92
|
};
|
|
220
93
|
}
|
|
@@ -261,7 +134,7 @@ function checkoutNeedsHumanHandoff(status) {
|
|
|
261
134
|
return !new Set(["payment_succeeded", "completed", "failed", "expired", "refunded"]).has(status);
|
|
262
135
|
}
|
|
263
136
|
function checkoutPlainResult(result) {
|
|
264
|
-
return
|
|
137
|
+
return Object.entries(result).map(([key, value]) => `${key}: ${typeof value === "string" ? value : JSON.stringify(value)}`);
|
|
265
138
|
}
|
|
266
139
|
function formatMoney(amountMinor, currency) {
|
|
267
140
|
return `${(amountMinor / 100).toFixed(2)} ${currency}`;
|
|
@@ -270,20 +143,6 @@ function checkoutPageURL(baseURL, checkoutID, displayToken) {
|
|
|
270
143
|
const root = publicRoot(baseURL);
|
|
271
144
|
return `${root}/checkout/${encodeURIComponent(checkoutID)}?display_token=${encodeURIComponent(displayToken)}`;
|
|
272
145
|
}
|
|
273
|
-
function savedCheckoutURLOrFallback(savedURL, checkoutID, displayToken, fallback) {
|
|
274
|
-
if (!savedURL) {
|
|
275
|
-
return fallback;
|
|
276
|
-
}
|
|
277
|
-
try {
|
|
278
|
-
const parsed = new URL(savedURL);
|
|
279
|
-
const belongsToCheckout = parsed.pathname === `/checkout/${checkoutID}`;
|
|
280
|
-
const sameToken = parsed.searchParams.get("display_token") === displayToken;
|
|
281
|
-
return belongsToCheckout && sameToken ? savedURL : fallback;
|
|
282
|
-
}
|
|
283
|
-
catch {
|
|
284
|
-
return fallback;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
146
|
function checkoutCardURL(baseURL, checkoutID, displayToken) {
|
|
288
147
|
const root = publicRoot(baseURL);
|
|
289
148
|
return `${root}/v1/checkouts/${encodeURIComponent(checkoutID)}/card?display_token=${encodeURIComponent(displayToken)}`;
|