aba-payway-sdk-unofficial 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 rithsila
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,200 @@
1
+ # aba-payway-unofficial
2
+
3
+ ![npm version](https://img.shields.io/npm/v/aba-payway-sdk-unofficial)
4
+
5
+ Unofficial ABA PayWay SDK for Cambodia. Supports KHQR generation, purchase creation, status checking, and webhook verification. Works in Node.js 18+ and Deno.
6
+
7
+ > **Disclaimer**: This is an unofficial SDK. It is not affiliated with, endorsed by, or supported by ABA Bank or ABA PayWay.
8
+
9
+ ---
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install aba-payway-sdk-unofficial
15
+ ```
16
+
17
+ ---
18
+
19
+ ## Quick Start
20
+
21
+ ```typescript
22
+ import {
23
+ ABAPayWay,
24
+ generateKHQR,
25
+ generateTransactionId,
26
+ } from "aba-payway-sdk-unofficial";
27
+
28
+ // 1. Create client
29
+ const aba = new ABAPayWay({
30
+ merchantId: "your_merchant_id",
31
+ apiKey: "your_api_key",
32
+ baseUrl: "https://checkout.payway.com.kh",
33
+ webhookSecret: "your_webhook_secret", // optional
34
+ });
35
+
36
+ // 2. Create a purchase
37
+ const txnId = generateTransactionId();
38
+
39
+ const purchase = await aba.createPurchase({
40
+ transactionId: txnId,
41
+ amount: 10.0,
42
+ currency: "USD",
43
+ items: "Product A",
44
+ firstName: "Dara",
45
+ lastName: "Chan",
46
+ email: "dara@example.com",
47
+ returnUrl: "https://yoursite.com/success",
48
+ cancelUrl: "https://yoursite.com/cancel",
49
+ });
50
+
51
+ if (purchase.success) {
52
+ console.log("Checkout URL:", purchase.checkoutUrl);
53
+ }
54
+
55
+ // 3. Check payment status
56
+ const status = await aba.checkStatus(txnId);
57
+ console.log("Payment status:", status.status); // "APPROVED" | "PENDING" | ...
58
+
59
+ // 4. Verify a webhook (signature comes in the X-PAYWAY-HMAC-SHA512 header)
60
+ const isValid = await aba.verifyWebhook(
61
+ rawBody, // raw JSON callback body
62
+ signatureHeader, // value of the X-PAYWAY-HMAC-SHA512 header
63
+ webhookSecret,
64
+ );
65
+
66
+ // 5. Generate a KHQR image (base64 SVG data URI)
67
+ const khqrImage = await generateKHQR({
68
+ emvData: purchase.qrString ?? "",
69
+ amount: 10.0,
70
+ currency: "USD",
71
+ merchantName: "My Shop",
72
+ headerColor: "#d42b2b",
73
+ });
74
+ console.log("KHQR image:", khqrImage); // "data:image/svg+xml;base64,..."
75
+ ```
76
+
77
+ ---
78
+
79
+ ## API Reference
80
+
81
+ | Export | Type | Description |
82
+ | -------------------------- | -------- | --------------------------------------------------- |
83
+ | `ABAPayWay` | class | Main client. Constructor takes `ABAConfig`. |
84
+ | `ABAPayWay.createPurchase` | method | Create a payment. Returns `PurchaseResponse`. |
85
+ | `ABAPayWay.checkStatus` | method | Check transaction status. Returns `StatusResponse`. |
86
+ | `ABAPayWay.verifyWebhook` | method | Verify webhook signature. Returns `boolean`. |
87
+ | `generateKHQR` | function | Build a KHQR image (base64 SVG data URI) from EMV data. Async. |
88
+ | `generateABAHash` | function | Generate HMAC-SHA512 hash for ABA API calls. |
89
+ | `generateTransactionId` | function | Generate a unique transaction ID. |
90
+ | `getABATimestamp` | function | Get current timestamp in ABA format. |
91
+ | `formatPhoneForABA` | function | Normalize phone number for ABA API. |
92
+ | `getQRExpiration` | function | Get QR code expiration timestamp. |
93
+
94
+ ### Types
95
+
96
+ | Type | Description |
97
+ | ------------------ | ---------------------------------------------------------------- |
98
+ | `ABAConfig` | SDK configuration (merchantId, apiKey, baseUrl, webhookSecret) |
99
+ | `PurchaseRequest` | Input for `createPurchase` |
100
+ | `PurchaseResponse` | Result from `createPurchase` |
101
+ | `StatusResponse` | Result from `checkStatus` |
102
+ | `PaymentStatus` | `"PENDING" \| "APPROVED" \| "PRE-AUTH" \| "DECLINED" \| "REFUNDED" \| "CANCELLED" \| "ERROR"` |
103
+ | `KHQROptions` | Input for `generateKHQR` |
104
+ | `HashParams` | Raw parameters for hash generation |
105
+
106
+ ---
107
+
108
+ ## KHQR Customization
109
+
110
+ Use `KHQROptions` to customize the QR code display. `generateKHQR` is async and
111
+ returns a base64-encoded SVG data URI you can use directly as an `<img>` `src`:
112
+
113
+ ```typescript
114
+ import { generateKHQR } from "aba-payway-sdk-unofficial";
115
+
116
+ const dataUri = await generateKHQR({
117
+ emvData: "your_emv_qr_string",
118
+ amount: 5.5,
119
+ currency: "USD",
120
+ merchantName: "Rith Shop", // shown above the QR code
121
+ headerColor: "#1a73e8", // hex color for the header bar
122
+ });
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Environment Setup
128
+
129
+ You need ABA PayWay merchant credentials to use this SDK:
130
+
131
+ | Variable | Description |
132
+ | --------------- | ---------------------------------------------------------- |
133
+ | `merchantId` | Your ABA PayWay merchant ID |
134
+ | `apiKey` | Your ABA PayWay API key |
135
+ | `baseUrl` | ABA PayWay base URL (e.g.`https://checkout.payway.com.kh`) |
136
+ | `webhookSecret` | Secret for webhook signature verification (optional) |
137
+
138
+ Contact ABA Bank to get these credentials.
139
+
140
+ ---
141
+
142
+ ## Development
143
+
144
+ ```bash
145
+ # Run unit tests (mocked, no network)
146
+ npm test
147
+
148
+ # Build the package
149
+ npm run build
150
+ ```
151
+
152
+ ---
153
+
154
+ ## Sandbox Testing
155
+
156
+ You can test the SDK against the real ABA PayWay sandbox. This makes live
157
+ network calls, so it needs sandbox credentials.
158
+
159
+ ### 1. Get sandbox credentials
160
+
161
+ Register at https://sandbox.payway.com.kh/register-sandbox/. ABA will email you
162
+ a merchant ID, an API key, and a webhook secret.
163
+
164
+ ### 2. Set up your `.env` file
165
+
166
+ Copy the example file and fill in your values:
167
+
168
+ ```bash
169
+ cp .env.example .env
170
+ ```
171
+
172
+ Then edit `.env`:
173
+
174
+ ```bash
175
+ ABA_MERCHANT_ID=your_sandbox_merchant_id
176
+ ABA_API_KEY=your_sandbox_api_key
177
+ ABA_BASE_URL=https://checkout-sandbox.payway.com.kh
178
+ ABA_WEBHOOK_SECRET=your_sandbox_webhook_secret
179
+ ```
180
+
181
+ `.env` is gitignored. Never commit real credentials.
182
+
183
+ ### 3. Run the sandbox tests
184
+
185
+ ```bash
186
+ npm run test:sandbox
187
+ ```
188
+
189
+ This creates a real `$1.00` test purchase and checks its status. A new
190
+ transaction stays `PENDING` until you pay it by opening the returned checkout
191
+ URL in a browser.
192
+
193
+ If you run `npm run test:sandbox` without a `.env` file, the tests are skipped
194
+ (not failed), so they never block a normal `npm test` run.
195
+
196
+ ---
197
+
198
+ ## License
199
+
200
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,396 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ABAPayWay: () => ABAPayWay,
24
+ formatPhoneForABA: () => formatPhoneForABA,
25
+ generateABAHash: () => generateABAHash,
26
+ generateKHQR: () => generateKHQR,
27
+ generateTransactionId: () => generateTransactionId,
28
+ getABATimestamp: () => getABATimestamp,
29
+ getQRExpiration: () => getQRExpiration
30
+ });
31
+ module.exports = __toCommonJS(index_exports);
32
+
33
+ // src/hash.ts
34
+ async function generateABAHash(params, publicKey) {
35
+ const hashString = [
36
+ params.req_time,
37
+ params.merchant_id,
38
+ params.tran_id,
39
+ params.amount ?? "",
40
+ params.items ?? "",
41
+ params.shipping ?? "",
42
+ params.ctid ?? "",
43
+ params.pwt ?? "",
44
+ params.firstname ?? "",
45
+ params.lastname ?? "",
46
+ params.email ?? "",
47
+ params.phone ?? "",
48
+ params.type ?? "",
49
+ params.payment_option ?? "",
50
+ params.return_url ?? "",
51
+ params.cancel_url ?? "",
52
+ params.continue_success_url ?? "",
53
+ params.return_deeplink ?? "",
54
+ params.currency ?? "",
55
+ params.custom_fields ?? "",
56
+ params.return_params ?? ""
57
+ ].join("");
58
+ const encoder = new TextEncoder();
59
+ const key = await crypto.subtle.importKey(
60
+ "raw",
61
+ encoder.encode(publicKey),
62
+ { name: "HMAC", hash: "SHA-512" },
63
+ false,
64
+ ["sign"]
65
+ );
66
+ const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(hashString));
67
+ return btoa(String.fromCharCode(...new Uint8Array(signature)));
68
+ }
69
+
70
+ // src/utils.ts
71
+ function generateTransactionId() {
72
+ const timestamp = Date.now().toString(36).toUpperCase();
73
+ const random = Math.random().toString(36).substring(2, 6).toUpperCase();
74
+ return `EA${timestamp}${random}`.substring(0, 20);
75
+ }
76
+ function getABATimestamp() {
77
+ const now = /* @__PURE__ */ new Date();
78
+ const pad = (n) => n.toString().padStart(2, "0");
79
+ return now.getFullYear().toString() + pad(now.getMonth() + 1) + pad(now.getDate()) + pad(now.getHours()) + pad(now.getMinutes()) + pad(now.getSeconds());
80
+ }
81
+ function formatPhoneForABA(phone) {
82
+ if (!phone) return "";
83
+ const cleaned = phone.replace(/[\s-]/g, "");
84
+ if (cleaned.startsWith("+855")) return "0" + cleaned.slice(4);
85
+ if (cleaned.startsWith("855")) return "0" + cleaned.slice(3);
86
+ return cleaned;
87
+ }
88
+ function getQRExpiration() {
89
+ return new Date(Date.now() + 15 * 60 * 1e3);
90
+ }
91
+
92
+ // src/client.ts
93
+ var ABAPayWay = class {
94
+ config;
95
+ constructor(config) {
96
+ if (!config.merchantId) throw new Error("merchantId is required");
97
+ if (!config.apiKey) throw new Error("apiKey is required");
98
+ this.config = Object.freeze({ ...config });
99
+ }
100
+ async createPurchase(request) {
101
+ const reqTime = getABATimestamp();
102
+ const amount = request.currency === "KHR" ? Math.round(request.amount).toString() : request.amount.toFixed(2);
103
+ const phone = request.phone ? formatPhoneForABA(request.phone) : "";
104
+ const returnUrl = request.returnUrl ? base64(request.returnUrl) : "";
105
+ const cancelUrl = request.cancelUrl ? base64(request.cancelUrl) : "";
106
+ const continueSuccessUrl = request.continueSuccessUrl ? base64(request.continueSuccessUrl) : "";
107
+ const returnDeeplink = request.returnDeeplink ? base64(request.returnDeeplink) : "";
108
+ const hashParams = {
109
+ req_time: reqTime,
110
+ merchant_id: this.config.merchantId,
111
+ tran_id: request.transactionId,
112
+ amount,
113
+ items: request.items ?? "",
114
+ shipping: "",
115
+ ctid: "",
116
+ pwt: "",
117
+ firstname: request.firstName ?? "",
118
+ lastname: request.lastName ?? "",
119
+ email: request.email ?? "",
120
+ phone,
121
+ type: "",
122
+ payment_option: request.paymentOption ?? "",
123
+ return_url: returnUrl,
124
+ cancel_url: cancelUrl,
125
+ continue_success_url: continueSuccessUrl,
126
+ return_deeplink: returnDeeplink,
127
+ currency: request.currency,
128
+ custom_fields: request.customFields ?? "",
129
+ return_params: request.returnParams ?? ""
130
+ };
131
+ const hash = await generateABAHash(hashParams, this.config.apiKey);
132
+ const body = new URLSearchParams({
133
+ req_time: reqTime,
134
+ merchant_id: this.config.merchantId,
135
+ tran_id: request.transactionId,
136
+ amount,
137
+ items: request.items ?? "",
138
+ firstname: request.firstName ?? "",
139
+ lastname: request.lastName ?? "",
140
+ email: request.email ?? "",
141
+ phone,
142
+ payment_option: request.paymentOption ?? "",
143
+ return_url: returnUrl,
144
+ cancel_url: cancelUrl,
145
+ continue_success_url: continueSuccessUrl,
146
+ return_deeplink: returnDeeplink,
147
+ currency: request.currency,
148
+ custom_fields: request.customFields ?? "",
149
+ return_params: request.returnParams ?? "",
150
+ hash
151
+ });
152
+ const url = `${this.config.baseUrl}/api/payment-gateway/v1/payments/purchase`;
153
+ try {
154
+ const response = await fetch(url, {
155
+ method: "POST",
156
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
157
+ body: body.toString()
158
+ });
159
+ if (!response.ok) {
160
+ const text = await response.text();
161
+ return {
162
+ success: false,
163
+ transactionId: request.transactionId,
164
+ amount: request.amount,
165
+ currency: request.currency,
166
+ error: `HTTP ${response.status}: ${text}`
167
+ };
168
+ }
169
+ const data = await response.json();
170
+ if (!isABASuccess(data.status)) {
171
+ return {
172
+ success: false,
173
+ transactionId: request.transactionId,
174
+ amount: request.amount,
175
+ currency: request.currency,
176
+ error: abaErrorMessage(data),
177
+ errorCode: abaStatusCode(data.status)
178
+ };
179
+ }
180
+ return {
181
+ success: true,
182
+ transactionId: request.transactionId,
183
+ amount: request.amount,
184
+ currency: request.currency,
185
+ checkoutUrl: data.checkout_qr_url ?? data.checkout_url,
186
+ abapayDeeplink: data.abapay_deeplink,
187
+ qrString: data.qr_string
188
+ };
189
+ } catch (err) {
190
+ return {
191
+ success: false,
192
+ transactionId: request.transactionId,
193
+ amount: request.amount,
194
+ currency: request.currency,
195
+ error: err instanceof Error ? err.message : "Unknown error"
196
+ };
197
+ }
198
+ }
199
+ async checkStatus(transactionId) {
200
+ const reqTime = getABATimestamp();
201
+ const hashParams = {
202
+ req_time: reqTime,
203
+ merchant_id: this.config.merchantId,
204
+ tran_id: transactionId
205
+ };
206
+ const hash = await generateABAHash(hashParams, this.config.apiKey);
207
+ const body = JSON.stringify({
208
+ req_time: reqTime,
209
+ merchant_id: this.config.merchantId,
210
+ tran_id: transactionId,
211
+ hash
212
+ });
213
+ const url = `${this.config.baseUrl}/api/payment-gateway/v1/payments/check-transaction-2`;
214
+ try {
215
+ const response = await fetch(url, {
216
+ method: "POST",
217
+ headers: { "Content-Type": "application/json" },
218
+ body
219
+ });
220
+ if (!response.ok) {
221
+ const text = await response.text();
222
+ return {
223
+ success: false,
224
+ transactionId,
225
+ status: "ERROR",
226
+ error: `HTTP ${response.status}: ${text}`
227
+ };
228
+ }
229
+ const data = await response.json();
230
+ if (!isABASuccess(data.status)) {
231
+ return {
232
+ success: false,
233
+ transactionId,
234
+ status: "ERROR",
235
+ error: abaErrorMessage(data)
236
+ };
237
+ }
238
+ const tran = data.data ?? data;
239
+ const paymentStatus = mapPaymentStatus(tran.payment_status);
240
+ const rawAmount = tran.payment_amount ?? tran.amount;
241
+ const amount = rawAmount != null ? parseFloat(String(rawAmount)) : void 0;
242
+ return {
243
+ success: true,
244
+ transactionId,
245
+ status: paymentStatus,
246
+ amount: amount != null && Number.isFinite(amount) ? amount : void 0,
247
+ currency: tran.payment_currency ?? tran.currency,
248
+ paymentTime: tran.payment_datetime ?? tran.payment_time
249
+ };
250
+ } catch (err) {
251
+ return {
252
+ success: false,
253
+ transactionId,
254
+ status: "ERROR",
255
+ error: err instanceof Error ? err.message : "Unknown error"
256
+ };
257
+ }
258
+ }
259
+ /**
260
+ * Verify an ABA PayWay callback (pushback) signature.
261
+ *
262
+ * ABA does NOT sign the raw body. It sorts the callback fields by key
263
+ * (ascending), concatenates their values (JSON-encoding any nested
264
+ * object/array), then HMAC-SHA512 with the secret and Base64-encodes
265
+ * the result. The signature arrives in the `X-PAYWAY-HMAC-SHA512`
266
+ * request header.
267
+ *
268
+ * @param payload Raw JSON callback body.
269
+ * @param signature Value of the `X-PAYWAY-HMAC-SHA512` header.
270
+ * @param secret Merchant key used to sign callbacks.
271
+ */
272
+ async verifyWebhook(payload, signature, secret) {
273
+ try {
274
+ const parsed = JSON.parse(payload);
275
+ const b4hash = Object.keys(parsed).sort().map((k) => {
276
+ const v = parsed[k];
277
+ return v !== null && typeof v === "object" ? JSON.stringify(v) : String(v);
278
+ }).join("");
279
+ const encoder = new TextEncoder();
280
+ const key = await crypto.subtle.importKey(
281
+ "raw",
282
+ encoder.encode(secret),
283
+ { name: "HMAC", hash: "SHA-512" },
284
+ false,
285
+ ["sign"]
286
+ );
287
+ const sig = await crypto.subtle.sign("HMAC", key, encoder.encode(b4hash));
288
+ const expected = btoa(String.fromCharCode(...new Uint8Array(sig)));
289
+ if (expected.length !== signature.length) return false;
290
+ let mismatch = 0;
291
+ for (let i = 0; i < expected.length; i++) {
292
+ mismatch |= expected.charCodeAt(i) ^ signature.charCodeAt(i);
293
+ }
294
+ return mismatch === 0;
295
+ } catch {
296
+ return false;
297
+ }
298
+ }
299
+ };
300
+ function base64(value) {
301
+ const bytes = new TextEncoder().encode(value);
302
+ let binary = "";
303
+ for (const byte of bytes) binary += String.fromCharCode(byte);
304
+ return btoa(binary);
305
+ }
306
+ function isABASuccess(status) {
307
+ if (status === 0 || status === "0") return true;
308
+ if (status !== null && typeof status === "object") {
309
+ const code = status.code;
310
+ return code === "0" || code === "00";
311
+ }
312
+ return false;
313
+ }
314
+ function abaStatusCode(status) {
315
+ if (status !== null && typeof status === "object") {
316
+ return String(status.code ?? "");
317
+ }
318
+ return String(status);
319
+ }
320
+ function abaErrorMessage(data) {
321
+ if (data.status !== null && typeof data.status === "object") {
322
+ const message = data.status.message;
323
+ if (message) return String(message);
324
+ }
325
+ if (data.description) return String(data.description);
326
+ return "Unknown error";
327
+ }
328
+ function mapPaymentStatus(raw) {
329
+ const normalized = (raw ?? "").toUpperCase();
330
+ if (normalized === "APPROVED") return "APPROVED";
331
+ if (normalized === "PRE-AUTH") return "PRE-AUTH";
332
+ if (normalized === "DECLINED") return "DECLINED";
333
+ if (normalized === "REFUNDED") return "REFUNDED";
334
+ if (normalized === "PENDING") return "PENDING";
335
+ if (normalized === "CANCELLED") return "CANCELLED";
336
+ return "ERROR";
337
+ }
338
+
339
+ // src/khqr.ts
340
+ var DEFAULT_HEADER_COLOR = "#e63946";
341
+ var QR_API_BASE = "https://quickchart.io/qr";
342
+ function formatAmount(amount, currency) {
343
+ if (currency === "KHR") {
344
+ return amount.toLocaleString("en-US", { maximumFractionDigits: 0 }) + " KHR";
345
+ }
346
+ return "$" + amount.toFixed(2);
347
+ }
348
+ function escapeXml(str) {
349
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
350
+ }
351
+ async function generateKHQR(options) {
352
+ const {
353
+ emvData,
354
+ amount,
355
+ currency,
356
+ merchantName,
357
+ headerColor = DEFAULT_HEADER_COLOR
358
+ } = options;
359
+ let qrSvgContent = "";
360
+ try {
361
+ const qrUrl = `${QR_API_BASE}?text=${encodeURIComponent(emvData)}&size=280&margin=1&format=svg`;
362
+ const qrResponse = await fetch(qrUrl);
363
+ if (qrResponse.ok) qrSvgContent = await qrResponse.text();
364
+ } catch {
365
+ }
366
+ const innerQr = qrSvgContent.replace(/<\?xml[^>]*\?>/g, "").replace(/<svg[^>]*>/g, "").replace(/<\/svg>/g, "");
367
+ const formattedAmount = formatAmount(amount, currency);
368
+ const safeMerchantName = escapeXml(merchantName);
369
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 520" width="400" height="520">
370
+ <defs><clipPath id="qr-clip"><rect x="60" y="140" width="280" height="280" rx="8"/></clipPath></defs>
371
+ <rect width="400" height="520" rx="16" fill="white" stroke="#e0e0e0" stroke-width="1"/>
372
+ <rect width="400" height="80" rx="16" fill="${headerColor}"/>
373
+ <rect y="16" width="400" height="64" fill="${headerColor}"/>
374
+ <text x="200" y="42" text-anchor="middle" fill="white" font-family="system-ui,sans-serif" font-size="18" font-weight="bold">${safeMerchantName}</text>
375
+ <text x="200" y="65" text-anchor="middle" fill="rgba(255,255,255,0.9)" font-family="system-ui,sans-serif" font-size="13">KHQR Payment</text>
376
+ <text x="200" y="115" text-anchor="middle" fill="#1a1a1a" font-family="system-ui,sans-serif" font-size="28" font-weight="bold">${formattedAmount}</text>
377
+ <g clip-path="url(#qr-clip)" transform="translate(60,140)">
378
+ ${innerQr || '<rect width="280" height="280" fill="#f5f5f5"/><text x="140" y="140" text-anchor="middle" fill="#999" font-size="14">QR Code</text>'}
379
+ </g>
380
+ <rect x="60" y="140" width="280" height="280" rx="8" fill="none" stroke="#e0e0e0" stroke-width="1"/>
381
+ <text x="200" y="455" text-anchor="middle" fill="#666" font-family="system-ui,sans-serif" font-size="11">Scan with any KHQR-compatible app</text>
382
+ <text x="200" y="475" text-anchor="middle" fill="#999" font-family="system-ui,sans-serif" font-size="10">Powered by Bakong</text>
383
+ </svg>`;
384
+ return "data:image/svg+xml;base64," + btoa(svg);
385
+ }
386
+ // Annotate the CommonJS export names for ESM import in node:
387
+ 0 && (module.exports = {
388
+ ABAPayWay,
389
+ formatPhoneForABA,
390
+ generateABAHash,
391
+ generateKHQR,
392
+ generateTransactionId,
393
+ getABATimestamp,
394
+ getQRExpiration
395
+ });
396
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/hash.ts","../src/utils.ts","../src/client.ts","../src/khqr.ts"],"sourcesContent":["export { ABAPayWay } from \"./client\";\nexport { generateKHQR } from \"./khqr\";\nexport { generateABAHash } from \"./hash\";\nexport {\n generateTransactionId,\n getABATimestamp,\n formatPhoneForABA,\n getQRExpiration,\n} from \"./utils\";\nexport type {\n ABAConfig,\n PurchaseRequest,\n PurchaseResponse,\n StatusResponse,\n PaymentStatus,\n KHQROptions,\n HashParams,\n} from \"./types\";\n","import type { HashParams } from \"./types\";\n\nexport async function generateABAHash(\n params: HashParams,\n publicKey: string\n): Promise<string> {\n // ABA requires parameters concatenated in this EXACT order\n const hashString = [\n params.req_time,\n params.merchant_id,\n params.tran_id,\n params.amount ?? \"\",\n params.items ?? \"\",\n params.shipping ?? \"\",\n params.ctid ?? \"\",\n params.pwt ?? \"\",\n params.firstname ?? \"\",\n params.lastname ?? \"\",\n params.email ?? \"\",\n params.phone ?? \"\",\n params.type ?? \"\",\n params.payment_option ?? \"\",\n params.return_url ?? \"\",\n params.cancel_url ?? \"\",\n params.continue_success_url ?? \"\",\n params.return_deeplink ?? \"\",\n params.currency ?? \"\",\n params.custom_fields ?? \"\",\n params.return_params ?? \"\",\n ].join(\"\");\n\n const encoder = new TextEncoder();\n const key = await crypto.subtle.importKey(\n \"raw\",\n encoder.encode(publicKey),\n { name: \"HMAC\", hash: \"SHA-512\" },\n false,\n [\"sign\"]\n );\n const signature = await crypto.subtle.sign(\"HMAC\", key, encoder.encode(hashString));\n return btoa(String.fromCharCode(...new Uint8Array(signature)));\n}\n","export function generateTransactionId(): string {\n const timestamp = Date.now().toString(36).toUpperCase();\n const random = Math.random().toString(36).substring(2, 6).toUpperCase();\n return `EA${timestamp}${random}`.substring(0, 20);\n}\n\nexport function getABATimestamp(): string {\n const now = new Date();\n const pad = (n: number) => n.toString().padStart(2, \"0\");\n return (\n now.getFullYear().toString() +\n pad(now.getMonth() + 1) +\n pad(now.getDate()) +\n pad(now.getHours()) +\n pad(now.getMinutes()) +\n pad(now.getSeconds())\n );\n}\n\nexport function formatPhoneForABA(phone: string): string {\n if (!phone) return \"\";\n const cleaned = phone.replace(/[\\s-]/g, \"\");\n if (cleaned.startsWith(\"+855\")) return \"0\" + cleaned.slice(4);\n if (cleaned.startsWith(\"855\")) return \"0\" + cleaned.slice(3);\n return cleaned;\n}\n\nexport function getQRExpiration(): Date {\n return new Date(Date.now() + 15 * 60 * 1000);\n}\n","import type {\n ABAConfig,\n PurchaseRequest,\n PurchaseResponse,\n StatusResponse,\n PaymentStatus,\n} from \"./types\";\nimport { generateABAHash } from \"./hash\";\nimport { getABATimestamp, formatPhoneForABA } from \"./utils\";\n\nexport class ABAPayWay {\n readonly config: Readonly<ABAConfig>;\n\n constructor(config: ABAConfig) {\n if (!config.merchantId) throw new Error(\"merchantId is required\");\n if (!config.apiKey) throw new Error(\"apiKey is required\");\n this.config = Object.freeze({ ...config });\n }\n\n async createPurchase(request: PurchaseRequest): Promise<PurchaseResponse> {\n const reqTime = getABATimestamp();\n // ABA requires KHR amounts as whole numbers (no decimals); USD uses 2.\n const amount =\n request.currency === \"KHR\"\n ? Math.round(request.amount).toString()\n : request.amount.toFixed(2);\n const phone = request.phone ? formatPhoneForABA(request.phone) : \"\";\n\n // ABA expects these URL fields Base64-encoded. The same encoded\n // value must be used in both the hash input and the request body.\n const returnUrl = request.returnUrl ? base64(request.returnUrl) : \"\";\n const cancelUrl = request.cancelUrl ? base64(request.cancelUrl) : \"\";\n const continueSuccessUrl = request.continueSuccessUrl\n ? base64(request.continueSuccessUrl)\n : \"\";\n const returnDeeplink = request.returnDeeplink\n ? base64(request.returnDeeplink)\n : \"\";\n\n const hashParams = {\n req_time: reqTime,\n merchant_id: this.config.merchantId,\n tran_id: request.transactionId,\n amount,\n items: request.items ?? \"\",\n shipping: \"\",\n ctid: \"\",\n pwt: \"\",\n firstname: request.firstName ?? \"\",\n lastname: request.lastName ?? \"\",\n email: request.email ?? \"\",\n phone,\n type: \"\",\n payment_option: request.paymentOption ?? \"\",\n return_url: returnUrl,\n cancel_url: cancelUrl,\n continue_success_url: continueSuccessUrl,\n return_deeplink: returnDeeplink,\n currency: request.currency,\n custom_fields: request.customFields ?? \"\",\n return_params: request.returnParams ?? \"\",\n };\n\n const hash = await generateABAHash(hashParams, this.config.apiKey);\n\n const body = new URLSearchParams({\n req_time: reqTime,\n merchant_id: this.config.merchantId,\n tran_id: request.transactionId,\n amount,\n items: request.items ?? \"\",\n firstname: request.firstName ?? \"\",\n lastname: request.lastName ?? \"\",\n email: request.email ?? \"\",\n phone,\n payment_option: request.paymentOption ?? \"\",\n return_url: returnUrl,\n cancel_url: cancelUrl,\n continue_success_url: continueSuccessUrl,\n return_deeplink: returnDeeplink,\n currency: request.currency,\n custom_fields: request.customFields ?? \"\",\n return_params: request.returnParams ?? \"\",\n hash,\n });\n\n const url = `${this.config.baseUrl}/api/payment-gateway/v1/payments/purchase`;\n\n try {\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const text = await response.text();\n return {\n success: false,\n transactionId: request.transactionId,\n amount: request.amount,\n currency: request.currency,\n error: `HTTP ${response.status}: ${text}`,\n };\n }\n\n const data = await response.json();\n\n if (!isABASuccess(data.status)) {\n return {\n success: false,\n transactionId: request.transactionId,\n amount: request.amount,\n currency: request.currency,\n error: abaErrorMessage(data),\n errorCode: abaStatusCode(data.status),\n };\n }\n\n return {\n success: true,\n transactionId: request.transactionId,\n amount: request.amount,\n currency: request.currency,\n checkoutUrl: data.checkout_qr_url ?? data.checkout_url,\n abapayDeeplink: data.abapay_deeplink,\n qrString: data.qr_string,\n };\n } catch (err) {\n return {\n success: false,\n transactionId: request.transactionId,\n amount: request.amount,\n currency: request.currency,\n error: err instanceof Error ? err.message : \"Unknown error\",\n };\n }\n }\n\n async checkStatus(transactionId: string): Promise<StatusResponse> {\n const reqTime = getABATimestamp();\n\n const hashParams = {\n req_time: reqTime,\n merchant_id: this.config.merchantId,\n tran_id: transactionId,\n };\n\n const hash = await generateABAHash(hashParams, this.config.apiKey);\n\n // check-transaction-2 expects a JSON body.\n const body = JSON.stringify({\n req_time: reqTime,\n merchant_id: this.config.merchantId,\n tran_id: transactionId,\n hash,\n });\n\n const url = `${this.config.baseUrl}/api/payment-gateway/v1/payments/check-transaction-2`;\n\n try {\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body,\n });\n\n if (!response.ok) {\n const text = await response.text();\n return {\n success: false,\n transactionId,\n status: \"ERROR\",\n error: `HTTP ${response.status}: ${text}`,\n };\n }\n\n const data = await response.json();\n\n // Success envelope: { status: { code: \"00\" }, data: { ... } }.\n if (!isABASuccess(data.status)) {\n return {\n success: false,\n transactionId,\n status: \"ERROR\",\n error: abaErrorMessage(data),\n };\n }\n\n const tran = data.data ?? data;\n const paymentStatus = mapPaymentStatus(tran.payment_status);\n const rawAmount = tran.payment_amount ?? tran.amount;\n const amount = rawAmount != null ? parseFloat(String(rawAmount)) : undefined;\n\n return {\n success: true,\n transactionId,\n status: paymentStatus,\n amount: amount != null && Number.isFinite(amount) ? amount : undefined,\n currency: tran.payment_currency ?? tran.currency,\n paymentTime: tran.payment_datetime ?? tran.payment_time,\n };\n } catch (err) {\n return {\n success: false,\n transactionId,\n status: \"ERROR\",\n error: err instanceof Error ? err.message : \"Unknown error\",\n };\n }\n }\n\n /**\n * Verify an ABA PayWay callback (pushback) signature.\n *\n * ABA does NOT sign the raw body. It sorts the callback fields by key\n * (ascending), concatenates their values (JSON-encoding any nested\n * object/array), then HMAC-SHA512 with the secret and Base64-encodes\n * the result. The signature arrives in the `X-PAYWAY-HMAC-SHA512`\n * request header.\n *\n * @param payload Raw JSON callback body.\n * @param signature Value of the `X-PAYWAY-HMAC-SHA512` header.\n * @param secret Merchant key used to sign callbacks.\n */\n async verifyWebhook(\n payload: string,\n signature: string,\n secret: string\n ): Promise<boolean> {\n try {\n const parsed = JSON.parse(payload) as Record<string, unknown>;\n const b4hash = Object.keys(parsed)\n .sort()\n .map((k) => {\n const v = parsed[k];\n return v !== null && typeof v === \"object\" ? JSON.stringify(v) : String(v);\n })\n .join(\"\");\n\n const encoder = new TextEncoder();\n const key = await crypto.subtle.importKey(\n \"raw\",\n encoder.encode(secret),\n { name: \"HMAC\", hash: \"SHA-512\" },\n false,\n [\"sign\"]\n );\n const sig = await crypto.subtle.sign(\"HMAC\", key, encoder.encode(b4hash));\n const expected = btoa(String.fromCharCode(...new Uint8Array(sig)));\n\n // Length pre-check (Base64 HMAC length is not secret), then a\n // bitwise compare that does not short-circuit on content.\n if (expected.length !== signature.length) return false;\n\n let mismatch = 0;\n for (let i = 0; i < expected.length; i++) {\n mismatch |= expected.charCodeAt(i) ^ signature.charCodeAt(i);\n }\n return mismatch === 0;\n } catch {\n return false;\n }\n }\n}\n\n// Base64-encode a string (UTF-8 safe) for ABA's URL/encoded fields.\n// Build the binary string with a loop (not a spread) so long inputs\n// don't overflow the call-stack argument limit.\nfunction base64(value: string): string {\n const bytes = new TextEncoder().encode(value);\n let binary = \"\";\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary);\n}\n\n// ABA reports status either as a number (0) or an object ({ code: \"00\" }).\nfunction isABASuccess(status: unknown): boolean {\n if (status === 0 || status === \"0\") return true;\n if (status !== null && typeof status === \"object\") {\n const code = (status as { code?: unknown }).code;\n return code === \"0\" || code === \"00\";\n }\n return false;\n}\n\nfunction abaStatusCode(status: unknown): string {\n if (status !== null && typeof status === \"object\") {\n return String((status as { code?: unknown }).code ?? \"\");\n }\n return String(status);\n}\n\nfunction abaErrorMessage(data: {\n status?: unknown;\n description?: unknown;\n}): string {\n if (data.status !== null && typeof data.status === \"object\") {\n const message = (data.status as { message?: unknown }).message;\n if (message) return String(message);\n }\n if (data.description) return String(data.description);\n return \"Unknown error\";\n}\n\nfunction mapPaymentStatus(raw: string | undefined): PaymentStatus {\n const normalized = (raw ?? \"\").toUpperCase();\n if (normalized === \"APPROVED\") return \"APPROVED\";\n if (normalized === \"PRE-AUTH\") return \"PRE-AUTH\";\n if (normalized === \"DECLINED\") return \"DECLINED\";\n if (normalized === \"REFUNDED\") return \"REFUNDED\";\n if (normalized === \"PENDING\") return \"PENDING\";\n if (normalized === \"CANCELLED\") return \"CANCELLED\";\n return \"ERROR\";\n}\n","import type { KHQROptions } from \"./types\";\n\nconst DEFAULT_HEADER_COLOR = \"#e63946\";\nconst QR_API_BASE = \"https://quickchart.io/qr\";\n\nfunction formatAmount(amount: number, currency: string): string {\n if (currency === \"KHR\") {\n return amount.toLocaleString(\"en-US\", { maximumFractionDigits: 0 }) + \" KHR\";\n }\n return \"$\" + amount.toFixed(2);\n}\n\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&apos;\");\n}\n\nexport async function generateKHQR(options: KHQROptions): Promise<string> {\n const {\n emvData,\n amount,\n currency,\n merchantName,\n headerColor = DEFAULT_HEADER_COLOR,\n } = options;\n\n let qrSvgContent = \"\";\n try {\n const qrUrl = `${QR_API_BASE}?text=${encodeURIComponent(emvData)}&size=280&margin=1&format=svg`;\n const qrResponse = await fetch(qrUrl);\n if (qrResponse.ok) qrSvgContent = await qrResponse.text();\n } catch { /* fallback: empty QR area */ }\n\n const innerQr = qrSvgContent\n .replace(/<\\?xml[^>]*\\?>/g, \"\")\n .replace(/<svg[^>]*>/g, \"\")\n .replace(/<\\/svg>/g, \"\");\n\n const formattedAmount = formatAmount(amount, currency);\n const safeMerchantName = escapeXml(merchantName);\n\n const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 400 520\" width=\"400\" height=\"520\">\n <defs><clipPath id=\"qr-clip\"><rect x=\"60\" y=\"140\" width=\"280\" height=\"280\" rx=\"8\"/></clipPath></defs>\n <rect width=\"400\" height=\"520\" rx=\"16\" fill=\"white\" stroke=\"#e0e0e0\" stroke-width=\"1\"/>\n <rect width=\"400\" height=\"80\" rx=\"16\" fill=\"${headerColor}\"/>\n <rect y=\"16\" width=\"400\" height=\"64\" fill=\"${headerColor}\"/>\n <text x=\"200\" y=\"42\" text-anchor=\"middle\" fill=\"white\" font-family=\"system-ui,sans-serif\" font-size=\"18\" font-weight=\"bold\">${safeMerchantName}</text>\n <text x=\"200\" y=\"65\" text-anchor=\"middle\" fill=\"rgba(255,255,255,0.9)\" font-family=\"system-ui,sans-serif\" font-size=\"13\">KHQR Payment</text>\n <text x=\"200\" y=\"115\" text-anchor=\"middle\" fill=\"#1a1a1a\" font-family=\"system-ui,sans-serif\" font-size=\"28\" font-weight=\"bold\">${formattedAmount}</text>\n <g clip-path=\"url(#qr-clip)\" transform=\"translate(60,140)\">\n ${innerQr || '<rect width=\"280\" height=\"280\" fill=\"#f5f5f5\"/><text x=\"140\" y=\"140\" text-anchor=\"middle\" fill=\"#999\" font-size=\"14\">QR Code</text>'}\n </g>\n <rect x=\"60\" y=\"140\" width=\"280\" height=\"280\" rx=\"8\" fill=\"none\" stroke=\"#e0e0e0\" stroke-width=\"1\"/>\n <text x=\"200\" y=\"455\" text-anchor=\"middle\" fill=\"#666\" font-family=\"system-ui,sans-serif\" font-size=\"11\">Scan with any KHQR-compatible app</text>\n <text x=\"200\" y=\"475\" text-anchor=\"middle\" fill=\"#999\" font-family=\"system-ui,sans-serif\" font-size=\"10\">Powered by Bakong</text>\n</svg>`;\n\n return \"data:image/svg+xml;base64,\" + btoa(svg);\n}\n"],"mappings":"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","names":[]}