@voyant-travel/quotes 0.122.5 → 0.122.7
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.
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ReserveTripDeps, type StartCheckoutDeps, type TripSnapshot } from "@voyant-travel/trips";
|
|
1
|
+
import { type CancelTripComponentsDeps, type ReserveTripDeps, type StartCheckoutDeps, type TripSnapshot } from "@voyant-travel/trips";
|
|
2
2
|
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
3
3
|
import { type Context, Hono } from "hono";
|
|
4
4
|
import type { QuoteVersion, QuoteVersionLine } from "./schema.js";
|
|
@@ -23,6 +23,11 @@ export interface QuoteProposalRoutesOptions {
|
|
|
23
23
|
reserveTripDeps(c: Context): ReserveTripDeps;
|
|
24
24
|
/** Build the trips checkout deps for a request (payment-session wiring). */
|
|
25
25
|
startCheckoutDeps(c: Context): StartCheckoutDeps;
|
|
26
|
+
/**
|
|
27
|
+
* Build the trips cancel deps for a request (provider hold-release wiring).
|
|
28
|
+
* Used to release a reserved Trip when final CRM acceptance loses a race.
|
|
29
|
+
*/
|
|
30
|
+
cancelTripComponentsDeps(c: Context): CancelTripComponentsDeps;
|
|
26
31
|
/**
|
|
27
32
|
* Resolve the deployment's public operator profile, surfaced on the public
|
|
28
33
|
* proposal payload. Returns `null` when no profile is configured.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proposal-routes.d.ts","sourceRoot":"","sources":["../src/proposal-routes.ts"],"names":[],"mappings":"AA4BA,OAAO,EACL,KAAK,eAAe,
|
|
1
|
+
{"version":3,"file":"proposal-routes.d.ts","sourceRoot":"","sources":["../src/proposal-routes.ts"],"names":[],"mappings":"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"}
|
package/dist/proposal-routes.js
CHANGED
|
@@ -202,30 +202,78 @@ async function handleAcceptPublicProposal(c, options) {
|
|
|
202
202
|
const proposalForLock = await quotesService.getQuoteVersionProposal(db, quoteVersionId);
|
|
203
203
|
if (!proposalForLock)
|
|
204
204
|
return c.json({ error: "Proposal not found" }, 404);
|
|
205
|
+
const quoteId = proposalForLock.quote.id;
|
|
205
206
|
try {
|
|
206
|
-
|
|
207
|
+
// Phase 1 — prepare under the quote-accept lock (txn 1). Validates the
|
|
208
|
+
// proposal/snapshot and either fast-paths an accepted replay or returns a
|
|
209
|
+
// prepared snapshot ready to reserve.
|
|
210
|
+
const prepared = await db.transaction((tx) => preparePublicProposalAcceptWithQuoteLock({
|
|
207
211
|
c,
|
|
208
|
-
options,
|
|
209
212
|
db: tx,
|
|
210
|
-
quoteId
|
|
213
|
+
quoteId,
|
|
211
214
|
quoteVersionId,
|
|
212
215
|
body,
|
|
213
216
|
}));
|
|
214
|
-
if (
|
|
215
|
-
return
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
217
|
+
if (prepared.kind === "response")
|
|
218
|
+
return prepared.response;
|
|
219
|
+
if (prepared.kind === "accepted") {
|
|
220
|
+
return respondWithAcceptedProposal({
|
|
221
|
+
c,
|
|
222
|
+
options,
|
|
223
|
+
snapshot: prepared.snapshot,
|
|
224
|
+
body,
|
|
225
|
+
quoteVersionId,
|
|
226
|
+
accepted: prepared.accepted,
|
|
227
|
+
reserveWarnings: prepared.warnings,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
// Phase 2 — reserve OUTSIDE any transaction, on the durable request db.
|
|
231
|
+
// Sourced catalog adapters may create upstream supplier holds; running this
|
|
232
|
+
// outside the CRM accept transaction keeps those holds durably recorded.
|
|
233
|
+
// reserveTrip's own atomic claim serializes concurrent accepts so only one
|
|
234
|
+
// request can create holds for the same envelope.
|
|
235
|
+
const reserved = await reservePreparedPublicProposal(c, options, db, prepared.snapshot, body, quoteVersionId);
|
|
236
|
+
if (reserved.failures.length > 0) {
|
|
237
|
+
return c.json({
|
|
238
|
+
error: "Proposal could not be reserved",
|
|
239
|
+
failures: reserved.failures.map(({ code, reason }) => ({ code, reason })),
|
|
240
|
+
}, 409);
|
|
241
|
+
}
|
|
242
|
+
// Phase 3 — finalize CRM acceptance under the quote-accept lock (txn 2).
|
|
243
|
+
// If the final accept loses a race (declined/superseded/conflict), release
|
|
244
|
+
// the reservation so the supplier hold isn't orphaned.
|
|
245
|
+
let finalized;
|
|
246
|
+
try {
|
|
247
|
+
finalized = await db.transaction((tx) => finalizePublicProposalAcceptWithQuoteLock({
|
|
248
|
+
c,
|
|
249
|
+
db: tx,
|
|
250
|
+
quoteId,
|
|
251
|
+
quoteVersionId,
|
|
252
|
+
snapshot: prepared.snapshot,
|
|
253
|
+
}));
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
await releaseAcceptedProposalReservation(c, options, db, prepared.snapshot, reserved, {
|
|
257
|
+
quoteVersionId,
|
|
258
|
+
reason: "quote_accept_failed",
|
|
259
|
+
});
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
262
|
+
if (finalized.kind === "response") {
|
|
263
|
+
await releaseAcceptedProposalReservation(c, options, db, prepared.snapshot, reserved, {
|
|
264
|
+
quoteVersionId,
|
|
265
|
+
reason: "quote_accept_failed",
|
|
266
|
+
});
|
|
267
|
+
return finalized.response;
|
|
268
|
+
}
|
|
269
|
+
return respondWithAcceptedProposal({
|
|
270
|
+
c,
|
|
271
|
+
options,
|
|
272
|
+
snapshot: prepared.snapshot,
|
|
273
|
+
body,
|
|
274
|
+
quoteVersionId,
|
|
275
|
+
accepted: finalized.accepted,
|
|
276
|
+
reserveWarnings: reserved.warnings,
|
|
229
277
|
});
|
|
230
278
|
}
|
|
231
279
|
catch (error) {
|
|
@@ -238,7 +286,7 @@ async function handleAcceptPublicProposal(c, options) {
|
|
|
238
286
|
throw error;
|
|
239
287
|
}
|
|
240
288
|
}
|
|
241
|
-
async function
|
|
289
|
+
async function preparePublicProposalAcceptWithQuoteLock({ c, db, quoteId, quoteVersionId, body, }) {
|
|
242
290
|
await lockQuoteAccept(db, quoteId);
|
|
243
291
|
await quotesService.expireQuoteVersionIfPastValidUntil(db, quoteVersionId);
|
|
244
292
|
const proposal = await quotesService.getQuoteVersionProposal(db, quoteVersionId);
|
|
@@ -282,7 +330,6 @@ async function acceptPublicProposalWithQuoteLock({ c, options, db, quoteId, quot
|
|
|
282
330
|
}
|
|
283
331
|
return { kind: "accepted", accepted, snapshot, warnings: [] };
|
|
284
332
|
}
|
|
285
|
-
assertSnapshotCanUsePublicAcceptReserve(snapshot);
|
|
286
333
|
const liveTrip = await tripsService.getTrip(db, snapshot.envelopeId);
|
|
287
334
|
if (!liveTrip) {
|
|
288
335
|
return {
|
|
@@ -290,11 +337,61 @@ async function acceptPublicProposalWithQuoteLock({ c, options, db, quoteId, quot
|
|
|
290
337
|
response: c.json({ error: "Proposal Trip envelope not found" }, 409),
|
|
291
338
|
};
|
|
292
339
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
340
|
+
// Resume a crashed acceptance: if the Trip was already reserved under this
|
|
341
|
+
// proposal's reserve key (reserve succeeded, finalize never ran), the live
|
|
342
|
+
// Trip is no longer `priced`, so skip the frozen-snapshot comparison and let
|
|
343
|
+
// phase 2 replay the reservation idempotently before finalize accepts.
|
|
344
|
+
if (!isResumableProposalReservation(liveTrip.envelope, proposalReserveIdempotencyKey(quoteVersionId, body))) {
|
|
345
|
+
assertLiveTripMatchesSnapshot(liveTrip, snapshot);
|
|
346
|
+
}
|
|
347
|
+
return { kind: "prepared", snapshot };
|
|
348
|
+
}
|
|
349
|
+
async function finalizePublicProposalAcceptWithQuoteLock({ c, db, quoteId, quoteVersionId, snapshot, }) {
|
|
350
|
+
await lockQuoteAccept(db, quoteId);
|
|
351
|
+
await quotesService.expireQuoteVersionIfPastValidUntil(db, quoteVersionId);
|
|
352
|
+
const proposal = await quotesService.getQuoteVersionProposal(db, quoteVersionId);
|
|
353
|
+
if (!proposal)
|
|
354
|
+
return { kind: "response", response: c.json({ error: "Proposal not found" }, 404) };
|
|
355
|
+
if (proposal.quoteVersion.status === "draft") {
|
|
356
|
+
return { kind: "response", response: c.json({ error: "Proposal not found" }, 404) };
|
|
357
|
+
}
|
|
358
|
+
if (proposal.quoteVersion.status === "superseded") {
|
|
359
|
+
return {
|
|
360
|
+
kind: "response",
|
|
361
|
+
response: c.json({ error: "Proposal has been superseded" }, 410),
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
if (proposal.quoteVersion.status === "accepted" &&
|
|
365
|
+
proposal.quote.acceptedVersionId === proposal.quoteVersion.id) {
|
|
366
|
+
const accepted = await quotesService.acceptQuoteVersion(db, quoteVersionId, {});
|
|
367
|
+
if (!accepted) {
|
|
368
|
+
return { kind: "response", response: c.json({ error: "Proposal not found" }, 404) };
|
|
369
|
+
}
|
|
370
|
+
return { kind: "accepted", accepted };
|
|
371
|
+
}
|
|
372
|
+
if (proposal.quoteVersion.status !== "sent") {
|
|
373
|
+
return {
|
|
374
|
+
kind: "response",
|
|
375
|
+
response: c.json({ error: "Proposal can no longer be accepted" }, 409),
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
// The frozen snapshot must not have changed between prepare and finalize.
|
|
379
|
+
if (proposal.quoteVersion.tripSnapshotId !== snapshot.id) {
|
|
380
|
+
return {
|
|
381
|
+
kind: "response",
|
|
382
|
+
response: c.json({ error: "Proposal Trip snapshot changed before acceptance" }, 409),
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
assertProposalMatchesTripSnapshot(proposal, snapshot);
|
|
386
|
+
const accepted = await quotesService.acceptQuoteVersion(db, quoteVersionId, {});
|
|
387
|
+
if (!accepted)
|
|
388
|
+
return { kind: "response", response: c.json({ error: "Proposal not found" }, 404) };
|
|
389
|
+
return { kind: "accepted", accepted };
|
|
390
|
+
}
|
|
391
|
+
function reservePreparedPublicProposal(c, options, db, snapshot, body, quoteVersionId) {
|
|
392
|
+
return tripsService.reserveTrip(db, {
|
|
296
393
|
envelopeId: snapshot.envelopeId,
|
|
297
|
-
idempotencyKey:
|
|
394
|
+
idempotencyKey: proposalReserveIdempotencyKey(quoteVersionId, body),
|
|
298
395
|
refreshScope: {
|
|
299
396
|
locale: "en-US",
|
|
300
397
|
audience: "customer",
|
|
@@ -302,19 +399,66 @@ async function acceptPublicProposalWithQuoteLock({ c, options, db, quoteId, quot
|
|
|
302
399
|
currency: snapshot.currency,
|
|
303
400
|
},
|
|
304
401
|
}, options.reserveTripDeps(c));
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Deterministic reserve idempotency key for a proposal acceptance. Stable
|
|
405
|
+
* across retries with the same request body, so a crashed accept can replay the
|
|
406
|
+
* same reservation instead of creating a second supplier hold.
|
|
407
|
+
*/
|
|
408
|
+
function proposalReserveIdempotencyKey(quoteVersionId, body) {
|
|
409
|
+
return `proposal-accept-reserve:${quoteVersionId}:${body.idempotencyKey ?? "default"}`;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* A live Trip that has already been claimed/reserved under THIS proposal's
|
|
413
|
+
* reserve idempotency key is a resumable in-flight acceptance — not a "Trip
|
|
414
|
+
* changed since sent" conflict. Recognising it lets a retry replay the
|
|
415
|
+
* reservation and finalize, instead of wedging on the frozen `priced` snapshot
|
|
416
|
+
* comparison and stranding the supplier hold.
|
|
417
|
+
*/
|
|
418
|
+
function isResumableProposalReservation(envelope, reserveKey) {
|
|
419
|
+
return (envelope.reserveIdempotencyKey === reserveKey &&
|
|
420
|
+
["reserve_in_progress", "reserved", "checkout_started", "booked"].includes(envelope.status));
|
|
421
|
+
}
|
|
422
|
+
async function releaseAcceptedProposalReservation(c, options, db, snapshot, reserved, release) {
|
|
423
|
+
// An idempotent replay returns the existing holds without creating new ones,
|
|
424
|
+
// so it must never trigger a cancellation of components owned by the request
|
|
425
|
+
// that actually reserved them.
|
|
426
|
+
if (reserved.warnings.includes("idempotent_replay"))
|
|
427
|
+
return;
|
|
428
|
+
const reservedComponentIds = reserved.reserved.map((component) => component.componentId);
|
|
429
|
+
if (reservedComponentIds.length === 0)
|
|
430
|
+
return;
|
|
431
|
+
try {
|
|
432
|
+
await tripsService.cancelComponents(db, {
|
|
433
|
+
envelopeId: snapshot.envelopeId,
|
|
434
|
+
componentIds: reservedComponentIds,
|
|
435
|
+
reason: release.reason,
|
|
436
|
+
idempotencyKey: `proposal-accept-release:${release.quoteVersionId}:${release.reason}`,
|
|
437
|
+
request: {
|
|
438
|
+
initiatedBy: "public-proposal-accept",
|
|
439
|
+
quoteVersionId: release.quoteVersionId,
|
|
440
|
+
},
|
|
441
|
+
}, options.cancelTripComponentsDeps(c));
|
|
313
442
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
443
|
+
catch (error) {
|
|
444
|
+
console.warn("[proposal] failed to release reservation after proposal accept conflict:", error);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
async function respondWithAcceptedProposal({ c, options, snapshot, body, quoteVersionId, accepted, reserveWarnings, }) {
|
|
448
|
+
const checkout = await startAcceptedProposalCheckout(c, options, snapshot, body, quoteVersionId);
|
|
449
|
+
const checkoutWarnings = checkout
|
|
450
|
+
? checkout.failures.map((failure) => failure.reason)
|
|
451
|
+
: ["checkout_start_failed"];
|
|
452
|
+
return c.json({
|
|
453
|
+
data: {
|
|
454
|
+
status: "accepted",
|
|
455
|
+
checkoutUrl: checkout?.target.checkoutUrl ?? null,
|
|
456
|
+
paymentSessionId: checkout?.target.paymentSessionId ?? null,
|
|
457
|
+
currency: checkout?.target.currency ?? accepted.quoteVersion.currency,
|
|
458
|
+
totalAmountCents: checkout?.target.totalAmountCents ?? accepted.quoteVersion.totalAmountCents,
|
|
459
|
+
warnings: [...reserveWarnings, ...(checkout?.warnings ?? []), ...checkoutWarnings],
|
|
460
|
+
},
|
|
461
|
+
});
|
|
318
462
|
}
|
|
319
463
|
function lockQuoteAccept(db, quoteId) {
|
|
320
464
|
return db.execute(
|
|
@@ -324,22 +468,6 @@ function lockQuoteAccept(db, quoteId) {
|
|
|
324
468
|
function quoteAcceptLockKey(quoteId) {
|
|
325
469
|
return `quote-accept:${quoteId}`;
|
|
326
470
|
}
|
|
327
|
-
function assertSnapshotCanUsePublicAcceptReserve(snapshot) {
|
|
328
|
-
const sourcedCatalogComponent = snapshot.frozenComponents.find(isSourcedCatalogSnapshotComponent);
|
|
329
|
-
if (!sourcedCatalogComponent)
|
|
330
|
-
return;
|
|
331
|
-
// reserveTrip runs under the Quote accept transaction. Owned catalog holds
|
|
332
|
-
// and manual placeholders are DB-local, but sourced catalog adapters can
|
|
333
|
-
// create upstream holds before local release records commit.
|
|
334
|
-
throw new QuoteVersionConflictError("Sourced catalog components cannot be accepted from public proposals yet");
|
|
335
|
-
}
|
|
336
|
-
function isSourcedCatalogSnapshotComponent(component) {
|
|
337
|
-
return Boolean(component.kind === "catalog_booking" &&
|
|
338
|
-
component.entityModule &&
|
|
339
|
-
component.entityId &&
|
|
340
|
-
component.sourceKind &&
|
|
341
|
-
component.sourceKind !== "owned");
|
|
342
|
-
}
|
|
343
471
|
async function startAcceptedProposalCheckout(c, options, snapshot, body, quoteVersionId) {
|
|
344
472
|
try {
|
|
345
473
|
return await tripsService.startCheckout(options.resolveDb(c), {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voyant-travel/quotes",
|
|
3
|
-
"version": "0.122.
|
|
3
|
+
"version": "0.122.7",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"@voyant-travel/quotes-contracts": "^0.108.0",
|
|
44
44
|
"@voyant-travel/db": "^0.108.4",
|
|
45
45
|
"@voyant-travel/hono": "^0.112.2",
|
|
46
|
-
"@voyant-travel/trips": "^0.
|
|
46
|
+
"@voyant-travel/trips": "^0.121.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"drizzle-kit": "^0.31.10",
|