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 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,