@voyant-travel/finance 0.191.0 → 0.192.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/card-payment.d.ts +2 -0
- package/dist/card-payment.js +59 -43
- package/dist/checkout-routes.d.ts +4 -0
- package/dist/checkout-routes.js +6 -0
- package/dist/checkout-service.js +24 -14
- package/dist/checkout-validation.d.ts +10 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/payment-adapter-events.d.ts +96 -2
- package/dist/payment-adapter-events.js +150 -24
- package/dist/payment-adapter-session-guard.d.ts +18 -0
- package/dist/payment-adapter-session-guard.js +64 -0
- package/dist/payment-adapter-status.d.ts +61 -0
- package/dist/payment-adapter-status.js +49 -0
- package/dist/payment-link.js +29 -3
- package/dist/routes-booking-billing.d.ts +10 -0
- package/dist/routes-invoice-core.d.ts +5 -0
- package/dist/routes-invoice-schemas.d.ts +1 -0
- package/dist/routes-invoice-schemas.js +1 -0
- package/dist/routes-payment-processing.d.ts +18 -0
- package/dist/routes-payment-processing.js +1 -0
- package/dist/routes-public.d.ts +34 -0
- package/dist/routes-public.js +16 -1
- package/dist/routes.d.ts +33 -0
- package/dist/runtime.js +11 -0
- package/dist/schema/payment-sessions.d.ts +17 -0
- package/dist/schema/payment-sessions.js +2 -0
- package/dist/service-booking-billing.d.ts +3 -0
- package/dist/service-booking-guarantees.d.ts +1 -0
- package/dist/service-booking-payment-schedules.d.ts +2 -0
- package/dist/service-payment-processing.d.ts +10 -1
- package/dist/service-payment-session-completion.d.ts +10 -1
- package/dist/service-payment-session-completion.js +104 -24
- package/dist/service-payment-sessions.d.ts +8 -0
- package/dist/service-payment-sessions.js +27 -8
- package/dist/service-public.d.ts +4 -0
- package/dist/service-public.js +1 -0
- package/dist/service.d.ts +13 -1
- package/migrations/20260722053500_payment_session_provider_connection.sql +2 -0
- package/migrations/meta/_journal.json +8 -1
- package/package.json +13 -13
package/dist/card-payment.d.ts
CHANGED
|
@@ -41,6 +41,8 @@ export interface CardPaymentStartArgs {
|
|
|
41
41
|
billing: CardPaymentBilling;
|
|
42
42
|
description?: string;
|
|
43
43
|
returnUrl?: string;
|
|
44
|
+
cancelUrl?: string;
|
|
45
|
+
shipping?: Record<string, unknown>;
|
|
44
46
|
metadata?: Record<string, unknown>;
|
|
45
47
|
}
|
|
46
48
|
/** Result of a card-payment start: the hosted-payment URL to redirect to. */
|
package/dist/card-payment.js
CHANGED
|
@@ -1,66 +1,82 @@
|
|
|
1
|
-
import { eq } from "drizzle-orm";
|
|
1
|
+
import { and, eq, isNull, sql } from "drizzle-orm";
|
|
2
|
+
import { applyPaymentAdapterInitiationResult } from "./payment-adapter-events.js";
|
|
2
3
|
import { paymentSessions } from "./schema/payment-sessions.js";
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
const PAYMENT_ADAPTER_INITIATION_CLAIM_KEY = "paymentAdapterInitiationClaim";
|
|
5
|
+
const PAYMENT_ADAPTER_INITIATION_STATE_KEY = "paymentAdapterInitiationState";
|
|
5
6
|
/**
|
|
6
7
|
* Start a payment through a selected adapter without requiring an HTTP
|
|
7
8
|
* framework context. Package runtimes use this function; the Hono-oriented
|
|
8
9
|
* starter below remains the checkout-surface convenience wrapper.
|
|
9
10
|
*/
|
|
10
11
|
export async function startPaymentAdapterCardPayment(adapter, args, execution) {
|
|
11
|
-
const [
|
|
12
|
+
const [initialSession] = await args.db
|
|
12
13
|
.select()
|
|
13
14
|
.from(paymentSessions)
|
|
14
15
|
.where(eq(paymentSessions.id, args.sessionId))
|
|
15
16
|
.limit(1);
|
|
16
|
-
if (!
|
|
17
|
+
if (!initialSession)
|
|
17
18
|
return null;
|
|
18
|
-
const idempotencyKey =
|
|
19
|
+
const idempotencyKey = initialSession.idempotencyKey ?? execution.idempotencyKey ?? `payment:${initialSession.id}`;
|
|
20
|
+
const [session] = await args.db
|
|
21
|
+
.update(paymentSessions)
|
|
22
|
+
.set({
|
|
23
|
+
status: "processing",
|
|
24
|
+
idempotencyKey,
|
|
25
|
+
metadata: sql `coalesce(${paymentSessions.metadata}, '{}'::jsonb) || ${JSON.stringify({
|
|
26
|
+
[PAYMENT_ADAPTER_INITIATION_CLAIM_KEY]: idempotencyKey,
|
|
27
|
+
[PAYMENT_ADAPTER_INITIATION_STATE_KEY]: "in_flight",
|
|
28
|
+
})}::jsonb`,
|
|
29
|
+
updatedAt: new Date(),
|
|
30
|
+
})
|
|
31
|
+
.where(and(eq(paymentSessions.id, args.sessionId), eq(paymentSessions.status, "pending"), isNull(paymentSessions.providerConnectionId), isNull(paymentSessions.providerSessionId), isNull(paymentSessions.providerPaymentId)))
|
|
32
|
+
.returning();
|
|
33
|
+
if (!session) {
|
|
34
|
+
const [continuation] = await args.db
|
|
35
|
+
.select({ redirectUrl: paymentSessions.redirectUrl })
|
|
36
|
+
.from(paymentSessions)
|
|
37
|
+
.where(eq(paymentSessions.id, args.sessionId))
|
|
38
|
+
.limit(1);
|
|
39
|
+
return continuation ? { redirectUrl: continuation.redirectUrl } : null;
|
|
40
|
+
}
|
|
19
41
|
const metadata = {
|
|
20
42
|
...(args.metadata ?? {}),
|
|
21
43
|
billing: args.billing,
|
|
22
44
|
...(execution.notifyUrl ? { notifyUrl: execution.notifyUrl } : {}),
|
|
23
45
|
};
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
phone: args.billing.phone ?? null,
|
|
33
|
-
firstName: args.billing.firstName,
|
|
34
|
-
lastName: args.billing.lastName ?? null,
|
|
35
|
-
},
|
|
36
|
-
metadata,
|
|
37
|
-
});
|
|
38
|
-
const providerData = {
|
|
39
|
-
providerSessionId: result.processorSessionId ?? undefined,
|
|
40
|
-
providerPaymentId: result.processorPaymentId ?? undefined,
|
|
41
|
-
providerPayload: result.raw === undefined ? undefined : { initiation: result.raw },
|
|
42
|
-
metadata: { paymentAdapterInitiationIdempotencyKey: result.idempotencyKey },
|
|
43
|
-
};
|
|
44
|
-
if (result.nextState === "paid" || result.nextState === "authorized") {
|
|
45
|
-
await financePaymentSessionService.updatePaymentSession(args.db, session.id, {
|
|
46
|
-
provider: adapter.id,
|
|
47
|
-
redirectUrl: result.checkout?.url ?? undefined,
|
|
46
|
+
let result;
|
|
47
|
+
try {
|
|
48
|
+
result = await adapter.initiate(execution.context, {
|
|
49
|
+
paymentSessionId: session.id,
|
|
50
|
+
money: { amountMinor: session.amountCents, currency: session.currency },
|
|
51
|
+
description: args.description ?? session.notes ?? undefined,
|
|
52
|
+
returnUrl: args.returnUrl ?? session.returnUrl ?? undefined,
|
|
53
|
+
cancelUrl: args.cancelUrl ?? session.cancelUrl ?? undefined,
|
|
48
54
|
idempotencyKey,
|
|
55
|
+
customer: {
|
|
56
|
+
email: args.billing.email,
|
|
57
|
+
phone: args.billing.phone ?? null,
|
|
58
|
+
firstName: args.billing.firstName,
|
|
59
|
+
lastName: args.billing.lastName ?? null,
|
|
60
|
+
},
|
|
61
|
+
shipping: args.shipping,
|
|
62
|
+
metadata,
|
|
49
63
|
});
|
|
50
|
-
await
|
|
51
|
-
status: result.nextState,
|
|
52
|
-
captureMode: "automatic",
|
|
53
|
-
...providerData,
|
|
54
|
-
}, execution.runtime);
|
|
64
|
+
await applyPaymentAdapterInitiationResult(args.db, session.id, adapter.id, result, { idempotencyKey, claimedAt: session.updatedAt }, execution.runtime);
|
|
55
65
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
catch (error) {
|
|
67
|
+
const retrySafe = adapter.capabilities.idempotencyKeys && adapter.capabilities.retrySafeInitiation;
|
|
68
|
+
await args.db
|
|
69
|
+
.update(paymentSessions)
|
|
70
|
+
.set({
|
|
71
|
+
status: retrySafe ? "pending" : "processing",
|
|
72
|
+
metadata: sql `coalesce(${paymentSessions.metadata}, '{}'::jsonb) || ${JSON.stringify({
|
|
73
|
+
[PAYMENT_ADAPTER_INITIATION_CLAIM_KEY]: retrySafe ? null : idempotencyKey,
|
|
74
|
+
[PAYMENT_ADAPTER_INITIATION_STATE_KEY]: retrySafe ? "retryable" : "uncertain",
|
|
75
|
+
})}::jsonb`,
|
|
76
|
+
updatedAt: new Date(),
|
|
77
|
+
})
|
|
78
|
+
.where(and(eq(paymentSessions.id, session.id), eq(paymentSessions.status, "processing"), eq(paymentSessions.idempotencyKey, idempotencyKey), eq(paymentSessions.updatedAt, session.updatedAt), isNull(paymentSessions.providerConnectionId), isNull(paymentSessions.providerSessionId), isNull(paymentSessions.providerPaymentId), sql `${paymentSessions.metadata}->>${PAYMENT_ADAPTER_INITIATION_CLAIM_KEY} = ${idempotencyKey}`));
|
|
79
|
+
throw error;
|
|
64
80
|
}
|
|
65
81
|
return { redirectUrl: result.checkout?.url ?? null };
|
|
66
82
|
}
|
|
@@ -281,6 +281,7 @@ export declare function createCheckoutRoutes(options?: CheckoutRoutesOptions): i
|
|
|
281
281
|
targetType: "other" | "booking" | "order" | "invoice" | "booking_payment_schedule" | "booking_guarantee" | "flight_order";
|
|
282
282
|
targetId: string | null;
|
|
283
283
|
paymentId: string | null;
|
|
284
|
+
providerConnectionId: string | null;
|
|
284
285
|
providerSessionId: string | null;
|
|
285
286
|
providerPaymentId: string | null;
|
|
286
287
|
externalReference: string | null;
|
|
@@ -520,6 +521,7 @@ export declare function createCheckoutRoutes(options?: CheckoutRoutesOptions): i
|
|
|
520
521
|
targetType: "other" | "booking" | "order" | "invoice" | "booking_payment_schedule" | "booking_guarantee" | "flight_order";
|
|
521
522
|
targetId: string | null;
|
|
522
523
|
paymentId: string | null;
|
|
524
|
+
providerConnectionId: string | null;
|
|
523
525
|
providerSessionId: string | null;
|
|
524
526
|
providerPaymentId: string | null;
|
|
525
527
|
externalReference: string | null;
|
|
@@ -853,6 +855,7 @@ export declare function createCheckoutAdminRoutes(options?: CheckoutRoutesOption
|
|
|
853
855
|
targetType: "other" | "booking" | "order" | "invoice" | "booking_payment_schedule" | "booking_guarantee" | "flight_order";
|
|
854
856
|
targetId: string | null;
|
|
855
857
|
paymentId: string | null;
|
|
858
|
+
providerConnectionId: string | null;
|
|
856
859
|
providerSessionId: string | null;
|
|
857
860
|
providerPaymentId: string | null;
|
|
858
861
|
externalReference: string | null;
|
|
@@ -1092,6 +1095,7 @@ export declare function createCheckoutAdminRoutes(options?: CheckoutRoutesOption
|
|
|
1092
1095
|
targetType: "other" | "booking" | "order" | "invoice" | "booking_payment_schedule" | "booking_guarantee" | "flight_order";
|
|
1093
1096
|
targetId: string | null;
|
|
1094
1097
|
paymentId: string | null;
|
|
1098
|
+
providerConnectionId: string | null;
|
|
1095
1099
|
providerSessionId: string | null;
|
|
1096
1100
|
providerPaymentId: string | null;
|
|
1097
1101
|
externalReference: string | null;
|
package/dist/checkout-routes.js
CHANGED
|
@@ -121,6 +121,12 @@ function resolveCheckoutRouteRuntime(bindings, options, container) {
|
|
|
121
121
|
return buildCheckoutRouteRuntime(bindings, options);
|
|
122
122
|
}
|
|
123
123
|
function assertCheckoutRuntimeSupportsCollection(runtime, input) {
|
|
124
|
+
if (input.method === "card" && input.startProvider && !input.startProvider.provider) {
|
|
125
|
+
if (!runtime.selectedPaymentStarter) {
|
|
126
|
+
throw new CheckoutRouteRuntimeNotConfiguredError();
|
|
127
|
+
}
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
124
130
|
if (input.method === "card" &&
|
|
125
131
|
!runtime.selectedPaymentStarter &&
|
|
126
132
|
Object.keys(runtime.paymentStarters).length === 0) {
|
package/dist/checkout-service.js
CHANGED
|
@@ -190,6 +190,27 @@ async function createCollectionInvoice(db, context, plan, notes) {
|
|
|
190
190
|
return invoice;
|
|
191
191
|
}
|
|
192
192
|
export async function initiateCheckoutCollection(db, bookingId, input, options = {}, runtime = {}) {
|
|
193
|
+
let providerStarter = null;
|
|
194
|
+
if (input.startProvider) {
|
|
195
|
+
if (input.method !== "card") {
|
|
196
|
+
throw new Error("Provider start is only available for card collections");
|
|
197
|
+
}
|
|
198
|
+
// Resolve deployment-owned processor readiness before any checkout read
|
|
199
|
+
// that may materialize a default payment plan. The selected PaymentAdapter
|
|
200
|
+
// wins over legacy provider hints; keyed starters remain only for explicit
|
|
201
|
+
// self-hosted provider requests. A missing adapter is a zero-mutation
|
|
202
|
+
// failure.
|
|
203
|
+
const requestedProvider = input.startProvider.provider;
|
|
204
|
+
providerStarter =
|
|
205
|
+
runtime.selectedPaymentStarter ??
|
|
206
|
+
(requestedProvider ? runtime.paymentStarters?.[requestedProvider] : undefined) ??
|
|
207
|
+
null;
|
|
208
|
+
if (!providerStarter) {
|
|
209
|
+
throw new Error(requestedProvider
|
|
210
|
+
? `Payment provider "${requestedProvider}" is not configured`
|
|
211
|
+
: "No payment adapter is selected for card collection");
|
|
212
|
+
}
|
|
213
|
+
}
|
|
193
214
|
const context = await loadBookingContext(db, bookingId);
|
|
194
215
|
if (!context)
|
|
195
216
|
return null;
|
|
@@ -250,24 +271,13 @@ export async function initiateCheckoutCollection(db, bookingId, input, options =
|
|
|
250
271
|
}
|
|
251
272
|
}
|
|
252
273
|
if (input.startProvider) {
|
|
253
|
-
if (input.method !== "card") {
|
|
254
|
-
throw new Error("Provider start is only available for card collections");
|
|
255
|
-
}
|
|
256
274
|
if (!paymentSession) {
|
|
257
275
|
throw new Error("No payment session available for provider start");
|
|
258
276
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
// Keyed starters remain as a compatibility path for self-hosted plugins.
|
|
262
|
-
const requestedProvider = input.startProvider.provider;
|
|
263
|
-
const starter = runtime.selectedPaymentStarter ??
|
|
264
|
-
(requestedProvider ? runtime.paymentStarters?.[requestedProvider] : undefined);
|
|
265
|
-
if (!starter) {
|
|
266
|
-
throw new Error(requestedProvider
|
|
267
|
-
? `Payment provider "${requestedProvider}" is not configured`
|
|
268
|
-
: "No payment adapter is selected for card collection");
|
|
277
|
+
if (!providerStarter) {
|
|
278
|
+
throw new Error("No payment adapter is selected for card collection");
|
|
269
279
|
}
|
|
270
|
-
providerStart = await
|
|
280
|
+
providerStart = await providerStarter({
|
|
271
281
|
db,
|
|
272
282
|
bookingId,
|
|
273
283
|
plan,
|
|
@@ -249,6 +249,7 @@ export declare const initiateCheckoutCollectionSchema: z.ZodObject<{
|
|
|
249
249
|
idempotencyKey: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
250
250
|
}, z.core.$strip>>;
|
|
251
251
|
provider: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
252
|
+
providerConnectionId: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
252
253
|
paymentMethod: z.ZodNullable<z.ZodOptional<z.ZodEnum<{
|
|
253
254
|
bank_transfer: "bank_transfer";
|
|
254
255
|
credit_card: "credit_card";
|
|
@@ -322,6 +323,7 @@ export declare const initiateCheckoutCollectionSchema: z.ZodObject<{
|
|
|
322
323
|
idempotencyKey: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
323
324
|
}, z.core.$strip>>;
|
|
324
325
|
provider: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
326
|
+
providerConnectionId: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
325
327
|
paymentMethod: z.ZodNullable<z.ZodOptional<z.ZodEnum<{
|
|
326
328
|
bank_transfer: "bank_transfer";
|
|
327
329
|
credit_card: "credit_card";
|
|
@@ -501,6 +503,7 @@ export declare const bootstrapCheckoutCollectionSchema: z.ZodObject<{
|
|
|
501
503
|
idempotencyKey: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
502
504
|
}, z.core.$strip>>;
|
|
503
505
|
provider: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
506
|
+
providerConnectionId: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
504
507
|
paymentMethod: z.ZodNullable<z.ZodOptional<z.ZodEnum<{
|
|
505
508
|
bank_transfer: "bank_transfer";
|
|
506
509
|
credit_card: "credit_card";
|
|
@@ -574,6 +577,7 @@ export declare const bootstrapCheckoutCollectionSchema: z.ZodObject<{
|
|
|
574
577
|
idempotencyKey: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
575
578
|
}, z.core.$strip>>;
|
|
576
579
|
provider: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
580
|
+
providerConnectionId: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
577
581
|
paymentMethod: z.ZodNullable<z.ZodOptional<z.ZodEnum<{
|
|
578
582
|
bank_transfer: "bank_transfer";
|
|
579
583
|
credit_card: "credit_card";
|
|
@@ -954,6 +958,7 @@ export declare const initiatedCheckoutCollectionSchema: z.ZodObject<{
|
|
|
954
958
|
expired: "expired";
|
|
955
959
|
}>;
|
|
956
960
|
provider: z.ZodNullable<z.ZodString>;
|
|
961
|
+
providerConnectionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
957
962
|
providerSessionId: z.ZodNullable<z.ZodString>;
|
|
958
963
|
providerPaymentId: z.ZodNullable<z.ZodString>;
|
|
959
964
|
externalReference: z.ZodNullable<z.ZodString>;
|
|
@@ -983,6 +988,7 @@ export declare const initiatedCheckoutCollectionSchema: z.ZodObject<{
|
|
|
983
988
|
notes: z.ZodNullable<z.ZodString>;
|
|
984
989
|
}, z.core.$strip>, z.ZodTransform<{
|
|
985
990
|
legacyOrderId: string | null;
|
|
991
|
+
providerConnectionId: string | null;
|
|
986
992
|
target: {
|
|
987
993
|
type: "booking";
|
|
988
994
|
bookingId: string;
|
|
@@ -1113,6 +1119,7 @@ export declare const initiatedCheckoutCollectionSchema: z.ZodObject<{
|
|
|
1113
1119
|
idempotencyKey?: string | null | undefined;
|
|
1114
1120
|
} | null | undefined;
|
|
1115
1121
|
legacyOrderId?: string | null | undefined;
|
|
1122
|
+
providerConnectionId?: string | null | undefined;
|
|
1116
1123
|
}>>>;
|
|
1117
1124
|
invoiceNotification: z.ZodNullable<z.ZodObject<{
|
|
1118
1125
|
id: z.ZodString;
|
|
@@ -1331,6 +1338,7 @@ export declare const bootstrappedCheckoutCollectionSchema: z.ZodObject<{
|
|
|
1331
1338
|
expired: "expired";
|
|
1332
1339
|
}>;
|
|
1333
1340
|
provider: z.ZodNullable<z.ZodString>;
|
|
1341
|
+
providerConnectionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
1334
1342
|
providerSessionId: z.ZodNullable<z.ZodString>;
|
|
1335
1343
|
providerPaymentId: z.ZodNullable<z.ZodString>;
|
|
1336
1344
|
externalReference: z.ZodNullable<z.ZodString>;
|
|
@@ -1360,6 +1368,7 @@ export declare const bootstrappedCheckoutCollectionSchema: z.ZodObject<{
|
|
|
1360
1368
|
notes: z.ZodNullable<z.ZodString>;
|
|
1361
1369
|
}, z.core.$strip>, z.ZodTransform<{
|
|
1362
1370
|
legacyOrderId: string | null;
|
|
1371
|
+
providerConnectionId: string | null;
|
|
1363
1372
|
target: {
|
|
1364
1373
|
type: "booking";
|
|
1365
1374
|
bookingId: string;
|
|
@@ -1490,6 +1499,7 @@ export declare const bootstrappedCheckoutCollectionSchema: z.ZodObject<{
|
|
|
1490
1499
|
idempotencyKey?: string | null | undefined;
|
|
1491
1500
|
} | null | undefined;
|
|
1492
1501
|
legacyOrderId?: string | null | undefined;
|
|
1502
|
+
providerConnectionId?: string | null | undefined;
|
|
1493
1503
|
}>>>;
|
|
1494
1504
|
invoiceNotification: z.ZodNullable<z.ZodObject<{
|
|
1495
1505
|
id: z.ZodString;
|
package/dist/index.d.ts
CHANGED
|
@@ -33,7 +33,8 @@ export { createPaymentAdapterCardPaymentStarter, startPaymentAdapterCardPayment,
|
|
|
33
33
|
export { type DocumentDownloadEnvelope, type DocumentDownloadResolution, type DocumentDownloadResolver, resolveStoredDocumentDownload, type StoredDocumentReference, } from "./document-download.js";
|
|
34
34
|
export { createInvoiceFxApiExtension, createInvoiceFxRoutes, createVoyantDataFxExchangeRateResolver, type InvoiceExchangeRateResolution, type InvoiceFxContext, type InvoiceFxOptions, type InvoiceFxRouteOptions, type InvoiceFxSettings, mountInvoiceFxRoutes, type ResolvedInvoiceFxSettings, type ResolveInvoiceExchangeRate, type ResolveInvoiceExchangeRateInput, type ResolveInvoiceFxSettings, resolveInvoiceFxContext, resolveInvoiceFxSettingsOrDefault, type UpdateInvoiceFxSettings, type VoyantDataFxResolverOptions, } from "./invoice-fx.js";
|
|
35
35
|
export { type CreateOrderPaymentSessionsOptions, createOrderPaymentSessions, type EnsureOrderSessionParams, type OrderPaymentSessionSummary, type OrderPaymentSessions, type OrderPaymentSessionTargetType, type StartOrderPaymentProvider, } from "./order-payment-sessions.js";
|
|
36
|
-
export { applyPaymentAdapterCallbackEvent } from "./payment-adapter-events.js";
|
|
36
|
+
export { applyPaymentAdapterCallbackEvent, applyPaymentAdapterInitiationResult, applyPaymentAdapterStatusResult, } from "./payment-adapter-events.js";
|
|
37
|
+
export { type PaymentAdapterStatusRefreshExecution, refreshPaymentAdapterStatus, } from "./payment-adapter-status.js";
|
|
37
38
|
export type { ComputedScheduleEntry, ComputeScheduleInput, DepositKind, DepositRule, PaymentPolicy, PaymentPolicyCascadeLayers, PaymentPolicySource, PaymentScheduleEntryType, ResolvedPaymentPolicy, } from "./payment-policy.js";
|
|
38
39
|
export { computePaymentSchedule, isPaymentPolicyEmpty, noDepositPolicy, normalizePaymentPolicy, policyShouldRequireFullPayment, resolveEffectivePaymentPolicy, } from "./payment-policy.js";
|
|
39
40
|
export { createPaymentPolicyCascade, type PaymentPolicyCascade, type PaymentPolicyCascadeOptions, type PaymentPolicyCascadeReaders, readPolicySourceFromInternalNotes, stampPolicySourceOnBooking, } from "./payment-policy-cascade.js";
|
package/dist/index.js
CHANGED
|
@@ -98,7 +98,8 @@ export { createPaymentAdapterCardPaymentStarter, startPaymentAdapterCardPayment,
|
|
|
98
98
|
export { resolveStoredDocumentDownload, } from "./document-download.js";
|
|
99
99
|
export { createInvoiceFxApiExtension, createInvoiceFxRoutes, createVoyantDataFxExchangeRateResolver, mountInvoiceFxRoutes, resolveInvoiceFxContext, resolveInvoiceFxSettingsOrDefault, } from "./invoice-fx.js";
|
|
100
100
|
export { createOrderPaymentSessions, } from "./order-payment-sessions.js";
|
|
101
|
-
export { applyPaymentAdapterCallbackEvent } from "./payment-adapter-events.js";
|
|
101
|
+
export { applyPaymentAdapterCallbackEvent, applyPaymentAdapterInitiationResult, applyPaymentAdapterStatusResult, } from "./payment-adapter-events.js";
|
|
102
|
+
export { refreshPaymentAdapterStatus, } from "./payment-adapter-status.js";
|
|
102
103
|
export { computePaymentSchedule, isPaymentPolicyEmpty, noDepositPolicy, normalizePaymentPolicy, policyShouldRequireFullPayment, resolveEffectivePaymentPolicy, } from "./payment-policy.js";
|
|
103
104
|
export { createPaymentPolicyCascade, readPolicySourceFromInternalNotes, stampPolicySourceOnBooking, } from "./payment-policy-cascade.js";
|
|
104
105
|
export { createBookingScheduleAdminRoutes, createBookingScheduleApiExtension, createBookingScheduleVoyantRuntime, createPaymentPolicyPublicRoutes, generatePaymentScheduleForBooking, } from "./payment-schedule/routes.js";
|
|
@@ -1,6 +1,54 @@
|
|
|
1
|
-
import type { PaymentCallbackEvent } from "@voyant-travel/payments";
|
|
1
|
+
import type { PaymentCallbackEvent, PaymentInitiationResult, PaymentStatusResult } from "@voyant-travel/payments";
|
|
2
2
|
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
3
|
-
import type
|
|
3
|
+
import { type FinanceServiceRuntime } from "./service-shared.js";
|
|
4
|
+
export declare function applyPaymentAdapterInitiationResult(db: PostgresJsDatabase, paymentSessionId: string, adapterId: string, result: PaymentInitiationResult, claim: {
|
|
5
|
+
idempotencyKey: string;
|
|
6
|
+
claimedAt: Date;
|
|
7
|
+
}, runtime?: FinanceServiceRuntime): Promise<{
|
|
8
|
+
id: string;
|
|
9
|
+
targetType: "other" | "booking" | "order" | "invoice" | "booking_payment_schedule" | "booking_guarantee" | "flight_order";
|
|
10
|
+
targetId: string | null;
|
|
11
|
+
bookingId: string | null;
|
|
12
|
+
orderId: string | null;
|
|
13
|
+
invoiceId: string | null;
|
|
14
|
+
bookingPaymentScheduleId: string | null;
|
|
15
|
+
bookingGuaranteeId: string | null;
|
|
16
|
+
paymentInstrumentId: string | null;
|
|
17
|
+
paymentAuthorizationId: string | null;
|
|
18
|
+
paymentCaptureId: string | null;
|
|
19
|
+
paymentId: string | null;
|
|
20
|
+
status: "paid" | "pending" | "failed" | "requires_redirect" | "processing" | "authorized" | "cancelled" | "expired";
|
|
21
|
+
provider: string | null;
|
|
22
|
+
providerConnectionId: string | null;
|
|
23
|
+
providerSessionId: string | null;
|
|
24
|
+
providerPaymentId: string | null;
|
|
25
|
+
externalReference: string | null;
|
|
26
|
+
idempotencyKey: string | null;
|
|
27
|
+
clientReference: string | null;
|
|
28
|
+
currency: string;
|
|
29
|
+
amountCents: number;
|
|
30
|
+
paymentMethod: "bank_transfer" | "credit_card" | "debit_card" | "cash" | "cheque" | "wallet" | "direct_bill" | "travel_credit" | "other" | null;
|
|
31
|
+
payerPersonId: string | null;
|
|
32
|
+
payerOrganizationId: string | null;
|
|
33
|
+
payerEmail: string | null;
|
|
34
|
+
payerName: string | null;
|
|
35
|
+
redirectUrl: string | null;
|
|
36
|
+
returnUrl: string | null;
|
|
37
|
+
cancelUrl: string | null;
|
|
38
|
+
callbackUrl: string | null;
|
|
39
|
+
expiresAt: Date | null;
|
|
40
|
+
completedAt: Date | null;
|
|
41
|
+
failedAt: Date | null;
|
|
42
|
+
cancelledAt: Date | null;
|
|
43
|
+
expiredAt: Date | null;
|
|
44
|
+
failureCode: string | null;
|
|
45
|
+
failureMessage: string | null;
|
|
46
|
+
notes: string | null;
|
|
47
|
+
providerPayload: Record<string, unknown> | null;
|
|
48
|
+
metadata: Record<string, unknown> | null;
|
|
49
|
+
createdAt: Date;
|
|
50
|
+
updatedAt: Date;
|
|
51
|
+
} | null>;
|
|
4
52
|
export declare function applyPaymentAdapterCallbackEvent(db: PostgresJsDatabase, event: PaymentCallbackEvent, runtime?: FinanceServiceRuntime): Promise<{
|
|
5
53
|
id: string;
|
|
6
54
|
targetType: "other" | "booking" | "order" | "invoice" | "booking_payment_schedule" | "booking_guarantee" | "flight_order";
|
|
@@ -16,6 +64,52 @@ export declare function applyPaymentAdapterCallbackEvent(db: PostgresJsDatabase,
|
|
|
16
64
|
paymentId: string | null;
|
|
17
65
|
status: "paid" | "pending" | "failed" | "requires_redirect" | "processing" | "authorized" | "cancelled" | "expired";
|
|
18
66
|
provider: string | null;
|
|
67
|
+
providerConnectionId: string | null;
|
|
68
|
+
providerSessionId: string | null;
|
|
69
|
+
providerPaymentId: string | null;
|
|
70
|
+
externalReference: string | null;
|
|
71
|
+
idempotencyKey: string | null;
|
|
72
|
+
clientReference: string | null;
|
|
73
|
+
currency: string;
|
|
74
|
+
amountCents: number;
|
|
75
|
+
paymentMethod: "bank_transfer" | "credit_card" | "debit_card" | "cash" | "cheque" | "wallet" | "direct_bill" | "travel_credit" | "other" | null;
|
|
76
|
+
payerPersonId: string | null;
|
|
77
|
+
payerOrganizationId: string | null;
|
|
78
|
+
payerEmail: string | null;
|
|
79
|
+
payerName: string | null;
|
|
80
|
+
redirectUrl: string | null;
|
|
81
|
+
returnUrl: string | null;
|
|
82
|
+
cancelUrl: string | null;
|
|
83
|
+
callbackUrl: string | null;
|
|
84
|
+
expiresAt: Date | null;
|
|
85
|
+
completedAt: Date | null;
|
|
86
|
+
failedAt: Date | null;
|
|
87
|
+
cancelledAt: Date | null;
|
|
88
|
+
expiredAt: Date | null;
|
|
89
|
+
failureCode: string | null;
|
|
90
|
+
failureMessage: string | null;
|
|
91
|
+
notes: string | null;
|
|
92
|
+
providerPayload: Record<string, unknown> | null;
|
|
93
|
+
metadata: Record<string, unknown> | null;
|
|
94
|
+
createdAt: Date;
|
|
95
|
+
updatedAt: Date;
|
|
96
|
+
} | null>;
|
|
97
|
+
export declare function applyPaymentAdapterStatusResult(db: PostgresJsDatabase, paymentSessionId: string, result: PaymentStatusResult, statusLeaseToken: string, runtime?: FinanceServiceRuntime, checkedAt?: Date): Promise<{
|
|
98
|
+
id: string;
|
|
99
|
+
targetType: "other" | "booking" | "order" | "invoice" | "booking_payment_schedule" | "booking_guarantee" | "flight_order";
|
|
100
|
+
targetId: string | null;
|
|
101
|
+
bookingId: string | null;
|
|
102
|
+
orderId: string | null;
|
|
103
|
+
invoiceId: string | null;
|
|
104
|
+
bookingPaymentScheduleId: string | null;
|
|
105
|
+
bookingGuaranteeId: string | null;
|
|
106
|
+
paymentInstrumentId: string | null;
|
|
107
|
+
paymentAuthorizationId: string | null;
|
|
108
|
+
paymentCaptureId: string | null;
|
|
109
|
+
paymentId: string | null;
|
|
110
|
+
status: "paid" | "pending" | "failed" | "requires_redirect" | "processing" | "authorized" | "cancelled" | "expired";
|
|
111
|
+
provider: string | null;
|
|
112
|
+
providerConnectionId: string | null;
|
|
19
113
|
providerSessionId: string | null;
|
|
20
114
|
providerPaymentId: string | null;
|
|
21
115
|
externalReference: string | null;
|
|
@@ -1,34 +1,160 @@
|
|
|
1
|
+
import { eq } from "drizzle-orm";
|
|
2
|
+
import { assertPaymentAdapterProcessorIdentityForLockedSession, assertPaymentAdapterProcessorReferencesForLockedSession, canApplyPaymentAdapterStateTransition, PAYMENT_ADAPTER_STATUS_LEASE_TOKEN_KEY, } from "./payment-adapter-session-guard.js";
|
|
3
|
+
import { paymentSessions } from "./schema/payment-sessions.js";
|
|
1
4
|
import { financePaymentSessionCompletionService } from "./service-payment-session-completion.js";
|
|
2
|
-
import {
|
|
5
|
+
import { PaymentValidationError, sql, toTimestamp, touchLinkedBookingUpdatedAt, } from "./service-shared.js";
|
|
6
|
+
function mergeJsonbColumn(column, value) {
|
|
7
|
+
if (value === undefined)
|
|
8
|
+
return undefined;
|
|
9
|
+
if (value === null)
|
|
10
|
+
return null;
|
|
11
|
+
return sql `coalesce(${column}, '{}'::jsonb) || ${JSON.stringify(value)}::jsonb`;
|
|
12
|
+
}
|
|
13
|
+
const PAYMENT_ADAPTER_INITIATION_CLAIM_KEY = "paymentAdapterInitiationClaim";
|
|
14
|
+
async function applyLockedNonCompletionStateUpdate(db, update, providerData) {
|
|
15
|
+
return db.transaction(async (tx) => {
|
|
16
|
+
const [session] = await tx
|
|
17
|
+
.select()
|
|
18
|
+
.from(paymentSessions)
|
|
19
|
+
.where(eq(paymentSessions.id, update.paymentSessionId))
|
|
20
|
+
.for("update")
|
|
21
|
+
.limit(1);
|
|
22
|
+
if (!session)
|
|
23
|
+
return null;
|
|
24
|
+
if (update.source === "status" &&
|
|
25
|
+
session.metadata?.[PAYMENT_ADAPTER_STATUS_LEASE_TOKEN_KEY] !== update.statusLeaseToken) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const adoptedIdentity = assertPaymentAdapterProcessorIdentityForLockedSession(session, update.processorIdentity);
|
|
29
|
+
const pinnedReferences = assertPaymentAdapterProcessorReferencesForLockedSession(session, {
|
|
30
|
+
processorSessionId: update.processorSessionId,
|
|
31
|
+
processorPaymentId: update.processorPaymentId,
|
|
32
|
+
});
|
|
33
|
+
const provider = adoptedIdentity.provider ?? session.provider ?? providerData.provider;
|
|
34
|
+
const providerConnectionId = adoptedIdentity.providerConnectionId ?? providerData.providerConnectionId;
|
|
35
|
+
const nextState = update.nextState;
|
|
36
|
+
const mayFinalizeUncontestedInitiationClaim = update.source === "initiation" &&
|
|
37
|
+
session.status === "processing" &&
|
|
38
|
+
(nextState === "pending" || nextState === "requires_redirect") &&
|
|
39
|
+
session.metadata?.[PAYMENT_ADAPTER_INITIATION_CLAIM_KEY] === update.idempotencyKey &&
|
|
40
|
+
session.updatedAt.getTime() === update.initiationClaimedAt?.getTime() &&
|
|
41
|
+
!session.providerConnectionId &&
|
|
42
|
+
!session.providerSessionId &&
|
|
43
|
+
!session.providerPaymentId;
|
|
44
|
+
const shouldTransition = mayFinalizeUncontestedInitiationClaim ||
|
|
45
|
+
canApplyPaymentAdapterStateTransition(session.status, nextState);
|
|
46
|
+
const failedAt = shouldTransition && nextState === "failed" ? new Date() : undefined;
|
|
47
|
+
const cancelledAt = shouldTransition && nextState === "cancelled" ? new Date() : undefined;
|
|
48
|
+
const expiredAt = shouldTransition && nextState === "expired" ? new Date() : undefined;
|
|
49
|
+
const [updated] = await tx
|
|
50
|
+
.update(paymentSessions)
|
|
51
|
+
.set({
|
|
52
|
+
status: shouldTransition ? nextState : undefined,
|
|
53
|
+
provider,
|
|
54
|
+
providerConnectionId,
|
|
55
|
+
providerSessionId: pinnedReferences.providerSessionId,
|
|
56
|
+
providerPaymentId: pinnedReferences.providerPaymentId,
|
|
57
|
+
providerPayload: mergeJsonbColumn(paymentSessions.providerPayload, providerData.providerPayload),
|
|
58
|
+
metadata: mergeJsonbColumn(paymentSessions.metadata, providerData.metadata),
|
|
59
|
+
redirectUrl: update.redirectUrl,
|
|
60
|
+
idempotencyKey: update.idempotencyKey,
|
|
61
|
+
failedAt,
|
|
62
|
+
cancelledAt,
|
|
63
|
+
expiredAt,
|
|
64
|
+
failureCode: shouldTransition && nextState === "failed"
|
|
65
|
+
? `payment_adapter_${update.source}`
|
|
66
|
+
: undefined,
|
|
67
|
+
failureMessage: shouldTransition && nextState === "failed"
|
|
68
|
+
? `Payment adapter ${update.source} mapped this session to failed.`
|
|
69
|
+
: undefined,
|
|
70
|
+
completedAt: shouldTransition && nextState === "paid" ? new Date() : undefined,
|
|
71
|
+
expiresAt: shouldTransition && nextState === "expired" ? toTimestamp(update.occurredAt) : undefined,
|
|
72
|
+
updatedAt: new Date(),
|
|
73
|
+
})
|
|
74
|
+
.where(eq(paymentSessions.id, update.paymentSessionId))
|
|
75
|
+
.returning();
|
|
76
|
+
await touchLinkedBookingUpdatedAt(tx, updated?.bookingId);
|
|
77
|
+
return updated ?? null;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async function applyPaymentAdapterStateUpdate(db, update, providerData, runtime) {
|
|
81
|
+
if (update.nextState === "paid" || update.nextState === "authorized") {
|
|
82
|
+
return financePaymentSessionCompletionService.completePaymentSession(db, update.paymentSessionId, {
|
|
83
|
+
status: update.nextState,
|
|
84
|
+
captureMode: "automatic",
|
|
85
|
+
...providerData,
|
|
86
|
+
}, runtime, {
|
|
87
|
+
requireProcessorIdentityWhenConnectionPinned: true,
|
|
88
|
+
expectedPaymentAdapterStatusLeaseToken: update.statusLeaseToken,
|
|
89
|
+
sessionUpdate: {
|
|
90
|
+
redirectUrl: update.redirectUrl,
|
|
91
|
+
idempotencyKey: update.idempotencyKey,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return applyLockedNonCompletionStateUpdate(db, update, providerData);
|
|
96
|
+
}
|
|
97
|
+
export async function applyPaymentAdapterInitiationResult(db, paymentSessionId, adapterId, result, claim, runtime = {}) {
|
|
98
|
+
if (result.idempotencyKey !== claim.idempotencyKey) {
|
|
99
|
+
throw new PaymentValidationError("Payment adapter initiation returned a different idempotency key", { paymentSessionId, expectedIdempotencyKey: claim.idempotencyKey }, { status: 409, code: "payment_adapter_idempotency_mismatch" });
|
|
100
|
+
}
|
|
101
|
+
return applyPaymentAdapterStateUpdate(db, {
|
|
102
|
+
source: "initiation",
|
|
103
|
+
paymentSessionId,
|
|
104
|
+
nextState: result.nextState,
|
|
105
|
+
occurredAt: new Date().toISOString(),
|
|
106
|
+
processorIdentity: result.processorIdentity,
|
|
107
|
+
processorSessionId: result.processorSessionId,
|
|
108
|
+
processorPaymentId: result.processorPaymentId,
|
|
109
|
+
redirectUrl: result.checkout?.url ?? null,
|
|
110
|
+
idempotencyKey: result.idempotencyKey,
|
|
111
|
+
initiationClaimedAt: claim.claimedAt,
|
|
112
|
+
}, {
|
|
113
|
+
provider: result.processorIdentity?.providerId ?? adapterId,
|
|
114
|
+
providerConnectionId: result.processorIdentity?.connectionId,
|
|
115
|
+
providerSessionId: result.processorSessionId ?? undefined,
|
|
116
|
+
providerPaymentId: result.processorPaymentId ?? undefined,
|
|
117
|
+
providerPayload: result.raw === undefined ? undefined : { initiation: result.raw },
|
|
118
|
+
metadata: {
|
|
119
|
+
paymentAdapterInitiationClaim: null,
|
|
120
|
+
paymentAdapterInitiationIdempotencyKey: result.idempotencyKey,
|
|
121
|
+
paymentAdapterInitiationState: "complete",
|
|
122
|
+
},
|
|
123
|
+
}, runtime);
|
|
124
|
+
}
|
|
3
125
|
export async function applyPaymentAdapterCallbackEvent(db, event, runtime = {}) {
|
|
126
|
+
const update = { ...event, source: "callback" };
|
|
4
127
|
const providerData = {
|
|
128
|
+
provider: event.processorIdentity?.providerId,
|
|
129
|
+
providerConnectionId: event.processorIdentity?.connectionId,
|
|
5
130
|
providerSessionId: event.processorSessionId ?? undefined,
|
|
6
131
|
providerPaymentId: event.processorPaymentId ?? undefined,
|
|
7
132
|
providerPayload: event.raw === undefined ? undefined : { callback: event.raw },
|
|
8
133
|
metadata: { paymentAdapterEventId: event.eventId, paymentAdapterOccurredAt: event.occurredAt },
|
|
9
134
|
};
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
135
|
+
return applyPaymentAdapterStateUpdate(db, update, providerData, runtime);
|
|
136
|
+
}
|
|
137
|
+
export async function applyPaymentAdapterStatusResult(db, paymentSessionId, result, statusLeaseToken, runtime = {}, checkedAt = new Date()) {
|
|
138
|
+
const occurredAt = checkedAt.toISOString();
|
|
139
|
+
return applyPaymentAdapterStateUpdate(db, {
|
|
140
|
+
source: "status",
|
|
141
|
+
paymentSessionId,
|
|
142
|
+
nextState: result.nextState,
|
|
143
|
+
occurredAt,
|
|
144
|
+
processorIdentity: result.processorIdentity,
|
|
145
|
+
processorSessionId: result.processorSessionId,
|
|
146
|
+
processorPaymentId: result.processorPaymentId,
|
|
147
|
+
statusLeaseToken,
|
|
148
|
+
}, {
|
|
149
|
+
provider: result.processorIdentity?.providerId,
|
|
150
|
+
providerConnectionId: result.processorIdentity?.connectionId,
|
|
151
|
+
providerSessionId: result.processorSessionId ?? undefined,
|
|
152
|
+
providerPaymentId: result.processorPaymentId ?? undefined,
|
|
153
|
+
providerPayload: result.raw === undefined ? undefined : { status: result.raw },
|
|
154
|
+
metadata: {
|
|
155
|
+
paymentAdapterStatusCheckedAt: occurredAt,
|
|
156
|
+
paymentAdapterStatusRefreshAfter: checkedAt.getTime() + 30_000,
|
|
157
|
+
[PAYMENT_ADAPTER_STATUS_LEASE_TOKEN_KEY]: null,
|
|
158
|
+
},
|
|
33
159
|
}, runtime);
|
|
34
160
|
}
|