@agenzo/merchant-cli 0.9.0 → 0.9.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
@@ -1197,6 +1197,22 @@ var ridePayOrderSchema = {
1197
1197
  default: false,
1198
1198
  description: "[\u65B9\u6848 B/R16] Explicit opt-in to settle this pay_per_call ride via the EVO bound-card fallback rail. Set true ONLY when the user explicitly chose EVO; forwarded to the pay body as evo_explicit=true. Without it, a pay_per_call settlement lacking --payment-token-id is hard-gated server-side. Ignored for monthly_settlement and on the payment-token-id direct-charge path."
1199
1199
  },
1200
+ authenticate: {
1201
+ type: "bool",
1202
+ required: false,
1203
+ default: false,
1204
+ description: "Opt-in to require cardholder 3DS on the network-token direct charge (payment-token-id, EVO/Mastercard). On step-up the response is AUTHENTICATION_REQUIRED with three_ds_url + charge_no; resume via authorized-charge-no. Default off = frictionless. Ignored on the EVO preauth path and for UnionPay/Visa."
1205
+ },
1206
+ "return-url": {
1207
+ type: "string",
1208
+ required: false,
1209
+ description: "Optional 3DS/passkey return URL for the network-token authenticate path; falls back to the platform EVO_3DS_RETURN_URL."
1210
+ },
1211
+ "authorized-charge-no": {
1212
+ type: "string",
1213
+ required: false,
1214
+ description: "Resume a network-token 3DS challenge: charge_no from a prior AUTHENTICATION_REQUIRED response (settled via resume_token). Relaxes the exactly-one-credential rule (continuation, not a fresh charge)."
1215
+ },
1200
1216
  "idempotency-key": {
1201
1217
  type: "string",
1202
1218
  required: true,
@@ -1662,6 +1678,22 @@ var hotelPayOrderSchema = {
1662
1678
  default: false,
1663
1679
  description: "[\u65B9\u6848 B/R16] Explicit opt-in to settle this pay_per_call order via the EVO bound-card fallback rail. Set true ONLY when the user explicitly chose EVO; forwarded to the pay body as evo_explicit=true. Without it, a pay_per_call settlement lacking --payment-token-id is hard-gated server-side. Ignored for monthly_settlement and on the payment-token-id direct-charge path."
1664
1680
  },
1681
+ authenticate: {
1682
+ type: "bool",
1683
+ required: false,
1684
+ default: false,
1685
+ description: "Opt-in to require cardholder 3DS on the network-token direct charge (payment-token-id, EVO/Mastercard). On step-up the response is AUTHENTICATION_REQUIRED with three_ds_url + charge_no; resume via authorized-charge-no. Default off = frictionless. Ignored on the EVO preauth path and for UnionPay/Visa."
1686
+ },
1687
+ "return-url": {
1688
+ type: "string",
1689
+ required: false,
1690
+ description: "Optional 3DS/passkey return URL for the network-token authenticate path; falls back to the platform EVO_3DS_RETURN_URL."
1691
+ },
1692
+ "authorized-charge-no": {
1693
+ type: "string",
1694
+ required: false,
1695
+ description: "Resume a network-token 3DS challenge: charge_no from a prior AUTHENTICATION_REQUIRED response (settled via resume_token), then triggers upstream payOrder on success (pay_per_call only)."
1696
+ },
1665
1697
  "idempotency-key": {
1666
1698
  type: "string",
1667
1699
  required: true,
@@ -2363,30 +2395,38 @@ var flightListNationalitiesSchema = flightSchema(
2363
2395
  );
2364
2396
  var flightSearchSchema = flightSchema(
2365
2397
  "search",
2366
- "Search flights (one-way/round-trip/multi-city). journeys always carries all legs; journey-id accumulates progress and only the final search returns a real priceKey (product_token).",
2398
+ "Search flights (one-way/round-trip/multi-city). journeys always carries all legs; for round-trip/multi-city relay the CHOSEN candidate's next_journey_ids into --journey-id until is_final_leg is true \u2014 only then do offers carry a real priceKey (product_token).",
2367
2399
  {
2368
2400
  "trip-type": { type: "int", required: true, description: "1=one-way, 2=round-trip, 3=multi-city." },
2369
- journeys: { type: "json", required: true, description: "JSON array of {date, origin, destination, origin_type?, destination_type?}. origin/destination are IATA 3-letter codes; *_type 1=city, 2=airport." },
2401
+ journeys: { type: "json", required: true, description: "JSON array of {date, origin, destination, origin_type?, destination_type?}. origin/destination are IATA 3-letter codes; *_type 1=city, 2=airport. Carries ALL legs on every call, including relay calls." },
2370
2402
  "cabin-class": { type: "string", required: false, default: "economy", description: "economy | premium_economy | business | first." },
2371
2403
  "adult-num": { type: "int", required: false, default: 1, description: "Adults (1-9)." },
2372
2404
  "child-num": { type: "int", required: false, default: 0, description: "Children (0-9)." },
2373
2405
  "infant-num": { type: "int", required: false, default: 0, description: "Infants (0-9)." },
2374
2406
  airline: { type: "string", required: false, description: "Airline 2-letter code filter." },
2375
2407
  "transfer-number": { type: "int", required: false, default: 0, description: "0=any,1=direct,2=1 stop,3=2 stops." },
2376
- "journey-id": { type: "json", required: false, description: "JSON array of already-selected journey ids (relay for round-trip/multi-city)." }
2408
+ "journey-id": { type: "json", required: false, description: "Relay key: copy offers[].next_journey_ids VERBATIM from the candidate the user chose in the previous search. Omit on the first search. Do not build it from the top-level journey_ids, and never send more ids than there are legs (rejected)." }
2377
2409
  },
2378
2410
  {
2379
- offers: { type: "array", description: "Offers; each has product_token (when price_key_ready), total_sale_price, currency." },
2411
+ offers: { type: "array", description: "Candidates for the current leg. Each has next_journey_ids (relay key), journey_id, segment_ids, paired_segment_ids, total_sale_price (WHOLE-TRIP total), currency, cabin_class, baggage/refund/change_rules, and product_token when price_key_ready." },
2380
2412
  price_key_ready: { type: "bool", description: "True only when all legs are selected (final search)." },
2381
- journey_ids: { type: "array", description: "journeyId values to relay into the next search." }
2413
+ current_leg: { type: "int", description: "Which leg this result is choosing (1-based)." },
2414
+ total_legs: { type: "int", description: "Total legs in the trip." },
2415
+ is_final_leg: { type: "bool", description: "True when these offers can be bookable. False means relay another round with the chosen candidate's next_journey_ids." },
2416
+ journey_ids: { type: "array", description: "DEPRECATED for relay: flattened across all candidates, so it cannot tell you which id belongs to which. Relay offers[].next_journey_ids instead." }
2382
2417
  },
2383
- { command: `agenzo-merchant-cli flight-flink search --trip-type 1 --journeys '[{"date":"2026-08-01","origin":"PEK","destination":"SHA"}]'`, output_summary: "Returns offers[]; use a product_token where price_key_ready is true, then verify." }
2418
+ { command: `agenzo-merchant-cli flight-flink search --trip-type 1 --journeys '[{"date":"2026-08-01","origin":"PEK","destination":"SHA"}]'`, output_summary: "Returns offers[]; use a product_token where price_key_ready is true, then verify. Round-trip: the first search has is_final_leg=false and no product_token \u2014 relay the chosen candidate's next_journey_ids to get bookable offers." }
2384
2419
  );
2385
2420
  var flightMoreOffersSchema = flightSchema(
2386
2421
  "more-offers",
2387
2422
  "More fare offers for a priceKey (carried by product_token).",
2388
2423
  { "product-token": { type: "string", required: true, description: "Opaque token from search." } },
2389
- { offers: { type: "array", description: "Additional fare candidates." } },
2424
+ {
2425
+ offers: { type: "array", description: "Additional fare candidates for the same flight (same shape as search offers[]); all are bookable." },
2426
+ current_leg: { type: "int", description: "Always 1 \u2014 alternative fares for one chosen flight, not a relay round." },
2427
+ total_legs: { type: "int", description: "Always 1." },
2428
+ is_final_leg: { type: "bool", description: "Always true." }
2429
+ },
2390
2430
  { command: "agenzo-merchant-cli flight-flink more-offers --product-token pt_...", output_summary: "Additional fare offers." }
2391
2431
  );
2392
2432
  var flightVerifySchema = flightSchema(
@@ -2397,7 +2437,8 @@ var flightVerifySchema = flightSchema(
2397
2437
  product_token: { type: "string", description: "Authoritative token to pass to create-order." },
2398
2438
  total_price: { type: "int", description: "Verified total price (integer)." },
2399
2439
  currency: { type: "string", description: "Currency code." },
2400
- price_changed: { type: "bool", description: "True if the price changed since search; re-confirm with the user." }
2440
+ price_changed: { type: "bool", description: "True if the price changed since search; re-confirm with the user." },
2441
+ journeys: { type: "array", description: "The COMPLETE itinerary being priced \u2014 every leg, not just the last one relayed. Use it to show the full trip on a confirmation screen." }
2401
2442
  },
2402
2443
  { command: "agenzo-merchant-cli flight-flink verify --product-token pt_...", output_summary: "Returns the authoritative product_token + price_changed." }
2403
2444
  );
@@ -2440,7 +2481,10 @@ var flightPayOrderSchema = flightSchema(
2440
2481
  "order-no": { type: "string", required: true, description: "Our order reference from create-order." },
2441
2482
  "payment-method-id": { type: "string", required: false, description: "Optional bound-card id to charge (pay_per_call only; omit to use the default card)." },
2442
2483
  "payment-token-id": { type: "string", required: false, description: "Optional network-token id; settles via direct charge instead of EVO preauth." },
2443
- "authorized-merchant-trans-id": { type: "string", required: false, description: "Resume a 3DS challenge with an already-authorised preauth trans id." },
2484
+ "authorized-merchant-trans-id": { type: "string", required: false, description: "Resume an EVO-card 3DS challenge with an already-authorised preauth trans id." },
2485
+ authenticate: { type: "bool", required: false, default: false, description: "Opt-in to require cardholder 3DS on the network-token direct charge (payment-token-id, EVO/Mastercard). On step-up the response is AUTHENTICATION_REQUIRED with three_ds_url + charge_no; resume via authorized-charge-no. Default off = frictionless." },
2486
+ "return-url": { type: "string", required: false, description: "Optional 3DS/passkey return URL for the network-token authenticate path; falls back to the platform EVO_3DS_RETURN_URL." },
2487
+ "authorized-charge-no": { type: "string", required: false, description: "Resume a network-token 3DS challenge: charge_no from a prior AUTHENTICATION_REQUIRED response (settled via resume_token), then triggers ticketing on success." },
2444
2488
  "evo-explicit": { type: "bool", required: false, default: false, description: "[\u65B9\u6848 B/R16] Explicit opt-in to settle this pay_per_call order via the EVO bound-card fallback rail. Set true ONLY when the user explicitly chose EVO; forwarded to the pay body as evo_explicit=true. Without it, a pay_per_call settlement lacking --payment-token-id is hard-gated server-side." },
2445
2489
  "idempotency-key": { type: "string", required: true, description: "Forwarded verbatim as the Idempotency-Key header." }
2446
2490
  },
@@ -3103,6 +3147,17 @@ function formatPayOrder(data) {
3103
3147
  lines.push(["Amount", `${data.price.amount} ${data.price.currency}`]);
3104
3148
  if (data.price.quote_id) lines.push(["Quote ID", String(data.price.quote_id)]);
3105
3149
  }
3150
+ if (data.charge_no) lines.push(["Charge no", String(data.charge_no)]);
3151
+ if (data.three_ds_url) lines.push(["3DS URL", String(data.three_ds_url)]);
3152
+ if (String(data.status) === "AUTHENTICATION_REQUIRED") {
3153
+ return [
3154
+ Formatter.keyValue(lines),
3155
+ Formatter.status(
3156
+ "warning",
3157
+ `Order ${orderRef} needs cardholder 3DS \u2014 open the 3DS URL, then resume pay-order with --authorized-charge-no ${data.charge_no ?? ""}.`
3158
+ )
3159
+ ].join("\n\n");
3160
+ }
3106
3161
  return [
3107
3162
  Formatter.keyValue(lines),
3108
3163
  Formatter.status(
@@ -3120,7 +3175,16 @@ function registerRidePayOrderCommand(parent, deps) {
3120
3175
  "Bound-card id to charge via EVO preauth+capture (fallback path). Exactly one of --payment-token-id / --payment-method-id is required."
3121
3176
  ).option(
3122
3177
  "--authorized-merchant-trans-id <id>",
3123
- "Resume a 3DS challenge: merchant trans id of an already-authorised preauth"
3178
+ "Resume an EVO-card 3DS challenge: merchant trans id of an already-authorised preauth"
3179
+ ).option(
3180
+ "--authenticate",
3181
+ "Opt-in to require cardholder 3DS on the network-token direct charge (--payment-token-id, EVO/Mastercard). When a step-up is needed the response is AUTHENTICATION_REQUIRED with three_ds_url + charge_no; resume with --authorized-charge-no. Default off = frictionless."
3182
+ ).option(
3183
+ "--return-url <url>",
3184
+ "Optional 3DS/passkey return URL for the network-token authenticate path (falls back to the platform EVO_3DS_RETURN_URL)"
3185
+ ).option(
3186
+ "--authorized-charge-no <no>",
3187
+ "Resume a network-token 3DS challenge: charge_no returned by a prior AUTHENTICATION_REQUIRED response (settled via ChargeService.resume_token). When set, the exactly-one-credential rule is relaxed (this is a continuation, not a fresh charge)."
3124
3188
  ).option(
3125
3189
  "--evo-explicit",
3126
3190
  "[\u65B9\u6848 B/R16] Explicit opt-in to settle this pay_per_call ride via the EVO bound-card fallback rail. Set ONLY when the user explicitly chose EVO; forwarded to the pay body as evo_explicit=true. Without it, a pay_per_call settlement lacking --payment-token-id is hard-gated server-side."
@@ -3136,7 +3200,10 @@ function registerRidePayOrderCommand(parent, deps) {
3136
3200
  const orderId = need4(opts.orderId, "order-id");
3137
3201
  const paymentTokenId = opts.paymentTokenId;
3138
3202
  const paymentMethodId = opts.paymentMethodId;
3139
- assertExactlyOneCredential(paymentTokenId, paymentMethodId);
3203
+ const authorizedChargeNo = opts.authorizedChargeNo;
3204
+ if (authorizedChargeNo === void 0) {
3205
+ assertExactlyOneCredential(paymentTokenId, paymentMethodId);
3206
+ }
3140
3207
  const apiKey = await PromptEngine.resolveInput(opts.apiKey, {
3141
3208
  message: "API Key:",
3142
3209
  type: "password"
@@ -3147,6 +3214,9 @@ function registerRidePayOrderCommand(parent, deps) {
3147
3214
  if (opts.authorizedMerchantTransId !== void 0) {
3148
3215
  body.authorized_merchant_trans_id = opts.authorizedMerchantTransId;
3149
3216
  }
3217
+ if (opts.authenticate) body.authenticate = true;
3218
+ if (opts.returnUrl !== void 0) body.return_url = opts.returnUrl;
3219
+ if (authorizedChargeNo !== void 0) body.authorized_charge_no = authorizedChargeNo;
3150
3220
  if (opts.evoExplicit) body.evo_explicit = true;
3151
3221
  if (!isYes) {
3152
3222
  const confirmed = await confirm3({
@@ -4864,11 +4934,14 @@ function formatPayOrder2(data) {
4864
4934
  ];
4865
4935
  if (data.settlement_path) lines.push(["Settlement path", String(data.settlement_path)]);
4866
4936
  if (data.pay_status) lines.push(["Pay status", String(data.pay_status)]);
4937
+ if (data.status) lines.push(["Status", String(data.status)]);
4867
4938
  if (data.total_amount != null && data.currency) {
4868
4939
  lines.push(["Amount", `${data.total_amount} ${data.currency}`]);
4869
4940
  }
4870
4941
  if (data.billing_entry_id) lines.push(["Billing entry", String(data.billing_entry_id)]);
4871
4942
  if (data.merchant_trans_id) lines.push(["Merchant trans ID", String(data.merchant_trans_id)]);
4943
+ if (data.charge_no) lines.push(["Charge no", String(data.charge_no)]);
4944
+ if (data.three_ds_url) lines.push(["3DS URL", String(data.three_ds_url)]);
4872
4945
  return Formatter.keyValue(lines);
4873
4946
  }
4874
4947
  function isPaymentTerminal(record) {
@@ -4878,7 +4951,16 @@ function isPaymentTerminal(record) {
4878
4951
  function registerHotelPayOrderCommand(parent, deps) {
4879
4952
  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("--payment-method-id <id>", "Optional bound-card id to charge (pay_per_call / EVO preauth)").option("--payment-token-id <id>", "Optional network-token id (unionpay/visa charge path)").option(
4880
4953
  "--authorized-merchant-trans-id <id>",
4881
- "Resume a 3DS challenge: merchant trans id of an already-authorised preauth"
4954
+ "Resume an EVO-card 3DS challenge: merchant trans id of an already-authorised preauth"
4955
+ ).option(
4956
+ "--authenticate",
4957
+ "Opt-in to require cardholder 3DS on the network-token direct charge (--payment-token-id, EVO/Mastercard). When a step-up is needed the response is AUTHENTICATION_REQUIRED with three_ds_url + charge_no; resume with --authorized-charge-no. Default off = frictionless."
4958
+ ).option(
4959
+ "--return-url <url>",
4960
+ "Optional 3DS/passkey return URL for the network-token authenticate path (falls back to the platform EVO_3DS_RETURN_URL)"
4961
+ ).option(
4962
+ "--authorized-charge-no <no>",
4963
+ "Resume a network-token 3DS challenge: charge_no returned by a prior AUTHENTICATION_REQUIRED response (settled via ChargeService.resume_token)"
4882
4964
  ).option(
4883
4965
  "--evo-explicit",
4884
4966
  "[\u65B9\u6848 B/R16] Explicit opt-in to settle this pay_per_call order via the EVO bound-card fallback rail. Set ONLY when the user explicitly chose EVO; forwarded to the pay body as evo_explicit=true. Without it, a pay_per_call settlement lacking --payment-token-id is hard-gated server-side."
@@ -4920,6 +5002,11 @@ function registerHotelPayOrderCommand(parent, deps) {
4920
5002
  if (opts.authorizedMerchantTransId !== void 0) {
4921
5003
  body.authorized_merchant_trans_id = opts.authorizedMerchantTransId;
4922
5004
  }
5005
+ if (opts.authenticate) body.authenticate = true;
5006
+ if (opts.returnUrl !== void 0) body.return_url = opts.returnUrl;
5007
+ if (opts.authorizedChargeNo !== void 0) {
5008
+ body.authorized_charge_no = opts.authorizedChargeNo;
5009
+ }
4923
5010
  if (opts.evoExplicit) body.evo_explicit = true;
4924
5011
  if (!isYes) {
4925
5012
  const confirmed = await confirm8({
@@ -6151,7 +6238,7 @@ function registerListNationalitiesCommand(parent, deps) {
6151
6238
 
6152
6239
  // src/flight-flink/search.ts
6153
6240
  function registerSearchCommand(parent, deps) {
6154
- const cmd = parent.command("search").description("Search flights (one-way/round-trip/multi-city, journeyId relay)").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--trip-type <n>", "1=one-way, 2=round-trip, 3=multi-city").option("--journeys <json>", "JSON array of {date, origin, destination, origin_type?, destination_type?}").option("--cabin-class <c>", "economy | premium_economy | business | first", "economy").option("--adult-num <n>", "Adults (1-9)", "1").option("--child-num <n>", "Children (0-9)", "0").option("--infant-num <n>", "Infants (0-9)", "0").option("--airline <code>", "Airline 2-letter code filter").option("--transfer-number <n>", "0=any,1=direct,2=1 stop,3=2 stops", "0").option("--journey-id <json>", "JSON array of already-selected journey ids (relay)");
6241
+ const cmd = parent.command("search").description("Search flights (one-way/round-trip/multi-city, journeyId relay)").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--trip-type <n>", "1=one-way, 2=round-trip, 3=multi-city").option("--journeys <json>", "JSON array of {date, origin, destination, origin_type?, destination_type?}").option("--cabin-class <c>", "economy | premium_economy | business | first", "economy").option("--adult-num <n>", "Adults (1-9)", "1").option("--child-num <n>", "Children (0-9)", "0").option("--infant-num <n>", "Infants (0-9)", "0").option("--airline <code>", "Airline 2-letter code filter").option("--transfer-number <n>", "0=any,1=direct,2=1 stop,3=2 stops", "0").option("--journey-id <json>", "Relay: the chosen offer's next_journey_ids, verbatim (round-trip/multi-city)");
6155
6242
  attachSchemaHelp(cmd, flightSearchSchema);
6156
6243
  cmd.action(async () => {
6157
6244
  const opts = cmd.optsWithGlobals();
@@ -6310,7 +6397,16 @@ import { confirm as confirm11 } from "@inquirer/prompts";
6310
6397
  function registerPayOrderCommand(parent, deps) {
6311
6398
  const cmd = parent.command("pay-order").description("Settle a created order by --order-no (triggers ticketing)").option("--api-key <key>", "API Key for authentication (X-Api-Key)").option("--order-no <id>", "Our order reference from create-order").option("--payment-method-id <id>", "Optional bound-card id to charge (pay_per_call only)").option("--payment-token-id <id>", "Optional network-token id (unionpay/visa charge path)").option(
6312
6399
  "--authorized-merchant-trans-id <id>",
6313
- "Resume a 3DS challenge: merchant trans id of an already-authorised preauth"
6400
+ "Resume an EVO-card 3DS challenge: merchant trans id of an already-authorised preauth"
6401
+ ).option(
6402
+ "--authenticate",
6403
+ "Opt-in to require cardholder 3DS on the network-token direct charge (--payment-token-id, EVO/Mastercard). When a step-up is needed the response is AUTHENTICATION_REQUIRED with three_ds_url + charge_no; resume with --authorized-charge-no. Default off = frictionless."
6404
+ ).option(
6405
+ "--return-url <url>",
6406
+ "Optional 3DS/passkey return URL for the network-token authenticate path (falls back to the platform EVO_3DS_RETURN_URL)"
6407
+ ).option(
6408
+ "--authorized-charge-no <no>",
6409
+ "Resume a network-token 3DS challenge: charge_no returned by a prior AUTHENTICATION_REQUIRED response (settled via ChargeService.resume_token)"
6314
6410
  ).option(
6315
6411
  "--evo-explicit",
6316
6412
  "[\u65B9\u6848 B/R16] Explicit opt-in to settle this pay_per_call order via the EVO bound-card fallback rail. Set ONLY when the user explicitly chose EVO; forwarded to the pay body as evo_explicit=true. Without it, a pay_per_call settlement lacking --payment-token-id is hard-gated server-side."
@@ -6340,6 +6436,11 @@ function registerPayOrderCommand(parent, deps) {
6340
6436
  if (opts.authorizedMerchantTransId !== void 0) {
6341
6437
  body.authorized_merchant_trans_id = opts.authorizedMerchantTransId;
6342
6438
  }
6439
+ if (opts.authenticate) body.authenticate = true;
6440
+ if (opts.returnUrl !== void 0) body.return_url = opts.returnUrl;
6441
+ if (opts.authorizedChargeNo !== void 0) {
6442
+ body.authorized_charge_no = opts.authorizedChargeNo;
6443
+ }
6343
6444
  if (opts.evoExplicit) body.evo_explicit = true;
6344
6445
  const result = await deps.apiClient.post(
6345
6446
  `/flight/${encodeURIComponent(orderNo)}/pay`,
@@ -6349,15 +6450,16 @@ function registerPayOrderCommand(parent, deps) {
6349
6450
  );
6350
6451
  spinner?.stop();
6351
6452
  if (!result.success) throw CliError.fromApi(result, { auth: "api-key" });
6352
- await render2(
6353
- result.data,
6354
- opts.format,
6355
- (d) => Formatter.keyValue([
6453
+ await render2(result.data, opts.format, (d) => {
6454
+ const rows = [
6356
6455
  ["Order no", String(d.order_no ?? "-")],
6357
6456
  ["Status", String(d.status ?? "-")],
6358
6457
  ["Amount", `${d.amount ?? "-"} ${d.currency ?? ""}`.trim()]
6359
- ])
6360
- );
6458
+ ];
6459
+ if (d.charge_no) rows.push(["Charge no", String(d.charge_no)]);
6460
+ if (d.three_ds_url) rows.push(["3DS URL", String(d.three_ds_url)]);
6461
+ return Formatter.keyValue(rows);
6462
+ });
6361
6463
  });
6362
6464
  }
6363
6465
 
@@ -6810,10 +6912,18 @@ verbs and pass data between them. For exact flags and field types, read the sche
6810
6912
  ## Workflow (book)
6811
6913
  1. find-airport --keyword "<place>" \u2192 resolve to IATA city/airport codes
6812
6914
  2. search --trip-type <1|2|3> --journeys '<json>'
6813
- - journeys ALWAYS carries ALL legs. For round-trip/multi-city, relay journey-id
6814
- from the previous search into the next until price_key_ready is true.
6815
- - Only offers where price_key_ready is true carry a real product_token; earlier-leg
6816
- offers carry only an identifier (product_token null).
6915
+ - journeys ALWAYS carries ALL legs, on every call including relay calls.
6916
+ - Round-trip/multi-city: keep searching until is_final_leg is true. Each round, present the
6917
+ candidates, let the USER pick one, and pass THAT candidate's next_journey_ids verbatim as
6918
+ --journey-id on the next search. Never assemble the list yourself (the top-level journey_ids
6919
+ is flattened across all candidates) and never send more ids than there are legs \u2014 it is
6920
+ rejected, because upstream would otherwise silently answer with the FIRST leg's candidates.
6921
+ - Only offers where price_key_ready is true carry a real product_token. Earlier-leg candidates
6922
+ are NOT bare flight rows: total_sale_price is already the WHOLE-TRIP total and
6923
+ paired_segment_ids names the legs upstream paired with it, so you can show the full
6924
+ itinerary and real total from the first round \u2014 they just are not bookable yet.
6925
+ - A relay search returning ZERO offers means the journey ids went stale OR that leg sold out
6926
+ (upstream reports both as an empty list). Restart from leg 1 for fresh ids.
6817
6927
  - Code<->type consistency: tag each code with its matching type \u2014 an AIRPORT code (e.g. NRT)
6818
6928
  uses type 2, a CITY code (e.g. TYO) uses type 1. Mislabeling (e.g. NRT with type 1, since NRT
6819
6929
  is an airport whose city is TYO) is rejected with code 400 "destination not exist".
@@ -6849,7 +6959,7 @@ verbs and pass data between them. For exact flags and field types, read the sche
6849
6959
  ## Data passing
6850
6960
  - product_token : search \u2192 verify \u2192 create-order (opaque; never fabricate or edit; verify's token wins)
6851
6961
  - order_no : create-order \u2192 pay-order / get-order / cancel-order
6852
- - journey_id : search \u2192 next search (relay for round-trip/multi-city)
6962
+ - next_journey_ids : chosen offer \u2192 next search's --journey-id (relay for round-trip/multi-city)
6853
6963
  - change_order_no / refund_order_no : *-apply \u2192 *-detail / *-confirm / pay-order (change)
6854
6964
 
6855
6965
  ## Invariants
@@ -7002,7 +7112,8 @@ Key notes:
7002
7112
  Workflow (typical order):
7003
7113
  1. find-airport Resolve a place name \u2192 IATA city/airport codes
7004
7114
  2. search Search flights (one-way/round-trip/multi-city). journeys carries ALL legs;
7005
- relay --journey-id for round-trip/multi-city until price_key_ready is true
7115
+ round-trip/multi-city: relay the chosen offer's next_journey_ids into
7116
+ --journey-id until is_final_leg is true
7006
7117
  3. verify Verify the fare \u2192 authoritative product_token + price_changed flag
7007
7118
  4. create-order Create the order using verify's product_token (locks the fare, no charge)
7008
7119
  5. pay-order Settle by --order-no (triggers ticketing); order \u2192 PAID