9pay-integrate 1.0.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.
@@ -0,0 +1,278 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/next/index.ts
31
+ var next_exports = {};
32
+ __export(next_exports, {
33
+ handle9PayIpn: () => handle9PayIpn,
34
+ handlePaymentStatus: () => handlePaymentStatus,
35
+ verifyNinePayCallback: () => verifyNinePayCallback
36
+ });
37
+ module.exports = __toCommonJS(next_exports);
38
+
39
+ // src/next/handlers.ts
40
+ var import_server = require("next/server");
41
+
42
+ // src/core/verification.ts
43
+ var crypto = __toESM(require("crypto"), 1);
44
+ function verifyChecksum(result, checksum, checksumKey) {
45
+ if (!result || !checksum) {
46
+ return { valid: false, data: null, error: "Missing result or checksum" };
47
+ }
48
+ const expected = crypto.createHash("sha256").update(result + checksumKey).digest("hex").toUpperCase();
49
+ if (expected !== checksum) {
50
+ return { valid: false, data: null, error: "Checksum mismatch" };
51
+ }
52
+ try {
53
+ const decoded = JSON.parse(
54
+ Buffer.from(result, "base64").toString("utf-8")
55
+ );
56
+ return { valid: true, data: decoded };
57
+ } catch {
58
+ return { valid: false, data: null, error: "Failed to decode result" };
59
+ }
60
+ }
61
+
62
+ // src/core/verify-callback.ts
63
+ function verifyCallback(searchParams, config) {
64
+ const result = searchParams.get("result") || "";
65
+ const checksum = searchParams.get("checksum") || "";
66
+ const verification = verifyChecksum(result, checksum, config.checksumKey);
67
+ if (!verification.valid || !verification.data) {
68
+ return {
69
+ success: false,
70
+ rawParams: {},
71
+ error: verification.error || "Invalid callback payload"
72
+ };
73
+ }
74
+ const paymentInfo = verification.data;
75
+ const invoiceNo = paymentInfo.invoice_no;
76
+ const orderId = typeof invoiceNo === "string" && invoiceNo.trim().length > 0 ? invoiceNo : typeof invoiceNo === "number" ? String(invoiceNo) : null;
77
+ if (!orderId) {
78
+ return {
79
+ success: false,
80
+ rawParams: paymentInfo,
81
+ error: "Missing invoice_no in callback payload"
82
+ };
83
+ }
84
+ return {
85
+ success: true,
86
+ orderId,
87
+ amount: Number(paymentInfo.amount ?? 0),
88
+ currency: String(paymentInfo.currency ?? ""),
89
+ status: Number(paymentInfo.status ?? 0),
90
+ rawParams: paymentInfo
91
+ };
92
+ }
93
+
94
+ // src/core/constants.ts
95
+ var NINEPAY_STATUS = {
96
+ PENDING: 1,
97
+ PROCESSING: 2,
98
+ CANCELLED: 3,
99
+ REFUNDED: 4,
100
+ SUCCESS: 5,
101
+ FAILED: 6
102
+ };
103
+ var SUCCESS_STATUS = NINEPAY_STATUS.SUCCESS;
104
+
105
+ // src/core/ipn-processor.ts
106
+ async function processIpn(payload, options) {
107
+ const { config, orderRepository, notifications } = options;
108
+ const verification = verifyChecksum(
109
+ payload.result,
110
+ payload.checksum,
111
+ config.checksumKey
112
+ );
113
+ if (!verification.valid || !verification.data) {
114
+ return {
115
+ success: false,
116
+ message: verification.error || "Invalid checksum",
117
+ statusCode: 403
118
+ };
119
+ }
120
+ const paymentInfo = verification.data;
121
+ const invoiceNo = paymentInfo.invoice_no;
122
+ const orderId = typeof invoiceNo === "string" && invoiceNo.trim().length > 0 ? invoiceNo : typeof invoiceNo === "number" ? String(invoiceNo) : null;
123
+ if (!orderId) {
124
+ return {
125
+ success: false,
126
+ message: "Missing invoice_no in IPN payload",
127
+ statusCode: 400
128
+ };
129
+ }
130
+ const isSuccess = Number(paymentInfo.status) === SUCCESS_STATUS;
131
+ const amount = Number(paymentInfo.amount ?? 0);
132
+ const currency = String(paymentInfo.currency ?? "");
133
+ if (isSuccess) {
134
+ try {
135
+ await orderRepository.updateOrderStatus(orderId, "completed");
136
+ } catch (error) {
137
+ return {
138
+ success: false,
139
+ message: `Failed to update order: ${error instanceof Error ? error.message : String(error)}`,
140
+ statusCode: 500,
141
+ orderId
142
+ };
143
+ }
144
+ if (notifications?.onSuccess) {
145
+ try {
146
+ await notifications.onSuccess({
147
+ orderId,
148
+ status: "completed",
149
+ amount,
150
+ currency,
151
+ paymentMethod: "9pay",
152
+ metadata: paymentInfo
153
+ });
154
+ } catch {
155
+ }
156
+ }
157
+ return {
158
+ success: true,
159
+ message: "Order updated successfully",
160
+ statusCode: 200,
161
+ orderId
162
+ };
163
+ }
164
+ try {
165
+ await orderRepository.updateOrderStatus(orderId, "failed");
166
+ } catch (error) {
167
+ return {
168
+ success: false,
169
+ message: `Failed to update order: ${error instanceof Error ? error.message : String(error)}`,
170
+ statusCode: 500,
171
+ orderId
172
+ };
173
+ }
174
+ if (notifications?.onFailure) {
175
+ try {
176
+ await notifications.onFailure({
177
+ orderId,
178
+ status: "failed",
179
+ amount,
180
+ currency,
181
+ paymentMethod: "9pay",
182
+ metadata: paymentInfo
183
+ });
184
+ } catch {
185
+ }
186
+ }
187
+ return {
188
+ success: false,
189
+ message: "Payment failed",
190
+ statusCode: 200,
191
+ orderId
192
+ };
193
+ }
194
+
195
+ // src/core/ipn.ts
196
+ function extractIpnFromFormData(formData) {
197
+ return {
198
+ result: String(formData.get("result") || ""),
199
+ checksum: String(formData.get("checksum") || "")
200
+ };
201
+ }
202
+ function extractIpnFromUrlEncoded(body) {
203
+ const params = new URLSearchParams(body);
204
+ return {
205
+ result: params.get("result") || "",
206
+ checksum: params.get("checksum") || ""
207
+ };
208
+ }
209
+
210
+ // src/next/handlers.ts
211
+ function verifyNinePayCallback(request, config) {
212
+ const { searchParams } = new URL(request.url);
213
+ return verifyCallback(searchParams, config);
214
+ }
215
+ async function parseIpnRequest(request) {
216
+ const contentType = request.headers.get("content-type") || "";
217
+ if (contentType.includes("multipart/form-data")) {
218
+ const formData = await request.formData();
219
+ const extracted = extractIpnFromFormData(formData);
220
+ return extracted;
221
+ }
222
+ if (contentType.includes("application/x-www-form-urlencoded")) {
223
+ const body = await request.text();
224
+ const extracted = extractIpnFromUrlEncoded(body);
225
+ return extracted;
226
+ }
227
+ throw new Error(`Unsupported IPN content-type: ${contentType || "unknown"}`);
228
+ }
229
+ async function handle9PayIpn(request, options) {
230
+ try {
231
+ const payload = await parseIpnRequest(request);
232
+ const result = await processIpn(payload, {
233
+ config: options.config,
234
+ orderRepository: options.orderRepository,
235
+ notifications: options.notifications
236
+ });
237
+ return import_server.NextResponse.json(
238
+ { success: result.success, message: result.message },
239
+ { status: result.statusCode }
240
+ );
241
+ } catch {
242
+ return import_server.NextResponse.json(
243
+ { success: false, message: "Unsupported or invalid form payload" },
244
+ { status: 400 }
245
+ );
246
+ }
247
+ }
248
+ async function handlePaymentStatus(request, options) {
249
+ const { searchParams } = new URL(request.url);
250
+ const orderId = searchParams.get("orderId");
251
+ if (!orderId) {
252
+ return import_server.NextResponse.json(
253
+ { success: false, message: "Missing orderId" },
254
+ { status: 400 }
255
+ );
256
+ }
257
+ try {
258
+ const status = await options.orderRepository.getOrderStatus(orderId);
259
+ if (status === null) {
260
+ return import_server.NextResponse.json(
261
+ { success: false, message: "Order not found" },
262
+ { status: 404 }
263
+ );
264
+ }
265
+ return import_server.NextResponse.json({ success: true, status });
266
+ } catch {
267
+ return import_server.NextResponse.json(
268
+ { success: false, message: "Internal server error" },
269
+ { status: 500 }
270
+ );
271
+ }
272
+ }
273
+ // Annotate the CommonJS export names for ESM import in node:
274
+ 0 && (module.exports = {
275
+ handle9PayIpn,
276
+ handlePaymentStatus,
277
+ verifyNinePayCallback
278
+ });
@@ -0,0 +1,101 @@
1
+ import { NextResponse } from 'next/server';
2
+
3
+ /**
4
+ * Core type definitions for 9Pay integration.
5
+ * Framework-agnostic — no Next.js, React, or Express dependencies.
6
+ */
7
+ interface NinePayConfig {
8
+ merchantKey: string;
9
+ secretKey: string;
10
+ checksumKey: string;
11
+ baseUrl?: string;
12
+ returnUrl: string;
13
+ cancelUrl?: string;
14
+ }
15
+ interface VerifyCallbackResult {
16
+ success: boolean;
17
+ orderId?: string;
18
+ amount?: number;
19
+ currency?: string;
20
+ status?: number;
21
+ /** Raw decoded payload for consumer access */
22
+ rawParams: Record<string, unknown>;
23
+ error?: string;
24
+ }
25
+ type OrderStatus = "neworder" | "pending" | "completed" | "failed";
26
+ interface CreateOrderParams {
27
+ orderId?: string;
28
+ totalAmount: number;
29
+ currency: string;
30
+ paymentMethod: string;
31
+ metadata?: Record<string, unknown>;
32
+ }
33
+ interface OrderRepository<TOrder = unknown> {
34
+ createOrder(params: CreateOrderParams): Promise<TOrder>;
35
+ getOrderStatus(orderId: string): Promise<OrderStatus | null>;
36
+ updateOrderStatus(orderId: string, status: "completed" | "failed"): Promise<TOrder | null>;
37
+ }
38
+ interface NotificationContext {
39
+ orderId: string;
40
+ status: "completed" | "failed";
41
+ amount: number;
42
+ currency: string;
43
+ paymentMethod: string;
44
+ metadata?: Record<string, unknown>;
45
+ }
46
+ type NotificationHandler = (ctx: NotificationContext) => Promise<void> | void;
47
+ interface NotificationHooks {
48
+ onSuccess?: NotificationHandler;
49
+ onFailure?: NotificationHandler;
50
+ }
51
+
52
+ /**
53
+ * Verify 9Pay browser redirect callback (GET /api/checkout/9pay/callback).
54
+ *
55
+ * Returns a result object — consumer decides the redirect target.
56
+ * This is Option B: package does NOT redirect, only verifies and returns data.
57
+ *
58
+ * Example consumer usage:
59
+ * const result = verifyNinePayCallback(request, config);
60
+ * if (!result.success) return NextResponse.redirect(new URL("/payment/error", request.url));
61
+ * return NextResponse.redirect(new URL(`/payment/process?orderId=${result.orderId}`, request.url));
62
+ */
63
+ declare function verifyNinePayCallback(request: Request, config: NinePayConfig): VerifyCallbackResult;
64
+ /**
65
+ * Handle 9Pay IPN webhook (POST /api/checkout/9pay/ipn).
66
+ *
67
+ * This is Option C: the handler encapsulates all 6 steps:
68
+ * 1. Parse request (content-type detection + extraction)
69
+ * 2. Verify checksum (via core)
70
+ * 3. Decode payment info
71
+ * 4. Call orderRepository.updateOrderStatus()
72
+ * 5. Call notification hooks
73
+ * 6. Return NextResponse with appropriate status code
74
+ *
75
+ * Consumer usage:
76
+ * export async function POST(request: Request) {
77
+ * return handle9PayIpn(request, {
78
+ * config,
79
+ * orderRepository: repo,
80
+ * notifications: { onSuccess, onFailure },
81
+ * });
82
+ * }
83
+ */
84
+ declare function handle9PayIpn(request: Request, options: {
85
+ config: NinePayConfig;
86
+ orderRepository: OrderRepository;
87
+ notifications?: NotificationHooks;
88
+ }): Promise<NextResponse>;
89
+ /**
90
+ * Handle payment status polling (GET /api/payment/status).
91
+ *
92
+ * Consumer usage:
93
+ * export async function GET(request: Request) {
94
+ * return handlePaymentStatus(request, { orderRepository: repo });
95
+ * }
96
+ */
97
+ declare function handlePaymentStatus(request: Request, options: {
98
+ orderRepository: OrderRepository;
99
+ }): Promise<NextResponse>;
100
+
101
+ export { type NinePayConfig, type NotificationHooks, type OrderRepository, type VerifyCallbackResult, handle9PayIpn, handlePaymentStatus, verifyNinePayCallback };
@@ -0,0 +1,101 @@
1
+ import { NextResponse } from 'next/server';
2
+
3
+ /**
4
+ * Core type definitions for 9Pay integration.
5
+ * Framework-agnostic — no Next.js, React, or Express dependencies.
6
+ */
7
+ interface NinePayConfig {
8
+ merchantKey: string;
9
+ secretKey: string;
10
+ checksumKey: string;
11
+ baseUrl?: string;
12
+ returnUrl: string;
13
+ cancelUrl?: string;
14
+ }
15
+ interface VerifyCallbackResult {
16
+ success: boolean;
17
+ orderId?: string;
18
+ amount?: number;
19
+ currency?: string;
20
+ status?: number;
21
+ /** Raw decoded payload for consumer access */
22
+ rawParams: Record<string, unknown>;
23
+ error?: string;
24
+ }
25
+ type OrderStatus = "neworder" | "pending" | "completed" | "failed";
26
+ interface CreateOrderParams {
27
+ orderId?: string;
28
+ totalAmount: number;
29
+ currency: string;
30
+ paymentMethod: string;
31
+ metadata?: Record<string, unknown>;
32
+ }
33
+ interface OrderRepository<TOrder = unknown> {
34
+ createOrder(params: CreateOrderParams): Promise<TOrder>;
35
+ getOrderStatus(orderId: string): Promise<OrderStatus | null>;
36
+ updateOrderStatus(orderId: string, status: "completed" | "failed"): Promise<TOrder | null>;
37
+ }
38
+ interface NotificationContext {
39
+ orderId: string;
40
+ status: "completed" | "failed";
41
+ amount: number;
42
+ currency: string;
43
+ paymentMethod: string;
44
+ metadata?: Record<string, unknown>;
45
+ }
46
+ type NotificationHandler = (ctx: NotificationContext) => Promise<void> | void;
47
+ interface NotificationHooks {
48
+ onSuccess?: NotificationHandler;
49
+ onFailure?: NotificationHandler;
50
+ }
51
+
52
+ /**
53
+ * Verify 9Pay browser redirect callback (GET /api/checkout/9pay/callback).
54
+ *
55
+ * Returns a result object — consumer decides the redirect target.
56
+ * This is Option B: package does NOT redirect, only verifies and returns data.
57
+ *
58
+ * Example consumer usage:
59
+ * const result = verifyNinePayCallback(request, config);
60
+ * if (!result.success) return NextResponse.redirect(new URL("/payment/error", request.url));
61
+ * return NextResponse.redirect(new URL(`/payment/process?orderId=${result.orderId}`, request.url));
62
+ */
63
+ declare function verifyNinePayCallback(request: Request, config: NinePayConfig): VerifyCallbackResult;
64
+ /**
65
+ * Handle 9Pay IPN webhook (POST /api/checkout/9pay/ipn).
66
+ *
67
+ * This is Option C: the handler encapsulates all 6 steps:
68
+ * 1. Parse request (content-type detection + extraction)
69
+ * 2. Verify checksum (via core)
70
+ * 3. Decode payment info
71
+ * 4. Call orderRepository.updateOrderStatus()
72
+ * 5. Call notification hooks
73
+ * 6. Return NextResponse with appropriate status code
74
+ *
75
+ * Consumer usage:
76
+ * export async function POST(request: Request) {
77
+ * return handle9PayIpn(request, {
78
+ * config,
79
+ * orderRepository: repo,
80
+ * notifications: { onSuccess, onFailure },
81
+ * });
82
+ * }
83
+ */
84
+ declare function handle9PayIpn(request: Request, options: {
85
+ config: NinePayConfig;
86
+ orderRepository: OrderRepository;
87
+ notifications?: NotificationHooks;
88
+ }): Promise<NextResponse>;
89
+ /**
90
+ * Handle payment status polling (GET /api/payment/status).
91
+ *
92
+ * Consumer usage:
93
+ * export async function GET(request: Request) {
94
+ * return handlePaymentStatus(request, { orderRepository: repo });
95
+ * }
96
+ */
97
+ declare function handlePaymentStatus(request: Request, options: {
98
+ orderRepository: OrderRepository;
99
+ }): Promise<NextResponse>;
100
+
101
+ export { type NinePayConfig, type NotificationHooks, type OrderRepository, type VerifyCallbackResult, handle9PayIpn, handlePaymentStatus, verifyNinePayCallback };