@voyantjs/plugin-netopia 0.2.0 → 0.3.0
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/plugin.d.ts +32 -32
- package/dist/service-callback.d.ts +5 -0
- package/dist/service-callback.d.ts.map +1 -0
- package/dist/service-callback.js +124 -0
- package/dist/service-collect.d.ts +9 -0
- package/dist/service-collect.d.ts.map +1 -0
- package/dist/service-collect.js +70 -0
- package/dist/service-shared.d.ts +33 -0
- package/dist/service-shared.d.ts.map +1 -0
- package/dist/service-shared.js +44 -0
- package/dist/service-start.d.ts +6 -0
- package/dist/service-start.d.ts.map +1 -0
- package/dist/service-start.js +81 -0
- package/dist/service.d.ts +10 -33
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +10 -312
- package/package.json +5 -5
package/dist/plugin.d.ts
CHANGED
|
@@ -21,14 +21,14 @@ export declare function createNetopiaFinanceRoutes(options?: NetopiaRuntimeOptio
|
|
|
21
21
|
data: {
|
|
22
22
|
session: {
|
|
23
23
|
id: string;
|
|
24
|
-
status: "
|
|
25
|
-
notes: string | null;
|
|
26
|
-
createdAt: string;
|
|
27
|
-
updatedAt: string;
|
|
24
|
+
status: "expired" | "cancelled" | "pending" | "failed" | "paid" | "requires_redirect" | "processing" | "authorized";
|
|
28
25
|
expiredAt: string | null;
|
|
29
26
|
cancelledAt: string | null;
|
|
30
27
|
completedAt: string | null;
|
|
28
|
+
createdAt: string;
|
|
29
|
+
updatedAt: string;
|
|
31
30
|
bookingId: string | null;
|
|
31
|
+
notes: string | null;
|
|
32
32
|
metadata: {
|
|
33
33
|
[x: string]: import("hono/utils/types").JSONValue;
|
|
34
34
|
} | null;
|
|
@@ -148,10 +148,10 @@ export declare function createNetopiaFinanceRoutes(options?: NetopiaRuntimeOptio
|
|
|
148
148
|
paymentSessionNotification: {
|
|
149
149
|
id: string;
|
|
150
150
|
status: "cancelled" | "pending" | "failed" | "sent";
|
|
151
|
-
createdAt: string;
|
|
152
|
-
updatedAt: string;
|
|
153
151
|
personId: string | null;
|
|
154
152
|
organizationId: string | null;
|
|
153
|
+
createdAt: string;
|
|
154
|
+
updatedAt: string;
|
|
155
155
|
bookingId: string | null;
|
|
156
156
|
metadata: {
|
|
157
157
|
[x: string]: import("hono/utils/types").JSONValue;
|
|
@@ -181,10 +181,10 @@ export declare function createNetopiaFinanceRoutes(options?: NetopiaRuntimeOptio
|
|
|
181
181
|
invoiceNotification: {
|
|
182
182
|
id: string;
|
|
183
183
|
status: "cancelled" | "pending" | "failed" | "sent";
|
|
184
|
-
createdAt: string;
|
|
185
|
-
updatedAt: string;
|
|
186
184
|
personId: string | null;
|
|
187
185
|
organizationId: string | null;
|
|
186
|
+
createdAt: string;
|
|
187
|
+
updatedAt: string;
|
|
188
188
|
bookingId: string | null;
|
|
189
189
|
metadata: {
|
|
190
190
|
[x: string]: import("hono/utils/types").JSONValue;
|
|
@@ -213,14 +213,14 @@ export declare function createNetopiaFinanceRoutes(options?: NetopiaRuntimeOptio
|
|
|
213
213
|
} | null;
|
|
214
214
|
session: {
|
|
215
215
|
id: string;
|
|
216
|
-
status: "
|
|
217
|
-
notes: string | null;
|
|
218
|
-
createdAt: string;
|
|
219
|
-
updatedAt: string;
|
|
216
|
+
status: "expired" | "cancelled" | "pending" | "failed" | "paid" | "requires_redirect" | "processing" | "authorized";
|
|
220
217
|
expiredAt: string | null;
|
|
221
218
|
cancelledAt: string | null;
|
|
222
219
|
completedAt: string | null;
|
|
220
|
+
createdAt: string;
|
|
221
|
+
updatedAt: string;
|
|
223
222
|
bookingId: string | null;
|
|
223
|
+
notes: string | null;
|
|
224
224
|
metadata: {
|
|
225
225
|
[x: string]: import("hono/utils/types").JSONValue;
|
|
226
226
|
} | null;
|
|
@@ -309,10 +309,10 @@ export declare function createNetopiaFinanceRoutes(options?: NetopiaRuntimeOptio
|
|
|
309
309
|
paymentSessionNotification: {
|
|
310
310
|
id: string;
|
|
311
311
|
status: "cancelled" | "pending" | "failed" | "sent";
|
|
312
|
-
createdAt: string;
|
|
313
|
-
updatedAt: string;
|
|
314
312
|
personId: string | null;
|
|
315
313
|
organizationId: string | null;
|
|
314
|
+
createdAt: string;
|
|
315
|
+
updatedAt: string;
|
|
316
316
|
bookingId: string | null;
|
|
317
317
|
metadata: {
|
|
318
318
|
[x: string]: import("hono/utils/types").JSONValue;
|
|
@@ -342,10 +342,10 @@ export declare function createNetopiaFinanceRoutes(options?: NetopiaRuntimeOptio
|
|
|
342
342
|
invoiceNotification: {
|
|
343
343
|
id: string;
|
|
344
344
|
status: "cancelled" | "pending" | "failed" | "sent";
|
|
345
|
-
createdAt: string;
|
|
346
|
-
updatedAt: string;
|
|
347
345
|
personId: string | null;
|
|
348
346
|
organizationId: string | null;
|
|
347
|
+
createdAt: string;
|
|
348
|
+
updatedAt: string;
|
|
349
349
|
bookingId: string | null;
|
|
350
350
|
metadata: {
|
|
351
351
|
[x: string]: import("hono/utils/types").JSONValue;
|
|
@@ -374,14 +374,14 @@ export declare function createNetopiaFinanceRoutes(options?: NetopiaRuntimeOptio
|
|
|
374
374
|
} | null;
|
|
375
375
|
session: {
|
|
376
376
|
id: string;
|
|
377
|
-
status: "
|
|
378
|
-
notes: string | null;
|
|
379
|
-
createdAt: string;
|
|
380
|
-
updatedAt: string;
|
|
377
|
+
status: "expired" | "cancelled" | "pending" | "failed" | "paid" | "requires_redirect" | "processing" | "authorized";
|
|
381
378
|
expiredAt: string | null;
|
|
382
379
|
cancelledAt: string | null;
|
|
383
380
|
completedAt: string | null;
|
|
381
|
+
createdAt: string;
|
|
382
|
+
updatedAt: string;
|
|
384
383
|
bookingId: string | null;
|
|
384
|
+
notes: string | null;
|
|
385
385
|
metadata: {
|
|
386
386
|
[x: string]: import("hono/utils/types").JSONValue;
|
|
387
387
|
} | null;
|
|
@@ -468,10 +468,10 @@ export declare function createNetopiaFinanceRoutes(options?: NetopiaRuntimeOptio
|
|
|
468
468
|
paymentSessionNotification: {
|
|
469
469
|
id: string;
|
|
470
470
|
status: "cancelled" | "pending" | "failed" | "sent";
|
|
471
|
-
createdAt: string;
|
|
472
|
-
updatedAt: string;
|
|
473
471
|
personId: string | null;
|
|
474
472
|
organizationId: string | null;
|
|
473
|
+
createdAt: string;
|
|
474
|
+
updatedAt: string;
|
|
475
475
|
bookingId: string | null;
|
|
476
476
|
metadata: {
|
|
477
477
|
[x: string]: import("hono/utils/types").JSONValue;
|
|
@@ -501,10 +501,10 @@ export declare function createNetopiaFinanceRoutes(options?: NetopiaRuntimeOptio
|
|
|
501
501
|
invoiceNotification: {
|
|
502
502
|
id: string;
|
|
503
503
|
status: "cancelled" | "pending" | "failed" | "sent";
|
|
504
|
-
createdAt: string;
|
|
505
|
-
updatedAt: string;
|
|
506
504
|
personId: string | null;
|
|
507
505
|
organizationId: string | null;
|
|
506
|
+
createdAt: string;
|
|
507
|
+
updatedAt: string;
|
|
508
508
|
bookingId: string | null;
|
|
509
509
|
metadata: {
|
|
510
510
|
[x: string]: import("hono/utils/types").JSONValue;
|
|
@@ -533,14 +533,14 @@ export declare function createNetopiaFinanceRoutes(options?: NetopiaRuntimeOptio
|
|
|
533
533
|
} | null;
|
|
534
534
|
session: {
|
|
535
535
|
id: string;
|
|
536
|
-
status: "
|
|
537
|
-
notes: string | null;
|
|
538
|
-
createdAt: string;
|
|
539
|
-
updatedAt: string;
|
|
536
|
+
status: "expired" | "cancelled" | "pending" | "failed" | "paid" | "requires_redirect" | "processing" | "authorized";
|
|
540
537
|
expiredAt: string | null;
|
|
541
538
|
cancelledAt: string | null;
|
|
542
539
|
completedAt: string | null;
|
|
540
|
+
createdAt: string;
|
|
541
|
+
updatedAt: string;
|
|
543
542
|
bookingId: string | null;
|
|
543
|
+
notes: string | null;
|
|
544
544
|
metadata: {
|
|
545
545
|
[x: string]: import("hono/utils/types").JSONValue;
|
|
546
546
|
} | null;
|
|
@@ -622,14 +622,14 @@ export declare function createNetopiaFinanceRoutes(options?: NetopiaRuntimeOptio
|
|
|
622
622
|
reason?: string | undefined;
|
|
623
623
|
session: {
|
|
624
624
|
id: string;
|
|
625
|
-
status: "
|
|
626
|
-
notes: string | null;
|
|
627
|
-
createdAt: string;
|
|
628
|
-
updatedAt: string;
|
|
625
|
+
status: "expired" | "cancelled" | "pending" | "failed" | "paid" | "requires_redirect" | "processing" | "authorized";
|
|
629
626
|
expiredAt: string | null;
|
|
630
627
|
cancelledAt: string | null;
|
|
631
628
|
completedAt: string | null;
|
|
629
|
+
createdAt: string;
|
|
630
|
+
updatedAt: string;
|
|
632
631
|
bookingId: string | null;
|
|
632
|
+
notes: string | null;
|
|
633
633
|
metadata: {
|
|
634
634
|
[x: string]: import("hono/utils/types").JSONValue;
|
|
635
635
|
} | null;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
2
|
+
import { type NetopiaCallbackResult } from "./service-shared.js";
|
|
3
|
+
import type { NetopiaRuntimeOptions, NetopiaWebhookPayload } from "./types.js";
|
|
4
|
+
export declare function handleCallback(db: PostgresJsDatabase, payload: NetopiaWebhookPayload, runtimeOptions?: NetopiaRuntimeOptions, bindings?: Record<string, unknown>): Promise<NetopiaCallbackResult>;
|
|
5
|
+
//# sourceMappingURL=service-callback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-callback.d.ts","sourceRoot":"","sources":["../src/service-callback.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAGjE,OAAO,EAKL,KAAK,qBAAqB,EAE3B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAE9E,wBAAsB,cAAc,CAClC,EAAE,EAAE,kBAAkB,EACtB,OAAO,EAAE,qBAAqB,EAC9B,cAAc,GAAE,qBAA0B,EAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,qBAAqB,CAAC,CA8IhC"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { resolveNetopiaRuntimeOptions } from "./client.js";
|
|
2
|
+
import { amountToCents, financeService, mapNetopiaPaymentStatus, mergeRecord, normalizeCurrency, } from "./service-shared.js";
|
|
3
|
+
export async function handleCallback(db, payload, runtimeOptions = {}, bindings) {
|
|
4
|
+
const runtime = resolveNetopiaRuntimeOptions(bindings, runtimeOptions);
|
|
5
|
+
const orderId = payload.order.orderID;
|
|
6
|
+
const lookup = await financeService.listPaymentSessions(db, {
|
|
7
|
+
provider: "netopia",
|
|
8
|
+
externalReference: orderId,
|
|
9
|
+
limit: 1,
|
|
10
|
+
offset: 0,
|
|
11
|
+
});
|
|
12
|
+
let session = lookup.data[0] ?? null;
|
|
13
|
+
if (!session) {
|
|
14
|
+
session = await financeService.getPaymentSessionById(db, orderId);
|
|
15
|
+
}
|
|
16
|
+
if (!session) {
|
|
17
|
+
return {
|
|
18
|
+
action: "ignored",
|
|
19
|
+
reason: "payment_session_not_found",
|
|
20
|
+
session: null,
|
|
21
|
+
orderId,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const callbackState = mapNetopiaPaymentStatus(payload.payment.status, runtime);
|
|
25
|
+
const providerPayload = mergeRecord(session.providerPayload, {
|
|
26
|
+
netopiaCallback: payload,
|
|
27
|
+
});
|
|
28
|
+
const normalizedCurrency = normalizeCurrency(payload.payment.currency);
|
|
29
|
+
const amountCents = amountToCents(payload.payment.amount);
|
|
30
|
+
if (callbackState === "completed" &&
|
|
31
|
+
(normalizedCurrency !== normalizeCurrency(session.currency) ||
|
|
32
|
+
amountCents !== session.amountCents)) {
|
|
33
|
+
const failed = await financeService.failPaymentSession(db, session.id, {
|
|
34
|
+
providerSessionId: payload.payment.ntpID,
|
|
35
|
+
providerPaymentId: payload.payment.ntpID,
|
|
36
|
+
externalReference: orderId,
|
|
37
|
+
failureCode: "amount_or_currency_mismatch",
|
|
38
|
+
failureMessage: `Expected ${session.amountCents} ${normalizeCurrency(session.currency)}, received ${amountCents} ${normalizedCurrency}`,
|
|
39
|
+
providerPayload,
|
|
40
|
+
});
|
|
41
|
+
return {
|
|
42
|
+
action: "failed",
|
|
43
|
+
reason: "amount_or_currency_mismatch",
|
|
44
|
+
session: failed,
|
|
45
|
+
orderId,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (callbackState === "processing") {
|
|
49
|
+
const updated = await financeService.updatePaymentSession(db, session.id, {
|
|
50
|
+
status: "processing",
|
|
51
|
+
provider: "netopia",
|
|
52
|
+
providerSessionId: payload.payment.ntpID,
|
|
53
|
+
providerPaymentId: payload.payment.ntpID,
|
|
54
|
+
externalReference: orderId,
|
|
55
|
+
providerPayload,
|
|
56
|
+
});
|
|
57
|
+
return {
|
|
58
|
+
action: "processing",
|
|
59
|
+
session: updated,
|
|
60
|
+
orderId,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (callbackState === "completed") {
|
|
64
|
+
if (session.status === "paid" || session.status === "authorized") {
|
|
65
|
+
const current = await financeService.updatePaymentSession(db, session.id, {
|
|
66
|
+
provider: "netopia",
|
|
67
|
+
providerSessionId: payload.payment.ntpID,
|
|
68
|
+
providerPaymentId: payload.payment.ntpID,
|
|
69
|
+
externalReference: orderId,
|
|
70
|
+
providerPayload,
|
|
71
|
+
});
|
|
72
|
+
return {
|
|
73
|
+
action: "ignored",
|
|
74
|
+
reason: "already_completed",
|
|
75
|
+
session: current,
|
|
76
|
+
orderId,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const completed = await financeService.completePaymentSession(db, session.id, {
|
|
80
|
+
status: "paid",
|
|
81
|
+
captureMode: "manual",
|
|
82
|
+
paymentMethod: "credit_card",
|
|
83
|
+
providerSessionId: payload.payment.ntpID,
|
|
84
|
+
providerPaymentId: payload.payment.ntpID,
|
|
85
|
+
externalReference: orderId,
|
|
86
|
+
externalAuthorizationId: typeof payload.payment.data?.AuthCode === "string"
|
|
87
|
+
? payload.payment.data.AuthCode
|
|
88
|
+
: payload.payment.ntpID,
|
|
89
|
+
externalCaptureId: typeof payload.payment.data?.RRN === "string"
|
|
90
|
+
? payload.payment.data.RRN
|
|
91
|
+
: payload.payment.ntpID,
|
|
92
|
+
approvalCode: typeof payload.payment.data?.AuthCode === "string"
|
|
93
|
+
? payload.payment.data.AuthCode
|
|
94
|
+
: undefined,
|
|
95
|
+
referenceNumber: typeof payload.payment.data?.RRN === "string" ? payload.payment.data.RRN : undefined,
|
|
96
|
+
authorizedAt: new Date().toISOString(),
|
|
97
|
+
capturedAt: new Date().toISOString(),
|
|
98
|
+
paymentDate: new Date().toISOString(),
|
|
99
|
+
providerPayload,
|
|
100
|
+
});
|
|
101
|
+
return {
|
|
102
|
+
action: "completed",
|
|
103
|
+
session: completed,
|
|
104
|
+
orderId,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
const failed = await financeService.failPaymentSession(db, session.id, {
|
|
108
|
+
providerSessionId: payload.payment.ntpID,
|
|
109
|
+
providerPaymentId: payload.payment.ntpID,
|
|
110
|
+
externalReference: orderId,
|
|
111
|
+
failureCode: typeof payload.payment.code === "string" && payload.payment.code.length > 0
|
|
112
|
+
? payload.payment.code
|
|
113
|
+
: `netopia_status_${payload.payment.status}`,
|
|
114
|
+
failureMessage: typeof payload.payment.message === "string" && payload.payment.message.length > 0
|
|
115
|
+
? payload.payment.message
|
|
116
|
+
: "Netopia payment was not approved",
|
|
117
|
+
providerPayload,
|
|
118
|
+
});
|
|
119
|
+
return {
|
|
120
|
+
action: "failed",
|
|
121
|
+
session: failed,
|
|
122
|
+
orderId,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type NotificationService } from "@voyantjs/notifications";
|
|
2
|
+
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
3
|
+
import type { NetopiaClientApi } from "./client.js";
|
|
4
|
+
import { type NetopiaCollectBookingGuaranteeInput, type NetopiaCollectBookingScheduleInput, type NetopiaCollectInvoiceInput, type NetopiaCollectPaymentResult } from "./service-shared.js";
|
|
5
|
+
import type { NetopiaRuntimeOptions } from "./types.js";
|
|
6
|
+
export declare function collectBookingSchedule(db: PostgresJsDatabase, scheduleId: string, input: NetopiaCollectBookingScheduleInput, runtimeOptions?: NetopiaRuntimeOptions, clientOverride?: NetopiaClientApi, dispatcherOverride?: NotificationService, bindings?: Record<string, unknown>): Promise<NetopiaCollectPaymentResult>;
|
|
7
|
+
export declare function collectBookingGuarantee(db: PostgresJsDatabase, guaranteeId: string, input: NetopiaCollectBookingGuaranteeInput, runtimeOptions?: NetopiaRuntimeOptions, clientOverride?: NetopiaClientApi, dispatcherOverride?: NotificationService, bindings?: Record<string, unknown>): Promise<NetopiaCollectPaymentResult>;
|
|
8
|
+
export declare function collectInvoice(db: PostgresJsDatabase, invoiceId: string, input: NetopiaCollectInvoiceInput, runtimeOptions?: NetopiaRuntimeOptions, clientOverride?: NetopiaClientApi, dispatcherOverride?: NotificationService, bindings?: Record<string, unknown>): Promise<NetopiaCollectPaymentResult>;
|
|
9
|
+
//# sourceMappingURL=service-collect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-collect.d.ts","sourceRoot":"","sources":["../src/service-collect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,mBAAmB,EAAwB,MAAM,yBAAyB,CAAA;AACxF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,EAEL,KAAK,mCAAmC,EACxC,KAAK,kCAAkC,EACvC,KAAK,0BAA0B,EAC/B,KAAK,2BAA2B,EAEjC,MAAM,qBAAqB,CAAA;AAE5B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAEvD,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,kBAAkB,EACtB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,kCAAkC,EACzC,cAAc,GAAE,qBAA0B,EAC1C,cAAc,CAAC,EAAE,gBAAgB,EACjC,kBAAkB,CAAC,EAAE,mBAAmB,EACxC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,2BAA2B,CAAC,CAsCtC;AAED,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,kBAAkB,EACtB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,mCAAmC,EAC1C,cAAc,GAAE,qBAA0B,EAC1C,cAAc,CAAC,EAAE,gBAAgB,EACjC,kBAAkB,CAAC,EAAE,mBAAmB,EACxC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,2BAA2B,CAAC,CAsCtC;AAED,wBAAsB,cAAc,CAClC,EAAE,EAAE,kBAAkB,EACtB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,0BAA0B,EACjC,cAAc,GAAE,qBAA0B,EAC1C,cAAc,CAAC,EAAE,gBAAgB,EACjC,kBAAkB,CAAC,EAAE,mBAAmB,EACxC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,2BAA2B,CAAC,CAiDtC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { notificationsService } from "@voyantjs/notifications";
|
|
2
|
+
import { financeService, resolveNotificationDispatcher, } from "./service-shared.js";
|
|
3
|
+
import * as startService from "./service-start.js";
|
|
4
|
+
export async function collectBookingSchedule(db, scheduleId, input, runtimeOptions = {}, clientOverride, dispatcherOverride, bindings) {
|
|
5
|
+
const session = await financeService.createPaymentSessionFromBookingSchedule(db, scheduleId, {
|
|
6
|
+
...(input.paymentSession ?? {}),
|
|
7
|
+
provider: "netopia",
|
|
8
|
+
});
|
|
9
|
+
if (!session) {
|
|
10
|
+
throw new Error("Payment schedule not found");
|
|
11
|
+
}
|
|
12
|
+
const started = await startService.startPaymentSession(db, session.id, input.netopia, runtimeOptions, clientOverride, bindings);
|
|
13
|
+
const dispatcher = input.notification
|
|
14
|
+
? resolveNotificationDispatcher(bindings, runtimeOptions, dispatcherOverride)
|
|
15
|
+
: null;
|
|
16
|
+
const paymentSessionNotification = input.notification && dispatcher
|
|
17
|
+
? await notificationsService.sendPaymentSessionNotification(db, dispatcher, started.session.id, input.notification)
|
|
18
|
+
: null;
|
|
19
|
+
return {
|
|
20
|
+
...started,
|
|
21
|
+
paymentSessionNotification,
|
|
22
|
+
invoiceNotification: null,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export async function collectBookingGuarantee(db, guaranteeId, input, runtimeOptions = {}, clientOverride, dispatcherOverride, bindings) {
|
|
26
|
+
const session = await financeService.createPaymentSessionFromBookingGuarantee(db, guaranteeId, {
|
|
27
|
+
...(input.paymentSession ?? {}),
|
|
28
|
+
provider: "netopia",
|
|
29
|
+
});
|
|
30
|
+
if (!session) {
|
|
31
|
+
throw new Error("Booking guarantee not found");
|
|
32
|
+
}
|
|
33
|
+
const started = await startService.startPaymentSession(db, session.id, input.netopia, runtimeOptions, clientOverride, bindings);
|
|
34
|
+
const dispatcher = input.notification
|
|
35
|
+
? resolveNotificationDispatcher(bindings, runtimeOptions, dispatcherOverride)
|
|
36
|
+
: null;
|
|
37
|
+
const paymentSessionNotification = input.notification && dispatcher
|
|
38
|
+
? await notificationsService.sendPaymentSessionNotification(db, dispatcher, started.session.id, input.notification)
|
|
39
|
+
: null;
|
|
40
|
+
return {
|
|
41
|
+
...started,
|
|
42
|
+
paymentSessionNotification,
|
|
43
|
+
invoiceNotification: null,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export async function collectInvoice(db, invoiceId, input, runtimeOptions = {}, clientOverride, dispatcherOverride, bindings) {
|
|
47
|
+
const session = await financeService.createPaymentSessionFromInvoice(db, invoiceId, {
|
|
48
|
+
...(input.paymentSession ?? {}),
|
|
49
|
+
provider: "netopia",
|
|
50
|
+
});
|
|
51
|
+
if (!session) {
|
|
52
|
+
throw new Error("Invoice not found");
|
|
53
|
+
}
|
|
54
|
+
const started = await startService.startPaymentSession(db, session.id, input.netopia, runtimeOptions, clientOverride, bindings);
|
|
55
|
+
const shouldNotify = Boolean(input.paymentSessionNotification || input.invoiceNotification);
|
|
56
|
+
const dispatcher = shouldNotify
|
|
57
|
+
? resolveNotificationDispatcher(bindings, runtimeOptions, dispatcherOverride)
|
|
58
|
+
: null;
|
|
59
|
+
const paymentSessionNotification = input.paymentSessionNotification && dispatcher
|
|
60
|
+
? await notificationsService.sendPaymentSessionNotification(db, dispatcher, started.session.id, input.paymentSessionNotification)
|
|
61
|
+
: null;
|
|
62
|
+
const invoiceNotification = input.invoiceNotification && dispatcher
|
|
63
|
+
? await notificationsService.sendInvoiceNotification(db, dispatcher, invoiceId, input.invoiceNotification)
|
|
64
|
+
: null;
|
|
65
|
+
return {
|
|
66
|
+
...started,
|
|
67
|
+
paymentSessionNotification,
|
|
68
|
+
invoiceNotification,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { financeService, type PaymentSession } from "@voyantjs/finance";
|
|
2
|
+
import { type NotificationDelivery, type NotificationService } from "@voyantjs/notifications";
|
|
3
|
+
import type { z } from "zod";
|
|
4
|
+
import type { NetopiaProductLine, NetopiaRuntimeOptions, NetopiaStartPaymentResponse, ResolvedNetopiaRuntimeOptions } from "./types.js";
|
|
5
|
+
import type { netopiaCollectBookingGuaranteeSchema, netopiaCollectBookingScheduleSchema, netopiaCollectInvoiceSchema } from "./validation.js";
|
|
6
|
+
export interface NetopiaStartPaymentResult {
|
|
7
|
+
session: PaymentSession;
|
|
8
|
+
providerResponse: NetopiaStartPaymentResponse;
|
|
9
|
+
orderId: string;
|
|
10
|
+
}
|
|
11
|
+
export interface NetopiaCallbackResult {
|
|
12
|
+
action: "processing" | "completed" | "failed" | "ignored";
|
|
13
|
+
reason?: string;
|
|
14
|
+
session: PaymentSession | null;
|
|
15
|
+
orderId: string;
|
|
16
|
+
}
|
|
17
|
+
export interface NetopiaCollectPaymentResult extends NetopiaStartPaymentResult {
|
|
18
|
+
paymentSessionNotification: NotificationDelivery | null;
|
|
19
|
+
invoiceNotification: NotificationDelivery | null;
|
|
20
|
+
}
|
|
21
|
+
export type NetopiaCollectBookingScheduleInput = z.infer<typeof netopiaCollectBookingScheduleSchema>;
|
|
22
|
+
export type NetopiaCollectBookingGuaranteeInput = z.infer<typeof netopiaCollectBookingGuaranteeSchema>;
|
|
23
|
+
export type NetopiaCollectInvoiceInput = z.infer<typeof netopiaCollectInvoiceSchema>;
|
|
24
|
+
export declare function centsToAmount(cents: number): number;
|
|
25
|
+
export declare function amountToCents(amount: number): number;
|
|
26
|
+
export declare function normalizeCurrency(currency: string): string;
|
|
27
|
+
export declare function mergeRecord(base: Record<string, unknown> | null | undefined, extra: Record<string, unknown>): Record<string, unknown>;
|
|
28
|
+
export declare function buildDefaultProducts(session: PaymentSession, description: string): NetopiaProductLine[];
|
|
29
|
+
export declare function deriveNetopiaOrderId(session: PaymentSession): string;
|
|
30
|
+
export declare function mapNetopiaPaymentStatus(status: number, options: Pick<ResolvedNetopiaRuntimeOptions, "successStatuses" | "processingStatuses">): "completed" | "processing" | "failed";
|
|
31
|
+
export declare function resolveNotificationDispatcher(bindings: Record<string, unknown> | undefined, runtimeOptions: NetopiaRuntimeOptions, dispatcherOverride?: NotificationService): NotificationService;
|
|
32
|
+
export { financeService };
|
|
33
|
+
//# sourceMappingURL=service-shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-shared.d.ts","sourceRoot":"","sources":["../src/service-shared.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACvE,OAAO,EAGL,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACzB,MAAM,yBAAyB,CAAA;AAChC,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAE5B,OAAO,KAAK,EACV,kBAAkB,EAClB,qBAAqB,EACrB,2BAA2B,EAC3B,6BAA6B,EAC9B,MAAM,YAAY,CAAA;AACnB,OAAO,KAAK,EACV,oCAAoC,EACpC,mCAAmC,EACnC,2BAA2B,EAC5B,MAAM,iBAAiB,CAAA;AAExB,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,cAAc,CAAA;IACvB,gBAAgB,EAAE,2BAA2B,CAAA;IAC7C,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,YAAY,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAA;IACzD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,cAAc,GAAG,IAAI,CAAA;IAC9B,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,2BAA4B,SAAQ,yBAAyB;IAC5E,0BAA0B,EAAE,oBAAoB,GAAG,IAAI,CAAA;IACvD,mBAAmB,EAAE,oBAAoB,GAAG,IAAI,CAAA;CACjD;AAED,MAAM,MAAM,kCAAkC,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mCAAmC,CAAC,CAAA;AACpG,MAAM,MAAM,mCAAmC,GAAG,CAAC,CAAC,KAAK,CACvD,OAAO,oCAAoC,CAC5C,CAAA;AACD,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAA;AAEpF,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,UAE1C;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,UAE3C;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,UAEjD;AAED,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,EAChD,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAKzB;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,cAAc,EACvB,WAAW,EAAE,MAAM,GAClB,kBAAkB,EAAE,CAQtB;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,cAAc,UAE3D;AAED,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,IAAI,CAAC,6BAA6B,EAAE,iBAAiB,GAAG,oBAAoB,CAAC,GACrF,WAAW,GAAG,YAAY,GAAG,QAAQ,CAIvC;AAED,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC7C,cAAc,EAAE,qBAAqB,EACrC,kBAAkB,CAAC,EAAE,mBAAmB,uBAUzC;AAED,OAAO,EAAE,cAAc,EAAE,CAAA"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { financeService } from "@voyantjs/finance";
|
|
2
|
+
import { createDefaultNotificationProviders, createNotificationService, } from "@voyantjs/notifications";
|
|
3
|
+
export function centsToAmount(cents) {
|
|
4
|
+
return Number((cents / 100).toFixed(2));
|
|
5
|
+
}
|
|
6
|
+
export function amountToCents(amount) {
|
|
7
|
+
return Math.round(amount * 100);
|
|
8
|
+
}
|
|
9
|
+
export function normalizeCurrency(currency) {
|
|
10
|
+
return currency.trim().toUpperCase();
|
|
11
|
+
}
|
|
12
|
+
export function mergeRecord(base, extra) {
|
|
13
|
+
return {
|
|
14
|
+
...(base ?? {}),
|
|
15
|
+
...extra,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function buildDefaultProducts(session, description) {
|
|
19
|
+
return [
|
|
20
|
+
{
|
|
21
|
+
name: description,
|
|
22
|
+
price: centsToAmount(session.amountCents),
|
|
23
|
+
vat: 0,
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
export function deriveNetopiaOrderId(session) {
|
|
28
|
+
return session.externalReference ?? session.clientReference ?? session.id;
|
|
29
|
+
}
|
|
30
|
+
export function mapNetopiaPaymentStatus(status, options) {
|
|
31
|
+
if (options.successStatuses.includes(status))
|
|
32
|
+
return "completed";
|
|
33
|
+
if (options.processingStatuses.includes(status))
|
|
34
|
+
return "processing";
|
|
35
|
+
return "failed";
|
|
36
|
+
}
|
|
37
|
+
export function resolveNotificationDispatcher(bindings, runtimeOptions, dispatcherOverride) {
|
|
38
|
+
if (dispatcherOverride) {
|
|
39
|
+
return dispatcherOverride;
|
|
40
|
+
}
|
|
41
|
+
return createNotificationService(runtimeOptions.resolveNotificationProviders?.(bindings ?? {}) ??
|
|
42
|
+
createDefaultNotificationProviders(bindings ?? {}));
|
|
43
|
+
}
|
|
44
|
+
export { financeService };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
2
|
+
import { type NetopiaClientApi } from "./client.js";
|
|
3
|
+
import { type NetopiaStartPaymentResult } from "./service-shared.js";
|
|
4
|
+
import type { NetopiaRuntimeOptions, NetopiaStartPaymentInput } from "./types.js";
|
|
5
|
+
export declare function startPaymentSession(db: PostgresJsDatabase, sessionId: string, input: NetopiaStartPaymentInput, runtimeOptions?: NetopiaRuntimeOptions, clientOverride?: NetopiaClientApi, bindings?: Record<string, unknown>): Promise<NetopiaStartPaymentResult>;
|
|
6
|
+
//# sourceMappingURL=service-start.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-start.d.ts","sourceRoot":"","sources":["../src/service-start.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAEjE,OAAO,EAEL,KAAK,gBAAgB,EAEtB,MAAM,aAAa,CAAA;AACpB,OAAO,EAML,KAAK,yBAAyB,EAE/B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EACV,qBAAqB,EACrB,wBAAwB,EAEzB,MAAM,YAAY,CAAA;AAEnB,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,kBAAkB,EACtB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,wBAAwB,EAC/B,cAAc,GAAE,qBAA0B,EAC1C,cAAc,CAAC,EAAE,gBAAgB,EACjC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,yBAAyB,CAAC,CA0FpC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { createNetopiaClient, resolveNetopiaRuntimeOptions, } from "./client.js";
|
|
2
|
+
import { buildDefaultProducts, centsToAmount, deriveNetopiaOrderId, financeService, mergeRecord, normalizeCurrency, } from "./service-shared.js";
|
|
3
|
+
export async function startPaymentSession(db, sessionId, input, runtimeOptions = {}, clientOverride, bindings) {
|
|
4
|
+
const session = await financeService.getPaymentSessionById(db, sessionId);
|
|
5
|
+
if (!session) {
|
|
6
|
+
throw new Error("Payment session not found");
|
|
7
|
+
}
|
|
8
|
+
if (session.provider && session.provider !== "netopia") {
|
|
9
|
+
throw new Error(`Payment session ${sessionId} is already assigned to provider "${session.provider}"`);
|
|
10
|
+
}
|
|
11
|
+
if (["paid", "authorized", "cancelled", "expired"].includes(session.status)) {
|
|
12
|
+
throw new Error(`Payment session ${sessionId} is not startable from status "${session.status}"`);
|
|
13
|
+
}
|
|
14
|
+
const runtime = resolveNetopiaRuntimeOptions(bindings, runtimeOptions);
|
|
15
|
+
const client = clientOverride ??
|
|
16
|
+
createNetopiaClient({
|
|
17
|
+
apiUrl: runtime.apiUrl,
|
|
18
|
+
apiKey: runtime.apiKey,
|
|
19
|
+
fetch: runtime.fetch,
|
|
20
|
+
});
|
|
21
|
+
const description = input.description ?? session.notes ?? `Payment ${session.id}`;
|
|
22
|
+
const orderId = deriveNetopiaOrderId(session);
|
|
23
|
+
const request = {
|
|
24
|
+
config: {
|
|
25
|
+
emailTemplate: input.emailTemplate ?? runtime.emailTemplate,
|
|
26
|
+
notifyUrl: input.notifyUrl ?? runtime.notifyUrl,
|
|
27
|
+
redirectUrl: input.returnUrl ?? runtime.redirectUrl,
|
|
28
|
+
language: input.language ?? runtime.language,
|
|
29
|
+
},
|
|
30
|
+
payment: {
|
|
31
|
+
options: input.options ?? { installments: 1 },
|
|
32
|
+
instrument: input.instrument,
|
|
33
|
+
data: input.browserData,
|
|
34
|
+
},
|
|
35
|
+
order: {
|
|
36
|
+
ntpID: "",
|
|
37
|
+
posSignature: runtime.posSignature,
|
|
38
|
+
dateTime: new Date().toISOString(),
|
|
39
|
+
description,
|
|
40
|
+
orderID: orderId,
|
|
41
|
+
amount: centsToAmount(session.amountCents),
|
|
42
|
+
currency: normalizeCurrency(session.currency),
|
|
43
|
+
billing: input.billing,
|
|
44
|
+
shipping: input.shipping ?? input.billing,
|
|
45
|
+
products: input.products && input.products.length > 0
|
|
46
|
+
? input.products
|
|
47
|
+
: buildDefaultProducts(session, description),
|
|
48
|
+
installments: input.installments ?? { selected: 1, available: [0] },
|
|
49
|
+
data: input.orderData,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
const providerResponse = await client.startCardPayment(request);
|
|
53
|
+
const payment = providerResponse.payment;
|
|
54
|
+
if (!payment?.paymentURL) {
|
|
55
|
+
throw new Error("Netopia start payment succeeded without paymentURL");
|
|
56
|
+
}
|
|
57
|
+
const updated = await financeService.markPaymentSessionRequiresRedirect(db, session.id, {
|
|
58
|
+
provider: "netopia",
|
|
59
|
+
providerSessionId: payment.ntpID ?? null,
|
|
60
|
+
providerPaymentId: payment.ntpID ?? null,
|
|
61
|
+
externalReference: orderId,
|
|
62
|
+
redirectUrl: payment.paymentURL,
|
|
63
|
+
returnUrl: input.returnUrl ?? runtime.redirectUrl,
|
|
64
|
+
cancelUrl: input.cancelUrl ?? null,
|
|
65
|
+
callbackUrl: input.callbackUrl ?? input.notifyUrl ?? runtime.notifyUrl,
|
|
66
|
+
providerPayload: mergeRecord(session.providerPayload, {
|
|
67
|
+
netopiaStartRequest: request,
|
|
68
|
+
netopiaStartResponse: providerResponse,
|
|
69
|
+
}),
|
|
70
|
+
metadata: input.metadata ?? undefined,
|
|
71
|
+
notes: input.notes ?? session.notes ?? undefined,
|
|
72
|
+
});
|
|
73
|
+
if (!updated) {
|
|
74
|
+
throw new Error("Payment session disappeared while saving Netopia redirect state");
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
session: updated,
|
|
78
|
+
providerResponse,
|
|
79
|
+
orderId,
|
|
80
|
+
};
|
|
81
|
+
}
|
package/dist/service.d.ts
CHANGED
|
@@ -1,36 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import type
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
import type { NetopiaRuntimeOptions, NetopiaStartPaymentInput, NetopiaStartPaymentResponse, NetopiaWebhookPayload, ResolvedNetopiaRuntimeOptions } from "./types.js";
|
|
7
|
-
import type { netopiaCollectBookingGuaranteeSchema, netopiaCollectBookingScheduleSchema, netopiaCollectInvoiceSchema } from "./validation.js";
|
|
8
|
-
export interface NetopiaStartPaymentResult {
|
|
9
|
-
session: PaymentSession;
|
|
10
|
-
providerResponse: NetopiaStartPaymentResponse;
|
|
11
|
-
orderId: string;
|
|
12
|
-
}
|
|
13
|
-
export interface NetopiaCallbackResult {
|
|
14
|
-
action: "processing" | "completed" | "failed" | "ignored";
|
|
15
|
-
reason?: string;
|
|
16
|
-
session: PaymentSession | null;
|
|
17
|
-
orderId: string;
|
|
18
|
-
}
|
|
19
|
-
export interface NetopiaCollectPaymentResult extends NetopiaStartPaymentResult {
|
|
20
|
-
paymentSessionNotification: NotificationDelivery | null;
|
|
21
|
-
invoiceNotification: NotificationDelivery | null;
|
|
22
|
-
}
|
|
23
|
-
type NetopiaCollectBookingScheduleInput = z.infer<typeof netopiaCollectBookingScheduleSchema>;
|
|
24
|
-
type NetopiaCollectBookingGuaranteeInput = z.infer<typeof netopiaCollectBookingGuaranteeSchema>;
|
|
25
|
-
type NetopiaCollectInvoiceInput = z.infer<typeof netopiaCollectInvoiceSchema>;
|
|
26
|
-
export declare function deriveNetopiaOrderId(session: PaymentSession): string;
|
|
27
|
-
export declare function mapNetopiaPaymentStatus(status: number, options: Pick<ResolvedNetopiaRuntimeOptions, "successStatuses" | "processingStatuses">): "completed" | "processing" | "failed";
|
|
1
|
+
import { handleCallback } from "./service-callback.js";
|
|
2
|
+
import { collectBookingGuarantee, collectBookingSchedule, collectInvoice } from "./service-collect.js";
|
|
3
|
+
import { deriveNetopiaOrderId, mapNetopiaPaymentStatus, type NetopiaCallbackResult, type NetopiaCollectPaymentResult, type NetopiaStartPaymentResult } from "./service-shared.js";
|
|
4
|
+
import { startPaymentSession } from "./service-start.js";
|
|
5
|
+
export { deriveNetopiaOrderId, mapNetopiaPaymentStatus, type NetopiaCallbackResult, type NetopiaCollectPaymentResult, type NetopiaStartPaymentResult, };
|
|
28
6
|
export declare const netopiaService: {
|
|
29
|
-
startPaymentSession
|
|
30
|
-
collectBookingSchedule
|
|
31
|
-
collectBookingGuarantee
|
|
32
|
-
collectInvoice
|
|
33
|
-
handleCallback
|
|
7
|
+
startPaymentSession: typeof startPaymentSession;
|
|
8
|
+
collectBookingSchedule: typeof collectBookingSchedule;
|
|
9
|
+
collectBookingGuarantee: typeof collectBookingGuarantee;
|
|
10
|
+
collectInvoice: typeof collectInvoice;
|
|
11
|
+
handleCallback: typeof handleCallback;
|
|
34
12
|
};
|
|
35
|
-
export {};
|
|
36
13
|
//# sourceMappingURL=service.d.ts.map
|
package/dist/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EACL,uBAAuB,EACvB,sBAAsB,EACtB,cAAc,EACf,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,2BAA2B,EAChC,KAAK,yBAAyB,EAC/B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAExD,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,2BAA2B,EAChC,KAAK,yBAAyB,GAC/B,CAAA;AAED,eAAO,MAAM,cAAc;;;;;;CAM1B,CAAA"}
|
package/dist/service.js
CHANGED
|
@@ -1,314 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
7
|
-
function amountToCents(amount) {
|
|
8
|
-
return Math.round(amount * 100);
|
|
9
|
-
}
|
|
10
|
-
function normalizeCurrency(currency) {
|
|
11
|
-
return currency.trim().toUpperCase();
|
|
12
|
-
}
|
|
13
|
-
function mergeRecord(base, extra) {
|
|
14
|
-
return {
|
|
15
|
-
...(base ?? {}),
|
|
16
|
-
...extra,
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
function buildDefaultProducts(session, description) {
|
|
20
|
-
return [
|
|
21
|
-
{
|
|
22
|
-
name: description,
|
|
23
|
-
price: centsToAmount(session.amountCents),
|
|
24
|
-
vat: 0,
|
|
25
|
-
},
|
|
26
|
-
];
|
|
27
|
-
}
|
|
28
|
-
export function deriveNetopiaOrderId(session) {
|
|
29
|
-
return session.externalReference ?? session.clientReference ?? session.id;
|
|
30
|
-
}
|
|
31
|
-
export function mapNetopiaPaymentStatus(status, options) {
|
|
32
|
-
if (options.successStatuses.includes(status))
|
|
33
|
-
return "completed";
|
|
34
|
-
if (options.processingStatuses.includes(status))
|
|
35
|
-
return "processing";
|
|
36
|
-
return "failed";
|
|
37
|
-
}
|
|
38
|
-
function resolveNotificationDispatcher(bindings, runtimeOptions, dispatcherOverride) {
|
|
39
|
-
if (dispatcherOverride) {
|
|
40
|
-
return dispatcherOverride;
|
|
41
|
-
}
|
|
42
|
-
return createNotificationService(runtimeOptions.resolveNotificationProviders?.(bindings ?? {}) ??
|
|
43
|
-
createDefaultNotificationProviders(bindings ?? {}));
|
|
44
|
-
}
|
|
1
|
+
import { handleCallback } from "./service-callback.js";
|
|
2
|
+
import { collectBookingGuarantee, collectBookingSchedule, collectInvoice, } from "./service-collect.js";
|
|
3
|
+
import { deriveNetopiaOrderId, mapNetopiaPaymentStatus, } from "./service-shared.js";
|
|
4
|
+
import { startPaymentSession } from "./service-start.js";
|
|
5
|
+
export { deriveNetopiaOrderId, mapNetopiaPaymentStatus, };
|
|
45
6
|
export const netopiaService = {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (session.provider && session.provider !== "netopia") {
|
|
52
|
-
throw new Error(`Payment session ${sessionId} is already assigned to provider "${session.provider}"`);
|
|
53
|
-
}
|
|
54
|
-
if (["paid", "authorized", "cancelled", "expired"].includes(session.status)) {
|
|
55
|
-
throw new Error(`Payment session ${sessionId} is not startable from status "${session.status}"`);
|
|
56
|
-
}
|
|
57
|
-
const runtime = resolveNetopiaRuntimeOptions(bindings, runtimeOptions);
|
|
58
|
-
const client = clientOverride ??
|
|
59
|
-
createNetopiaClient({
|
|
60
|
-
apiUrl: runtime.apiUrl,
|
|
61
|
-
apiKey: runtime.apiKey,
|
|
62
|
-
fetch: runtime.fetch,
|
|
63
|
-
});
|
|
64
|
-
const description = input.description ?? session.notes ?? `Payment ${session.id}`;
|
|
65
|
-
const orderId = deriveNetopiaOrderId(session);
|
|
66
|
-
const request = {
|
|
67
|
-
config: {
|
|
68
|
-
emailTemplate: input.emailTemplate ?? runtime.emailTemplate,
|
|
69
|
-
notifyUrl: input.notifyUrl ?? runtime.notifyUrl,
|
|
70
|
-
redirectUrl: input.returnUrl ?? runtime.redirectUrl,
|
|
71
|
-
language: input.language ?? runtime.language,
|
|
72
|
-
},
|
|
73
|
-
payment: {
|
|
74
|
-
options: input.options ?? { installments: 1 },
|
|
75
|
-
instrument: input.instrument,
|
|
76
|
-
data: input.browserData,
|
|
77
|
-
},
|
|
78
|
-
order: {
|
|
79
|
-
ntpID: "",
|
|
80
|
-
posSignature: runtime.posSignature,
|
|
81
|
-
dateTime: new Date().toISOString(),
|
|
82
|
-
description,
|
|
83
|
-
orderID: orderId,
|
|
84
|
-
amount: centsToAmount(session.amountCents),
|
|
85
|
-
currency: normalizeCurrency(session.currency),
|
|
86
|
-
billing: input.billing,
|
|
87
|
-
shipping: input.shipping ?? input.billing,
|
|
88
|
-
products: input.products && input.products.length > 0
|
|
89
|
-
? input.products
|
|
90
|
-
: buildDefaultProducts(session, description),
|
|
91
|
-
installments: input.installments ?? { selected: 1, available: [0] },
|
|
92
|
-
data: input.orderData,
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
const providerResponse = await client.startCardPayment(request);
|
|
96
|
-
const payment = providerResponse.payment;
|
|
97
|
-
if (!payment?.paymentURL) {
|
|
98
|
-
throw new Error("Netopia start payment succeeded without paymentURL");
|
|
99
|
-
}
|
|
100
|
-
const updated = await financeService.markPaymentSessionRequiresRedirect(db, session.id, {
|
|
101
|
-
provider: "netopia",
|
|
102
|
-
providerSessionId: payment.ntpID ?? null,
|
|
103
|
-
providerPaymentId: payment.ntpID ?? null,
|
|
104
|
-
externalReference: orderId,
|
|
105
|
-
redirectUrl: payment.paymentURL,
|
|
106
|
-
returnUrl: input.returnUrl ?? runtime.redirectUrl,
|
|
107
|
-
cancelUrl: input.cancelUrl ?? null,
|
|
108
|
-
callbackUrl: input.callbackUrl ?? input.notifyUrl ?? runtime.notifyUrl,
|
|
109
|
-
providerPayload: mergeRecord(session.providerPayload, {
|
|
110
|
-
netopiaStartRequest: request,
|
|
111
|
-
netopiaStartResponse: providerResponse,
|
|
112
|
-
}),
|
|
113
|
-
metadata: input.metadata ?? undefined,
|
|
114
|
-
notes: input.notes ?? session.notes ?? undefined,
|
|
115
|
-
});
|
|
116
|
-
if (!updated) {
|
|
117
|
-
throw new Error("Payment session disappeared while saving Netopia redirect state");
|
|
118
|
-
}
|
|
119
|
-
return {
|
|
120
|
-
session: updated,
|
|
121
|
-
providerResponse,
|
|
122
|
-
orderId,
|
|
123
|
-
};
|
|
124
|
-
},
|
|
125
|
-
async collectBookingSchedule(db, scheduleId, input, runtimeOptions = {}, clientOverride, dispatcherOverride, bindings) {
|
|
126
|
-
const session = await financeService.createPaymentSessionFromBookingSchedule(db, scheduleId, {
|
|
127
|
-
...(input.paymentSession ?? {}),
|
|
128
|
-
provider: "netopia",
|
|
129
|
-
});
|
|
130
|
-
if (!session) {
|
|
131
|
-
throw new Error("Payment schedule not found");
|
|
132
|
-
}
|
|
133
|
-
const started = await this.startPaymentSession(db, session.id, input.netopia, runtimeOptions, clientOverride, bindings);
|
|
134
|
-
const dispatcher = input.notification
|
|
135
|
-
? resolveNotificationDispatcher(bindings, runtimeOptions, dispatcherOverride)
|
|
136
|
-
: null;
|
|
137
|
-
const paymentSessionNotification = input.notification && dispatcher
|
|
138
|
-
? await notificationsService.sendPaymentSessionNotification(db, dispatcher, started.session.id, input.notification)
|
|
139
|
-
: null;
|
|
140
|
-
return {
|
|
141
|
-
...started,
|
|
142
|
-
paymentSessionNotification,
|
|
143
|
-
invoiceNotification: null,
|
|
144
|
-
};
|
|
145
|
-
},
|
|
146
|
-
async collectBookingGuarantee(db, guaranteeId, input, runtimeOptions = {}, clientOverride, dispatcherOverride, bindings) {
|
|
147
|
-
const session = await financeService.createPaymentSessionFromBookingGuarantee(db, guaranteeId, {
|
|
148
|
-
...(input.paymentSession ?? {}),
|
|
149
|
-
provider: "netopia",
|
|
150
|
-
});
|
|
151
|
-
if (!session) {
|
|
152
|
-
throw new Error("Booking guarantee not found");
|
|
153
|
-
}
|
|
154
|
-
const started = await this.startPaymentSession(db, session.id, input.netopia, runtimeOptions, clientOverride, bindings);
|
|
155
|
-
const dispatcher = input.notification
|
|
156
|
-
? resolveNotificationDispatcher(bindings, runtimeOptions, dispatcherOverride)
|
|
157
|
-
: null;
|
|
158
|
-
const paymentSessionNotification = input.notification && dispatcher
|
|
159
|
-
? await notificationsService.sendPaymentSessionNotification(db, dispatcher, started.session.id, input.notification)
|
|
160
|
-
: null;
|
|
161
|
-
return {
|
|
162
|
-
...started,
|
|
163
|
-
paymentSessionNotification,
|
|
164
|
-
invoiceNotification: null,
|
|
165
|
-
};
|
|
166
|
-
},
|
|
167
|
-
async collectInvoice(db, invoiceId, input, runtimeOptions = {}, clientOverride, dispatcherOverride, bindings) {
|
|
168
|
-
const session = await financeService.createPaymentSessionFromInvoice(db, invoiceId, {
|
|
169
|
-
...(input.paymentSession ?? {}),
|
|
170
|
-
provider: "netopia",
|
|
171
|
-
});
|
|
172
|
-
if (!session) {
|
|
173
|
-
throw new Error("Invoice not found");
|
|
174
|
-
}
|
|
175
|
-
const started = await this.startPaymentSession(db, session.id, input.netopia, runtimeOptions, clientOverride, bindings);
|
|
176
|
-
const shouldNotify = Boolean(input.paymentSessionNotification || input.invoiceNotification);
|
|
177
|
-
const dispatcher = shouldNotify
|
|
178
|
-
? resolveNotificationDispatcher(bindings, runtimeOptions, dispatcherOverride)
|
|
179
|
-
: null;
|
|
180
|
-
const paymentSessionNotification = input.paymentSessionNotification && dispatcher
|
|
181
|
-
? await notificationsService.sendPaymentSessionNotification(db, dispatcher, started.session.id, input.paymentSessionNotification)
|
|
182
|
-
: null;
|
|
183
|
-
const invoiceNotification = input.invoiceNotification && dispatcher
|
|
184
|
-
? await notificationsService.sendInvoiceNotification(db, dispatcher, invoiceId, input.invoiceNotification)
|
|
185
|
-
: null;
|
|
186
|
-
return {
|
|
187
|
-
...started,
|
|
188
|
-
paymentSessionNotification,
|
|
189
|
-
invoiceNotification,
|
|
190
|
-
};
|
|
191
|
-
},
|
|
192
|
-
async handleCallback(db, payload, runtimeOptions = {}, bindings) {
|
|
193
|
-
const runtime = resolveNetopiaRuntimeOptions(bindings, runtimeOptions);
|
|
194
|
-
const orderId = payload.order.orderID;
|
|
195
|
-
const lookup = await financeService.listPaymentSessions(db, {
|
|
196
|
-
provider: "netopia",
|
|
197
|
-
externalReference: orderId,
|
|
198
|
-
limit: 1,
|
|
199
|
-
offset: 0,
|
|
200
|
-
});
|
|
201
|
-
let session = lookup.data[0] ?? null;
|
|
202
|
-
if (!session) {
|
|
203
|
-
session = await financeService.getPaymentSessionById(db, orderId);
|
|
204
|
-
}
|
|
205
|
-
if (!session) {
|
|
206
|
-
return {
|
|
207
|
-
action: "ignored",
|
|
208
|
-
reason: "payment_session_not_found",
|
|
209
|
-
session: null,
|
|
210
|
-
orderId,
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
const callbackState = mapNetopiaPaymentStatus(payload.payment.status, runtime);
|
|
214
|
-
const providerPayload = mergeRecord(session.providerPayload, {
|
|
215
|
-
netopiaCallback: payload,
|
|
216
|
-
});
|
|
217
|
-
const normalizedCurrency = normalizeCurrency(payload.payment.currency);
|
|
218
|
-
const amountCents = amountToCents(payload.payment.amount);
|
|
219
|
-
if (callbackState === "completed" &&
|
|
220
|
-
(normalizedCurrency !== normalizeCurrency(session.currency) ||
|
|
221
|
-
amountCents !== session.amountCents)) {
|
|
222
|
-
const failed = await financeService.failPaymentSession(db, session.id, {
|
|
223
|
-
providerSessionId: payload.payment.ntpID,
|
|
224
|
-
providerPaymentId: payload.payment.ntpID,
|
|
225
|
-
externalReference: orderId,
|
|
226
|
-
failureCode: "amount_or_currency_mismatch",
|
|
227
|
-
failureMessage: `Expected ${session.amountCents} ${normalizeCurrency(session.currency)}, received ${amountCents} ${normalizedCurrency}`,
|
|
228
|
-
providerPayload,
|
|
229
|
-
});
|
|
230
|
-
return {
|
|
231
|
-
action: "failed",
|
|
232
|
-
reason: "amount_or_currency_mismatch",
|
|
233
|
-
session: failed,
|
|
234
|
-
orderId,
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
if (callbackState === "processing") {
|
|
238
|
-
const updated = await financeService.updatePaymentSession(db, session.id, {
|
|
239
|
-
status: "processing",
|
|
240
|
-
provider: "netopia",
|
|
241
|
-
providerSessionId: payload.payment.ntpID,
|
|
242
|
-
providerPaymentId: payload.payment.ntpID,
|
|
243
|
-
externalReference: orderId,
|
|
244
|
-
providerPayload,
|
|
245
|
-
});
|
|
246
|
-
return {
|
|
247
|
-
action: "processing",
|
|
248
|
-
session: updated,
|
|
249
|
-
orderId,
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
if (callbackState === "completed") {
|
|
253
|
-
if (session.status === "paid" || session.status === "authorized") {
|
|
254
|
-
const current = await financeService.updatePaymentSession(db, session.id, {
|
|
255
|
-
provider: "netopia",
|
|
256
|
-
providerSessionId: payload.payment.ntpID,
|
|
257
|
-
providerPaymentId: payload.payment.ntpID,
|
|
258
|
-
externalReference: orderId,
|
|
259
|
-
providerPayload,
|
|
260
|
-
});
|
|
261
|
-
return {
|
|
262
|
-
action: "ignored",
|
|
263
|
-
reason: "already_completed",
|
|
264
|
-
session: current,
|
|
265
|
-
orderId,
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
const completed = await financeService.completePaymentSession(db, session.id, {
|
|
269
|
-
status: "paid",
|
|
270
|
-
captureMode: "manual",
|
|
271
|
-
paymentMethod: "credit_card",
|
|
272
|
-
providerSessionId: payload.payment.ntpID,
|
|
273
|
-
providerPaymentId: payload.payment.ntpID,
|
|
274
|
-
externalReference: orderId,
|
|
275
|
-
externalAuthorizationId: typeof payload.payment.data?.AuthCode === "string"
|
|
276
|
-
? payload.payment.data.AuthCode
|
|
277
|
-
: payload.payment.ntpID,
|
|
278
|
-
externalCaptureId: typeof payload.payment.data?.RRN === "string"
|
|
279
|
-
? payload.payment.data.RRN
|
|
280
|
-
: payload.payment.ntpID,
|
|
281
|
-
approvalCode: typeof payload.payment.data?.AuthCode === "string"
|
|
282
|
-
? payload.payment.data.AuthCode
|
|
283
|
-
: undefined,
|
|
284
|
-
referenceNumber: typeof payload.payment.data?.RRN === "string" ? payload.payment.data.RRN : undefined,
|
|
285
|
-
authorizedAt: new Date().toISOString(),
|
|
286
|
-
capturedAt: new Date().toISOString(),
|
|
287
|
-
paymentDate: new Date().toISOString(),
|
|
288
|
-
providerPayload,
|
|
289
|
-
});
|
|
290
|
-
return {
|
|
291
|
-
action: "completed",
|
|
292
|
-
session: completed,
|
|
293
|
-
orderId,
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
const failed = await financeService.failPaymentSession(db, session.id, {
|
|
297
|
-
providerSessionId: payload.payment.ntpID,
|
|
298
|
-
providerPaymentId: payload.payment.ntpID,
|
|
299
|
-
externalReference: orderId,
|
|
300
|
-
failureCode: typeof payload.payment.code === "string" && payload.payment.code.length > 0
|
|
301
|
-
? payload.payment.code
|
|
302
|
-
: `netopia_status_${payload.payment.status}`,
|
|
303
|
-
failureMessage: typeof payload.payment.message === "string" && payload.payment.message.length > 0
|
|
304
|
-
? payload.payment.message
|
|
305
|
-
: "Netopia payment was not approved",
|
|
306
|
-
providerPayload,
|
|
307
|
-
});
|
|
308
|
-
return {
|
|
309
|
-
action: "failed",
|
|
310
|
-
session: failed,
|
|
311
|
-
orderId,
|
|
312
|
-
};
|
|
313
|
-
},
|
|
7
|
+
startPaymentSession,
|
|
8
|
+
collectBookingSchedule,
|
|
9
|
+
collectBookingGuarantee,
|
|
10
|
+
collectInvoice,
|
|
11
|
+
handleCallback,
|
|
314
12
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voyantjs/plugin-netopia",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"license": "FSL-1.1-Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
"drizzle-orm": "^0.45.2",
|
|
34
34
|
"hono": "^4.12.10",
|
|
35
35
|
"zod": "^4.3.6",
|
|
36
|
-
"@voyantjs/core": "0.
|
|
37
|
-
"@voyantjs/finance": "0.
|
|
38
|
-
"@voyantjs/hono": "0.
|
|
39
|
-
"@voyantjs/notifications": "0.
|
|
36
|
+
"@voyantjs/core": "0.3.0",
|
|
37
|
+
"@voyantjs/finance": "0.3.0",
|
|
38
|
+
"@voyantjs/hono": "0.3.0",
|
|
39
|
+
"@voyantjs/notifications": "0.3.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"typescript": "^6.0.2",
|