@voyantjs/plugin-netopia 0.2.0 → 0.3.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/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: "cancelled" | "expired" | "pending" | "failed" | "paid" | "requires_redirect" | "processing" | "authorized";
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: "cancelled" | "expired" | "pending" | "failed" | "paid" | "requires_redirect" | "processing" | "authorized";
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;
@@ -291,7 +291,7 @@ export declare function createNetopiaFinanceRoutes(options?: NetopiaRuntimeOptio
291
291
  error: string;
292
292
  };
293
293
  outputFormat: "json";
294
- status: 500 | 404 | 409 | 502;
294
+ status: 404 | 409 | 500 | 502;
295
295
  };
296
296
  };
297
297
  } & {
@@ -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: "cancelled" | "expired" | "pending" | "failed" | "paid" | "requires_redirect" | "processing" | "authorized";
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;
@@ -452,7 +452,7 @@ export declare function createNetopiaFinanceRoutes(options?: NetopiaRuntimeOptio
452
452
  error: string;
453
453
  };
454
454
  outputFormat: "json";
455
- status: 500 | 404 | 409 | 502;
455
+ status: 404 | 409 | 500 | 502;
456
456
  };
457
457
  };
458
458
  } & {
@@ -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: "cancelled" | "expired" | "pending" | "failed" | "paid" | "requires_redirect" | "processing" | "authorized";
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;
@@ -609,7 +609,7 @@ export declare function createNetopiaFinanceRoutes(options?: NetopiaRuntimeOptio
609
609
  error: string;
610
610
  };
611
611
  outputFormat: "json";
612
- status: 500 | 404 | 409 | 502;
612
+ status: 404 | 409 | 500 | 502;
613
613
  };
614
614
  };
615
615
  } & {
@@ -622,14 +622,14 @@ export declare function createNetopiaFinanceRoutes(options?: NetopiaRuntimeOptio
622
622
  reason?: string | undefined;
623
623
  session: {
624
624
  id: string;
625
- status: "cancelled" | "expired" | "pending" | "failed" | "paid" | "requires_redirect" | "processing" | "authorized";
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 { type PaymentSession } from "@voyantjs/finance";
2
- import { type NotificationDelivery, type NotificationService } from "@voyantjs/notifications";
3
- import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
4
- import type { z } from "zod";
5
- import { type NetopiaClientApi } from "./client.js";
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(db: PostgresJsDatabase, sessionId: string, input: NetopiaStartPaymentInput, runtimeOptions?: NetopiaRuntimeOptions, clientOverride?: NetopiaClientApi, bindings?: Record<string, unknown>): Promise<NetopiaStartPaymentResult>;
30
- collectBookingSchedule(db: PostgresJsDatabase, scheduleId: string, input: NetopiaCollectBookingScheduleInput, runtimeOptions?: NetopiaRuntimeOptions, clientOverride?: NetopiaClientApi, dispatcherOverride?: NotificationService, bindings?: Record<string, unknown>): Promise<NetopiaCollectPaymentResult>;
31
- collectBookingGuarantee(db: PostgresJsDatabase, guaranteeId: string, input: NetopiaCollectBookingGuaranteeInput, runtimeOptions?: NetopiaRuntimeOptions, clientOverride?: NetopiaClientApi, dispatcherOverride?: NotificationService, bindings?: Record<string, unknown>): Promise<NetopiaCollectPaymentResult>;
32
- collectInvoice(db: PostgresJsDatabase, invoiceId: string, input: NetopiaCollectInvoiceInput, runtimeOptions?: NetopiaRuntimeOptions, clientOverride?: NetopiaClientApi, dispatcherOverride?: NotificationService, bindings?: Record<string, unknown>): Promise<NetopiaCollectPaymentResult>;
33
- handleCallback(db: PostgresJsDatabase, payload: NetopiaWebhookPayload, runtimeOptions?: NetopiaRuntimeOptions, bindings?: Record<string, unknown>): Promise<NetopiaCallbackResult>;
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
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACvE,OAAO,EAGL,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EAEzB,MAAM,yBAAyB,CAAA;AAChC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAE5B,OAAO,EAEL,KAAK,gBAAgB,EAEtB,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EAEV,qBAAqB,EACrB,wBAAwB,EAExB,2BAA2B,EAC3B,qBAAqB,EACrB,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,KAAK,kCAAkC,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mCAAmC,CAAC,CAAA;AAC7F,KAAK,mCAAmC,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oCAAoC,CAAC,CAAA;AAC/F,KAAK,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAA;AAkC7E,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;AAiBD,eAAO,MAAM,cAAc;4BAEnB,kBAAkB,aACX,MAAM,SACV,wBAAwB,mBACf,qBAAqB,mBACpB,gBAAgB,aACtB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,yBAAyB,CAAC;+BA+F/B,kBAAkB,cACV,MAAM,SACX,kCAAkC,mBACzB,qBAAqB,mBACpB,gBAAgB,uBACZ,mBAAmB,aAC7B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,2BAA2B,CAAC;gCAyCjC,kBAAkB,eACT,MAAM,SACZ,mCAAmC,mBAC1B,qBAAqB,mBACpB,gBAAgB,uBACZ,mBAAmB,aAC7B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,2BAA2B,CAAC;uBAyCjC,kBAAkB,aACX,MAAM,SACV,0BAA0B,mBACjB,qBAAqB,mBACpB,gBAAgB,uBACZ,mBAAmB,aAC7B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,2BAA2B,CAAC;uBAoDjC,kBAAkB,WACb,qBAAqB,mBACd,qBAAqB,aAC1B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,qBAAqB,CAAC;CA+IlC,CAAA"}
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 { financeService } from "@voyantjs/finance";
2
- import { createDefaultNotificationProviders, createNotificationService, notificationsService, } from "@voyantjs/notifications";
3
- import { createNetopiaClient, resolveNetopiaRuntimeOptions, } from "./client.js";
4
- function centsToAmount(cents) {
5
- return Number((cents / 100).toFixed(2));
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
- async startPaymentSession(db, sessionId, input, runtimeOptions = {}, clientOverride, bindings) {
47
- const session = await financeService.getPaymentSessionById(db, sessionId);
48
- if (!session) {
49
- throw new Error("Payment session not found");
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.2.0",
3
+ "version": "0.3.1",
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.2.0",
37
- "@voyantjs/finance": "0.2.0",
38
- "@voyantjs/hono": "0.2.0",
39
- "@voyantjs/notifications": "0.2.0"
36
+ "@voyantjs/core": "0.3.1",
37
+ "@voyantjs/finance": "0.3.1",
38
+ "@voyantjs/hono": "0.3.1",
39
+ "@voyantjs/notifications": "0.3.1"
40
40
  },
41
41
  "devDependencies": {
42
42
  "typescript": "^6.0.2",