@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/README.md +12 -10
- package/dist/index.js +172 -299
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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
|
|
1292
|
-
currency: { type: "string", description: "ISO 4217 currency code
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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: "
|
|
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: "
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
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: '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
["
|
|
2523
|
-
["Description", String(s.description ?? s.summary ?? "")],
|
|
2482
|
+
["CLI Noun", String(s.cli_noun ?? "")],
|
|
2524
2483
|
["Category", String(s.category ?? "")],
|
|
2525
|
-
["
|
|
2484
|
+
["Name", String(s.name ?? "")],
|
|
2485
|
+
["Summary", String(s.summary ?? "")],
|
|
2486
|
+
["Status", String(s.status ?? "")],
|
|
2526
2487
|
["Provider", String(s.provider ?? "")],
|
|
2527
|
-
["
|
|
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
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
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
|
|
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
|
|
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 =
|
|
2800
|
-
const pageSize =
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
3218
|
-
|
|
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.
|
|
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("
|
|
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: `
|
|
3266
|
-
default:
|
|
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", "
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
3705
|
-
var
|
|
3706
|
-
function
|
|
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
|
|
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",
|
|
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:
|
|
3748
|
-
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: () =>
|
|
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 "
|
|
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.
|
|
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
|
-
-
|
|
4196
|
-
|
|
4197
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
4219
|
-
EVO
|
|
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 "
|
|
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.
|
|
4237
|
-
|
|
4238
|
-
|
|
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
|
|
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
|
|