aba-payway-sdk-unofficial 1.2.2 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +366 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +145 -2
- package/dist/index.d.ts +145 -2
- package/dist/index.js +364 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/dist/index.cjs
CHANGED
|
@@ -23,7 +23,9 @@ __export(index_exports, {
|
|
|
23
23
|
ABAPayWay: () => ABAPayWay,
|
|
24
24
|
encodeItemsForABA: () => encodeItemsForABA,
|
|
25
25
|
encodeReturnDeeplinkForABA: () => encodeReturnDeeplinkForABA,
|
|
26
|
+
encryptRsaChunks: () => encryptRsaChunks,
|
|
26
27
|
formatPhoneForABA: () => formatPhoneForABA,
|
|
28
|
+
formatRsaPublicKey: () => formatRsaPublicKey,
|
|
27
29
|
generateABAHash: () => generateABAHash,
|
|
28
30
|
generateKHQR: () => generateKHQR,
|
|
29
31
|
generateTransactionId: () => generateTransactionId,
|
|
@@ -82,9 +84,15 @@ function readAbaStatus(data) {
|
|
|
82
84
|
const nested = raw;
|
|
83
85
|
code = nested.code == null ? "" : String(nested.code);
|
|
84
86
|
message = firstString(nested.message, body.description);
|
|
85
|
-
} else {
|
|
87
|
+
} else if (raw !== void 0) {
|
|
86
88
|
code = raw == null ? "" : String(raw);
|
|
87
89
|
message = firstString(body.description, body.message);
|
|
90
|
+
} else if (body.code !== void 0) {
|
|
91
|
+
code = String(body.code);
|
|
92
|
+
message = firstString(body.message, body.description);
|
|
93
|
+
} else {
|
|
94
|
+
code = "";
|
|
95
|
+
message = firstString(body.description, body.message);
|
|
88
96
|
}
|
|
89
97
|
return { code, ok: isSuccessCode(code), message: message || "Unknown error" };
|
|
90
98
|
}
|
|
@@ -99,6 +107,41 @@ function firstString(...values) {
|
|
|
99
107
|
return "";
|
|
100
108
|
}
|
|
101
109
|
|
|
110
|
+
// src/rsa.ts
|
|
111
|
+
var import_node_crypto = require("crypto");
|
|
112
|
+
function formatRsaPublicKey(key) {
|
|
113
|
+
const trimmed = key.trim();
|
|
114
|
+
if (trimmed.includes("-----BEGIN")) {
|
|
115
|
+
return trimmed;
|
|
116
|
+
}
|
|
117
|
+
return `-----BEGIN PUBLIC KEY-----
|
|
118
|
+
${trimmed}
|
|
119
|
+
-----END PUBLIC KEY-----`;
|
|
120
|
+
}
|
|
121
|
+
function encryptRsaChunks(data, rsaPublicKey, chunkSize = 117) {
|
|
122
|
+
if (!data) {
|
|
123
|
+
throw new Error("Data to encrypt cannot be empty");
|
|
124
|
+
}
|
|
125
|
+
if (!rsaPublicKey || !rsaPublicKey.trim()) {
|
|
126
|
+
throw new Error("RSA public key is required for encryption");
|
|
127
|
+
}
|
|
128
|
+
const pemKey = formatRsaPublicKey(rsaPublicKey);
|
|
129
|
+
const dataBuffer = Buffer.from(data, "utf8");
|
|
130
|
+
const encryptedChunks = [];
|
|
131
|
+
for (let offset = 0; offset < dataBuffer.length; offset += chunkSize) {
|
|
132
|
+
const chunk = dataBuffer.subarray(offset, Math.min(offset + chunkSize, dataBuffer.length));
|
|
133
|
+
const encrypted = (0, import_node_crypto.publicEncrypt)(
|
|
134
|
+
{
|
|
135
|
+
key: pemKey,
|
|
136
|
+
padding: import_node_crypto.constants.RSA_PKCS1_PADDING
|
|
137
|
+
},
|
|
138
|
+
chunk
|
|
139
|
+
);
|
|
140
|
+
encryptedChunks.push(encrypted);
|
|
141
|
+
}
|
|
142
|
+
return Buffer.concat(encryptedChunks).toString("base64");
|
|
143
|
+
}
|
|
144
|
+
|
|
102
145
|
// src/utils.ts
|
|
103
146
|
function generateTransactionId() {
|
|
104
147
|
const timestamp = Date.now().toString(36).toUpperCase();
|
|
@@ -326,6 +369,325 @@ var ABAPayWay = class {
|
|
|
326
369
|
return failure(err instanceof Error ? err.message : "Unknown error");
|
|
327
370
|
}
|
|
328
371
|
}
|
|
372
|
+
async closeTransaction(transactionId) {
|
|
373
|
+
const failure = (error, code, message) => ({
|
|
374
|
+
success: false,
|
|
375
|
+
transactionId: transactionId ?? "",
|
|
376
|
+
code,
|
|
377
|
+
message: message ?? error,
|
|
378
|
+
error
|
|
379
|
+
});
|
|
380
|
+
if (!transactionId || !transactionId.trim()) {
|
|
381
|
+
return failure("transactionId is required");
|
|
382
|
+
}
|
|
383
|
+
const reqTime = getABATimestamp();
|
|
384
|
+
const hash = await hmacSha512Base64(
|
|
385
|
+
`${reqTime}${this.config.merchantId}${transactionId}`,
|
|
386
|
+
this.config.apiKey
|
|
387
|
+
);
|
|
388
|
+
const body = JSON.stringify({
|
|
389
|
+
req_time: reqTime,
|
|
390
|
+
merchant_id: this.config.merchantId,
|
|
391
|
+
tran_id: transactionId,
|
|
392
|
+
hash
|
|
393
|
+
});
|
|
394
|
+
const url = `${this.config.baseUrl}/api/payment-gateway/v1/payments/close-transaction`;
|
|
395
|
+
try {
|
|
396
|
+
const response = await fetch(url, {
|
|
397
|
+
method: "POST",
|
|
398
|
+
headers: { "Content-Type": "application/json" },
|
|
399
|
+
body
|
|
400
|
+
});
|
|
401
|
+
const parsed = await parseAbaJson(response);
|
|
402
|
+
if (parsed.error) return failure(parsed.error);
|
|
403
|
+
const status = readAbaStatus(parsed.data);
|
|
404
|
+
if (!status.ok) return failure(status.message, status.code || void 0, status.message);
|
|
405
|
+
return {
|
|
406
|
+
success: true,
|
|
407
|
+
transactionId: parsed.data?.status?.tran_id ?? transactionId,
|
|
408
|
+
code: status.code,
|
|
409
|
+
message: status.message
|
|
410
|
+
};
|
|
411
|
+
} catch (err) {
|
|
412
|
+
return failure(err instanceof Error ? err.message : "Unknown error");
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
async refund(request) {
|
|
416
|
+
const failure = (error, code, message) => ({
|
|
417
|
+
success: false,
|
|
418
|
+
transactionId: request?.transactionId ?? "",
|
|
419
|
+
refundAmount: request?.refundAmount ?? 0,
|
|
420
|
+
code,
|
|
421
|
+
message: message ?? error,
|
|
422
|
+
error
|
|
423
|
+
});
|
|
424
|
+
if (!request?.transactionId || !request.transactionId.trim()) {
|
|
425
|
+
return failure("transactionId is required");
|
|
426
|
+
}
|
|
427
|
+
if (typeof request?.refundAmount !== "number" || !Number.isFinite(request.refundAmount) || request.refundAmount <= 0) {
|
|
428
|
+
return failure("refundAmount must be a positive number");
|
|
429
|
+
}
|
|
430
|
+
const rsaPublicKey = request.rsaPublicKey ?? this.config.rsaPublicKey;
|
|
431
|
+
if (!rsaPublicKey || !rsaPublicKey.trim()) {
|
|
432
|
+
return failure(
|
|
433
|
+
"RSA public key is required for refund. Provide rsaPublicKey in config or request."
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
const reqTime = getABATimestamp();
|
|
437
|
+
let merchantAuth;
|
|
438
|
+
try {
|
|
439
|
+
const authPayload = JSON.stringify({
|
|
440
|
+
mc_id: this.config.merchantId,
|
|
441
|
+
tran_id: request.transactionId,
|
|
442
|
+
refund_amount: request.refundAmount
|
|
443
|
+
});
|
|
444
|
+
merchantAuth = encryptRsaChunks(authPayload, rsaPublicKey);
|
|
445
|
+
} catch (err) {
|
|
446
|
+
return failure(
|
|
447
|
+
err instanceof Error ? err.message : "RSA encryption failed"
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
const hash = await hmacSha512Base64(
|
|
451
|
+
`${reqTime}${this.config.merchantId}${merchantAuth}`,
|
|
452
|
+
this.config.apiKey
|
|
453
|
+
);
|
|
454
|
+
const body = JSON.stringify({
|
|
455
|
+
request_time: reqTime,
|
|
456
|
+
merchant_id: this.config.merchantId,
|
|
457
|
+
merchant_auth: merchantAuth,
|
|
458
|
+
hash
|
|
459
|
+
});
|
|
460
|
+
const url = `${this.config.baseUrl}/api/merchant-portal/merchant-access/online-transaction/refund`;
|
|
461
|
+
try {
|
|
462
|
+
const response = await fetch(url, {
|
|
463
|
+
method: "POST",
|
|
464
|
+
headers: { "Content-Type": "application/json" },
|
|
465
|
+
body
|
|
466
|
+
});
|
|
467
|
+
const parsed = await parseAbaJson(response);
|
|
468
|
+
if (parsed.error) return failure(parsed.error);
|
|
469
|
+
const status = readAbaStatus(parsed.data);
|
|
470
|
+
if (!status.ok) {
|
|
471
|
+
return failure(status.message, status.code || void 0, status.message);
|
|
472
|
+
}
|
|
473
|
+
return {
|
|
474
|
+
success: true,
|
|
475
|
+
transactionId: request.transactionId,
|
|
476
|
+
refundAmount: request.refundAmount,
|
|
477
|
+
grandTotal: toNumber(parsed.data?.grand_total),
|
|
478
|
+
totalRefunded: toNumber(parsed.data?.total_refunded),
|
|
479
|
+
currency: parsed.data?.currency,
|
|
480
|
+
transactionStatus: parsed.data?.transaction_status,
|
|
481
|
+
code: status.code,
|
|
482
|
+
message: status.message
|
|
483
|
+
};
|
|
484
|
+
} catch (err) {
|
|
485
|
+
return failure(err instanceof Error ? err.message : "Unknown error");
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
async getExchangeRates() {
|
|
489
|
+
const failure = (error, code) => ({
|
|
490
|
+
success: false,
|
|
491
|
+
error,
|
|
492
|
+
code
|
|
493
|
+
});
|
|
494
|
+
const reqTime = getABATimestamp();
|
|
495
|
+
const hash = await hmacSha512Base64(
|
|
496
|
+
`${reqTime}${this.config.merchantId}`,
|
|
497
|
+
this.config.apiKey
|
|
498
|
+
);
|
|
499
|
+
const body = JSON.stringify({
|
|
500
|
+
req_time: reqTime,
|
|
501
|
+
merchant_id: this.config.merchantId,
|
|
502
|
+
hash
|
|
503
|
+
});
|
|
504
|
+
const url = `${this.config.baseUrl}/api/payment-gateway/v1/exchange-rate`;
|
|
505
|
+
try {
|
|
506
|
+
const response = await fetch(url, {
|
|
507
|
+
method: "POST",
|
|
508
|
+
headers: { "Content-Type": "application/json" },
|
|
509
|
+
body
|
|
510
|
+
});
|
|
511
|
+
const parsed = await parseAbaJson(response);
|
|
512
|
+
if (parsed.error) return failure(parsed.error);
|
|
513
|
+
const status = readAbaStatus(parsed.data);
|
|
514
|
+
if (!status.ok) return failure(status.message, status.code || void 0);
|
|
515
|
+
const rates = {};
|
|
516
|
+
const collectRates = (obj) => {
|
|
517
|
+
if (!obj || typeof obj !== "object") return;
|
|
518
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
519
|
+
if (val && typeof val === "object" && "buy" in val && "sell" in val) {
|
|
520
|
+
rates[key.toLowerCase()] = {
|
|
521
|
+
buy: String(val.buy),
|
|
522
|
+
sell: String(val.sell)
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
collectRates(parsed.data);
|
|
528
|
+
collectRates(parsed.data?.exchange_rates);
|
|
529
|
+
return {
|
|
530
|
+
success: true,
|
|
531
|
+
rates,
|
|
532
|
+
code: status.code,
|
|
533
|
+
message: status.message
|
|
534
|
+
};
|
|
535
|
+
} catch (err) {
|
|
536
|
+
return failure(err instanceof Error ? err.message : "Unknown error");
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
async getTransactionDetail(transactionId) {
|
|
540
|
+
const failure = (error, code, message) => ({
|
|
541
|
+
success: false,
|
|
542
|
+
transactionId: transactionId ?? "",
|
|
543
|
+
status: "ERROR",
|
|
544
|
+
code,
|
|
545
|
+
message: message ?? error,
|
|
546
|
+
error
|
|
547
|
+
});
|
|
548
|
+
if (!transactionId || !transactionId.trim()) {
|
|
549
|
+
return failure("transactionId is required");
|
|
550
|
+
}
|
|
551
|
+
const reqTime = getABATimestamp();
|
|
552
|
+
const hash = await hmacSha512Base64(
|
|
553
|
+
`${reqTime}${this.config.merchantId}${transactionId}`,
|
|
554
|
+
this.config.apiKey
|
|
555
|
+
);
|
|
556
|
+
const body = JSON.stringify({
|
|
557
|
+
req_time: reqTime,
|
|
558
|
+
merchant_id: this.config.merchantId,
|
|
559
|
+
tran_id: transactionId,
|
|
560
|
+
hash
|
|
561
|
+
});
|
|
562
|
+
const url = `${this.config.baseUrl}/api/payment-gateway/v1/payments/transaction-detail`;
|
|
563
|
+
try {
|
|
564
|
+
const response = await fetch(url, {
|
|
565
|
+
method: "POST",
|
|
566
|
+
headers: { "Content-Type": "application/json" },
|
|
567
|
+
body
|
|
568
|
+
});
|
|
569
|
+
const parsed = await parseAbaJson(response);
|
|
570
|
+
if (parsed.error) return failure(parsed.error);
|
|
571
|
+
const detail = parsed.data?.data ?? parsed.data ?? {};
|
|
572
|
+
const status = readAbaStatus(detail?.status ?? parsed.data?.status);
|
|
573
|
+
if (!status.ok) {
|
|
574
|
+
return failure(status.message, status.code || void 0, status.message);
|
|
575
|
+
}
|
|
576
|
+
const operations = Array.isArray(
|
|
577
|
+
detail.transaction_operations
|
|
578
|
+
) ? detail.transaction_operations.map((op) => ({
|
|
579
|
+
status: String(op.status ?? ""),
|
|
580
|
+
amount: toNumber(op.amount) ?? 0,
|
|
581
|
+
transactionDate: String(op.transaction_date ?? ""),
|
|
582
|
+
bankRef: op.bank_ref ? String(op.bank_ref) : void 0
|
|
583
|
+
})) : [];
|
|
584
|
+
return {
|
|
585
|
+
success: true,
|
|
586
|
+
transactionId: String(detail.transaction_id ?? transactionId),
|
|
587
|
+
status: mapPaymentStatus(detail.payment_status),
|
|
588
|
+
statusCode: typeof detail.payment_status_code === "number" ? detail.payment_status_code : void 0,
|
|
589
|
+
originalAmount: toNumber(detail.original_amount),
|
|
590
|
+
originalCurrency: detail.original_currency ? String(detail.original_currency) : void 0,
|
|
591
|
+
paymentAmount: toNumber(detail.payment_amount),
|
|
592
|
+
paymentCurrency: detail.payment_currency ? String(detail.payment_currency) : void 0,
|
|
593
|
+
totalAmount: toNumber(detail.total_amount),
|
|
594
|
+
refundAmount: toNumber(detail.refund_amount),
|
|
595
|
+
discountAmount: toNumber(detail.discount_amount),
|
|
596
|
+
apv: detail.apv ? String(detail.apv) : void 0,
|
|
597
|
+
transactionDate: detail.transaction_date ? String(detail.transaction_date) : void 0,
|
|
598
|
+
firstName: detail.first_name ? String(detail.first_name) : void 0,
|
|
599
|
+
lastName: detail.last_name ? String(detail.last_name) : void 0,
|
|
600
|
+
email: detail.email ? String(detail.email) : void 0,
|
|
601
|
+
phone: detail.phone ? String(detail.phone) : void 0,
|
|
602
|
+
bankRef: detail.bank_ref ? String(detail.bank_ref) : void 0,
|
|
603
|
+
paymentType: detail.payment_type ? String(detail.payment_type) : void 0,
|
|
604
|
+
payerAccount: detail.payer_account ? String(detail.payer_account) : void 0,
|
|
605
|
+
bankName: detail.bank_name ? String(detail.bank_name) : void 0,
|
|
606
|
+
cardSource: detail.card_source ? String(detail.card_source) : void 0,
|
|
607
|
+
operations: operations.length > 0 ? operations : void 0,
|
|
608
|
+
code: status.code,
|
|
609
|
+
message: status.message
|
|
610
|
+
};
|
|
611
|
+
} catch (err) {
|
|
612
|
+
return failure(err instanceof Error ? err.message : "Unknown error");
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
async listTransactions(filter = {}) {
|
|
616
|
+
const failure = (error, code) => ({
|
|
617
|
+
success: false,
|
|
618
|
+
transactions: [],
|
|
619
|
+
error,
|
|
620
|
+
code
|
|
621
|
+
});
|
|
622
|
+
const reqTime = getABATimestamp();
|
|
623
|
+
const fromDate = filter.fromDate ?? "";
|
|
624
|
+
const toDate = filter.toDate ?? "";
|
|
625
|
+
const fromAmount = filter.fromAmount !== void 0 ? String(filter.fromAmount) : "";
|
|
626
|
+
const toAmount = filter.toAmount !== void 0 ? String(filter.toAmount) : "";
|
|
627
|
+
const statusVal = filter.status ?? "";
|
|
628
|
+
const page = filter.page !== void 0 ? String(filter.page) : "1";
|
|
629
|
+
const pagination = filter.pagination !== void 0 ? String(filter.pagination) : "40";
|
|
630
|
+
const hashSequence = `${reqTime}${this.config.merchantId}${fromDate}${toDate}${fromAmount}${toAmount}${statusVal}${page}${pagination}`;
|
|
631
|
+
const hash = await hmacSha512Base64(hashSequence, this.config.apiKey);
|
|
632
|
+
const body = JSON.stringify({
|
|
633
|
+
req_time: reqTime,
|
|
634
|
+
merchant_id: this.config.merchantId,
|
|
635
|
+
from_date: fromDate || null,
|
|
636
|
+
to_date: toDate || null,
|
|
637
|
+
from_amount: fromAmount || null,
|
|
638
|
+
to_amount: toAmount || null,
|
|
639
|
+
status: statusVal || null,
|
|
640
|
+
page,
|
|
641
|
+
pagination,
|
|
642
|
+
hash
|
|
643
|
+
});
|
|
644
|
+
const url = `${this.config.baseUrl}/api/payment-gateway/v1/payments/transaction-list-2`;
|
|
645
|
+
try {
|
|
646
|
+
const response = await fetch(url, {
|
|
647
|
+
method: "POST",
|
|
648
|
+
headers: { "Content-Type": "application/json" },
|
|
649
|
+
body
|
|
650
|
+
});
|
|
651
|
+
const parsed = await parseAbaJson(response);
|
|
652
|
+
if (parsed.error) return failure(parsed.error);
|
|
653
|
+
const status = readAbaStatus(parsed.data);
|
|
654
|
+
if (!status.ok) return failure(status.message, status.code || void 0);
|
|
655
|
+
const rawItems = Array.isArray(parsed.data?.data) ? parsed.data.data : [];
|
|
656
|
+
const transactions = rawItems.map((item) => ({
|
|
657
|
+
transactionId: String(item.transaction_id ?? ""),
|
|
658
|
+
transactionDate: String(item.transaction_date ?? ""),
|
|
659
|
+
apv: item.apv ? String(item.apv) : void 0,
|
|
660
|
+
paymentStatus: mapPaymentStatus(item.payment_status),
|
|
661
|
+
paymentStatusCode: typeof item.payment_status_code === "number" ? item.payment_status_code : void 0,
|
|
662
|
+
originalAmount: toNumber(item.original_amount),
|
|
663
|
+
originalCurrency: item.original_currency ? String(item.original_currency) : void 0,
|
|
664
|
+
totalAmount: toNumber(item.total_amount),
|
|
665
|
+
discountAmount: toNumber(item.discount_amount),
|
|
666
|
+
refundAmount: toNumber(item.refund_amount),
|
|
667
|
+
paymentAmount: toNumber(item.payment_amount),
|
|
668
|
+
paymentCurrency: item.payment_currency ? String(item.payment_currency) : void 0,
|
|
669
|
+
firstName: item.first_name ? String(item.first_name) : void 0,
|
|
670
|
+
lastName: item.last_name ? String(item.last_name) : void 0,
|
|
671
|
+
email: item.email ? String(item.email) : void 0,
|
|
672
|
+
phone: item.phone ? String(item.phone) : void 0,
|
|
673
|
+
bankRef: item.bank_ref ? String(item.bank_ref) : void 0,
|
|
674
|
+
payerAccount: item.payer_account ? String(item.payer_account) : void 0,
|
|
675
|
+
bankName: item.bank_name ? String(item.bank_name) : void 0,
|
|
676
|
+
cardSource: item.card_source ? String(item.card_source) : void 0,
|
|
677
|
+
paymentType: item.payment_type ? String(item.payment_type) : void 0
|
|
678
|
+
}));
|
|
679
|
+
return {
|
|
680
|
+
success: true,
|
|
681
|
+
transactions,
|
|
682
|
+
page: parsed.data?.page ? String(parsed.data.page) : page,
|
|
683
|
+
pagination: parsed.data?.pagination ? String(parsed.data.pagination) : pagination,
|
|
684
|
+
code: status.code,
|
|
685
|
+
message: status.message
|
|
686
|
+
};
|
|
687
|
+
} catch (err) {
|
|
688
|
+
return failure(err instanceof Error ? err.message : "Unknown error");
|
|
689
|
+
}
|
|
690
|
+
}
|
|
329
691
|
/**
|
|
330
692
|
* Verify an ABA pushback (callback) against its `X-PayWay-HMAC-SHA512`
|
|
331
693
|
* header. Returns false for anything it cannot positively verify — a bad
|
|
@@ -390,6 +752,7 @@ function mapPaymentStatus(raw) {
|
|
|
390
752
|
if (normalized === "APPROVED") return "APPROVED";
|
|
391
753
|
if (normalized === "DECLINED") return "DECLINED";
|
|
392
754
|
if (normalized === "REFUNDED") return "REFUNDED";
|
|
755
|
+
if (normalized === "CANCELLED" || normalized === "CANCELED") return "CANCELLED";
|
|
393
756
|
if (normalized === "PENDING") return "PENDING";
|
|
394
757
|
return "ERROR";
|
|
395
758
|
}
|
|
@@ -482,7 +845,9 @@ async function generateKHQR(options) {
|
|
|
482
845
|
ABAPayWay,
|
|
483
846
|
encodeItemsForABA,
|
|
484
847
|
encodeReturnDeeplinkForABA,
|
|
848
|
+
encryptRsaChunks,
|
|
485
849
|
formatPhoneForABA,
|
|
850
|
+
formatRsaPublicKey,
|
|
486
851
|
generateABAHash,
|
|
487
852
|
generateKHQR,
|
|
488
853
|
generateTransactionId,
|