wahdx-api 1.0.4 → 2.0.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/LICENSE +21 -0
- package/README.md +45 -22
- package/dist/index.cjs +3 -13
- package/dist/index.mjs +3 -13
- package/dist/payment-checker.cjs +21 -19
- package/dist/payment-checker.mjs +21 -19
- package/dist/qr-generator.cjs +57 -112
- package/dist/qr-generator.mjs +57 -115
- package/dist/receipt-generator.cjs +2 -2
- package/dist/receipt-generator.mjs +2 -2
- package/package.json +5 -7
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 wahdalo
|
|
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
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
# wahdx-api
|
|
2
2
|
|
|
3
|
-
Package untuk generate QRIS dan cek payment status secara realtime dengan API OrderKuota dari [https://
|
|
3
|
+
Package untuk generate QRIS dan cek payment status secara realtime dengan API OrderKuota dari [https://eqris.com](https://eqris.com).
|
|
4
|
+
|
|
5
|
+
> **⚠️ PENTING: Perubahan di Versi 2.0.0**
|
|
6
|
+
>
|
|
7
|
+
> Pada versi 2.0.0, kami melakukan perubahan besar pada package ini:
|
|
8
|
+
> - Fitur `generateQRFromImage` telah **DIHAPUS** sepenuhnya
|
|
9
|
+
> - Digantikan dengan `generateQRFromAPI` yang lebih valid dan reliable
|
|
10
|
+
> - Dependency `sharp` dan `jsqr` telah dihapus untuk membuat package lebih ringan
|
|
11
|
+
>
|
|
12
|
+
> Jika Anda mengupgrade dari versi sebelumnya, harap perbarikan kode Anda untuk menggunakan `generateQRFromAPI` alih-alih `generateQRFromImage`.
|
|
4
13
|
|
|
5
14
|
## Fitur
|
|
6
15
|
|
|
7
|
-
-
|
|
8
|
-
- Generate kode QR dengan nominal tertentu
|
|
16
|
+
- Generate kode QR dengan nominal tertentu (lokal atau via API)
|
|
9
17
|
- Cek status pembayaran secara realtime
|
|
10
18
|
- Generate bukti transaksi (receipt)
|
|
11
19
|
- Kompatibel di semua platform (Windows, Linux, Mac)
|
|
@@ -26,14 +34,14 @@ ORKUT_TOKEN_AUTH=your_orkut_token_auth
|
|
|
26
34
|
ORKUT_USERNAME=your_orkut_username
|
|
27
35
|
```
|
|
28
36
|
|
|
29
|
-
> **Catatan:** Untuk mendapatkan tokenKey, Anda bisa membelinya di halaman [https://
|
|
37
|
+
> **Catatan:** Untuk mendapatkan tokenKey, Anda bisa membelinya di halaman [https://eqris.com](https://eqris.com)
|
|
30
38
|
|
|
31
39
|
### Opsi Konfigurasi
|
|
32
40
|
|
|
33
41
|
| Opsi | Deskripsi | Default |
|
|
34
42
|
|---------------------|------------------------------------------------------------------|-------------|
|
|
35
43
|
| storeName | Nama toko yang akan ditampilkan pada receipt | - |
|
|
36
|
-
|
|
|
44
|
+
| baseQrString | Base QR string untuk generate QR secara lokal | - |
|
|
37
45
|
| tokenKey | Token key dari WAHDX | - |
|
|
38
46
|
| auth_token | Token autentikasi OrderKuota | - |
|
|
39
47
|
| auth_username | Username OrderKuota | - |
|
|
@@ -51,10 +59,10 @@ import fs from 'fs';
|
|
|
51
59
|
// Konfigurasi
|
|
52
60
|
const config = {
|
|
53
61
|
storeName: 'AHDX STORE',
|
|
54
|
-
|
|
55
|
-
tokenKey: process.env.WAHDX_TOKENKEY,
|
|
56
|
-
auth_token: process.env.ORKUT_TOKEN_AUTH,
|
|
57
|
-
auth_username: process.env.ORKUT_USERNAME,
|
|
62
|
+
baseQrString: 'base-qr-string-anda', // Diperlukan untuk generateQR secara lokal
|
|
63
|
+
tokenKey: process.env.WAHDX_TOKENKEY, // Diperlukan untuk generateQRFromAPI
|
|
64
|
+
auth_token: process.env.ORKUT_TOKEN_AUTH, // Diperlukan untuk generateQRFromAPI
|
|
65
|
+
auth_username: process.env.ORKUT_USERNAME, // Diperlukan untuk generateQRFromAPI
|
|
58
66
|
autoGenerateReceipt: true // Atur false jika tidak ingin otomatis membuat receipt
|
|
59
67
|
};
|
|
60
68
|
|
|
@@ -68,8 +76,8 @@ async function main() {
|
|
|
68
76
|
const amount = 100 + randomAmount; // Base 100 + random amount
|
|
69
77
|
const reference = 'REF' + Date.now();
|
|
70
78
|
|
|
71
|
-
// Generate QR code
|
|
72
|
-
const { qrBuffer } = await qrisPayment.
|
|
79
|
+
// Generate QR code menggunakan API OrderKuota
|
|
80
|
+
const { qrBuffer } = await qrisPayment.generateQRFromAPI(amount);
|
|
73
81
|
|
|
74
82
|
// Save QR code image
|
|
75
83
|
fs.writeFileSync(`qr-${amount}.png`, qrBuffer);
|
|
@@ -119,29 +127,44 @@ import QRISPayment from 'wahdx-api';
|
|
|
119
127
|
|
|
120
128
|
const config = {
|
|
121
129
|
storeName: 'NAMA TOKO',
|
|
122
|
-
|
|
123
|
-
tokenKey: 'your_wahdx_token_key',
|
|
124
|
-
auth_token: 'your_orkut_token_auth',
|
|
125
|
-
auth_username: 'your_orkut_username',
|
|
130
|
+
baseQrString: 'base-qr-string-anda', // Diperlukan untuk generateQR secara lokal
|
|
131
|
+
tokenKey: 'your_wahdx_token_key', // Diperlukan untuk generateQRFromAPI
|
|
132
|
+
auth_token: 'your_orkut_token_auth', // Diperlukan untuk generateQRFromAPI
|
|
133
|
+
auth_username: 'your_orkut_username', // Diperlukan untuk generateQRFromAPI
|
|
126
134
|
autoGenerateReceipt: true // Atur false jika tidak ingin otomatis membuat receipt
|
|
127
135
|
};
|
|
128
136
|
|
|
129
137
|
const qrisPayment = new QRISPayment(config);
|
|
130
138
|
```
|
|
131
139
|
|
|
132
|
-
#### Generate QR
|
|
140
|
+
#### Generate QR
|
|
141
|
+
|
|
142
|
+
Ada dua metode untuk generate QR:
|
|
143
|
+
|
|
144
|
+
##### 1. Generate QR secara lokal
|
|
145
|
+
|
|
146
|
+
```javascript
|
|
147
|
+
const amount = 10000; // Rp 10.000
|
|
148
|
+
// Generate QR secara lokal menggunakan baseQrString
|
|
149
|
+
const { qrString, qrBuffer } = await qrisPayment.generateQR(amount);
|
|
150
|
+
|
|
151
|
+
// Simpan QR ke file
|
|
152
|
+
fs.writeFileSync('qr-output.png', qrBuffer);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
##### 2. Generate QR menggunakan API OrderKuota (Lebih direkomendasikan)
|
|
133
156
|
|
|
134
157
|
```javascript
|
|
135
158
|
const amount = 10000; // Rp 10.000
|
|
136
|
-
//
|
|
137
|
-
const { qrString, qrBuffer } = await qrisPayment.
|
|
138
|
-
// Atau dengan path QR spesifik
|
|
139
|
-
// const { qrString, qrBuffer } = await qrisPayment.generateQRFromImage(amount, 'path/to/qris/template.png');
|
|
159
|
+
// Generate QR menggunakan API OrderKuota
|
|
160
|
+
const { qrString, qrBuffer } = await qrisPayment.generateQRFromAPI(amount);
|
|
140
161
|
|
|
141
162
|
// Simpan QR ke file
|
|
142
163
|
fs.writeFileSync('qr-output.png', qrBuffer);
|
|
143
164
|
```
|
|
144
165
|
|
|
166
|
+
> **Catatan:** Metode `generateQRFromAPI` lebih direkomendasikan karena QR code dihasilkan langsung dari server OrderKuota, sehingga lebih valid dan reliable.
|
|
167
|
+
|
|
145
168
|
#### Cek Status Pembayaran
|
|
146
169
|
|
|
147
170
|
```javascript
|
|
@@ -186,10 +209,10 @@ if (result.success && result.data.status === 'PAID') {
|
|
|
186
209
|
## FAQ
|
|
187
210
|
|
|
188
211
|
### Q: Bagaimana cara mendapatkan tokenKey agar bisa menggunakan module ini?
|
|
189
|
-
A: Anda bisa membeli tokenKey di halaman utama [https://
|
|
212
|
+
A: Anda bisa membeli tokenKey di halaman utama [https://eqris.com](https://eqris.com)
|
|
190
213
|
|
|
191
214
|
### Q: Bagaimana cara mendapatkan kredensial API OrderKuota?
|
|
192
|
-
A: Silahkan kunjungi dokumentasi api [https://
|
|
215
|
+
A: Silahkan kunjungi dokumentasi api [https://eqris.com/api-docs](https://eqris.com/api-docs) untuk mendapatkan token pada akun orderkuota anda.
|
|
193
216
|
|
|
194
217
|
### Q: Apakah module ini bisa digunakan di project CommonJS?
|
|
195
218
|
A: Ya! Package ini mendukung dual module system (ESM dan CommonJS). Anda bisa menggunakan dengan dua cara:
|
package/dist/index.cjs
CHANGED
|
@@ -15,15 +15,6 @@ class QRISPayment {
|
|
|
15
15
|
this.receiptGenerator = new ReceiptGenerator(config);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
/**
|
|
19
|
-
* Membaca QR code dari file gambar dan mengekstrak baseQrString
|
|
20
|
-
* @param {string} imagePath - Path ke file gambar QR
|
|
21
|
-
* @returns {Promise<string>} - Promise yang menghasilkan baseQrString
|
|
22
|
-
*/
|
|
23
|
-
async readQRCode(imagePath) {
|
|
24
|
-
return await this.qrGenerator.readQRFromImage(imagePath);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
18
|
/**
|
|
28
19
|
* Generate QR dengan nominal tertentu
|
|
29
20
|
* @param {number} amount - Nominal pembayaran
|
|
@@ -34,13 +25,12 @@ class QRISPayment {
|
|
|
34
25
|
}
|
|
35
26
|
|
|
36
27
|
/**
|
|
37
|
-
*
|
|
28
|
+
* Generate QR dengan nominal tertentu menggunakan API OrderKuota
|
|
38
29
|
* @param {number} amount - Nominal pembayaran
|
|
39
|
-
* @param {string} qrImagePath - Path ke gambar QR (opsional)
|
|
40
30
|
* @returns {Promise<{qrString: string, qrBuffer: Buffer}>}
|
|
41
31
|
*/
|
|
42
|
-
async
|
|
43
|
-
return await this.qrGenerator.
|
|
32
|
+
async generateQRFromAPI(amount) {
|
|
33
|
+
return await this.qrGenerator.generateQRFromAPI(amount);
|
|
44
34
|
}
|
|
45
35
|
|
|
46
36
|
async checkPayment(reference, amount) {
|
package/dist/index.mjs
CHANGED
|
@@ -15,15 +15,6 @@ class QRISPayment {
|
|
|
15
15
|
this.receiptGenerator = new ReceiptGenerator(config);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
/**
|
|
19
|
-
* Membaca QR code dari file gambar dan mengekstrak baseQrString
|
|
20
|
-
* @param {string} imagePath - Path ke file gambar QR
|
|
21
|
-
* @returns {Promise<string>} - Promise yang menghasilkan baseQrString
|
|
22
|
-
*/
|
|
23
|
-
async readQRCode(imagePath) {
|
|
24
|
-
return await this.qrGenerator.readQRFromImage(imagePath);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
18
|
/**
|
|
28
19
|
* Generate QR dengan nominal tertentu
|
|
29
20
|
* @param {number} amount - Nominal pembayaran
|
|
@@ -34,13 +25,12 @@ class QRISPayment {
|
|
|
34
25
|
}
|
|
35
26
|
|
|
36
27
|
/**
|
|
37
|
-
*
|
|
28
|
+
* Generate QR dengan nominal tertentu menggunakan API OrderKuota
|
|
38
29
|
* @param {number} amount - Nominal pembayaran
|
|
39
|
-
* @param {string} qrImagePath - Path ke gambar QR (opsional)
|
|
40
30
|
* @returns {Promise<{qrString: string, qrBuffer: Buffer}>}
|
|
41
31
|
*/
|
|
42
|
-
async
|
|
43
|
-
return await this.qrGenerator.
|
|
32
|
+
async generateQRFromAPI(amount) {
|
|
33
|
+
return await this.qrGenerator.generateQRFromAPI(amount);
|
|
44
34
|
}
|
|
45
35
|
|
|
46
36
|
async checkPayment(reference, amount) {
|
package/dist/payment-checker.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const axios = require("axios");
|
|
2
|
+
const moment = require("moment-timezone");
|
|
2
3
|
|
|
3
4
|
class PaymentChecker {
|
|
4
5
|
constructor(config) {
|
|
@@ -11,15 +12,15 @@ class PaymentChecker {
|
|
|
11
12
|
auth_token: config.auth_token
|
|
12
13
|
};
|
|
13
14
|
}
|
|
14
|
-
|
|
15
|
+
|
|
15
16
|
async checkPaymentStatus(reference, amount) {
|
|
16
17
|
try {
|
|
17
18
|
if (!reference || !amount || amount <= 0) {
|
|
18
19
|
throw new Error('Reference dan amount harus diisi dengan benar');
|
|
19
20
|
}
|
|
20
|
-
|
|
21
|
+
|
|
21
22
|
const response = await axios.post(
|
|
22
|
-
'https://
|
|
23
|
+
'https://eqris.com/api/mutasi-orkut-v2',
|
|
23
24
|
{
|
|
24
25
|
username_orkut: this.config.auth_username,
|
|
25
26
|
token_orkut: this.config.auth_token
|
|
@@ -31,32 +32,33 @@ class PaymentChecker {
|
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
);
|
|
34
|
-
|
|
35
|
+
|
|
35
36
|
if (!response.data || !response.data.status || !response.data.data) {
|
|
36
37
|
throw new Error('Response tidak valid dari server');
|
|
37
38
|
}
|
|
38
|
-
|
|
39
|
+
|
|
39
40
|
const transactions = response.data.data;
|
|
40
|
-
|
|
41
|
+
|
|
41
42
|
const matchingTransactions = transactions.filter(tx => {
|
|
42
43
|
const txAmount = parseInt(tx.amount);
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
// Parse tanggal dengan timezone Jakarta
|
|
45
|
+
const txDate = moment.tz(tx.date, 'YYYY-MM-DD HH:mm', 'Asia/Jakarta');
|
|
46
|
+
const now = moment().tz("Asia/Jakarta");
|
|
47
|
+
const timeDiff = now.diff(txDate, 'milliseconds');
|
|
48
|
+
|
|
49
|
+
return txAmount === amount &&
|
|
50
|
+
tx.qris === "static" &&
|
|
51
|
+
tx.type === "CR" &&
|
|
52
|
+
timeDiff <= 5 * 60 * 1000;
|
|
51
53
|
});
|
|
52
54
|
|
|
53
55
|
if (matchingTransactions.length > 0) {
|
|
54
56
|
const latestTransaction = matchingTransactions.reduce((latest, current) => {
|
|
55
|
-
const currentDate =
|
|
56
|
-
const latestDate =
|
|
57
|
-
return currentDate
|
|
57
|
+
const currentDate = moment.tz(current.date, 'YYYY-MM-DD HH:mm', 'Asia/Jakarta');
|
|
58
|
+
const latestDate = moment.tz(latest.date, 'YYYY-MM-DD HH:mm', 'Asia/Jakarta');
|
|
59
|
+
return currentDate.isAfter(latestDate) ? current : latest;
|
|
58
60
|
});
|
|
59
|
-
|
|
61
|
+
|
|
60
62
|
return {
|
|
61
63
|
success: true,
|
|
62
64
|
data: {
|
|
@@ -69,7 +71,7 @@ class PaymentChecker {
|
|
|
69
71
|
}
|
|
70
72
|
};
|
|
71
73
|
}
|
|
72
|
-
|
|
74
|
+
|
|
73
75
|
return {
|
|
74
76
|
success: true,
|
|
75
77
|
data: {
|
package/dist/payment-checker.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
|
+
import moment from 'moment-timezone';
|
|
2
3
|
|
|
3
4
|
class PaymentChecker {
|
|
4
5
|
constructor(config) {
|
|
@@ -11,15 +12,15 @@ class PaymentChecker {
|
|
|
11
12
|
auth_token: config.auth_token
|
|
12
13
|
};
|
|
13
14
|
}
|
|
14
|
-
|
|
15
|
+
|
|
15
16
|
async checkPaymentStatus(reference, amount) {
|
|
16
17
|
try {
|
|
17
18
|
if (!reference || !amount || amount <= 0) {
|
|
18
19
|
throw new Error('Reference dan amount harus diisi dengan benar');
|
|
19
20
|
}
|
|
20
|
-
|
|
21
|
+
|
|
21
22
|
const response = await axios.post(
|
|
22
|
-
'https://
|
|
23
|
+
'https://eqris.com/api/mutasi-orkut-v2',
|
|
23
24
|
{
|
|
24
25
|
username_orkut: this.config.auth_username,
|
|
25
26
|
token_orkut: this.config.auth_token
|
|
@@ -31,32 +32,33 @@ class PaymentChecker {
|
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
);
|
|
34
|
-
|
|
35
|
+
|
|
35
36
|
if (!response.data || !response.data.status || !response.data.data) {
|
|
36
37
|
throw new Error('Response tidak valid dari server');
|
|
37
38
|
}
|
|
38
|
-
|
|
39
|
+
|
|
39
40
|
const transactions = response.data.data;
|
|
40
|
-
|
|
41
|
+
|
|
41
42
|
const matchingTransactions = transactions.filter(tx => {
|
|
42
43
|
const txAmount = parseInt(tx.amount);
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
// Parse tanggal dengan timezone Jakarta
|
|
45
|
+
const txDate = moment.tz(tx.date, 'YYYY-MM-DD HH:mm', 'Asia/Jakarta');
|
|
46
|
+
const now = moment().tz("Asia/Jakarta");
|
|
47
|
+
const timeDiff = now.diff(txDate, 'milliseconds');
|
|
48
|
+
|
|
49
|
+
return txAmount === amount &&
|
|
50
|
+
tx.qris === "static" &&
|
|
51
|
+
tx.type === "CR" &&
|
|
52
|
+
timeDiff <= 5 * 60 * 1000;
|
|
51
53
|
});
|
|
52
54
|
|
|
53
55
|
if (matchingTransactions.length > 0) {
|
|
54
56
|
const latestTransaction = matchingTransactions.reduce((latest, current) => {
|
|
55
|
-
const currentDate =
|
|
56
|
-
const latestDate =
|
|
57
|
-
return currentDate
|
|
57
|
+
const currentDate = moment.tz(current.date, 'YYYY-MM-DD HH:mm', 'Asia/Jakarta');
|
|
58
|
+
const latestDate = moment.tz(latest.date, 'YYYY-MM-DD HH:mm', 'Asia/Jakarta');
|
|
59
|
+
return currentDate.isAfter(latestDate) ? current : latest;
|
|
58
60
|
});
|
|
59
|
-
|
|
61
|
+
|
|
60
62
|
return {
|
|
61
63
|
success: true,
|
|
62
64
|
data: {
|
|
@@ -69,7 +71,7 @@ class PaymentChecker {
|
|
|
69
71
|
}
|
|
70
72
|
};
|
|
71
73
|
}
|
|
72
|
-
|
|
74
|
+
|
|
73
75
|
return {
|
|
74
76
|
success: true,
|
|
75
77
|
data: {
|
package/dist/qr-generator.cjs
CHANGED
|
@@ -1,87 +1,16 @@
|
|
|
1
1
|
const QRCode = require("qrcode");
|
|
2
|
-
const
|
|
3
|
-
const sharp = require("sharp");
|
|
4
|
-
const jsQR = require("jsqr");
|
|
5
|
-
const path = require("path");
|
|
6
|
-
// CJS sudah menyediakan __filename dan __dirname secara otomatis
|
|
2
|
+
const axios = require("axios");
|
|
7
3
|
|
|
8
4
|
class QRISGenerator {
|
|
9
5
|
constructor(config = {}) {
|
|
10
6
|
this.config = {
|
|
11
7
|
baseQrString: config.baseQrString || '',
|
|
12
|
-
|
|
8
|
+
tokenKey: config.tokenKey,
|
|
9
|
+
auth_username: config.auth_username,
|
|
10
|
+
auth_token: config.auth_token
|
|
13
11
|
};
|
|
14
12
|
}
|
|
15
13
|
|
|
16
|
-
/**
|
|
17
|
-
* Membaca QR code dari gambar dan mengekstrak baseQrString
|
|
18
|
-
* @param {string} imagePath - Path relatif atau absolut ke file gambar QR
|
|
19
|
-
* @returns {Promise<string>} - Promise yang menghasilkan baseQrString
|
|
20
|
-
*/
|
|
21
|
-
async readQRFromImage(imagePath) {
|
|
22
|
-
try {
|
|
23
|
-
// Konversi path relatif ke absolut jika diperlukan
|
|
24
|
-
const absolutePath = path.isAbsolute(imagePath)
|
|
25
|
-
? imagePath
|
|
26
|
-
: path.resolve(process.cwd(), imagePath);
|
|
27
|
-
|
|
28
|
-
// Periksa apakah file ada
|
|
29
|
-
if (!fs.existsSync(absolutePath)) {
|
|
30
|
-
throw new Error(`File tidak ditemukan: ${absolutePath}`);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Baca gambar menggunakan Sharp
|
|
34
|
-
const image = sharp(absolutePath);
|
|
35
|
-
const metadata = await image.metadata();
|
|
36
|
-
const { width, height } = metadata;
|
|
37
|
-
|
|
38
|
-
// Konversi ke raw pixel data
|
|
39
|
-
const rawData = await image
|
|
40
|
-
.raw()
|
|
41
|
-
.toBuffer();
|
|
42
|
-
|
|
43
|
-
// Format data untuk jsQR
|
|
44
|
-
const imageData = new Uint8ClampedArray(width * height * 4);
|
|
45
|
-
|
|
46
|
-
for (let i = 0; i < rawData.length; i += 3) { // RGB format
|
|
47
|
-
const pixelIndex = (i / 3) * 4; // Convert RGB index to RGBA index
|
|
48
|
-
imageData[pixelIndex] = rawData[i]; // R
|
|
49
|
-
imageData[pixelIndex + 1] = rawData[i + 1]; // G
|
|
50
|
-
imageData[pixelIndex + 2] = rawData[i + 2]; // B
|
|
51
|
-
imageData[pixelIndex + 3] = 255; // A (full opacity)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Dekode QR code menggunakan jsQR
|
|
55
|
-
const code = jsQR(imageData, width, height);
|
|
56
|
-
|
|
57
|
-
if (code) {
|
|
58
|
-
// Simpan baseQrString yang dibaca ke config
|
|
59
|
-
this.config.baseQrString = code.data;
|
|
60
|
-
return code.data;
|
|
61
|
-
} else {
|
|
62
|
-
throw new Error('QR code tidak terdeteksi dalam gambar');
|
|
63
|
-
}
|
|
64
|
-
} catch (error) {
|
|
65
|
-
console.error('Error saat membaca QR code:', error);
|
|
66
|
-
throw error;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Membaca QR code dari file default (biasanya QRIS.png)
|
|
72
|
-
* @returns {Promise<string>} - Promise yang menghasilkan baseQrString
|
|
73
|
-
*/
|
|
74
|
-
async readDefaultQR() {
|
|
75
|
-
try {
|
|
76
|
-
// Path default ke file QRIS.png
|
|
77
|
-
const defaultPath = path.resolve(process.cwd(), this.config.defaultQrPath);
|
|
78
|
-
return await this.readQRFromImage(defaultPath);
|
|
79
|
-
} catch (error) {
|
|
80
|
-
console.error(`Error saat membaca gambar default (${this.config.defaultQrPath}):`, error);
|
|
81
|
-
throw error;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
14
|
/**
|
|
86
15
|
* Generate QR code sebagai PNG buffer
|
|
87
16
|
* @param {string} qrString - String QR code yang akan digenerate
|
|
@@ -92,7 +21,7 @@ class QRISGenerator {
|
|
|
92
21
|
if (!qrString) {
|
|
93
22
|
throw new Error('qrString tidak boleh kosong');
|
|
94
23
|
}
|
|
95
|
-
|
|
24
|
+
|
|
96
25
|
// Gunakan QRCode.toBuffer langsung tanpa canvas
|
|
97
26
|
const qrBuffer = await QRCode.toBuffer(qrString, {
|
|
98
27
|
errorCorrectionLevel: 'H',
|
|
@@ -103,7 +32,7 @@ class QRISGenerator {
|
|
|
103
32
|
light: '#ffffff'
|
|
104
33
|
}
|
|
105
34
|
});
|
|
106
|
-
|
|
35
|
+
|
|
107
36
|
return qrBuffer;
|
|
108
37
|
} catch (error) {
|
|
109
38
|
throw new Error('Gagal generate QR: ' + error.message);
|
|
@@ -111,65 +40,81 @@ class QRISGenerator {
|
|
|
111
40
|
}
|
|
112
41
|
|
|
113
42
|
/**
|
|
114
|
-
*
|
|
43
|
+
* Generate QR dengan nominal tertentu
|
|
44
|
+
* @param {number} amount - Nominal pembayaran
|
|
45
|
+
* @returns {Promise<{qrString: string, qrBuffer: Buffer}>}
|
|
46
|
+
*/
|
|
47
|
+
async generateQR(amount) {
|
|
48
|
+
const qrString = this.generateQrString(amount);
|
|
49
|
+
const qrBuffer = await this.generateQRImage(qrString);
|
|
50
|
+
return {
|
|
51
|
+
qrString,
|
|
52
|
+
qrBuffer
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Generate QR dengan nominal tertentu menggunakan API OrderKuota
|
|
115
58
|
* @param {number} amount - Nominal pembayaran
|
|
116
|
-
* @param {string} qrImagePath - Path ke gambar QR (opsional)
|
|
117
59
|
* @returns {Promise<{qrString: string, qrBuffer: Buffer}>}
|
|
118
60
|
*/
|
|
119
|
-
async
|
|
61
|
+
async generateQRFromAPI(amount) {
|
|
120
62
|
try {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
63
|
+
if (!amount || amount <= 0) {
|
|
64
|
+
throw new Error('Nominal harus lebih besar dari 0');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!this.config.tokenKey || !this.config.auth_username || !this.config.auth_token) {
|
|
68
|
+
throw new Error('tokenKey, auth_username, dan auth_token harus diisi');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const response = await axios.post(
|
|
72
|
+
'https://eqris.com/api/qr-orkut-v2',
|
|
73
|
+
{
|
|
74
|
+
username_orkut: this.config.auth_username,
|
|
75
|
+
token_orkut: this.config.auth_token,
|
|
76
|
+
nominal: amount.toString()
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
headers: {
|
|
80
|
+
'tokenKey': this.config.tokenKey,
|
|
81
|
+
'Content-Type': 'application/json'
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
if (!response.data || !response.data.status || !response.data.qrString) {
|
|
87
|
+
throw new Error('Response tidak valid dari server');
|
|
128
88
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
// Generate QR image
|
|
89
|
+
|
|
90
|
+
const qrString = response.data.qrString;
|
|
91
|
+
|
|
92
|
+
// Generate QR image dari qrString yang didapat dari API
|
|
134
93
|
const qrBuffer = await this.generateQRImage(qrString);
|
|
135
|
-
|
|
94
|
+
|
|
136
95
|
return {
|
|
137
96
|
qrString,
|
|
138
97
|
qrBuffer
|
|
139
98
|
};
|
|
140
99
|
} catch (error) {
|
|
141
|
-
throw new Error('Gagal generate QR dari
|
|
100
|
+
throw new Error('Gagal generate QR dari API: ' + error.message);
|
|
142
101
|
}
|
|
143
102
|
}
|
|
144
103
|
|
|
145
|
-
/**
|
|
146
|
-
* Generate QR dengan nominal tertentu
|
|
147
|
-
* @param {number} amount - Nominal pembayaran
|
|
148
|
-
* @returns {Promise<{qrString: string, qrBuffer: Buffer}>}
|
|
149
|
-
*/
|
|
150
|
-
async generateQR(amount) {
|
|
151
|
-
const qrString = this.generateQrString(amount);
|
|
152
|
-
const qrBuffer = await this.generateQRImage(qrString);
|
|
153
|
-
return {
|
|
154
|
-
qrString,
|
|
155
|
-
qrBuffer
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
104
|
generateQrString(amount) {
|
|
160
105
|
try {
|
|
161
106
|
if (!amount || amount <= 0) {
|
|
162
107
|
throw new Error('Nominal harus lebih besar dari 0');
|
|
163
108
|
}
|
|
164
|
-
|
|
109
|
+
|
|
165
110
|
if (!this.config.baseQrString) {
|
|
166
111
|
throw new Error('BaseQrString tidak tersedia. Gunakan readQRFromImage terlebih dahulu atau berikan baseQrString pada config.');
|
|
167
112
|
}
|
|
168
|
-
|
|
113
|
+
|
|
169
114
|
if (!this.config.baseQrString.includes("5802ID")) {
|
|
170
115
|
throw new Error("Format QRIS tidak valid");
|
|
171
116
|
}
|
|
172
|
-
|
|
117
|
+
|
|
173
118
|
const finalAmount = Math.floor(amount);
|
|
174
119
|
const qrisBase = this.config.baseQrString.slice(0, -4).replace("010211", "010212");
|
|
175
120
|
const nominalStr = finalAmount.toString();
|
|
@@ -177,7 +122,7 @@ class QRISGenerator {
|
|
|
177
122
|
const insertPosition = qrisBase.indexOf("5802ID");
|
|
178
123
|
const qrisWithNominal = qrisBase.slice(0, insertPosition) + nominalTag + qrisBase.slice(insertPosition);
|
|
179
124
|
const checksum = this.calculateCRC16(qrisWithNominal);
|
|
180
|
-
|
|
125
|
+
|
|
181
126
|
return qrisWithNominal + checksum;
|
|
182
127
|
} catch (error) {
|
|
183
128
|
throw new Error('Gagal generate string QRIS: ' + error.message);
|
package/dist/qr-generator.mjs
CHANGED
|
@@ -1,90 +1,16 @@
|
|
|
1
1
|
import QRCode from 'qrcode';
|
|
2
|
-
import
|
|
3
|
-
import sharp from 'sharp';
|
|
4
|
-
import jsQR from 'jsqr';
|
|
5
|
-
import path from 'path';
|
|
6
|
-
import { fileURLToPath } from 'url';
|
|
7
|
-
|
|
8
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
-
const __dirname = path.dirname(__filename);
|
|
2
|
+
import axios from 'axios';
|
|
10
3
|
|
|
11
4
|
class QRISGenerator {
|
|
12
5
|
constructor(config = {}) {
|
|
13
6
|
this.config = {
|
|
14
7
|
baseQrString: config.baseQrString || '',
|
|
15
|
-
|
|
8
|
+
tokenKey: config.tokenKey,
|
|
9
|
+
auth_username: config.auth_username,
|
|
10
|
+
auth_token: config.auth_token
|
|
16
11
|
};
|
|
17
12
|
}
|
|
18
13
|
|
|
19
|
-
/**
|
|
20
|
-
* Membaca QR code dari gambar dan mengekstrak baseQrString
|
|
21
|
-
* @param {string} imagePath - Path relatif atau absolut ke file gambar QR
|
|
22
|
-
* @returns {Promise<string>} - Promise yang menghasilkan baseQrString
|
|
23
|
-
*/
|
|
24
|
-
async readQRFromImage(imagePath) {
|
|
25
|
-
try {
|
|
26
|
-
// Konversi path relatif ke absolut jika diperlukan
|
|
27
|
-
const absolutePath = path.isAbsolute(imagePath)
|
|
28
|
-
? imagePath
|
|
29
|
-
: path.resolve(process.cwd(), imagePath);
|
|
30
|
-
|
|
31
|
-
// Periksa apakah file ada
|
|
32
|
-
if (!fs.existsSync(absolutePath)) {
|
|
33
|
-
throw new Error(`File tidak ditemukan: ${absolutePath}`);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Baca gambar menggunakan Sharp
|
|
37
|
-
const image = sharp(absolutePath);
|
|
38
|
-
const metadata = await image.metadata();
|
|
39
|
-
const { width, height } = metadata;
|
|
40
|
-
|
|
41
|
-
// Konversi ke raw pixel data
|
|
42
|
-
const rawData = await image
|
|
43
|
-
.raw()
|
|
44
|
-
.toBuffer();
|
|
45
|
-
|
|
46
|
-
// Format data untuk jsQR
|
|
47
|
-
const imageData = new Uint8ClampedArray(width * height * 4);
|
|
48
|
-
|
|
49
|
-
for (let i = 0; i < rawData.length; i += 3) { // RGB format
|
|
50
|
-
const pixelIndex = (i / 3) * 4; // Convert RGB index to RGBA index
|
|
51
|
-
imageData[pixelIndex] = rawData[i]; // R
|
|
52
|
-
imageData[pixelIndex + 1] = rawData[i + 1]; // G
|
|
53
|
-
imageData[pixelIndex + 2] = rawData[i + 2]; // B
|
|
54
|
-
imageData[pixelIndex + 3] = 255; // A (full opacity)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Dekode QR code menggunakan jsQR
|
|
58
|
-
const code = jsQR(imageData, width, height);
|
|
59
|
-
|
|
60
|
-
if (code) {
|
|
61
|
-
// Simpan baseQrString yang dibaca ke config
|
|
62
|
-
this.config.baseQrString = code.data;
|
|
63
|
-
return code.data;
|
|
64
|
-
} else {
|
|
65
|
-
throw new Error('QR code tidak terdeteksi dalam gambar');
|
|
66
|
-
}
|
|
67
|
-
} catch (error) {
|
|
68
|
-
console.error('Error saat membaca QR code:', error);
|
|
69
|
-
throw error;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Membaca QR code dari file default (biasanya QRIS.png)
|
|
75
|
-
* @returns {Promise<string>} - Promise yang menghasilkan baseQrString
|
|
76
|
-
*/
|
|
77
|
-
async readDefaultQR() {
|
|
78
|
-
try {
|
|
79
|
-
// Path default ke file QRIS.png
|
|
80
|
-
const defaultPath = path.resolve(process.cwd(), this.config.defaultQrPath);
|
|
81
|
-
return await this.readQRFromImage(defaultPath);
|
|
82
|
-
} catch (error) {
|
|
83
|
-
console.error(`Error saat membaca gambar default (${this.config.defaultQrPath}):`, error);
|
|
84
|
-
throw error;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
14
|
/**
|
|
89
15
|
* Generate QR code sebagai PNG buffer
|
|
90
16
|
* @param {string} qrString - String QR code yang akan digenerate
|
|
@@ -95,7 +21,7 @@ class QRISGenerator {
|
|
|
95
21
|
if (!qrString) {
|
|
96
22
|
throw new Error('qrString tidak boleh kosong');
|
|
97
23
|
}
|
|
98
|
-
|
|
24
|
+
|
|
99
25
|
// Gunakan QRCode.toBuffer langsung tanpa canvas
|
|
100
26
|
const qrBuffer = await QRCode.toBuffer(qrString, {
|
|
101
27
|
errorCorrectionLevel: 'H',
|
|
@@ -106,7 +32,7 @@ class QRISGenerator {
|
|
|
106
32
|
light: '#ffffff'
|
|
107
33
|
}
|
|
108
34
|
});
|
|
109
|
-
|
|
35
|
+
|
|
110
36
|
return qrBuffer;
|
|
111
37
|
} catch (error) {
|
|
112
38
|
throw new Error('Gagal generate QR: ' + error.message);
|
|
@@ -114,65 +40,81 @@ class QRISGenerator {
|
|
|
114
40
|
}
|
|
115
41
|
|
|
116
42
|
/**
|
|
117
|
-
*
|
|
43
|
+
* Generate QR dengan nominal tertentu
|
|
118
44
|
* @param {number} amount - Nominal pembayaran
|
|
119
|
-
* @param {string} qrImagePath - Path ke gambar QR (opsional)
|
|
120
45
|
* @returns {Promise<{qrString: string, qrBuffer: Buffer}>}
|
|
121
46
|
*/
|
|
122
|
-
async
|
|
47
|
+
async generateQR(amount) {
|
|
48
|
+
const qrString = this.generateQrString(amount);
|
|
49
|
+
const qrBuffer = await this.generateQRImage(qrString);
|
|
50
|
+
return {
|
|
51
|
+
qrString,
|
|
52
|
+
qrBuffer
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Generate QR dengan nominal tertentu menggunakan API OrderKuota
|
|
58
|
+
* @param {number} amount - Nominal pembayaran
|
|
59
|
+
* @returns {Promise<{qrString: string, qrBuffer: Buffer}>}
|
|
60
|
+
*/
|
|
61
|
+
async generateQRFromAPI(amount) {
|
|
123
62
|
try {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
63
|
+
if (!amount || amount <= 0) {
|
|
64
|
+
throw new Error('Nominal harus lebih besar dari 0');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!this.config.tokenKey || !this.config.auth_username || !this.config.auth_token) {
|
|
68
|
+
throw new Error('tokenKey, auth_username, dan auth_token harus diisi');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const response = await axios.post(
|
|
72
|
+
'https://eqris.com/api/qr-orkut-v2',
|
|
73
|
+
{
|
|
74
|
+
username_orkut: this.config.auth_username,
|
|
75
|
+
token_orkut: this.config.auth_token,
|
|
76
|
+
nominal: amount.toString()
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
headers: {
|
|
80
|
+
'tokenKey': this.config.tokenKey,
|
|
81
|
+
'Content-Type': 'application/json'
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
if (!response.data || !response.data.status || !response.data.qrString) {
|
|
87
|
+
throw new Error('Response tidak valid dari server');
|
|
131
88
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
// Generate QR image
|
|
89
|
+
|
|
90
|
+
const qrString = response.data.qrString;
|
|
91
|
+
|
|
92
|
+
// Generate QR image dari qrString yang didapat dari API
|
|
137
93
|
const qrBuffer = await this.generateQRImage(qrString);
|
|
138
|
-
|
|
94
|
+
|
|
139
95
|
return {
|
|
140
96
|
qrString,
|
|
141
97
|
qrBuffer
|
|
142
98
|
};
|
|
143
99
|
} catch (error) {
|
|
144
|
-
throw new Error('Gagal generate QR dari
|
|
100
|
+
throw new Error('Gagal generate QR dari API: ' + error.message);
|
|
145
101
|
}
|
|
146
102
|
}
|
|
147
103
|
|
|
148
|
-
/**
|
|
149
|
-
* Generate QR dengan nominal tertentu
|
|
150
|
-
* @param {number} amount - Nominal pembayaran
|
|
151
|
-
* @returns {Promise<{qrString: string, qrBuffer: Buffer}>}
|
|
152
|
-
*/
|
|
153
|
-
async generateQR(amount) {
|
|
154
|
-
const qrString = this.generateQrString(amount);
|
|
155
|
-
const qrBuffer = await this.generateQRImage(qrString);
|
|
156
|
-
return {
|
|
157
|
-
qrString,
|
|
158
|
-
qrBuffer
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
104
|
generateQrString(amount) {
|
|
163
105
|
try {
|
|
164
106
|
if (!amount || amount <= 0) {
|
|
165
107
|
throw new Error('Nominal harus lebih besar dari 0');
|
|
166
108
|
}
|
|
167
|
-
|
|
109
|
+
|
|
168
110
|
if (!this.config.baseQrString) {
|
|
169
111
|
throw new Error('BaseQrString tidak tersedia. Gunakan readQRFromImage terlebih dahulu atau berikan baseQrString pada config.');
|
|
170
112
|
}
|
|
171
|
-
|
|
113
|
+
|
|
172
114
|
if (!this.config.baseQrString.includes("5802ID")) {
|
|
173
115
|
throw new Error("Format QRIS tidak valid");
|
|
174
116
|
}
|
|
175
|
-
|
|
117
|
+
|
|
176
118
|
const finalAmount = Math.floor(amount);
|
|
177
119
|
const qrisBase = this.config.baseQrString.slice(0, -4).replace("010211", "010212");
|
|
178
120
|
const nominalStr = finalAmount.toString();
|
|
@@ -180,7 +122,7 @@ class QRISGenerator {
|
|
|
180
122
|
const insertPosition = qrisBase.indexOf("5802ID");
|
|
181
123
|
const qrisWithNominal = qrisBase.slice(0, insertPosition) + nominalTag + qrisBase.slice(insertPosition);
|
|
182
124
|
const checksum = this.calculateCRC16(qrisWithNominal);
|
|
183
|
-
|
|
125
|
+
|
|
184
126
|
return qrisWithNominal + checksum;
|
|
185
127
|
} catch (error) {
|
|
186
128
|
throw new Error('Gagal generate string QRIS: ' + error.message);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const PDFDocument = require("pdfkit");
|
|
2
2
|
const fs = require("fs");
|
|
3
|
-
const moment = require("moment");
|
|
3
|
+
const moment = require("moment-timezone");
|
|
4
4
|
const path = require("path");
|
|
5
5
|
|
|
6
6
|
class ReceiptGenerator {
|
|
@@ -63,7 +63,7 @@ class ReceiptGenerator {
|
|
|
63
63
|
doc.text(detailsTitle, 20 + (260 - detailsTitleWidth) / 2, doc.y, { width: detailsTitleWidth });
|
|
64
64
|
doc.moveDown(0.2);
|
|
65
65
|
|
|
66
|
-
const formattedDate = moment(transactionData.date).format('DD/MM/YYYY HH:mm:ss');
|
|
66
|
+
const formattedDate = moment.tz(transactionData.date, 'YYYY-MM-DD HH:mm', 'Asia/Jakarta').format('DD/MM/YYYY HH:mm:ss');
|
|
67
67
|
const details = [
|
|
68
68
|
['Reference', transactionData.reference],
|
|
69
69
|
['Date', formattedDate],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import PDFDocument from 'pdfkit';
|
|
2
2
|
import fs from 'fs';
|
|
3
|
-
import moment from 'moment';
|
|
3
|
+
import moment from 'moment-timezone';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
|
|
6
6
|
class ReceiptGenerator {
|
|
@@ -63,7 +63,7 @@ class ReceiptGenerator {
|
|
|
63
63
|
doc.text(detailsTitle, 20 + (260 - detailsTitleWidth) / 2, doc.y, { width: detailsTitleWidth });
|
|
64
64
|
doc.moveDown(0.2);
|
|
65
65
|
|
|
66
|
-
const formattedDate = moment(transactionData.date).format('DD/MM/YYYY HH:mm:ss');
|
|
66
|
+
const formattedDate = moment.tz(transactionData.date, 'YYYY-MM-DD HH:mm', 'Asia/Jakarta').format('DD/MM/YYYY HH:mm:ss');
|
|
67
67
|
const details = [
|
|
68
68
|
['Reference', transactionData.reference],
|
|
69
69
|
['Date', formattedDate],
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wahdx-api",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Package untuk generate QRIS dan cek payment status secara realtime dengan API OrderKuota dari https://
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "Package untuk generate QRIS dan cek payment status secara realtime dengan API OrderKuota dari https://eqris.com",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
7
7
|
"exports": {
|
|
@@ -38,13 +38,11 @@
|
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@dotenvx/dotenvx": "^1.47.5",
|
|
40
40
|
"axios": "^1.10.0",
|
|
41
|
-
"
|
|
42
|
-
"moment": "^2.29.4",
|
|
41
|
+
"moment-timezone": "^0.6.0",
|
|
43
42
|
"pdfkit": "^0.13.0",
|
|
44
|
-
"qrcode": "^1.5.4"
|
|
45
|
-
"sharp": "^0.32.6"
|
|
43
|
+
"qrcode": "^1.5.4"
|
|
46
44
|
},
|
|
47
45
|
"engines": {
|
|
48
46
|
"node": ">=20.18.3"
|
|
49
47
|
}
|
|
50
|
-
}
|
|
48
|
+
}
|