@itpay/cli 2.1.41 → 2.1.44
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 +12 -5
- package/assets/sell-preview/index.html +6 -0
- package/assets/sell-preview/preview.js +9 -0
- 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/client/backend.js +38 -2
- package/dist/src/client/http.js +2 -0
- package/dist/src/commands/aftercare.js +12 -0
- package/dist/src/commands/buy.js +17 -58
- package/dist/src/commands/cart.js +17 -15
- package/dist/src/commands/catalog.js +39 -14
- package/dist/src/commands/checkout.js +146 -5
- package/dist/src/commands/checkout_handoff.js +11 -61
- package/dist/src/commands/docs.js +7 -18
- package/dist/src/commands/guidance.js +1 -134
- package/dist/src/commands/install.js +6 -3
- package/dist/src/commands/order.js +34 -3
- package/dist/src/commands/orders.js +4 -9
- package/dist/src/commands/pay.js +1 -1
- package/dist/src/commands/presentation.js +153 -0
- package/dist/src/commands/rail_catalog.js +1 -0
- package/dist/src/commands/readyz.js +1 -1
- package/dist/src/commands/refund.js +13 -57
- package/dist/src/commands/services/actions.js +133 -0
- package/dist/src/commands/services/commerce.js +197 -0
- package/dist/src/commands/services/lifecycle.js +418 -0
- package/dist/src/commands/services/results.js +130 -0
- package/dist/src/commands/services.js +14 -1068
- package/dist/src/commands/skill.js +8 -5
- package/dist/src/commands/vault.js +27 -20
- package/dist/src/commands/vault_handoff.js +18 -0
- package/dist/src/main.js +332 -46
- package/dist/src/render/browser.js +65 -0
- package/dist/src/render/checkout_handoff.js +101 -0
- package/dist/src/render/checkout_plan.js +47 -0
- package/dist/src/render/markdown.js +0 -3
- package/dist/src/render/qr.js +1 -1
- package/dist/src/sell/commands.js +80 -0
- package/dist/src/sell/contract.js +86 -0
- package/dist/src/sell/local.js +260 -0
- package/dist/src/sell/mcp.js +49 -0
- package/dist/src/sell/preview.js +70 -0
- package/dist/src/sell/sync.js +145 -0
- package/dist/src/services/adapter.js +1 -0
- package/dist/src/services/presentation.js +198 -0
- package/dist/src/services/rail/actions.js +51 -0
- package/dist/src/services/rail/adapter.js +36 -0
- package/dist/src/services/rail/aftercare.js +45 -0
- package/dist/src/services/rail/catalog.js +261 -0
- package/dist/src/services/rail/checkout.js +19 -0
- package/dist/src/services/rail/fulfillment.js +76 -0
- package/dist/src/services/rail/guide.js +110 -0
- package/dist/src/services/rail/invocation.js +54 -0
- package/dist/src/services/rail/page.js +76 -0
- package/dist/src/services/rail/pagination.js +6 -0
- package/dist/src/services/rail/query.js +71 -0
- package/dist/src/services/rail/readers.js +107 -0
- package/dist/src/services/rail/reading.js +125 -0
- package/dist/src/services/rail/recovery.js +51 -0
- package/dist/src/services/rail/results.js +378 -0
- package/dist/src/services/rail/review.js +268 -0
- package/dist/src/services/rail/saved.js +49 -0
- package/dist/src/services/rail/wait.js +11 -0
- package/dist/src/services/registry.js +31 -0
- package/dist/src/services/schema.js +21 -0
- package/dist/src/services/shared/envelope.js +184 -0
- package/dist/src/services/shared/input.js +39 -0
- package/dist/src/services/shared/pagination.js +28 -0
- package/dist/src/services/shared/platform.js +365 -0
- package/dist/src/services/shared/refund.js +40 -0
- package/dist/src/services/shared/workflow.js +28 -0
- package/dist/src/state/account_auth.js +354 -0
- package/dist/src/state/cart_session.js +4 -1
- package/dist/src/state/client_context.js +72 -0
- package/dist/src/state/config.js +44 -14
- package/dist/src/state/device_authority.js +402 -72
- package/dist/src/state/task_journal.js +90 -0
- package/docs/agent/buyer/cart-checkout.json +8 -6
- package/docs/agent/buyer/catalog-list.json +29 -9
- package/docs/agent/buyer/identity-and-sessions.json +26 -9
- package/docs/agent/buyer/install-and-setup.json +17 -15
- package/docs/agent/buyer/orders-refunds.json +25 -9
- package/docs/agent/buyer/payment-flow.json +6 -4
- package/docs/agent/buyer/purchased-content.json +18 -4
- package/docs/agent/buyer/quickstart.json +26 -17
- package/docs/agent/buyer/rail-booking.json +176 -0
- package/docs/agent/buyer/rail-fast-checkout.json +48 -0
- package/docs/agent/buyer/render-hosts.json +23 -12
- package/docs/agent/buyer/service-feedback.json +4 -3
- package/docs/cli-reference/agent-types.md +7 -2
- package/docs/cli-reference/commands/aftercare.md +15 -0
- package/docs/cli-reference/commands/auth.md +96 -0
- package/docs/cli-reference/commands/buy.md +2 -1
- package/docs/cli-reference/commands/cart/add.md +5 -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 +5 -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 +12 -2
- package/docs/cli-reference/commands/device.md +18 -11
- 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 +8 -6
- 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 +11 -11
- 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/sell.md +1364 -0
- package/docs/cli-reference/commands/services/action.md +24 -3
- package/docs/cli-reference/commands/services/checkout.md +17 -2
- package/docs/cli-reference/commands/services/events.md +1 -1
- package/docs/cli-reference/commands/services/get.md +4 -2
- package/docs/cli-reference/commands/services/index.md +4 -3
- package/docs/cli-reference/commands/services/invoke.md +6 -4
- package/docs/cli-reference/commands/services/list.md +1 -1
- package/docs/cli-reference/commands/services/next.md +78 -12
- package/docs/cli-reference/commands/services/page.md +71 -0
- package/docs/cli-reference/commands/services/quote.md +4 -4
- package/docs/cli-reference/commands/services/read-result.md +17 -6
- package/docs/cli-reference/commands/services/run.md +152 -0
- package/docs/cli-reference/commands/services/start.md +5 -3
- package/docs/cli-reference/commands/vault/access.md +5 -1
- package/docs/cli-reference/conventions.md +4 -6
- package/docs/cli-reference/index.md +10 -1
- package/docs/sell.md +84 -0
- package/package.json +20 -12
- package/skills/itpay/SKILL.md +112 -102
- package/docs/skill-bundle-rollout/01-mcp-authentication.md +0 -134
- package/docs/skill-bundle-rollout/02-platform-bundle-repositories.md +0 -210
- package/docs/skill-bundle-rollout/03-platform-publishing.md +0 -161
- package/docs/skill-bundle-rollout/04-first-wave-platforms.md +0 -69
- package/docs/skill-bundle-rollout/05-sync-operations.md +0 -62
- package/docs/skill-bundle-rollout/README.md +0 -151
- /package/bin/{itp → itp.mjs} +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
|
|
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 existing `itpay sell` entry point.
|
|
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 sandbox runs may set `ITPAY_BACKEND_URL=https://sandbox.itpay.ai`; every other Backend URL is rejected before network or local state access.
|
|
29
29
|
|
|
30
30
|
## Output Contract
|
|
31
31
|
|
|
@@ -54,6 +54,7 @@ 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` |
|
|
57
58
|
| `kimi-code` | `terminal` |
|
|
58
59
|
| `openclaw` | 必须显式提供 |
|
|
59
60
|
|
|
@@ -67,7 +68,7 @@ The local installation keeps one Ed25519 private key and a separate registration
|
|
|
67
68
|
- `services start/invoke/action/checkout/next`: generic Service Execution flow.
|
|
68
69
|
- `cart add/show/remove/clear/next`, `buy`: canonical Cart and ordinary Checkout flow.
|
|
69
70
|
- `checkout`: authoritative payment and fulfillment recovery.
|
|
70
|
-
- `services read-result`: read
|
|
71
|
+
- `services read-result`: read saved railway query pages or human-granted protected content.
|
|
71
72
|
- `order`, `orders`: exact order and account order views.
|
|
72
73
|
- `vault list/access/read`: find and read previously purchased content after time-limited human authorization.
|
|
73
74
|
- `refund create/list/get/watch/cancel`: Refund Owner flow.
|
|
@@ -90,12 +91,12 @@ itpay --agent-type <agent_type> services checkout <service_execution_id> --resum
|
|
|
90
91
|
itpay checkout --id <checkout_id> --token <display_token> --json
|
|
91
92
|
```
|
|
92
93
|
|
|
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`;
|
|
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`; sandbox uses `cart.sandbox.json` / `operations.sandbox.json`. Backend state remains authoritative. Do not delete or rotate this identity to recover quota.
|
|
94
95
|
|
|
95
96
|
## Environment
|
|
96
97
|
|
|
97
98
|
- `ITPAY_AGENT_TYPE`: stable alternative to global `--agent-type`.
|
|
98
|
-
- `ITPAY_BACKEND_URL`: optional
|
|
99
|
+
- `ITPAY_BACKEND_URL`: optional sandbox override; only the exact official URL `https://sandbox.itpay.ai` is accepted. Unset it for production.
|
|
99
100
|
- `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`.
|
|
100
101
|
- `ITPAY_CART_SESSION_PATH`: local recovery-state path override.
|
|
101
102
|
- `ITPAY_CURRENCY`: ordinary Cart currency, default `CNY`.
|
|
@@ -127,3 +128,9 @@ Source boundaries:
|
|
|
127
128
|
- `src/state`: local Device Authority, idempotency journal, and recovery handles.
|
|
128
129
|
- `docs/cli-reference`: normative command contracts.
|
|
129
130
|
- `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.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>ItPay 工作流预览</title>
|
|
3
|
+
<style>body{max-width:1000px;margin:40px auto;padding:0 24px;font:16px/1.6 system-ui;color:#17212b;background:#fafbfc}pre{padding:20px;background:white;border:1px solid #dde3e8;border-radius:8px;overflow:auto;white-space:pre-wrap}small{color:#52616e}h2{margin-top:32px}</style>
|
|
4
|
+
<h1>工作流预览</h1><p id="status" role="status">正在读取本地项目…</p><small>只读本地预览。正式发布仍需平台验证、审核和 Canary。</small>
|
|
5
|
+
<h2>工作流</h2><pre id="document"></pre><h2>价格配置</h2><pre id="pricing"></pre><h2>最近测试</h2><pre id="run"></pre>
|
|
6
|
+
<script src="preview.js"></script></html>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
fetch('document', {cache:'no-store'}).then(response => {
|
|
2
|
+
if (!response.ok) throw new Error('请先初始化项目并保存工作流版本。');
|
|
3
|
+
return response.json();
|
|
4
|
+
}).then(data => {
|
|
5
|
+
document.getElementById('status').textContent = `版本:${data.version} · ${data.hash}`;
|
|
6
|
+
document.getElementById('document').textContent = data.document;
|
|
7
|
+
document.getElementById('pricing').textContent = JSON.stringify(data.pricing, null, 2);
|
|
8
|
+
document.getElementById('run').textContent = data.run ? JSON.stringify(data.run, null, 2) : '尚无本地测试记录';
|
|
9
|
+
}).catch(error => { document.getElementById('status').textContent = error.message; });
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -5,6 +5,19 @@ 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
|
+
}
|
|
8
21
|
readyz() {
|
|
9
22
|
return this.http.get("/v1/readyz");
|
|
10
23
|
}
|
|
@@ -90,6 +103,11 @@ export class BackendClient {
|
|
|
90
103
|
readBuyerVaultArtifact(artifactRef, sections) {
|
|
91
104
|
return this.http.post(`/v1/vault/artifacts/${encodeURIComponent(artifactRef)}/reads`, sections.length ? { sections } : {});
|
|
92
105
|
}
|
|
106
|
+
listRailAftercare(orderID) { return this.http.get(`/v1/orders/${encodeURIComponent(orderID)}/rail-aftercare`); }
|
|
107
|
+
createRailAftercare(orderID, input, idempotencyKey) { return this.http.post(`/v1/orders/${encodeURIComponent(orderID)}/rail-aftercare`, input, { idempotencyKey }); }
|
|
108
|
+
getRailAftercare(id) { return this.http.get(`/v1/rail-aftercare/${encodeURIComponent(id)}`); }
|
|
109
|
+
respondRailAftercare(id, input) { return this.http.post(`/v1/rail-aftercare/${encodeURIComponent(id)}/respond`, input); }
|
|
110
|
+
cancelRailAftercare(id, input) { return this.http.post(`/v1/rail-aftercare/${encodeURIComponent(id)}/cancel`, input); }
|
|
93
111
|
// --- Refund ---
|
|
94
112
|
createRefund(orderID, input, bearer, idempotencyKey) {
|
|
95
113
|
const options = { ...(bearer ? { bearer } : {}), ...(idempotencyKey ? { idempotencyKey } : {}) };
|
|
@@ -108,6 +126,15 @@ export class BackendClient {
|
|
|
108
126
|
startServiceExecution(input) {
|
|
109
127
|
return this.http.post("/v1/service-executions", input);
|
|
110
128
|
}
|
|
129
|
+
advanceServiceExecution(id, input, idempotencyKey) {
|
|
130
|
+
return this.http.post(`/v1/service-executions/${encodeURIComponent(id)}/advance`, { input, idempotency_key: idempotencyKey });
|
|
131
|
+
}
|
|
132
|
+
getServiceExecutionResultItemPage(serviceExecutionID, resultItemID, offset, limit, view) {
|
|
133
|
+
const qs = new URLSearchParams({ offset: String(offset), limit: String(limit) });
|
|
134
|
+
if (view)
|
|
135
|
+
qs.set("view", view);
|
|
136
|
+
return this.http.get(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/result-items/${encodeURIComponent(resultItemID)}?${qs}`);
|
|
137
|
+
}
|
|
111
138
|
invokeServiceCapability(serviceExecutionID, capabilityID, input) {
|
|
112
139
|
return this.http.post(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/capabilities/${encodeURIComponent(capabilityID)}/invoke`, input, { replaySafe: Boolean(input.idempotency_key) });
|
|
113
140
|
}
|
|
@@ -120,8 +147,9 @@ export class BackendClient {
|
|
|
120
147
|
prepareServiceQuote(serviceExecutionID, input) {
|
|
121
148
|
return this.http.post(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/quotes`, input, { replaySafe: true });
|
|
122
149
|
}
|
|
123
|
-
getServiceExecution(serviceExecutionID) {
|
|
124
|
-
|
|
150
|
+
getServiceExecution(serviceExecutionID, opts = {}) {
|
|
151
|
+
const query = opts.sinceSnapshot ? `?since_snapshot=${encodeURIComponent(opts.sinceSnapshot)}` : "";
|
|
152
|
+
return this.http.get(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}${query}`);
|
|
125
153
|
}
|
|
126
154
|
listServiceExecutions(limit = 50) {
|
|
127
155
|
return this.http.get(`/v1/service-executions?limit=${limit}`);
|
|
@@ -136,6 +164,14 @@ export class BackendClient {
|
|
|
136
164
|
getGrantedServiceResult(serviceExecutionID) {
|
|
137
165
|
return this.http.get(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/granted-result`);
|
|
138
166
|
}
|
|
167
|
+
getRailPlanningCatalog(serviceExecutionID, snapshotID) {
|
|
168
|
+
const query = snapshotID ? `?snapshot=${encodeURIComponent(snapshotID)}` : "";
|
|
169
|
+
return this.http.get(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/rail-planning/catalog${query}`);
|
|
170
|
+
}
|
|
171
|
+
getRailJourneyDetail(serviceExecutionID, journeyID, snapshotID) {
|
|
172
|
+
const query = snapshotID ? `?snapshot=${encodeURIComponent(snapshotID)}` : "";
|
|
173
|
+
return this.http.get(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/rail-planning/journeys/${encodeURIComponent(journeyID)}${query}`);
|
|
174
|
+
}
|
|
139
175
|
}
|
|
140
176
|
// --- SSE streaming helper ---
|
|
141
177
|
async function streamSSE(url, onEvent, signal) {
|
package/dist/src/client/http.js
CHANGED
|
@@ -14,6 +14,7 @@ export class HttpError extends Error {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
export class HttpClient {
|
|
17
|
+
localSimulation = false;
|
|
17
18
|
static MAX_TRANSPORT_RETRIES = 2;
|
|
18
19
|
baseURL;
|
|
19
20
|
fetchImpl;
|
|
@@ -70,6 +71,7 @@ export class HttpClient {
|
|
|
70
71
|
...(options.signal ? { signal: options.signal } : {}),
|
|
71
72
|
});
|
|
72
73
|
text = await response.text();
|
|
74
|
+
this.localSimulation = response.headers.get("X-ItPay-Environment") === "local_simulation";
|
|
73
75
|
}
|
|
74
76
|
catch (error) {
|
|
75
77
|
if (options.signal?.aborted)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { serviceAdapter } from "../services/registry.js";
|
|
2
|
+
import { CommandContractError } from "../services/shared/envelope.js";
|
|
3
|
+
function adapter(identity) {
|
|
4
|
+
const capability = serviceAdapter(identity).aftercare;
|
|
5
|
+
if (!capability)
|
|
6
|
+
throw new CommandContractError("aftercare_unsupported", "Locked service does not support this aftercare operation", "使用原服务声明的售后入口,不套用其他业务规则。", []);
|
|
7
|
+
return capability;
|
|
8
|
+
}
|
|
9
|
+
export async function runAftercareList(backend, orderID) { const value = await backend.listRailAftercare(orderID); return adapter(value.execution_identity).directory(value, orderID); }
|
|
10
|
+
export async function runAftercareGet(backend, id) { const value = await backend.getRailAftercare(id); return adapter(value.execution_identity).present(value); }
|
|
11
|
+
export async function runAftercareCreate(backend, config, orderID, input) { const value = await backend.listRailAftercare(orderID); return adapter(value.execution_identity).create(backend, config, orderID, input); }
|
|
12
|
+
export async function runAftercareRespond(backend, id, input, cancel = false) { const value = await backend.getRailAftercare(id); return adapter(value.execution_identity).respond(backend, id, input, cancel); }
|
package/dist/src/commands/buy.js
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
// 1. validate client context (host/target)
|
|
3
|
-
// 2. create cart via POST /v1/carts
|
|
4
|
-
// 3. create checkout via POST /v1/checkouts (consumes the cart)
|
|
5
|
-
// 4. build a checkout_qr render plan + dispatch to host renderer
|
|
6
|
-
// 5. [--pay] create payment intent → render payment QR → optionally SSE wait
|
|
7
|
-
// 6. [--json] output JSON instead of terminal text
|
|
8
|
-
//
|
|
9
|
-
// The display token is passed to the renderer and persisted in the local cart
|
|
10
|
-
// session so a restarted agent can resume the same checkout.
|
|
1
|
+
import { buildCheckoutQRPlan } from "../render/checkout_plan.js";
|
|
11
2
|
import { operationID } from "../state/config.js";
|
|
12
3
|
import { validateContext } from "../state/client_context.js";
|
|
13
4
|
import { dispatchRender } from "../render/index.js";
|
|
@@ -17,8 +8,7 @@ import { localizeCardURL, normalizeCardLocale } from "../render/locale.js";
|
|
|
17
8
|
import { buildAgentChatHandoff } from "../render/markdown.js";
|
|
18
9
|
import { formatMoney } from "../render/output.js";
|
|
19
10
|
import { CommandContractError, writeCommandEnvelope } from "./guidance.js";
|
|
20
|
-
import {
|
|
21
|
-
import { qualifyItPayCommand } from "../state/agent_type.js";
|
|
11
|
+
import { assertCheckoutPayable, buildCheckoutHandoff, shouldPrepareLocalCheckoutImage } from "./checkout_handoff.js";
|
|
22
12
|
export async function runBuy(backend, config, options) {
|
|
23
13
|
const err = validateContext(options.host, options.target);
|
|
24
14
|
if (err) {
|
|
@@ -62,6 +52,15 @@ export async function runBuy(backend, config, options) {
|
|
|
62
52
|
...(options.contact ? { delivery_contact: options.contact } : {}),
|
|
63
53
|
};
|
|
64
54
|
const checkout = await backend.createCheckout(checkoutRequest, idempotencyKey);
|
|
55
|
+
const authoritative = checkout.display_token
|
|
56
|
+
? await backend.getCheckoutPresentation(checkout.checkout.checkout_id, checkout.display_token)
|
|
57
|
+
: undefined;
|
|
58
|
+
assertCheckoutPayable(checkout, authoritative, cart.cart_id, latestLine?.service_execution_id);
|
|
59
|
+
const current = authoritative?.checkout ?? checkout.checkout;
|
|
60
|
+
// Only the returned checkout's items are authoritative for the handoff.
|
|
61
|
+
if (!authoritative)
|
|
62
|
+
throw new Error("checkout presentation unavailable");
|
|
63
|
+
cart = { ...cart, items: authoritative.items, amount_minor: current.amount_minor, currency: current.currency };
|
|
65
64
|
options.cartSession.rememberCheckout({
|
|
66
65
|
checkoutID: checkout.checkout.checkout_id,
|
|
67
66
|
displayToken: checkout.display_token,
|
|
@@ -106,6 +105,7 @@ export async function runBuy(backend, config, options) {
|
|
|
106
105
|
}
|
|
107
106
|
// --- Build render plan (after payment if --pay) ---
|
|
108
107
|
const planInput = {
|
|
108
|
+
localSimulation: backend.localSimulation,
|
|
109
109
|
host: options.host,
|
|
110
110
|
checkoutID,
|
|
111
111
|
checkoutURL,
|
|
@@ -135,7 +135,7 @@ export async function runBuy(backend, config, options) {
|
|
|
135
135
|
});
|
|
136
136
|
}
|
|
137
137
|
// --- Output ---
|
|
138
|
-
if (options.jsonOutput) {
|
|
138
|
+
if (options.jsonOutput || platformKeyForHost(options.host) === "plain_chat") {
|
|
139
139
|
const envelope = buildBuyEnvelope({
|
|
140
140
|
cart,
|
|
141
141
|
checkoutID,
|
|
@@ -148,7 +148,7 @@ export async function runBuy(backend, config, options) {
|
|
|
148
148
|
...(options.agentType ? { agentType: options.agentType } : {}),
|
|
149
149
|
...(options.target ? { target: options.target } : {}),
|
|
150
150
|
});
|
|
151
|
-
writeCommandEnvelope(envelope, { jsonOutput:
|
|
151
|
+
writeCommandEnvelope(envelope, { jsonOutput: Boolean(options.jsonOutput), ...(options.output ? { output: options.output } : {}), ...(options.agentType ? { agentType: options.agentType } : {}) });
|
|
152
152
|
return {
|
|
153
153
|
kind: "checkout_rendered",
|
|
154
154
|
plan,
|
|
@@ -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 } : {}),
|
|
@@ -218,7 +219,7 @@ function buildBuyEnvelope(input) {
|
|
|
218
219
|
result,
|
|
219
220
|
handoff: presentationHandoff.handoff,
|
|
220
221
|
instruction: presentationHandoff.instruction,
|
|
221
|
-
next: { command: input.plan.afterActionCommand ?? `itpay checkout --id ${input.checkoutID} --token ${input.displayToken} --json`, reason: "
|
|
222
|
+
next: { command: input.plan.afterActionCommand ?? `itpay checkout --id ${input.checkoutID} --token ${input.displayToken} --json`, reason: "仅用户完成付款或要求查询时,读取同一笔Checkout权威状态" },
|
|
222
223
|
recovery: [],
|
|
223
224
|
};
|
|
224
225
|
}
|
|
@@ -243,49 +244,6 @@ async function waitForPaymentSSE(backend, checkoutID, displayToken, timeoutSec)
|
|
|
243
244
|
});
|
|
244
245
|
});
|
|
245
246
|
}
|
|
246
|
-
// --- checkout QR plan ---
|
|
247
|
-
export function buildCheckoutQRPlan(input) {
|
|
248
|
-
const summary = `Scan the QR or open ${input.checkoutURL} to start the human checkout flow.`;
|
|
249
|
-
const isPayment = input.paymentIntentID != null;
|
|
250
|
-
const afterCommand = qualifyItPayCommand(`itpay checkout --id ${input.checkoutID} --token ${input.displayToken}${input.locale === "en" ? " --locale en" : ""} --json`, input.agentType);
|
|
251
|
-
const platform = {
|
|
252
|
-
text: summary,
|
|
253
|
-
links: [
|
|
254
|
-
{ label: "打开付款页面", url: input.checkoutURL },
|
|
255
|
-
],
|
|
256
|
-
buttons: [
|
|
257
|
-
{ label: "打开收银台", kind: "url", url: input.checkoutURL },
|
|
258
|
-
...(input.checkoutID
|
|
259
|
-
? [{ label: "查询 Checkout 状态", kind: "callback", intent: "check_checkout_status", ref: input.checkoutID }]
|
|
260
|
-
: []),
|
|
261
|
-
],
|
|
262
|
-
blocks: [],
|
|
263
|
-
...(input.qrPNGURL ? { media: [{ url: input.qrPNGURL, label: "Branded QR", mimeType: "image/png" }] } : {}),
|
|
264
|
-
};
|
|
265
|
-
const plan = {
|
|
266
|
-
kind: isPayment ? "payment_qr" : "checkout_qr",
|
|
267
|
-
host: input.host,
|
|
268
|
-
summary,
|
|
269
|
-
url: input.qrPayload,
|
|
270
|
-
...(isLinkOnlyBrowserAgent(input.agentType, platformKeyForHost(input.host)) && input.cardURL ? { linkOnlyURL: input.cardURL } : {}),
|
|
271
|
-
preferredQRSources: [input.qrPNGURL ?? input.qrPayload],
|
|
272
|
-
checkoutID: input.checkoutID,
|
|
273
|
-
platform,
|
|
274
|
-
afterActionCommand: afterCommand,
|
|
275
|
-
afterActionLabel: "扫码或点击链接完成支付后,执行以下命令查询状态:",
|
|
276
|
-
};
|
|
277
|
-
if (input.orderItems)
|
|
278
|
-
plan.orderItems = input.orderItems;
|
|
279
|
-
if (input.orderCurrency)
|
|
280
|
-
plan.orderCurrency = input.orderCurrency;
|
|
281
|
-
if (input.paymentMethod)
|
|
282
|
-
plan.paymentMethod = input.paymentMethod;
|
|
283
|
-
if (input.paymentStatus)
|
|
284
|
-
plan.paymentStatus = input.paymentStatus;
|
|
285
|
-
if (input.paymentIntentID)
|
|
286
|
-
plan.paymentIntentID = input.paymentIntentID;
|
|
287
|
-
return plan;
|
|
288
|
-
}
|
|
289
247
|
function fallbackCardURL(baseURL, checkoutID, displayToken) {
|
|
290
248
|
const root = baseURL.replace(/\/$/, "");
|
|
291
249
|
return `${root}/v1/checkouts/${encodeURIComponent(checkoutID)}/card?display_token=${encodeURIComponent(displayToken)}`;
|
|
@@ -327,3 +285,4 @@ function absolutePublicURL(baseURL, value) {
|
|
|
327
285
|
return value;
|
|
328
286
|
}
|
|
329
287
|
}
|
|
288
|
+
export { buildCheckoutQRPlan } from "../render/checkout_plan.js";
|
|
@@ -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: "仅写入本地兼容草稿,未验证目录、价格或服务合同;不要把它当作 服务端购物车。",
|
|
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
|
+
: "普通项目已加入 服务端购物车;先检查购物车,再创建 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: "检查 服务端购物车" },
|
|
81
81
|
recovery: [],
|
|
82
82
|
};
|
|
83
83
|
writeCommandEnvelope(envelope, {
|
|
@@ -117,11 +117,13 @@ export async function runCartAddQuoteServer(options) {
|
|
|
117
117
|
status: "quote_added",
|
|
118
118
|
result: {
|
|
119
119
|
cart_id: cart.cart_id,
|
|
120
|
+
service_quote_lock_id: options.serviceQuoteLockID,
|
|
121
|
+
service_execution_id: line.service_execution_id,
|
|
120
122
|
item_count: cart.items.length,
|
|
121
123
|
total: formatMoney(cart.amount_minor, cart.currency),
|
|
122
124
|
},
|
|
123
125
|
instruction: "付费服务报价已加入同一 Cart;每个项目仍保持独立 Execution 和交付。",
|
|
124
|
-
next: { command: `itpay buy --cart ${cart.cart_id} --json`, reason: "
|
|
126
|
+
next: { command: `itpay buy --cart ${cart.cart_id} --json`, reason: "仅已有购买授权且项目齐全时创建一次合并付款" },
|
|
125
127
|
recovery: [{ command: "itpay cart show --json", reason: "检查当前合并 Cart" }],
|
|
126
128
|
};
|
|
127
129
|
writeCommandEnvelope(envelope, {
|
|
@@ -144,7 +146,7 @@ export function runCartRemove(session, options) {
|
|
|
144
146
|
catalog_variant_id: options.catalogVariantID,
|
|
145
147
|
offer_id: options.offerID,
|
|
146
148
|
},
|
|
147
|
-
instruction: "本地兼容草稿已更新;这不代表任何
|
|
149
|
+
instruction: "本地兼容草稿已更新;这不代表任何 服务端购物车 或 Service Execution 已变更。",
|
|
148
150
|
next: { command: "itpay cart show --local --json", reason: "检查剩余本地草稿" },
|
|
149
151
|
recovery: [],
|
|
150
152
|
}, {
|
|
@@ -176,7 +178,7 @@ export async function runCartRemoveServer(backend, session, cartItemID, options
|
|
|
176
178
|
cart_item_id: lineID,
|
|
177
179
|
remaining_item_count: cart.items.length,
|
|
178
180
|
},
|
|
179
|
-
instruction: "
|
|
181
|
+
instruction: "服务端购物车 已更新;被删除的最后一个 服务项目 对应执行会由服务端同时取消。",
|
|
180
182
|
next: { command: "itpay cart next --json", reason: "检查剩余内容" },
|
|
181
183
|
recovery: [],
|
|
182
184
|
};
|
|
@@ -198,10 +200,10 @@ export function runCartShow(session, options = {}) {
|
|
|
198
200
|
status: items.length > 0 ? "shown_local" : "local_empty",
|
|
199
201
|
result: { currency: snap.currency, items },
|
|
200
202
|
instruction: items.length > 0
|
|
201
|
-
? "这是未验证的本地兼容草稿,只能用于明确的普通商品流程;不要把它当作
|
|
203
|
+
? "这是未验证的本地兼容草稿,只能用于明确的普通商品流程;不要把它当作 服务端购物车。"
|
|
202
204
|
: "本地兼容草稿为空;从已发布目录重新选择项目。",
|
|
203
205
|
next: items.length > 0
|
|
204
|
-
? { command: "itpay buy --json", reason: "将普通本地草稿提交为
|
|
206
|
+
? { command: "itpay buy --json", reason: "将普通本地草稿提交为 服务端购物车" }
|
|
205
207
|
: { command: "itpay catalog list --json", reason: "读取已发布目录" },
|
|
206
208
|
recovery: [],
|
|
207
209
|
};
|
|
@@ -215,7 +217,7 @@ export async function runCartShowServer(backend, session, options = {}) {
|
|
|
215
217
|
writeCommandEnvelope({
|
|
216
218
|
status: "cart_handle_missing",
|
|
217
219
|
result: {},
|
|
218
|
-
instruction: "本地没有
|
|
220
|
+
instruction: "本地没有 服务端购物车 句柄;不要把本地草稿默认为服务端 Cart。",
|
|
219
221
|
next: { command: "itpay catalog list --json", reason: "读取已发布目录" },
|
|
220
222
|
recovery: [{ command: "itpay cart show --local --json", reason: "仅在明确需要时检查本地兼容草稿" }],
|
|
221
223
|
}, {
|
|
@@ -241,7 +243,7 @@ export async function runCartShowServer(backend, session, options = {}) {
|
|
|
241
243
|
},
|
|
242
244
|
instruction: items.length > 0
|
|
243
245
|
? "使用 line 或 execution 句柄继续;不要使用内部 quote lock ID。"
|
|
244
|
-
: "
|
|
246
|
+
: "服务端购物车 当前为空;从已发布目录选择项目。",
|
|
245
247
|
next: items.length > 0
|
|
246
248
|
? { command: "itpay cart next --json", reason: "取得当前首选动作" }
|
|
247
249
|
: { command: "itpay catalog list --json", reason: "读取已发布目录" },
|
|
@@ -278,7 +280,7 @@ export async function runCartAbandonServer(backend, session, options = {}) {
|
|
|
278
280
|
writeCommandEnvelope({
|
|
279
281
|
status: "cart_handle_missing",
|
|
280
282
|
result: {},
|
|
281
|
-
instruction: "本地没有
|
|
283
|
+
instruction: "本地没有 服务端购物车 句柄;未修改任何 Backend 或本地资源。",
|
|
282
284
|
next: { command: "itpay next --json", reason: "检查其他可恢复句柄" },
|
|
283
285
|
recovery: [{ command: "itpay cart clear --local --json", reason: "仅在明确放弃本地草稿和句柄时执行" }],
|
|
284
286
|
}, {
|
|
@@ -292,7 +294,7 @@ export async function runCartAbandonServer(backend, session, options = {}) {
|
|
|
292
294
|
const envelope = {
|
|
293
295
|
status: "abandoned",
|
|
294
296
|
result: { cart_id: cart.cart_id, server_abandoned: true },
|
|
295
|
-
instruction: "
|
|
297
|
+
instruction: "服务端购物车 已放弃;服务端已清空有效项目,并取消关联的未付款服务执行。",
|
|
296
298
|
next: { command: "itpay catalog list --json", reason: "仅在用户提出新需求时重新选择" },
|
|
297
299
|
recovery: [],
|
|
298
300
|
};
|
|
@@ -308,7 +310,7 @@ export async function runCartNext(backend, session, options = {}) {
|
|
|
308
310
|
writeCartNextEnvelope({
|
|
309
311
|
status: "cart_handle_missing",
|
|
310
312
|
result: {},
|
|
311
|
-
instruction: "本地没有
|
|
313
|
+
instruction: "本地没有 服务端购物车 句柄;先恢复已有资源,不要创建重复 Cart。",
|
|
312
314
|
next: { command: "itpay next --json", reason: "检查其他可恢复句柄" },
|
|
313
315
|
recovery: [{ command: "itpay services list --json", reason: "从服务端恢复当前设备的执行" }],
|
|
314
316
|
}, options);
|
|
@@ -325,7 +327,7 @@ export async function runCartNext(backend, session, options = {}) {
|
|
|
325
327
|
cart_status: cart.status,
|
|
326
328
|
service_execution_id: unquotedServiceLine.service_execution_id,
|
|
327
329
|
},
|
|
328
|
-
instruction: "该 Cart 包含
|
|
330
|
+
instruction: "该 Cart 包含 服务项目;继续 Service Execution,不要从 Cart 猜 capability。",
|
|
329
331
|
next: {
|
|
330
332
|
command: `itpay services next ${unquotedServiceLine.service_execution_id} --json`,
|
|
331
333
|
reason: "读取服务端最新执行状态",
|
|
@@ -355,7 +357,7 @@ export async function runCartNext(backend, session, options = {}) {
|
|
|
355
357
|
instruction: cart.items.some((item) => item.service_quote_lock_id)
|
|
356
358
|
? "服务报价已锁定输入和价格;确认项目齐全后使用同一 Cart 创建一次 Checkout。"
|
|
357
359
|
: "该 Cart 是普通购买流程;使用同一 Cart 创建 Checkout,不要重复添加商品。",
|
|
358
|
-
next: { command: `itpay buy --cart ${cart.cart_id} --json`, reason: "
|
|
360
|
+
next: { command: `itpay buy --cart ${cart.cart_id} --json`, reason: "仅已有购买授权时继续服务端购物车结算" },
|
|
359
361
|
recovery: [],
|
|
360
362
|
};
|
|
361
363
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { writeCommandEnvelope } from "./guidance.js";
|
|
2
2
|
export async function runCatalogList(backend, options = {}) {
|
|
3
3
|
const manifest = await backend.getCatalogManifest();
|
|
4
|
-
const services = manifest.manifest.items.map(summarizeService);
|
|
4
|
+
const services = manifest.manifest.items.map(item => summarizeService(item, options.locale ?? "zh-CN"));
|
|
5
5
|
const firstServiceID = manifest.manifest.items.find((item) => item.service_id)?.service_id;
|
|
6
6
|
const empty = services.length === 0;
|
|
7
7
|
const jsonFlag = options.jsonOutput ? " --json" : "";
|
|
@@ -9,30 +9,40 @@ 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
|
-
? "
|
|
13
|
-
: "向用户解释主服务、辅助步骤和价格;得到用户意图后再启动对应 service_id。",
|
|
14
|
-
next:
|
|
15
|
-
? { command: `itpay
|
|
16
|
-
:
|
|
17
|
-
command: `itpay services start ${services.length === 1 && firstServiceID ? firstServiceID : "<service_id>"}${jsonFlag}`,
|
|
18
|
-
reason: "启动用户选择的服务",
|
|
19
|
-
},
|
|
12
|
+
? "当前没有已发布服务;本次目录读取结束,不猜测服务或自动重试。"
|
|
13
|
+
: options.locale === "en" ? "Explain the service, its operations and pricing; start only the declared operation matching the human request." : "向用户解释主服务、辅助步骤和价格;得到用户意图后再启动对应 service_id。",
|
|
14
|
+
next: services.length === 1 && firstServiceID
|
|
15
|
+
? { command: `itpay services start ${firstServiceID}${jsonFlag}`, reason: "仅当当前服务符合用户请求时读取输入合同;不表示购买同意" }
|
|
16
|
+
: null,
|
|
20
17
|
recovery: [],
|
|
21
18
|
}, {
|
|
22
19
|
...options,
|
|
23
20
|
plainResult: catalogPlainLines(manifest.version, services),
|
|
24
21
|
});
|
|
25
22
|
}
|
|
26
|
-
function summarizeService(item) {
|
|
23
|
+
function summarizeService(item, locale) {
|
|
24
|
+
const definition = item.service_definition;
|
|
25
|
+
if (definition) {
|
|
26
|
+
if (definition.schema_version !== "itpay.service-definition.v1")
|
|
27
|
+
return { service_id: item.service_id, title: item.title, description: "服务定义版本不受支持;升级CLI后读取,不猜操作。" };
|
|
28
|
+
return {
|
|
29
|
+
service_id: definition.service_id, title: definition.display_names[locale] ?? item.title,
|
|
30
|
+
entry: `itpay services start ${definition.service_id} --json`, definition_version: definition.definition_version,
|
|
31
|
+
...(definition.guide ? { guide: `itpay docs show ${definition.guide.topic}` } : {}),
|
|
32
|
+
operations: definition.operations.map(op => ({ operation_id: op.id, title: op.display_names[locale], role: op.role, price: op.billing_mode === "dynamic" ? (locale === "en" ? (definition.adapter_key === "rail.v1" ? "Live quote; free queries do not mean free tickets" : "Live quote; confirm the locked amount") : (definition.adapter_key === "rail.v1" ? "实时报价;免费查询不等于免费车票" : "实时报价,以锁定报价为准")) : op.billing_mode === "free" ? (locale === "en" ? "Free query" : "免费查询") : typeof op.price_amount_minor === "number" && op.price_currency ? formatProductMoney(op.price_amount_minor, op.price_currency) : (locale === "en" ? "Fixed price missing; stop purchase preparation" : "固定价格事实缺失;停止准备购买"), requires_continuation: op.requires_continuation ?? false })),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
27
35
|
const flow = item.service_flow;
|
|
36
|
+
const offer = item.variants?.[0];
|
|
28
37
|
return {
|
|
29
38
|
service_id: item.service_id ?? null,
|
|
39
|
+
...(item.service_id ? { entry: `itpay services start ${item.service_id} --json` } : {}),
|
|
30
40
|
title: item.title,
|
|
31
41
|
description: item.description ?? "",
|
|
32
42
|
...(flow ? {
|
|
33
43
|
discovery: {
|
|
34
44
|
title: flow.discovery.title,
|
|
35
|
-
description: flow.discovery.description,
|
|
45
|
+
description: flow.discovery.description ?? "",
|
|
36
46
|
...(flow.discovery.free_quota_limit !== undefined ? { free_quota: flow.discovery.free_quota_limit } : {}),
|
|
37
47
|
...(flow.discovery.paid_continuation ? {
|
|
38
48
|
paid_price: formatProductMoney(flow.discovery.paid_continuation.amount_minor, flow.discovery.paid_continuation.currency),
|
|
@@ -40,9 +50,15 @@ function summarizeService(item) {
|
|
|
40
50
|
},
|
|
41
51
|
primary_offer: {
|
|
42
52
|
title: flow.primary_service.title,
|
|
43
|
-
description: flow.primary_service.description,
|
|
53
|
+
description: flow.primary_service.description ?? "",
|
|
44
54
|
price: formatProductMoney(flow.primary_service.amount_minor, flow.primary_service.currency),
|
|
45
55
|
},
|
|
56
|
+
} : offer ? {
|
|
57
|
+
primary_offer: {
|
|
58
|
+
title: offer.title || item.title,
|
|
59
|
+
description: item.description ?? "",
|
|
60
|
+
price: formatProductMoney(offer.amount_minor, offer.currency),
|
|
61
|
+
},
|
|
46
62
|
} : {}),
|
|
47
63
|
};
|
|
48
64
|
}
|
|
@@ -51,19 +67,28 @@ function catalogPlainLines(version, services) {
|
|
|
51
67
|
for (const service of services) {
|
|
52
68
|
lines.push(`service: ${String(service.title)}`);
|
|
53
69
|
lines.push(` service_id: ${String(service.service_id ?? "unavailable")}`);
|
|
70
|
+
if (service.entry)
|
|
71
|
+
lines.push(` entry: ${String(service.entry)}`);
|
|
72
|
+
if (service.guide)
|
|
73
|
+
lines.push(` guide: ${String(service.guide)}`);
|
|
54
74
|
if (service.description)
|
|
55
75
|
lines.push(` description: ${String(service.description)}`);
|
|
76
|
+
if (Array.isArray(service.operations))
|
|
77
|
+
for (const op of service.operations)
|
|
78
|
+
lines.push(` operation: ${String(op.operation_id)} — ${String(op.title)}; ${String(op.price)}`);
|
|
56
79
|
const discovery = service.discovery;
|
|
57
80
|
if (discovery) {
|
|
58
81
|
const quota = discovery.free_quota !== undefined ? `; free_quota: ${String(discovery.free_quota)}` : "";
|
|
59
82
|
const paid = discovery.paid_price ? `; paid_price: ${String(discovery.paid_price)}` : "";
|
|
60
83
|
lines.push(` discovery: ${String(discovery.title)}${quota}${paid}`);
|
|
61
|
-
|
|
84
|
+
if (discovery.description)
|
|
85
|
+
lines.push(` ${String(discovery.description)}`);
|
|
62
86
|
}
|
|
63
87
|
const primary = service.primary_offer;
|
|
64
88
|
if (primary) {
|
|
65
89
|
lines.push(` primary_offer: ${String(primary.title)}; price: ${String(primary.price)}`);
|
|
66
|
-
|
|
90
|
+
if (primary.description)
|
|
91
|
+
lines.push(` ${String(primary.description)}`);
|
|
67
92
|
}
|
|
68
93
|
}
|
|
69
94
|
return lines;
|