@agenzo/merchant-cli 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -41,15 +41,6 @@ var CODE_NUM = {
41
41
  QUOTE_EXPIRED: 4202,
42
42
  BOOKING_FAILED: 4203,
43
43
  CANCELLATION_NOT_ALLOWED: 4204,
44
- PRICE_MISMATCH: 4205,
45
- VEHICLE_CLASS_MISMATCH: 4206,
46
- PAYMENT_METHOD_REQUIRED: 4207,
47
- QUOTE_CACHE_UNAVAILABLE: 4208,
48
- ARREARS_OUTSTANDING: 4209,
49
- CONTACT_REQUIRED: 4210,
50
- INVALID_PHONE: 4211,
51
- SEAT_LIMIT_EXCEEDED: 4213,
52
- RIDE_NOT_FOUND: 4212,
53
44
  NO_AVAILABILITY: 4301,
54
45
  PRICE_CHANGED: 4302,
55
46
  NAME_FORMAT_INVALID: 4303,
@@ -219,15 +210,6 @@ var STABLE_MESSAGE = {
219
210
  VEHICLE_UNAVAILABLE: "No vehicle is available for the requested trip. Please try a different time or class.",
220
211
  QUOTE_EXPIRED: "The quote has expired. Please request a new quote and retry.",
221
212
  BOOKING_FAILED: "The booking could not be completed. Please retry.",
222
- PRICE_MISMATCH: "The price does not match the quote. Re-quote and use the exact amount from the quote response.",
223
- VEHICLE_CLASS_MISMATCH: "The vehicle class does not match the quote. Use the vehicle class from the quote response.",
224
- PAYMENT_METHOD_REQUIRED: "Pay-per-call billing requires an active payment method. Please add a card first.",
225
- QUOTE_CACHE_UNAVAILABLE: "Quote cache is temporarily unavailable. Please request a new quote and retry.",
226
- ARREARS_OUTSTANDING: "You have outstanding arrears. Please settle your balance before booking a new ride.",
227
- CONTACT_REQUIRED: "Passenger contact information is required. Please provide a passenger name and phone number.",
228
- INVALID_PHONE: "The passenger phone number is not valid. Please provide a valid international number.",
229
- SEAT_LIMIT_EXCEEDED: "The number of child/infant/toddler seats exceeds the vehicle capacity. Reduce seats or choose a larger vehicle.",
230
- RIDE_NOT_FOUND: "The ride order was not found or does not belong to you.",
231
213
  CANCELLATION_NOT_ALLOWED: "This order cannot be cancelled in its current state.",
232
214
  NO_AVAILABILITY: "No rooms available for the selected hotel and dates.",
233
215
  PRICE_CHANGED: "The price changed since the quote. Re-quote and confirm.",
@@ -1259,7 +1241,7 @@ var hotelCreateOrderSchema = {
1259
1241
  cli: CLI_NAME,
1260
1242
  noun: HOTEL_NOUN,
1261
1243
  verb: "create-order",
1262
- description: "Create a hotel order WITHOUT charging any account (re-check availability + createOrder upstream), using a product_token and price_items from a recent quote. The order enters AWAITING_PAYMENT \u2014 no money is moved until pay-order. check-in/check-out, guest counts, total-amount and price-items must match the quote",
1244
+ description: "Create AND pay for a hotel order in one call, using a product_token and price_items from a recent quote. The backend re-checks availability, authorizes/captures payment via the platform payment gateway (funds path decided server-side by the developer billing_mode \u2014 monthly_settlement or pay_per_call), locks the room with the supplier, and returns the order already PAID. There is no separate settlement step. If any step after funds are taken fails, the funds are automatically released/refunded before the error is returned. check-in/check-out, guest counts, total-amount and price-items must match the quote",
1263
1245
  flags: {
1264
1246
  "product-token": { type: "string", required: true, description: "Chosen rate token from quote (quote.response.rates[].product_token). Pass verbatim" },
1265
1247
  "total-amount": { type: "float", required: true, description: "Total price for the stay (quote.response.rates[].total_price.amount)", constraints: "DECIMAL units (NOT cents), 0.01\u2013999999999.99. Must equal the chosen rate total_price.amount" },
@@ -1278,74 +1260,69 @@ var hotelCreateOrderSchema = {
1278
1260
  "contact-email": { type: "string", required: false, description: "Booking contact email" },
1279
1261
  "arrive-time": { type: "string", required: false, description: "Expected arrival time", constraints: "HH:mm, hotel local time" },
1280
1262
  "special-requests": { type: "string", required: false, description: "Free-text special requests (non-binding)" },
1263
+ "payment-method-id": { type: "string", required: false, description: "pay_per_call only: charge this SPECIFIC already-bound card instead of the platform auto-selected default/most-recent one. Ignored for monthly_settlement.", constraints: "Must be an ACTIVE card belonging to this developer, else PAYMENT_METHOD_REQUIRED" },
1281
1264
  "idempotency-key": {
1282
1265
  type: "string",
1283
1266
  required: true,
1284
- description: "Unique key forwarded verbatim as the Idempotency-Key header; never auto-generated. Derive from business intent (e.g. hash(user_id + product_token + check_in)) and reuse the SAME key when retrying the same order creation",
1267
+ description: "Unique key forwarded verbatim as the Idempotency-Key header; never auto-generated. Derive from business intent (e.g. hash(user_id + product_token + check_in)) and reuse the SAME key when retrying the identical request. This single call covers both order creation AND payment",
1285
1268
  constraints: "1-128 chars [A-Za-z0-9_-]"
1286
1269
  }
1287
1270
  },
1288
1271
  response: {
1289
- order_id: { type: "string", description: "Our order reference (coOrderCode). Use as --order-id in pay-order / get / cancel." },
1272
+ order_id: { type: "string", description: "Our order reference (coOrderCode). Use as --order-id in get / cancel." },
1290
1273
  fc_order_code: { type: "string", description: "Supplier order reference. Needed as --fc-order-code in cancel and checkout." },
1291
- total_amount: { type: "float", description: "Amount to be paid in pay-order (DECIMAL units). The order is locked in AWAITING_PAYMENT \u2014 no charge has been made." },
1292
- currency: { type: "string", description: "ISO 4217 currency code for the payment." }
1274
+ total_amount: { type: "float", description: "Amount actually charged (DECIMAL units) \u2014 the order is already PAID by the time this response is returned." },
1275
+ currency: { type: "string", description: "ISO 4217 currency code of the charge." }
1293
1276
  },
1294
1277
  example: {
1295
1278
  command: `agenzo-merchant-cli hotel-redaug create-order --product-token <tok> --total-amount 10.00 --currency CNY --price-items '[{"sale_date":"2026-08-20","sale_price":10.00,"breakfast_num":0}]' --check-in 2026-08-20 --check-out 2026-08-21 --adults 2 --guest-name "Zhang San" --contact-name "Zhang San" --contact-phone 13800138000 --idempotency-key create-h1n2`,
1296
- output_summary: "Returns {order_id, fc_order_code, total_amount, currency}. The order is AWAITING_PAYMENT (no charge). Use order_id as --order-id in pay-order to settle."
1279
+ output_summary: "Returns {order_id, fc_order_code, total_amount, currency}. The order is already PAID \u2014 proceed to get to poll for supplier CONFIRMED status."
1297
1280
  },
1298
1281
  error_recovery: {
1299
1282
  NO_AVAILABILITY: "Availability vanished between quote and create-order. Re-quote the same hotel; if still empty, quote another hotel or change dates. Use a NEW idempotency-key for the new product_token.",
1300
1283
  PRICE_CHANGED: "The price/price_items no longer match upstream. Re-quote to get fresh total_price and price_items, confirm the new price with the user, then create-order with the new values and a NEW idempotency-key.",
1301
- NAME_FORMAT_INVALID: "The supplier requires the guest name in a specific format (commonly Latin first/last name for international properties). Re-collect guest-name in Latin letters and retry with the SAME idempotency-key.",
1284
+ NAME_FORMAT_INVALID: "The supplier requires the guest name in a specific format (commonly Latin first/last name for international properties). Re-collect guest-name in Latin letters and retry with a NEW idempotency-key (the request is no longer identical).",
1285
+ PAYMENT_METHOD_REQUIRED: "pay_per_call developer has no ACTIVE bound card at all, OR the explicitly-passed --payment-method-id is missing/not ACTIVE/does not belong to this developer. Direct the user to bind a card, drop --payment-method-id to auto-select, or pass a valid one.",
1286
+ ACCOUNT_INSUFFICIENT_BALANCE: "monthly_settlement developer settlement credit is insufficient (check via admin-cli accounts get). Direct the user to top up offline; do NOT retry until funded.",
1287
+ ACCOUNT_NOT_FOUND: "monthly_settlement developer has no settlement account. Direct the user to complete contract signing.",
1288
+ ACCOUNT_SUSPENDED: "The settlement account is suspended. Direct the user to contact support; do NOT retry.",
1302
1289
  PARAM_IDEMPOTENCY_KEY_REQUIRED: "Add a unique --idempotency-key derived from business intent and retry.",
1303
1290
  PARAM_IDEMPOTENCY_KEY_CONFLICT: "The same idempotency-key was used with different parameters. Retry with the original parameters, or generate a NEW key for the new parameters.",
1304
- UPSTREAM_ERROR: "Transient upstream error during checkBooking or createOrder. Retry once after ~2s backoff with the SAME idempotency-key."
1291
+ UPSTREAM_ERROR: "Transient upstream error during checkBooking or createOrder. Any funds already taken are automatically released/refunded before this error is returned. Retry once after ~2s backoff with the SAME idempotency-key."
1305
1292
  }
1306
1293
  };
1307
1294
  var hotelPayOrderSchema = {
1308
1295
  cli: CLI_NAME,
1309
1296
  noun: HOTEL_NOUN,
1310
1297
  verb: "pay-order",
1311
- description: 'Settle an existing AWAITING_PAYMENT order created by create-order. Takes only --order-id; the billing path is decided server-side by the order billing_mode. monthly_settlement deducts from the developer credit account then confirms with the supplier; pay_per_call verifies the user EVO payment \u2014 the EVO merchantTransID IS the order_id (the user pays via EVO under the order_id), so the platform queries EVO for that order_id and requires an exact amount/currency match before confirming (the response settlement_path is then "active_payment"). On success the order becomes PAID. Supports --watch to poll on PAYMENT_NOT_COMPLETED until PAID',
1298
+ description: "Trigger supplier confirmation (upstream payOrder) for an order that was already paid via create-order. Takes only --order-id; no payment parameters needed. On success the supplier begins asynchronous confirmation \u2014 poll get until CONFIRMED (3). Idempotent: calling pay-order on an already-confirmed order replays the existing result",
1312
1299
  flags: {
1313
- "order-id": { type: "string", required: true, description: "Order to settle (create-order.response.order_id). For pay_per_call this is also the EVO merchantTransID the user paid under." },
1300
+ "order-id": { type: "string", required: true, description: "Order to confirm with the supplier (create-order.response.order_id)." },
1314
1301
  "idempotency-key": {
1315
1302
  type: "string",
1316
1303
  required: true,
1317
- description: "Unique key forwarded verbatim as the Idempotency-Key header; never auto-generated. Derive from business intent (e.g. hash(order_id + attempt)) and reuse the SAME key when retrying the same payment",
1304
+ description: "Unique key forwarded verbatim as the Idempotency-Key header; never auto-generated. Derive from business intent and reuse the SAME key when retrying the same pay-order call",
1318
1305
  constraints: "1-128 chars [A-Za-z0-9_-]"
1319
1306
  },
1320
- watch: { type: "bool", required: false, default: false, description: "Poll automatically on PAYMENT_NOT_COMPLETED until PAID or timeout. Each iteration emits one NDJSON line" },
1307
+ watch: { type: "bool", required: false, default: false, description: "Retained for backward compatibility. Each iteration emits one NDJSON line" },
1321
1308
  "watch-interval": { type: "int", required: false, default: 5, description: "Seconds between poll attempts in --watch mode" },
1322
1309
  "watch-timeout": { type: "int", required: false, default: 300, description: "Total seconds before giving up in --watch mode" }
1323
1310
  },
1324
1311
  response: {
1325
1312
  order_id: { type: "string", description: "Order reference." },
1326
- settlement_path: { type: "string", description: "Which billing path was used: 'monthly_settlement' or 'active_payment'." },
1327
- amount: { type: "float", description: "Settled amount in DECIMAL units." },
1328
- currency: { type: "string", description: "ISO 4217 currency code of the settlement." },
1329
- pay_status: { type: "int", description: "Upstream pay status (1 = paid). The order internal status is now PAID." },
1330
- settled_at: { type: "string", description: "ISO 8601 settlement timestamp. On idempotent replay, the original first settled_at is returned unchanged." }
1313
+ settlement_path: { type: "string", description: "The order's billing_mode echoed: 'monthly_settlement' or 'pay_per_call'." },
1314
+ status: { type: "string", description: "'PAID' \u2014 supplier confirmation is asynchronous; poll get until CONFIRMED." },
1315
+ amount: { type: "float", description: "Settled amount in DECIMAL units (from create-order)." },
1316
+ currency: { type: "string", description: "ISO 4217 currency code." }
1331
1317
  },
1332
1318
  example: {
1333
1319
  command: "agenzo-merchant-cli hotel-redaug pay-order --order-id hho_01KWC63Z5CD6CKBM33Q7SC2ZDT --idempotency-key pay-h1n2",
1334
- output_summary: 'Settlement path is chosen server-side by billing_mode. monthly_settlement: deducts from the credit account, confirms with the supplier. Returns {order_id, settlement_path:"monthly_settlement", amount, currency, pay_status:1, settled_at}. Order becomes PAID. For pay_per_call the user must have already paid via EVO using the order_id as the merchantTransID; the same command settles it (settlement_path:"active_payment").'
1320
+ output_summary: 'Triggers supplier payOrder (confirmation/ticket issuance). Returns {order_id, settlement_path, status:"PAID", amount, currency}. Then poll get until CONFIRMED.'
1335
1321
  },
1336
1322
  error_recovery: {
1337
- PAYMENT_NOT_COMPLETED: "pay_per_call only: EVO has not yet confirmed the payment for this order_id. Retry with the SAME idempotency-key after a delay, or use --watch to poll automatically until PAID.",
1338
- PAYMENT_NOT_FOUND: "No EVO transaction found for this order_id. Confirm the user paid via EVO using the order_id as the merchantTransID; do NOT retry blindly.",
1339
- PAYMENT_AMOUNT_MISMATCH: "The EVO-confirmed amount/currency does not match the order. The user must pay the exact order amount in the exact currency under the order_id. Do NOT retry until corrected.",
1340
- PAYMENT_QUERY_FAILED: "EVO gateway query failed (transport/timeout). Retry once after ~5s with the SAME idempotency-key.",
1341
- BILLING_MODE_MISMATCH: "The order billing_mode is not a recognized settlement mode (only monthly_settlement and pay_per_call are valid). Check the developer billing_mode; do NOT retry blindly.",
1342
- INVALID_ORDER_STATE: "The order is not in AWAITING_PAYMENT (it may already be PAID or CANCELLED). Check status via get; do NOT retry pay-order.",
1323
+ INVALID_ORDER_STATE: "The order is not in a payable state (it may already be confirmed, cancelled, or missing auth). Check status via get; do NOT retry pay-order.",
1343
1324
  ORDER_NOT_FOUND: "No order for this --order-id. Verify it is the order_id returned by create-order. Do NOT retry.",
1344
- ACCOUNT_INSUFFICIENT_BALANCE: "Settlement credit is insufficient (check via admin-cli accounts get). Direct the user to top up offline.",
1345
- ACCOUNT_NOT_FOUND: "Developer has no settlement account. Direct the user to complete contract signing.",
1346
- ACCOUNT_SUSPENDED: "The settlement account is suspended. Direct the user to contact support; do NOT retry.",
1347
- PAYORDER_FAILED: "Upstream payOrder failed after a successful deduction; the deduction was reversed and the order stays AWAITING_PAYMENT. Retry with the SAME idempotency-key after a delay.",
1348
- PAYORDER_FAILED_AFTER_PAYMENT: "EVO payment confirmed but upstream payOrder failed. The order is recoverable (the EVO payment under the order_id is preserved). Contact support for reconciliation; do NOT retry automatically.",
1325
+ UPSTREAM_ERROR: "Transient upstream error during supplier payOrder. Retry once after ~5s with the SAME idempotency-key, then poll get to check whether the confirmation actually took effect.",
1349
1326
  PARAM_IDEMPOTENCY_KEY_REQUIRED: "Add a unique --idempotency-key and retry.",
1350
1327
  PARAM_IDEMPOTENCY_KEY_CONFLICT: "Same idempotency-key used with different parameters. Use the original parameters, or generate a NEW key."
1351
1328
  }
@@ -1759,69 +1736,6 @@ var hotelDetailSchema = {
1759
1736
  PARAM_INVALID: "Ensure --hotel-id is a non-empty string, then retry."
1760
1737
  }
1761
1738
  };
1762
- var ORDERS_NOUN = "orders";
1763
- var unifiedOrdersListSchema = {
1764
- cli: CLI_NAME,
1765
- noun: ORDERS_NOUN,
1766
- verb: "list",
1767
- description: 'List orders across ALL providers (ride + hotel) in one call. Use this for generic "my orders" / "order history" requests. Prefer ride-elife/hotel-redaug list-orders when the user names a specific business.',
1768
- flags: {
1769
- "order-type": { type: "string", required: false, description: "Filter by provider: ride | hotel" },
1770
- status: { type: "string", required: false, description: "Filter by NORMALIZED status (NOT the domain-specific status): PENDING | CONFIRMED | COMPLETED | CANCELLED | FAILED" },
1771
- page: { type: "int", required: false, default: 1, description: "Page number", constraints: ">= 1" },
1772
- "page-size": { type: "int", required: false, default: 20, description: "Items per page", constraints: ">= 1" }
1773
- },
1774
- response: {
1775
- orders: {
1776
- type: "array",
1777
- description: "Slim cross-provider order-index items. For business-specific fields (vehicle_class, hotel_name, etc.), call `orders get --order-id <id>` or the domain-specific `get`.",
1778
- items: {
1779
- order_id: { type: "string", description: "Order id (rio_... for ride, hho_... for hotel). Pass to `orders get`." },
1780
- order_type: { type: "string", description: "'ride' or 'hotel'" },
1781
- status: { type: "string", description: "Normalized status: PENDING | CONFIRMED | COMPLETED | CANCELLED | FAILED" },
1782
- amount: { type: "float|null", description: "Order amount in DECIMAL currency units (NOT cents)" },
1783
- currency: { type: "string|null", description: "ISO 4217 currency code" },
1784
- created_at: { type: "string|null", description: "ISO 8601 datetime" },
1785
- updated_at: { type: "string|null", description: "ISO 8601 datetime" }
1786
- }
1787
- },
1788
- total: { type: "int", description: "Total matching orders across all providers" },
1789
- page: { type: "int", description: "Current page" },
1790
- page_size: { type: "int", description: "Items per page" }
1791
- },
1792
- example: {
1793
- command: "agenzo-merchant-cli orders list --page 1 --page-size 10",
1794
- output_summary: "Returns a paginated, cross-provider list of orders (both ride and hotel). If the user then asks about one order, use its order_id with `orders get`."
1795
- },
1796
- error_recovery: {
1797
- INVALID_REQUEST: "The --status value is not one of PENDING/CONFIRMED/COMPLETED/CANCELLED/FAILED. Fix and retry.",
1798
- INTERNAL_ERROR: "Transient backend error. Retry once after a short delay.",
1799
- PARAM_INVALID: "Ensure --page / --page-size are positive integers, then retry."
1800
- }
1801
- };
1802
- var unifiedOrdersGetSchema = {
1803
- cli: CLI_NAME,
1804
- noun: ORDERS_NOUN,
1805
- verb: "get",
1806
- description: "Get a single order detail by id, regardless of which provider (ride/hotel) it belongs to. The platform resolves order_id -> provider internally and returns that provider's own detail shape.",
1807
- flags: {
1808
- "order-id": { type: "string", required: true, description: "Order id from `orders list` (rio_... for ride, hho_... for hotel)" }
1809
- },
1810
- response: {
1811
- "(varies by order_type)": {
1812
- type: "object",
1813
- description: "The response shape is delegated to the owning provider and therefore varies: a ride order returns the same shape as `ride-elife get`; a hotel order returns the same shape as `hotel-redaug get`. Treat the result as an opaque object and surface whatever fields it contains \u2014 do NOT assume a fixed schema."
1814
- }
1815
- },
1816
- example: {
1817
- command: "agenzo-merchant-cli orders get --order-id hho_01K...",
1818
- output_summary: "Returns the order's detail, delegated to its owning provider (ride or hotel)."
1819
- },
1820
- error_recovery: {
1821
- ORDER_NOT_FOUND: "The order_id does not exist or does not belong to this developer/org. Verify the id from `orders list`. Do NOT retry blindly.",
1822
- INTERNAL_ERROR: "The order's provider detail lookup is temporarily unavailable. Retry once after a short delay."
1823
- }
1824
- };
1825
1739
 
1826
1740
  // src/ride-elife/quote.ts
1827
1741
  function need(value, flag) {
@@ -2351,7 +2265,10 @@ function registerListOrdersCommand(parent, deps) {
2351
2265
  // src/services/registry.ts
2352
2266
  var SERVICE_REGISTRY = [
2353
2267
  {
2354
- service_id: "ride-elife",
2268
+ // service_id 是平台级不透明内部引用(svc_ 前缀 + ULID),不进 Agent 日常拼的命令
2269
+ // ——Agent 拼命令用的是 cli_noun="ride-elife"。与后端
2270
+ // services/merchant_service/discovery/catalog.py 的 RIDE_ELIFE_SERVICE_ID 保持一致。
2271
+ service_id: "svc_01KWTPBWDH5YZ52H717BFVWZA8",
2355
2272
  name: "Ride hailing (eLife)",
2356
2273
  description: "On-demand ride ordering: quote a fare, book it, poll status, and cancel.",
2357
2274
  category: "ride",
@@ -2516,30 +2433,78 @@ function registerServicesListCommand(parent, deps) {
2516
2433
  }
2517
2434
 
2518
2435
  // src/services/get.ts
2436
+ function buildVerbsSummary(data) {
2437
+ const verbs = Array.isArray(data.verbs) ? data.verbs : [];
2438
+ const verbDescriptions = data.verb_descriptions ?? {};
2439
+ const schema = data.schema_content;
2440
+ const schemaVerbs = schema?.verbs;
2441
+ return verbs.map((verb) => {
2442
+ const item = {
2443
+ verb,
2444
+ description: verbDescriptions[verb] ?? String(schemaVerbs?.[verb]?.description ?? "")
2445
+ };
2446
+ const annotations = schemaVerbs?.[verb]?.annotations;
2447
+ if (annotations) item.annotations = annotations;
2448
+ return item;
2449
+ });
2450
+ }
2451
+ function toServiceLayerView(data) {
2452
+ const schema = data.schema_content;
2453
+ const view = {
2454
+ service_id: data.service_id,
2455
+ cli_noun: data.cli_noun,
2456
+ category: data.category,
2457
+ name: data.name,
2458
+ summary: data.description ?? schema?.summary,
2459
+ status: data.status ?? "available",
2460
+ provider: data.provider,
2461
+ version: data.version
2462
+ };
2463
+ if (schema?.selection_hints) view.selection_hints = schema.selection_hints;
2464
+ const discovery = data.discovery;
2465
+ view.schema_ref = schema?.schema_ref ?? {
2466
+ help_command: discovery?.help_command,
2467
+ ...discovery?.schema_url ? { schema_url: discovery.schema_url } : {}
2468
+ };
2469
+ if (schema?.conventions) view.conventions = schema.conventions;
2470
+ if (schema?.workflow) {
2471
+ view.workflow = schema.workflow;
2472
+ } else if (Array.isArray(data.workflow)) {
2473
+ view.workflow = { description: data.workflow.join(" \u2192 ") };
2474
+ }
2475
+ view.verbs_summary = buildVerbsSummary(data);
2476
+ if (schema?.cross_service_recovery) view.cross_service_recovery = schema.cross_service_recovery;
2477
+ return view;
2478
+ }
2519
2479
  function formatServiceGet(s) {
2520
2480
  const lines = [
2521
2481
  ["Service ID", String(s.service_id ?? "")],
2522
- ["Name", String(s.name ?? "")],
2523
- ["Description", String(s.description ?? s.summary ?? "")],
2482
+ ["CLI Noun", String(s.cli_noun ?? "")],
2524
2483
  ["Category", String(s.category ?? "")],
2525
- ["Version", String(s.version ?? "")],
2484
+ ["Name", String(s.name ?? "")],
2485
+ ["Summary", String(s.summary ?? "")],
2486
+ ["Status", String(s.status ?? "")],
2526
2487
  ["Provider", String(s.provider ?? "")],
2527
- ["CLI Noun", String(s.cli_noun ?? "")],
2528
- ["Verbs", Array.isArray(s.verbs) ? s.verbs.join(", ") : ""]
2488
+ ["Version", String(s.version ?? "")]
2529
2489
  ];
2530
- if (Array.isArray(s.workflow) && s.workflow.length > 0) {
2531
- lines.push(["Workflow", s.workflow.join(" \u2192 ")]);
2532
- }
2533
2490
  const block = Formatter.keyValue(lines);
2534
- const verbDescriptions = s.verb_descriptions;
2535
- if (verbDescriptions && Object.keys(verbDescriptions).length > 0) {
2536
- const detail = Object.entries(verbDescriptions).map(([verb, desc]) => ` ${verb}: ${desc}`).join("\n");
2537
- return `${block}
2538
-
2539
- Verb descriptions:
2540
- ${detail}`;
2491
+ const sections = [];
2492
+ for (const key of [
2493
+ "selection_hints",
2494
+ "schema_ref",
2495
+ "conventions",
2496
+ "workflow",
2497
+ "verbs_summary",
2498
+ "cross_service_recovery"
2499
+ ]) {
2500
+ if (s[key] !== void 0) {
2501
+ sections.push(`${key}:
2502
+ ${JSON.stringify(s[key], null, 2)}`);
2503
+ }
2541
2504
  }
2542
- return block;
2505
+ return sections.length > 0 ? `${block}
2506
+
2507
+ ${sections.join("\n\n")}` : block;
2543
2508
  }
2544
2509
  function gateCapability(data, localCaps) {
2545
2510
  const noun = String(data.cli_noun ?? "");
@@ -2560,7 +2525,7 @@ function gateCapability(data, localCaps) {
2560
2525
  return out;
2561
2526
  }
2562
2527
  function registerServiceGetCommand(parent, deps) {
2563
- const cmd = parent.command("get <service-id>").description("Retrieve a single merchant service by id (includes full schema)").option("--api-key <key>", "API Key for authentication (X-Api-Key)");
2528
+ const cmd = parent.command("get <service-id>").description("Retrieve a single merchant service (service-layer view: workflow + conventions + verbs_summary)").option("--api-key <key>", "API Key for authentication (X-Api-Key)");
2564
2529
  cmd.action(async (serviceId) => {
2565
2530
  const opts = cmd.optsWithGlobals();
2566
2531
  const format = resolveFormat(opts.format);
@@ -2589,7 +2554,7 @@ function registerServiceGetCommand(parent, deps) {
2589
2554
  `Service '${serviceId}' is not available in this CLI. Run "services list" to see available services.`
2590
2555
  );
2591
2556
  }
2592
- const resolved = data;
2557
+ const resolved = toServiceLayerView(data);
2593
2558
  const configManager = new ConfigManager();
2594
2559
  const commandResult = {
2595
2560
  data: resolved,
@@ -2599,105 +2564,6 @@ function registerServiceGetCommand(parent, deps) {
2599
2564
  });
2600
2565
  }
2601
2566
 
2602
- // src/orders/list.ts
2603
- var DEFAULT_PAGE2 = "1";
2604
- var DEFAULT_PAGE_SIZE2 = "20";
2605
- function positiveInt2(value, flag) {
2606
- const n = Number(value);
2607
- if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
2608
- throw new CliError("PARAM_INVALID", `--${flag} must be a positive integer.`);
2609
- }
2610
- return String(n);
2611
- }
2612
- function formatOrders2(data) {
2613
- const orders = data.orders ?? [];
2614
- if (orders.length === 0) {
2615
- return Formatter.status("info", "No orders found");
2616
- }
2617
- const headers = ["Order ID", "Type", "Status", "Amount", "Currency"];
2618
- const rows = orders.map((o) => [
2619
- String(o.order_id ?? "-"),
2620
- String(o.order_type ?? "-"),
2621
- String(o.status ?? "-"),
2622
- String(o.amount ?? "-"),
2623
- String(o.currency ?? "-")
2624
- ]);
2625
- const summary = [
2626
- ["Total", String(data.total ?? "-")],
2627
- ["Page", String(data.page ?? "-")],
2628
- ["Page size", String(data.page_size ?? "-")]
2629
- ];
2630
- return `${Formatter.table(headers, rows)}
2631
- ${Formatter.keyValue(summary)}`;
2632
- }
2633
- function registerOrdersListCommand(parent, deps) {
2634
- const cmd = parent.command("list").description("List orders across ALL providers (ride + hotel), with optional type/status filters").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--order-type <type>", "Filter by order type: ride | hotel").option("--status <status>", "Filter by normalized status: PENDING | CONFIRMED | COMPLETED | CANCELLED | FAILED").option("--page <page>", "Page number", DEFAULT_PAGE2).option("--page-size <size>", "Page size", DEFAULT_PAGE_SIZE2);
2635
- attachSchemaHelp(cmd, unifiedOrdersListSchema);
2636
- cmd.action(async () => {
2637
- const opts = cmd.optsWithGlobals();
2638
- const format = resolveFormat(opts.format);
2639
- const apiKey = await PromptEngine.resolveInput(opts.apiKey, {
2640
- message: "API Key:",
2641
- type: "password"
2642
- });
2643
- const params = {
2644
- page: positiveInt2(opts.page ?? DEFAULT_PAGE2, "page"),
2645
- page_size: positiveInt2(opts.pageSize ?? DEFAULT_PAGE_SIZE2, "page-size")
2646
- };
2647
- if (opts.orderType !== void 0) params.order_type = opts.orderType;
2648
- if (opts.status !== void 0) params.status = opts.status;
2649
- const spinner = format === "json" ? null : createSpinner("Fetching orders...");
2650
- const result = await deps.apiClient.get(
2651
- "/orders",
2652
- { type: "api-key", key: apiKey },
2653
- params
2654
- );
2655
- spinner?.stop();
2656
- if (!result.success) {
2657
- throw CliError.fromApi(result, { auth: "api-key" });
2658
- }
2659
- const data = result.data;
2660
- const configManager = new ConfigManager();
2661
- const commandResult = {
2662
- data,
2663
- text: () => formatOrders2(data)
2664
- };
2665
- await renderWithContext(commandResult, { format }, configManager);
2666
- });
2667
- }
2668
-
2669
- // src/orders/get.ts
2670
- function registerOrdersGetCommand(parent, deps) {
2671
- const cmd = parent.command("get").description("Get a single order detail by id, regardless of provider (ride/hotel)").option("--api-key <key>", "API Key for authentication (X-Api-Key)").requiredOption("--order-id <id>", "Order id (e.g. rio_... or hho_...)");
2672
- attachSchemaHelp(cmd, unifiedOrdersGetSchema);
2673
- cmd.action(async () => {
2674
- const opts = cmd.optsWithGlobals();
2675
- const format = resolveFormat(opts.format);
2676
- const apiKey = await PromptEngine.resolveInput(opts.apiKey, {
2677
- message: "API Key:",
2678
- type: "password"
2679
- });
2680
- const spinner = format === "json" ? null : createSpinner("Fetching order...");
2681
- const result = await deps.apiClient.get(
2682
- `/orders/${encodeURIComponent(opts.orderId)}`,
2683
- { type: "api-key", key: apiKey }
2684
- );
2685
- spinner?.stop();
2686
- if (!result.success) {
2687
- throw CliError.fromApi(result, { auth: "api-key" });
2688
- }
2689
- const data = result.data;
2690
- const configManager = new ConfigManager();
2691
- const commandResult = {
2692
- data,
2693
- text: () => Formatter.keyValue(
2694
- Object.entries(data).map(([k, v]) => [k, typeof v === "object" ? JSON.stringify(v) : String(v)])
2695
- )
2696
- };
2697
- await renderWithContext(commandResult, { format }, configManager);
2698
- });
2699
- }
2700
-
2701
2567
  // src/hotel-redaug/search.ts
2702
2568
  function need5(value, flag) {
2703
2569
  if (value === void 0) {
@@ -2719,7 +2585,7 @@ function coord(value, flag, min, max) {
2719
2585
  }
2720
2586
  return n;
2721
2587
  }
2722
- function positiveInt3(value, flag) {
2588
+ function positiveInt2(value, flag) {
2723
2589
  const n = num3(value, flag);
2724
2590
  if (!Number.isInteger(n) || n <= 0) {
2725
2591
  throw new CliError("PARAM_INVALID", `--${flag} must be a positive integer.`);
@@ -2796,8 +2662,8 @@ function registerHotelSearchCommand(parent, deps) {
2796
2662
  if (!(checkOut > checkIn)) {
2797
2663
  throw new CliError("PARAM_INVALID", "--check-out must be strictly after --check-in.");
2798
2664
  }
2799
- const page = positiveInt3(opts.page, "page");
2800
- const pageSize = positiveInt3(opts.pageSize, "page-size");
2665
+ const page = positiveInt2(opts.page, "page");
2666
+ const pageSize = positiveInt2(opts.pageSize, "page-size");
2801
2667
  const body = {};
2802
2668
  if (hasDestination) {
2803
2669
  body.destination_id = need5(opts.destinationId, "destination-id");
@@ -2995,19 +2861,15 @@ function formatCreateOrder(data) {
2995
2861
  const rows = rooms.map((r) => [String(r.room_index ?? "-"), String(r.guest_name ?? "-")]);
2996
2862
  out.push("", Formatter.table(headers, rows));
2997
2863
  }
2998
- out.push(
2999
- "",
3000
- Formatter.status(
3001
- "info",
3002
- `Order created with status AWAITING_PAYMENT \u2014 proceed with 'hotel-redaug pay-order --order-id ${data.order_id ?? "<order_id>"}' to settle.`
3003
- )
3004
- );
3005
2864
  return out.join("\n");
3006
2865
  }
3007
2866
  function registerHotelCreateOrderCommand(parent, deps) {
3008
- const cmd = parent.command("create-order").description("Create a hotel order without charging (lock inventory, await payment)").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--product-token <token>", "Chosen rate token from quote (rates[].product_token)").option("--total-amount <amount>", "Total stay price in decimal currency units (not cents)").option("--currency <currency>", "ISO 4217 currency code from the chosen rate").option("--price-items <json>", "Per-night price breakdown JSON array (copied verbatim from quote)").option("--check-in <date>", "Check-in date (YYYY-MM-DD, match quote)").option("--check-out <date>", "Check-out date (YYYY-MM-DD, strictly after check-in)").option("--room-num <n>", "Number of rooms (match quote)", "1").option("--adults <n>", "Adults per room (match quote)", "2").option("--children <n>", "Children per room (match quote)", "0").option("--nationality <code>", "Guest nationality (ISO country code)", "CN").option("--guest-name <name>", "Primary guest name").option("--contact-name <name>", "Booking contact name (may equal guest-name)").option("--contact-phone <phone>", "Booking contact phone number (digits)").option("--contact-country-code <code>", "Phone country calling code without '+'", "86").option("--contact-email <email>", "Booking contact email").option("--arrive-time <time>", "Expected arrival time (HH:mm, hotel local time)").option("--special-requests <text>", "Free-text special requests (non-binding)").option("--hotel-name <name>", "Hotel name (display-only, stored for order summary)").option(
2867
+ const cmd = parent.command("create-order").description("Create AND pay for a hotel order in one call (billing path decided server-side by billing_mode)").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--product-token <token>", "Chosen rate token from quote (rates[].product_token)").option("--total-amount <amount>", "Total stay price in decimal currency units (not cents)").option("--currency <currency>", "ISO 4217 currency code from the chosen rate").option("--price-items <json>", "Per-night price breakdown JSON array (copied verbatim from quote)").option("--check-in <date>", "Check-in date (YYYY-MM-DD, match quote)").option("--check-out <date>", "Check-out date (YYYY-MM-DD, strictly after check-in)").option("--room-num <n>", "Number of rooms (match quote)", "1").option("--adults <n>", "Adults per room (match quote)", "2").option("--children <n>", "Children per room (match quote)", "0").option("--nationality <code>", "Guest nationality (ISO country code)", "CN").option("--guest-name <name>", "Primary guest name").option("--contact-name <name>", "Booking contact name (may equal guest-name)").option("--contact-phone <phone>", "Booking contact phone number (digits)").option("--contact-country-code <code>", "Phone country calling code without '+'", "86").option("--contact-email <email>", "Booking contact email").option("--arrive-time <time>", "Expected arrival time (HH:mm, hotel local time)").option("--special-requests <text>", "Free-text special requests (non-binding)").option(
2868
+ "--payment-method-id <id>",
2869
+ "pay_per_call only: charge this SPECIFIC already-bound card instead of the platform auto-selected one"
2870
+ ).option(
3009
2871
  "--idempotency-key <key>",
3010
- "Idempotency key forwarded verbatim as the Idempotency-Key header"
2872
+ "Idempotency key forwarded verbatim as the Idempotency-Key header (covers both order creation AND payment)"
3011
2873
  );
3012
2874
  attachSchemaHelp(cmd, hotelCreateOrderSchema);
3013
2875
  cmd.action(async () => {
@@ -3049,10 +2911,10 @@ function registerHotelCreateOrderCommand(parent, deps) {
3049
2911
  if (opts.contactEmail !== void 0) body.contact_email = opts.contactEmail;
3050
2912
  if (opts.arriveTime !== void 0) body.arrive_time = opts.arriveTime;
3051
2913
  if (opts.specialRequests !== void 0) body.special_requests = opts.specialRequests;
3052
- if (opts.hotelName !== void 0) body.hotel_name = opts.hotelName;
2914
+ if (opts.paymentMethodId !== void 0) body.payment_method_id = opts.paymentMethodId;
3053
2915
  if (!isYes) {
3054
2916
  const confirmed = await confirm3({
3055
- message: `Create this hotel order for ${totalAmount} ${currency} (check-in ${checkIn}, check-out ${checkOut})? This locks the rate but does NOT charge anything yet.`,
2917
+ message: `Create and pay for this hotel order: ${totalAmount} ${currency} (check-in ${checkIn}, check-out ${checkOut})? This locks the rate AND charges the amount immediately.`,
3056
2918
  default: false
3057
2919
  });
3058
2920
  if (!confirmed) {
@@ -3211,23 +3073,20 @@ var DEFAULT_PAY_WATCH_TIMEOUT_SECONDS = 300;
3211
3073
  function formatPayOrder(data) {
3212
3074
  const lines = [
3213
3075
  ["Order ID", String(data.order_id ?? "-")],
3214
- ["Order status", String(data.order_status ?? "-")]
3076
+ ["Order status", String(data.status ?? "-")]
3215
3077
  ];
3216
3078
  if (data.settlement_path) lines.push(["Settlement path", String(data.settlement_path)]);
3217
- if (data.pay_status) lines.push(["Pay status", String(data.pay_status)]);
3218
- if (data.total_amount != null && data.currency) {
3219
- lines.push(["Amount", `${data.total_amount} ${data.currency}`]);
3079
+ if (data.amount != null && data.currency) {
3080
+ lines.push(["Amount", `${data.amount} ${data.currency}`]);
3220
3081
  }
3221
- if (data.billing_entry_id) lines.push(["Billing entry", String(data.billing_entry_id)]);
3222
- if (data.merchant_trans_id) lines.push(["Merchant trans ID", String(data.merchant_trans_id)]);
3223
3082
  return Formatter.keyValue(lines);
3224
3083
  }
3225
3084
  function isPaymentTerminal(record) {
3226
- const status = record.order_status;
3085
+ const status = record.status;
3227
3086
  return status === "PAID";
3228
3087
  }
3229
3088
  function registerHotelPayOrderCommand(parent, deps) {
3230
- const cmd = parent.command("pay-order").description("Settle an existing hotel order (path decided by billing_mode: monthly_settlement or pay_per_call)").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--order-id <id>", "Order ID to pay (from create-order response)").option(
3089
+ const cmd = parent.command("pay-order").description("Trigger supplier confirmation for a paid hotel order (upstream payOrder)").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--order-id <id>", "Order ID to pay (from create-order response)").option(
3231
3090
  "--idempotency-key <key>",
3232
3091
  "Idempotency key forwarded verbatim as the Idempotency-Key header"
3233
3092
  ).option("--watch", "Poll until PAID or timeout (NDJSON output)", false).option(
@@ -3262,11 +3121,11 @@ function registerHotelPayOrderCommand(parent, deps) {
3262
3121
  const body = {};
3263
3122
  if (!isYes) {
3264
3123
  const confirmed = await confirm4({
3265
- message: `Settle hotel order ${orderId}? This moves money via the order's configured billing mode (monthly_settlement or pay_per_call).`,
3266
- default: false
3124
+ message: `Confirm hotel order ${orderId} with the supplier? (Payment was already settled in create-order; this triggers supplier confirmation.)`,
3125
+ default: true
3267
3126
  });
3268
3127
  if (!confirmed) {
3269
- throw new CliError("CLIENT_ABORTED", "Payment aborted by user.");
3128
+ throw new CliError("CLIENT_ABORTED", "Confirmation aborted by user.");
3270
3129
  }
3271
3130
  }
3272
3131
  const path = `/hotel/${encodeURIComponent(orderId)}/pay`;
@@ -3309,19 +3168,7 @@ function registerHotelPayOrderCommand(parent, deps) {
3309
3168
  return;
3310
3169
  }
3311
3170
  } else {
3312
- const errorCode = result.code ?? result.errorCode;
3313
- const errorCodeStr = String(errorCode);
3314
- if (errorCodeStr === "PAYMENT_NOT_COMPLETED" || errorCodeStr === "1933") {
3315
- const errorLine = {
3316
- watch_status: "pending",
3317
- error_code: errorCodeStr === "1933" ? "PAYMENT_NOT_COMPLETED" : errorCodeStr,
3318
- message: result.errorMessage ?? "Payment not yet completed"
3319
- };
3320
- lastRecord = errorLine;
3321
- ndjsonWriteLine(errorLine);
3322
- } else {
3323
- throw CliError.fromApi(result, { auth: "api-key" });
3324
- }
3171
+ throw CliError.fromApi(result, { auth: "api-key" });
3325
3172
  }
3326
3173
  if (Date.now() + watchInterval * 1e3 >= deadline) {
3327
3174
  const timeoutLine = {
@@ -3362,10 +3209,29 @@ function formatGetOrder2(data) {
3362
3209
  if (data.check_in) lines.push(["Check-in", String(data.check_in)]);
3363
3210
  if (data.check_out) lines.push(["Check-out", String(data.check_out)]);
3364
3211
  if (data.total_amount !== void 0 && data.total_amount !== null) {
3365
- lines.push(["Total amount", String(data.total_amount)]);
3212
+ const currency = data.currency ? ` ${data.currency}` : "";
3213
+ lines.push(["Total amount", `${String(data.total_amount)}${currency}`]);
3214
+ }
3215
+ if (data.settlement_path) lines.push(["Settlement path", String(data.settlement_path)]);
3216
+ if (data.rooms && data.rooms.length > 0) {
3217
+ lines.push([
3218
+ "Rooms",
3219
+ data.rooms.map((r) => `${r.room_index}:${r.guest_name}`).join(", ")
3220
+ ]);
3221
+ }
3222
+ if (data.room_num !== void 0 && data.room_num !== null) {
3223
+ lines.push(["Rooms booked", String(data.room_num)]);
3224
+ }
3225
+ if (data.guest?.name) lines.push(["Guest", String(data.guest.name)]);
3226
+ if (data.contact?.name) {
3227
+ const phone2 = data.contact.phone ? ` (+${data.contact.country_code ?? ""} ${data.contact.phone})` : "";
3228
+ lines.push(["Contact", `${data.contact.name}${phone2}`]);
3366
3229
  }
3230
+ if (data.arrive_time) lines.push(["Arrive time", String(data.arrive_time)]);
3231
+ if (data.special_requests) lines.push(["Special requests", String(data.special_requests)]);
3367
3232
  if (data.channel_state) lines.push(["Channel state", String(data.channel_state)]);
3368
- if (data.source) lines.push(["Source", String(data.source)]);
3233
+ if (data.created_at) lines.push(["Created at", String(data.created_at)]);
3234
+ if (data.updated_at) lines.push(["Updated at", String(data.updated_at)]);
3369
3235
  return Formatter.keyValue(lines);
3370
3236
  }
3371
3237
  function registerHotelGetCommand(parent, deps) {
@@ -3701,16 +3567,16 @@ function registerHotelGetCheckoutCommand(parent, deps) {
3701
3567
  }
3702
3568
 
3703
3569
  // src/hotel-redaug/list-orders.ts
3704
- var DEFAULT_PAGE3 = "1";
3705
- var DEFAULT_PAGE_SIZE3 = "20";
3706
- function positiveInt4(value, flag) {
3570
+ var DEFAULT_PAGE2 = "1";
3571
+ var DEFAULT_PAGE_SIZE2 = "20";
3572
+ function positiveInt3(value, flag) {
3707
3573
  const n = Number(value);
3708
3574
  if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
3709
3575
  throw new CliError("PARAM_INVALID", `--${flag} must be a positive integer.`);
3710
3576
  }
3711
3577
  return String(n);
3712
3578
  }
3713
- function formatOrders3(data) {
3579
+ function formatOrders2(data) {
3714
3580
  const orders = data.orders ?? [];
3715
3581
  if (orders.length === 0) {
3716
3582
  return Formatter.status("info", "No hotel orders found");
@@ -3734,7 +3600,7 @@ function formatOrders3(data) {
3734
3600
  ${Formatter.keyValue(summary)}`;
3735
3601
  }
3736
3602
  function registerHotelListOrdersCommand(parent, deps) {
3737
- const cmd = parent.command("list-orders").description("List the developer's hotel orders with optional status filtering and pagination").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--status <status>", "Filter by order status (omitted entirely when absent \u2192 all statuses)").option("--page <page>", "Page number", DEFAULT_PAGE3).option("--page-size <size>", "Page size", DEFAULT_PAGE_SIZE3);
3603
+ const cmd = parent.command("list-orders").description("List the developer's hotel orders with optional status filtering and pagination").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--status <status>", "Filter by order status (omitted entirely when absent \u2192 all statuses)").option("--page <page>", "Page number", DEFAULT_PAGE2).option("--page-size <size>", "Page size", DEFAULT_PAGE_SIZE2);
3738
3604
  attachSchemaHelp(cmd, hotelListOrdersSchema);
3739
3605
  cmd.action(async () => {
3740
3606
  const opts = cmd.optsWithGlobals();
@@ -3744,8 +3610,8 @@ function registerHotelListOrdersCommand(parent, deps) {
3744
3610
  type: "password"
3745
3611
  });
3746
3612
  const params = {
3747
- page: positiveInt4(opts.page ?? DEFAULT_PAGE3, "page"),
3748
- page_size: positiveInt4(opts.pageSize ?? DEFAULT_PAGE_SIZE3, "page-size")
3613
+ page: positiveInt3(opts.page ?? DEFAULT_PAGE2, "page"),
3614
+ page_size: positiveInt3(opts.pageSize ?? DEFAULT_PAGE_SIZE2, "page-size")
3749
3615
  };
3750
3616
  if (opts.status !== void 0) params.status = opts.status;
3751
3617
  const spinner = format === "json" ? null : createSpinner("Fetching orders...");
@@ -3762,7 +3628,7 @@ function registerHotelListOrdersCommand(parent, deps) {
3762
3628
  const configManager = new ConfigManager();
3763
3629
  const commandResult = {
3764
3630
  data,
3765
- text: () => formatOrders3(data)
3631
+ text: () => formatOrders2(data)
3766
3632
  };
3767
3633
  await renderWithContext(commandResult, { format }, configManager);
3768
3634
  });
@@ -4134,7 +4000,7 @@ run \`hotel-redaug <verb> --help\`.
4134
4000
  - API key with merchant scope
4135
4001
  - Two billing modes supported (set on the developer, not chosen per-call):
4136
4002
  - **monthly_settlement**: developer has a settlement account with sufficient balance
4137
- - **pay_per_call**: developer collects payment from their end-user via EVO; the user pays using the create-order \`order_id\` as the EVO merchantTransID, and the platform verifies by querying EVO for that order_id (the pay-order response reports settlement_path "active_payment")
4003
+ - **pay_per_call**: developer collects payment from their end-user via EVO; the user pays using the create-order \`order_id\` as the EVO merchantTransID, and the platform verifies by querying EVO for that order_id (the pay-order response reports settlement_path "pay_per_call")
4138
4004
  - BILLING_MODE_MISMATCH means the order billing_mode is not one of the two modes above
4139
4005
  - Pass --api-key <key> --format json on every call; add --yes on writes
4140
4006
 
@@ -4179,32 +4045,40 @@ run \`hotel-redaug <verb> --help\`.
4179
4045
  - Generate a unique idempotency-key
4180
4046
  - Response \u2192 save order_id + fc_order_code + total_amount + currency
4181
4047
  - Order status: AWAITING_PAYMENT (no money charged yet)
4182
- - FOR pay_per_call: tell the user to pay total_amount+currency via EVO
4183
- USING THIS order_id as the EVO merchantTransID. The verification in step 6 works by
4184
- querying EVO for exactly this order_id, so paying under any other id will not settle.
4185
4048
 
4186
- 6. Pay order (settle the locked order) \u2014 MUST confirm with the user before calling this
4049
+ 6. BRANCH ON BILLING_MODE \u2014 decide THIS before calling pay-order, not inside it
4050
+ - You (the developer/Agent) know your own billing_mode; the order does not expose it.
4051
+ a) monthly_settlement \u2192 skip straight to step 7 (pay-order). Nothing to do here.
4052
+ b) pay_per_call \u2192 STOP. Do NOT call pay-order yet.
4053
+ - Drive YOUR OWN EVO payment integration now, using the order_id from step 5 AS THE
4054
+ EVO merchantTransID (this is the id you pass to your EVO payment call \u2014 not a new
4055
+ or random id).
4056
+ - Wait for the end-user to actually complete that EVO payment for total_amount+currency.
4057
+ - Only once EVO shows the payment as complete, proceed to step 7 \u2014 pay-order is what
4058
+ tells the platform "go verify and settle this", it does NOT itself collect payment.
4059
+
4060
+ 7. Pay order (settle the locked order) \u2014 MUST confirm with the user before calling this
4187
4061
  - Before calling: show the user total_amount + currency from create-order and which
4188
4062
  billing path applies, and get explicit go-ahead \u2014 pay-order is the step that actually
4189
- moves money
4063
+ moves money (for pay_per_call, confirm the EVO payment from step 6 was completed first)
4190
4064
  - pay-order --order-id <order_id from create-order> (that's the only identifier needed)
4191
4065
  - Billing path determined server-side by developer's billing_mode:
4192
4066
  a) monthly_settlement:
4193
4067
  - Platform deducts from settlement account \u2192 calls upstream payOrder
4194
4068
  b) pay_per_call:
4195
- - The user must have ALREADY paid via EVO using the order_id as the EVO
4196
- merchantTransID (see create-order step 5). Just call pay-order --order-id <id>;
4197
- the platform queries EVO for that order_id and verifies exact amount/currency.
4069
+ - Requires step 6 to have already happened (user paid via EVO under this order_id).
4070
+ Calling pay-order now just makes the platform query EVO for that order_id and
4071
+ verify exact amount/currency \u2014 it does NOT initiate the EVO payment itself.
4198
4072
  There is no merchant-transaction-id flag: the EVO key IS the order_id, so a
4199
4073
  foreign already-paid EVO transaction cannot be substituted.
4200
4074
  - If EVO not yet confirmed \u2192 PAYMENT_NOT_COMPLETED; use --watch to poll
4201
4075
  - On success \u2192 order status becomes PAID
4202
4076
 
4203
- 7. Confirm (upstream confirmation is async)
4077
+ 8. Confirm (upstream confirmation is async)
4204
4078
  - Poll get --order-id until CONFIRMED (success) or CANCELLED (failed)
4205
4079
  - Do not report success before CONFIRMED
4206
4080
 
4207
- 8. Post-booking (only when the user asks)
4081
+ 9. Post-booking (only when the user asks)
4208
4082
  - cancel: order_id + fc_order_code + reason
4209
4083
  - checkout: partial leave-early \u2192 then poll get-checkout with task_order_code
4210
4084
  - list-orders: view all orders
@@ -4215,8 +4089,9 @@ run \`hotel-redaug <verb> --help\`.
4215
4089
  - price_items : quote \u2192 create-order (JSON array; copy exactly)
4216
4090
  - order_id : create-order \u2192 pay-order / get / cancel
4217
4091
  - fc_order_code : create-order \u2192 cancel / checkout
4218
- - order_id (again): for pay_per_call it IS the EVO merchantTransID \u2014 the user pays via
4219
- EVO under the order_id; pay-order takes no separate merchant-transaction-id
4092
+ - order_id (again): for pay_per_call it IS the EVO merchantTransID \u2014 YOU (the developer/Agent)
4093
+ use it to drive your own EVO payment call BEFORE pay-order (see step 6); pay-order itself
4094
+ takes no separate merchant-transaction-id, it only verifies the payment already made
4220
4095
  - task_order_code : checkout \u2192 get-checkout
4221
4096
 
4222
4097
  ## Billing modes (chosen server-side by billing_mode)
@@ -4227,15 +4102,18 @@ routes purely by the order's billing_mode:
4227
4102
  | Developer billing_mode | Behavior |
4228
4103
  |------------------------|----------|
4229
4104
  | monthly_settlement | Deducts settlement balance \u2192 payOrder |
4230
- | pay_per_call | Platform queries EVO for the order_id \u2192 verify exact amount/currency \u2192 payOrder (response settlement_path is "active_payment") |
4105
+ | pay_per_call | Platform queries EVO for the order_id \u2192 verify exact amount/currency \u2192 payOrder (response settlement_path is "pay_per_call") |
4231
4106
  | anything else | ERROR: BILLING_MODE_MISMATCH (only the two modes above are valid) |
4232
4107
 
4233
4108
  ## pay_per_call flow \u2014 the EVO merchantTransID IS the order_id
4234
4109
 
4235
4110
  1. create-order returns order_id + total_amount + currency (no EVO credentials in response)
4236
- 2. User pays total_amount+currency via EVO, USING the order_id as the EVO merchantTransID
4237
- (the shared EVO parameters are onboarded separately, out of band)
4238
- 3. Agent calls pay-order --order-id <order_id> (no other identifier)
4111
+ 2. YOU drive your own EVO payment integration now, passing the order_id as the EVO
4112
+ merchantTransID, and get the user to pay total_amount+currency
4113
+ (the shared EVO parameters are onboarded separately, out of band \u2014 pay-order does not do
4114
+ this step for you)
4115
+ 3. ONLY once that EVO payment is complete, call pay-order --order-id <order_id>
4116
+ (no other identifier)
4239
4117
  4. Platform queries EVO for that order_id: paid + exact amount/currency match \u2192 payOrder \u2192 PAID
4240
4118
  5. If not yet paid \u2192 PAYMENT_NOT_COMPLETED; use --watch to auto-poll
4241
4119
  6. Why order_id (not an arbitrary id): it binds the payment to this exact order, so a caller
@@ -4252,7 +4130,7 @@ routes purely by the order's billing_mode:
4252
4130
  - IMPORTANT: --yes only skips this CLI's own interactive TTY prompt (needed because the Agent
4253
4131
  runs non-interactively). It is NOT a substitute for showing the user the hotel/rate/price and
4254
4132
  getting their decision in the chat UI. Passing --yes does not remove the confirmation steps in
4255
- step 3/4 (choose hotel + rate) or step 6 (confirm before pay-order) above.
4133
+ step 3/4 (choose hotel + rate) or step 7 (confirm before pay-order) above.
4256
4134
  - After a cancel call returns successfully (including the cancel_status='cancel_pending' shape),
4257
4135
  do NOT call cancel again for the same order \u2014 poll get instead. A pending acknowledgement is
4258
4136
  success, not failure.
@@ -4313,11 +4191,6 @@ async function main() {
4313
4191
  const servicesCmd = program.command("services").description("Merchant service discovery");
4314
4192
  registerServicesListCommand(servicesCmd, { discoveryClient, program });
4315
4193
  registerServiceGetCommand(servicesCmd, { discoveryClient, program });
4316
- const ordersCmd = program.command("orders").description(
4317
- 'Unified cross-provider order index (spans ride + hotel). Use for generic "my orders" requests; use ride-elife/hotel-redaug commands once the business is known.'
4318
- );
4319
- registerOrdersListCommand(ordersCmd, deps);
4320
- registerOrdersGetCommand(ordersCmd, deps);
4321
4194
  const hotelCmd = program.command("hotel-redaug").description(
4322
4195
  `Hotel booking (Redaug) \u2014 international hotel search, quote, create-order, pay-order, cancel, and check-out.
4323
4196