dra-qris-api 1.0.4 → 1.0.5
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.
Potentially problematic release.
This version of dra-qris-api might be problematic. Click here for more details.
- package/lib/handlePaymentProof.js +119 -10
- package/package.json +14 -3
- package/tsup.config.js +8 -0
|
@@ -7,7 +7,6 @@ const globalPending = require("./globalPending");
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Handle bukti transfer via gambar (QRIS / slip)
|
|
10
|
-
* Universal: tidak tahu ini buat panel/topup/dll.
|
|
11
10
|
*
|
|
12
11
|
* @param {Object} params
|
|
13
12
|
* @param {Object} params.m - message object (Baileys)
|
|
@@ -47,12 +46,12 @@ async function handlePaymentProof({
|
|
|
47
46
|
m,
|
|
48
47
|
"buffer",
|
|
49
48
|
{},
|
|
50
|
-
{ reuploadRequest: conn.waUploadToServer }
|
|
49
|
+
{ reuploadRequest: conn.waUploadToServer },
|
|
51
50
|
);
|
|
52
51
|
|
|
53
52
|
if (!buffer || buffer.length === 0) {
|
|
54
53
|
await reply(
|
|
55
|
-
"❌ Gagal download gambar. Pastikan file terkirim dengan benar."
|
|
54
|
+
"❌ Gagal download gambar. Pastikan file terkirim dengan benar.",
|
|
56
55
|
);
|
|
57
56
|
return null;
|
|
58
57
|
}
|
|
@@ -83,12 +82,119 @@ async function handlePaymentProof({
|
|
|
83
82
|
|
|
84
83
|
const parseOcrAmount = (text) => {
|
|
85
84
|
const lines = text.split(/\r?\n/);
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
const amounts = [];
|
|
86
|
+
|
|
87
|
+
const keywords = [
|
|
88
|
+
/nominal/i,
|
|
89
|
+
/jumlah\s*total/i,
|
|
90
|
+
/total\s*bayar/i,
|
|
91
|
+
/total/i,
|
|
92
|
+
/amount/i,
|
|
93
|
+
/transfer/i,
|
|
94
|
+
/dibayar/i,
|
|
95
|
+
/bayar/i,
|
|
96
|
+
/tagihan/i,
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
for (const line of lines) {
|
|
100
|
+
const hasKeyword = keywords.some((kw) => kw.test(line));
|
|
101
|
+
|
|
102
|
+
if (hasKeyword) {
|
|
103
|
+
const matches = extractAmounts(line);
|
|
104
|
+
amounts.push(...matches);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (amounts.length === 0) {
|
|
109
|
+
for (const line of lines) {
|
|
110
|
+
if (/\b(rp|idr)\s*[\d.,]+/i.test(line)) {
|
|
111
|
+
const matches = extractAmounts(line);
|
|
112
|
+
amounts.push(...matches);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (amounts.length === 0) {
|
|
118
|
+
for (const line of lines) {
|
|
119
|
+
// Cari angka dengan pemisah ribuan (. atau ,) dan minimal 3 digit
|
|
120
|
+
const matches = extractAmounts(line);
|
|
121
|
+
// Filter hanya angka >= 1000 (asumsi minimal nominal transfer)
|
|
122
|
+
const validMatches = matches.filter((amt) => amt >= 1000);
|
|
123
|
+
amounts.push(...validMatches);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (amounts.length > 0) {
|
|
128
|
+
return Math.max(...amounts);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const fallbackAmounts = extractAmounts(text);
|
|
132
|
+
return fallbackAmounts.length > 0 ? Math.max(...fallbackAmounts) : 0;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Ekstrak semua kemungkinan nominal dari string
|
|
137
|
+
* @param {string} str - text input
|
|
138
|
+
* @returns {number[]} - array of amounts
|
|
139
|
+
*/
|
|
140
|
+
const extractAmounts = (str) => {
|
|
141
|
+
const amounts = [];
|
|
142
|
+
|
|
143
|
+
// Pattern 1: Rp 50.000 / Rp50.000 / Rp 50000 / Rp50,000
|
|
144
|
+
const rpPattern = /\b(?:rp|idr)\s*([\d.,]+)/gi;
|
|
145
|
+
let match;
|
|
146
|
+
while ((match = rpPattern.exec(str)) !== null) {
|
|
147
|
+
const cleaned = cleanNumber(match[1]);
|
|
148
|
+
if (cleaned > 0) amounts.push(cleaned);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Pattern 2: Angka dengan pemisah ribuan (50.000 atau 50,000)
|
|
152
|
+
// Harus minimal 4 digit untuk menghindari false positive
|
|
153
|
+
const numberPattern =
|
|
154
|
+
/\b(\d{1,3}[.,]\d{3}(?:[.,]\d{3})*(?:[.,]\d{1,2})?)\b/g;
|
|
155
|
+
while ((match = numberPattern.exec(str)) !== null) {
|
|
156
|
+
const cleaned = cleanNumber(match[1]);
|
|
157
|
+
if (cleaned > 0) amounts.push(cleaned);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Pattern 3: Angka polos minimal 4 digit (50000)
|
|
161
|
+
const plainPattern = /\b(\d{4,})\b/g;
|
|
162
|
+
while ((match = plainPattern.exec(str)) !== null) {
|
|
163
|
+
const cleaned = cleanNumber(match[1]);
|
|
164
|
+
if (cleaned > 0) amounts.push(cleaned);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return amounts;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Bersihkan dan konversi string angka ke number
|
|
172
|
+
* Menangani format Indonesia (50.000,00) dan International (50,000.00)
|
|
173
|
+
*/
|
|
174
|
+
const cleanNumber = (str) => {
|
|
175
|
+
if (!str) return 0;
|
|
176
|
+
|
|
177
|
+
// Hitung jumlah titik dan koma
|
|
178
|
+
const dotCount = (str.match(/\./g) || []).length;
|
|
179
|
+
const commaCount = (str.match(/,/g) || []).length;
|
|
180
|
+
|
|
181
|
+
let cleaned = str;
|
|
182
|
+
|
|
183
|
+
// Format Indonesia: 50.000,00 (titik sebagai pemisah ribuan, koma desimal)
|
|
184
|
+
if (dotCount > 0 && commaCount <= 1) {
|
|
185
|
+
cleaned = str.replace(/\./g, "").replace(/,/g, ".");
|
|
186
|
+
}
|
|
187
|
+
// Format International: 50,000.00 (koma sebagai pemisah ribuan, titik desimal)
|
|
188
|
+
else if (commaCount > 0 && dotCount <= 1) {
|
|
189
|
+
cleaned = str.replace(/,/g, "");
|
|
190
|
+
}
|
|
191
|
+
// Angka polos tanpa pemisah
|
|
192
|
+
else {
|
|
193
|
+
cleaned = str.replace(/[.,]/g, "");
|
|
194
|
+
}
|
|
88
195
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return Number(cleaned) || 0;
|
|
196
|
+
const num = parseFloat(cleaned);
|
|
197
|
+
return isNaN(num) ? 0 : Math.floor(num); // Bulatkan ke bawah untuk amount
|
|
92
198
|
};
|
|
93
199
|
|
|
94
200
|
const ocrAmount = parseOcrAmount(text);
|
|
@@ -109,13 +215,16 @@ async function handlePaymentProof({
|
|
|
109
215
|
await reply(
|
|
110
216
|
`✅ Pembayaran untuk *${
|
|
111
217
|
pending.description || "transaksi kamu"
|
|
112
|
-
}* sebesar Rp${pending.nominal} terverifikasi
|
|
218
|
+
}* sebesar Rp${pending.nominal.toLocaleString("id-ID")} terverifikasi.\n\n` +
|
|
219
|
+
`💰 Nominal terdeteksi: Rp${ocrAmount.toLocaleString("id-ID")}`,
|
|
113
220
|
);
|
|
114
221
|
|
|
115
222
|
delete globalPending[m.sender];
|
|
116
223
|
} else {
|
|
117
224
|
await reply(
|
|
118
|
-
`⚠️ Pembayaran belum valid / belum masuk. Status: *${status}
|
|
225
|
+
`⚠️ Pembayaran belum valid / belum masuk. Status: *${status}*\n\n` +
|
|
226
|
+
`💰 Nominal terdeteksi: Rp${ocrAmount.toLocaleString("id-ID")}\n` +
|
|
227
|
+
`📋 Nominal seharusnya: Rp${pending.nominal.toLocaleString("id-ID")}`,
|
|
119
228
|
);
|
|
120
229
|
}
|
|
121
230
|
|
package/package.json
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dra-qris-api",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Connector and modules for QRIS API DenayRestAPI ",
|
|
5
|
-
"main": "index.js",
|
|
6
5
|
"scripts": {
|
|
7
|
-
"
|
|
6
|
+
"build": "tsup"
|
|
7
|
+
},
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"module": "./dist/index.mjs",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/index.mjs",
|
|
13
|
+
"require": "./dist/index.js"
|
|
14
|
+
}
|
|
8
15
|
},
|
|
9
16
|
"keywords": [
|
|
10
17
|
"draqrisapi"
|
|
@@ -14,5 +21,9 @@
|
|
|
14
21
|
"axios": "^1.12.2",
|
|
15
22
|
"crypto": "^1.0.1",
|
|
16
23
|
"tesseract.js": "^6.0.1"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"tsup": "^8.5.1",
|
|
27
|
+
"typescript": "^5.9.3"
|
|
17
28
|
}
|
|
18
29
|
}
|