dra-qris-api 1.0.0 → 1.0.4
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/README.md +253 -0
- package/index.js +5 -89
- package/lib/createQris.js +46 -0
- package/lib/globalPending.js +3 -0
- package/lib/handlePaymentProof.js +140 -0
- package/lib/submitProof.js +49 -0
- package/package.json +4 -3
package/README.md
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# DRA QRIS API
|
|
4
|
+
|
|
5
|
+
## QRIS Dinamis Generator + Auto Payment Handler
|
|
6
|
+
|
|
7
|
+
Library Node.js untuk membuat QRIS, membaca bukti transfer secara otomatis, dan memverifikasi pembayaran melalui API.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<img src="https://img.shields.io/badge/dra--qris--api-v1.0.4-purple?logo=npm" alt="Version"/>
|
|
13
|
+
<img src="https://img.shields.io/badge/Node.js-16%2B-green?logo=node.js" alt="Node.js"/>
|
|
14
|
+
<img src="https://img.shields.io/badge/OCR-Tesseract-blue?logo=tesseract" alt="OCR"/>
|
|
15
|
+
<img src="https://img.shields.io/badge/API-Secure-orange?logo=shield" alt="Secure"/>
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## ⚡ Fitur Utama
|
|
23
|
+
|
|
24
|
+
- ⚡ Generate QRIS dinamis otomatis
|
|
25
|
+
- 🔍 Membaca foto bukti transfer otomatis
|
|
26
|
+
- 🤖 Auto Payment Handler untuk bot WhatsApp (untuk saat ini hanya pada bot WhatsApp)
|
|
27
|
+
- 🔐 HMAC Signature SHA-256 untuk keamanan request
|
|
28
|
+
- 🗂️ Sangat mudah diintegrasikan ke bot / server mana pun
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 🛠️ Instalasi
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
npm i dra-qris-api
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 📁 Struktur Fungsi
|
|
39
|
+
|
|
40
|
+
Library ini menyediakan 4 fitur utama:
|
|
41
|
+
|
|
42
|
+
<table>
|
|
43
|
+
<thead>
|
|
44
|
+
<tr>
|
|
45
|
+
<th>Fungsi</th>
|
|
46
|
+
<th>Keterangan</th>
|
|
47
|
+
</tr>
|
|
48
|
+
</thead>
|
|
49
|
+
<tbody>
|
|
50
|
+
<tr>
|
|
51
|
+
<td><code>createQris()</code></td>
|
|
52
|
+
<td>Membuat QRIS dan mengembalikan image buffer</td>
|
|
53
|
+
</tr>
|
|
54
|
+
<tr>
|
|
55
|
+
<td><code>submitProof()</code></td>
|
|
56
|
+
<td>Mengirim hasil OCR untuk verifikasi ke API</td>
|
|
57
|
+
</tr>
|
|
58
|
+
<tr>
|
|
59
|
+
<td><code>handlePaymentProof()</code></td>
|
|
60
|
+
<td>Handler otomatis untuk bukti transfer (gambar)</td>
|
|
61
|
+
</tr>
|
|
62
|
+
<tr>
|
|
63
|
+
<td><code>globalPending</code></td>
|
|
64
|
+
<td>Store transaksi yang sedang menunggu pembayaran</td>
|
|
65
|
+
</tr>
|
|
66
|
+
</tbody>
|
|
67
|
+
</table>
|
|
68
|
+
|
|
69
|
+
## 🧠 Arsitektur Singkat
|
|
70
|
+
|
|
71
|
+
```mermaid
|
|
72
|
+
flowchart TD
|
|
73
|
+
A["User Kirim Command Order"] --> B["createQris()"]
|
|
74
|
+
B --> C["Generate QRIS + ID"]
|
|
75
|
+
C --> D["Simpan ke globalPending"]
|
|
76
|
+
D --> E["User Kirim Bukti Transfer"]
|
|
77
|
+
E --> F["handlePaymentProof()"]
|
|
78
|
+
F --> G["OCR Tesseract"]
|
|
79
|
+
G --> H["submitProof()"]
|
|
80
|
+
H --> I{"Status Paid?"}
|
|
81
|
+
I -->|YES| J["Success Callback (bot handle)"]
|
|
82
|
+
I -->|NO| K["Reply: Pembayaran belum valid"]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## 🚀 Cara Penggunaan
|
|
86
|
+
|
|
87
|
+
### 1. Import Function
|
|
88
|
+
|
|
89
|
+
```js
|
|
90
|
+
const {
|
|
91
|
+
createQris,
|
|
92
|
+
submitProof,
|
|
93
|
+
handlePaymentProof,
|
|
94
|
+
globalPending,
|
|
95
|
+
} = require("dra-qris-api");
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 2. Generate QRIS
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
const { id, buffer } = await createQris({
|
|
102
|
+
nominal: 15000,
|
|
103
|
+
merchantName: "NAMA MERCHANT",
|
|
104
|
+
qris: "STRING_QRIS",
|
|
105
|
+
apikey: "API_KEY", // Hubungi admin atau kunjungi https://api.denayrestapi.xyz untuk mendapatkan apikey
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Simpan transaksi:
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
globalPending[userId] = {
|
|
113
|
+
id,
|
|
114
|
+
nominal: 15000,
|
|
115
|
+
merchantName: "NAMA MERCHANT",
|
|
116
|
+
description: "Pembelian Paket Premium",
|
|
117
|
+
meta: { type: "premium" },
|
|
118
|
+
};
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 3. Handler Bukti Transfer
|
|
122
|
+
|
|
123
|
+
```js
|
|
124
|
+
if (m.message?.imageMessage) {
|
|
125
|
+
const result = await handlePaymentProof({
|
|
126
|
+
m,
|
|
127
|
+
NekonoidF,
|
|
128
|
+
reply,
|
|
129
|
+
botname: "MyBot",
|
|
130
|
+
apiKey: "API_KEY_KAMU", // Hubungi admin atau kunjungi https://api.denayrestapi.xyz untuk mendapatkan apikey
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
if (!result) return; // gambar biasa bukan bukti tf
|
|
134
|
+
|
|
135
|
+
if (result.status === "paid") {
|
|
136
|
+
await reply("🎉 Pembayaran berhasil diterima!");
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 4. Struktur Return dari handlePaymentProof()
|
|
142
|
+
|
|
143
|
+
```js
|
|
144
|
+
{
|
|
145
|
+
status: "paid",
|
|
146
|
+
ocrAmount: 15000,
|
|
147
|
+
ocrText: "...hasil bukti transfer...",
|
|
148
|
+
pending: {
|
|
149
|
+
id: "...",
|
|
150
|
+
nominal: 15000,
|
|
151
|
+
merchantName: "NAMA MERCHANT",
|
|
152
|
+
description: "Pembelian Paket Premium",
|
|
153
|
+
meta: {...},
|
|
154
|
+
},
|
|
155
|
+
apiResult: {...}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## 📦 Fitur: globalPending
|
|
160
|
+
|
|
161
|
+
Contoh penyimpanan transaksi:
|
|
162
|
+
|
|
163
|
+
```js
|
|
164
|
+
globalPending["628xxx@s.whatsapp.net"] = {
|
|
165
|
+
id: "PAY123",
|
|
166
|
+
nominal: 20000,
|
|
167
|
+
description: "Topup Saldo",
|
|
168
|
+
meta: {
|
|
169
|
+
type: "topup",
|
|
170
|
+
amount: 20000,
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Menghapus transaksi:
|
|
176
|
+
|
|
177
|
+
```js
|
|
178
|
+
delete globalPending[m.sender];
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Implementasi Lengkap
|
|
182
|
+
|
|
183
|
+
```js
|
|
184
|
+
if (m.message?.imageMessage) {
|
|
185
|
+
const result = await handlePaymentProof({
|
|
186
|
+
m,
|
|
187
|
+
NekonoidF: conn,
|
|
188
|
+
reply: (txt) => conn.sendMessage(m.chat, { text: txt }),
|
|
189
|
+
botname: "BotDigital",
|
|
190
|
+
apiKey: "API_KEY", // Hubungi admin atau kunjungi https://api.denayrestapi.xyz untuk mendapatkan apikey
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
if (result?.status === "paid") {
|
|
194
|
+
if (result.pending.meta.product === "A") {
|
|
195
|
+
// kasih produk
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
switch (command) {
|
|
201
|
+
case "buy":
|
|
202
|
+
{
|
|
203
|
+
const basePrice = 20000;
|
|
204
|
+
|
|
205
|
+
// Kode unik untuk mencegah gambar palsu
|
|
206
|
+
const uniqueCode = Math.floor(Math.random() * 100); // 0 - 99
|
|
207
|
+
const nominal = basePrice + uniqueCode;
|
|
208
|
+
|
|
209
|
+
const { id, buffer } = await createQris({
|
|
210
|
+
nominal,
|
|
211
|
+
merchantName: "TOKO SAYA",
|
|
212
|
+
qris: "QRIS STRING",
|
|
213
|
+
apikey: "API_KEY", // Hubungi admin atau kunjungi https://api.denayrestapi.xyz untuk mendapatkan apikey
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
globalPending[m.sender] = {
|
|
217
|
+
id,
|
|
218
|
+
nominal,
|
|
219
|
+
description: "Pembelian Produk A",
|
|
220
|
+
meta: { product: "A", uniqueCode },
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
await conn.sendMessage(m.chat, {
|
|
224
|
+
image: Buffer.from(buffer),
|
|
225
|
+
caption: `💳 *Pembayaran Produk A*\nNominal: Rp${nominal}\n\nSilakan scan QRIS dan unggah bukti transfer.`,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## 🛡️ Keamanan
|
|
233
|
+
|
|
234
|
+
- Semua request memakai HMAC-SHA256 signature
|
|
235
|
+
- Timestamp + request-id otomatis
|
|
236
|
+
|
|
237
|
+
## NOTE
|
|
238
|
+
|
|
239
|
+
> Ini hanya menggunakan metode validasi pembayaran via bukti transfer, tidak melakukan pembayaran otomatis layaknya API QRIS langsung. Silahkan gunakan API QRIS langsung jika cara ini terbilang rumit, cara ini hanya sebagai alternatif.
|
|
240
|
+
|
|
241
|
+
## 👨💻 Sosial Media Kreator
|
|
242
|
+
|
|
243
|
+
<p align="center">
|
|
244
|
+
<a href="https://github.com/irukadevsindie">
|
|
245
|
+
<img src="https://img.shields.io/badge/GitHub-100000?style=for-the-badge&logo=github&logoColor=white"/>
|
|
246
|
+
</a>
|
|
247
|
+
<a href="https://t.me/irukaid">
|
|
248
|
+
<img src="https://img.shields.io/badge/Telegram-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white"/>
|
|
249
|
+
</a>
|
|
250
|
+
<a href="https://instagram.com/irukadevs.id">
|
|
251
|
+
<img src="https://img.shields.io/badge/Instagram-E4405F?style=for-the-badge&logo=instagram&logoColor=white"/>
|
|
252
|
+
</a>
|
|
253
|
+
</p>
|
package/index.js
CHANGED
|
@@ -1,90 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
const createQris = require("./lib/createQris");
|
|
2
|
+
const submitProof = require("./lib/submitProof");
|
|
3
|
+
const globalPending = require("./lib/globalPending");
|
|
4
|
+
const handlePaymentProof = require("./lib/handlePaymentProof");
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
nominal,
|
|
7
|
-
merchantName,
|
|
8
|
-
qris,
|
|
9
|
-
returnMode = "image",
|
|
10
|
-
apikey,
|
|
11
|
-
requireSignature = true,
|
|
12
|
-
}) {
|
|
13
|
-
const timestamp = Date.now().toString();
|
|
14
|
-
const requestId = crypto.randomBytes(16).toString("hex");
|
|
15
|
-
|
|
16
|
-
const requestBody = { nominal, merchantName, qris, returnMode, apikey };
|
|
17
|
-
|
|
18
|
-
const signature = requireSignature
|
|
19
|
-
? crypto
|
|
20
|
-
.createHmac("sha256", apikey)
|
|
21
|
-
.update(JSON.stringify(requestBody) + timestamp)
|
|
22
|
-
.digest("hex")
|
|
23
|
-
: undefined;
|
|
24
|
-
|
|
25
|
-
const { data, headers } = await axios.post(
|
|
26
|
-
"https://api.denayrestapi.xyz/api/private/qris?op=create",
|
|
27
|
-
requestBody,
|
|
28
|
-
{
|
|
29
|
-
headers: {
|
|
30
|
-
"Content-Type": "application/json",
|
|
31
|
-
"x-api-key": apikey,
|
|
32
|
-
...(signature && { "x-signature": signature }),
|
|
33
|
-
"x-timestamp": timestamp,
|
|
34
|
-
"x-request-id": requestId,
|
|
35
|
-
},
|
|
36
|
-
responseType: "arraybuffer",
|
|
37
|
-
}
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
const id = headers?.["x-payment-id"] || data.result?.id;
|
|
41
|
-
|
|
42
|
-
if (!id) throw new Error("Gagal membuat QRIS: ID pembayaran tidak tersedia.");
|
|
43
|
-
|
|
44
|
-
return { id, buffer: data };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export async function submitProof({
|
|
48
|
-
id,
|
|
49
|
-
ocrAmount,
|
|
50
|
-
ocrMerchantName,
|
|
51
|
-
ocrRawText,
|
|
52
|
-
reporter,
|
|
53
|
-
apikey,
|
|
54
|
-
requireSignature = true,
|
|
55
|
-
}) {
|
|
56
|
-
const timestamp = Date.now().toString();
|
|
57
|
-
const requestId = crypto.randomBytes(16).toString("hex");
|
|
58
|
-
|
|
59
|
-
const requestBody = {
|
|
60
|
-
id,
|
|
61
|
-
ocrAmount,
|
|
62
|
-
ocrMerchantName,
|
|
63
|
-
ocrRawText,
|
|
64
|
-
reporter,
|
|
65
|
-
apikey,
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const signature = requireSignature
|
|
69
|
-
? crypto
|
|
70
|
-
.createHmac("sha256", apikey)
|
|
71
|
-
.update(JSON.stringify(requestBody) + timestamp)
|
|
72
|
-
.digest("hex")
|
|
73
|
-
: undefined;
|
|
74
|
-
|
|
75
|
-
const { data } = await axios.post(
|
|
76
|
-
"https://api.denayrestapi.xyz/api/private/qris?op=submit-proof",
|
|
77
|
-
requestBody,
|
|
78
|
-
{
|
|
79
|
-
headers: {
|
|
80
|
-
"Content-Type": "application/json",
|
|
81
|
-
"x-api-key": apikey,
|
|
82
|
-
...(signature && { "x-signature": signature }),
|
|
83
|
-
"x-timestamp": timestamp,
|
|
84
|
-
"x-request-id": requestId,
|
|
85
|
-
},
|
|
86
|
-
}
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
return data;
|
|
90
|
-
}
|
|
6
|
+
module.exports = { createQris, submitProof, globalPending, handlePaymentProof };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import crypto from "crypto";
|
|
3
|
+
|
|
4
|
+
async function createQris({
|
|
5
|
+
nominal,
|
|
6
|
+
merchantName,
|
|
7
|
+
qris,
|
|
8
|
+
returnMode = "image",
|
|
9
|
+
apikey,
|
|
10
|
+
requireSignature = true,
|
|
11
|
+
}) {
|
|
12
|
+
const timestamp = Date.now().toString();
|
|
13
|
+
const requestId = crypto.randomBytes(16).toString("hex");
|
|
14
|
+
|
|
15
|
+
const requestBody = { nominal, merchantName, qris, returnMode, apikey };
|
|
16
|
+
|
|
17
|
+
const signature = requireSignature
|
|
18
|
+
? crypto
|
|
19
|
+
.createHmac("sha256", apikey)
|
|
20
|
+
.update(JSON.stringify(requestBody) + timestamp)
|
|
21
|
+
.digest("hex")
|
|
22
|
+
: undefined;
|
|
23
|
+
|
|
24
|
+
const { data, headers } = await axios.post(
|
|
25
|
+
"https://api.denayrestapi.xyz/api/private/qris?op=create",
|
|
26
|
+
requestBody,
|
|
27
|
+
{
|
|
28
|
+
headers: {
|
|
29
|
+
"Content-Type": "application/json",
|
|
30
|
+
"x-api-key": apikey,
|
|
31
|
+
...(signature && { "x-signature": signature }),
|
|
32
|
+
"x-timestamp": timestamp,
|
|
33
|
+
"x-request-id": requestId,
|
|
34
|
+
},
|
|
35
|
+
responseType: "arraybuffer",
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const id = headers?.["x-payment-id"] || data.result?.id;
|
|
40
|
+
|
|
41
|
+
if (!id) throw new Error("Gagal membuat QRIS: ID pembayaran tidak tersedia.");
|
|
42
|
+
|
|
43
|
+
return { id, buffer: data };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = { createQris };
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const sharp = require("sharp");
|
|
4
|
+
const Tesseract = require("tesseract.js");
|
|
5
|
+
const { submitProof } = require("./submitProof");
|
|
6
|
+
const globalPending = require("./globalPending");
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Handle bukti transfer via gambar (QRIS / slip)
|
|
10
|
+
* Universal: tidak tahu ini buat panel/topup/dll.
|
|
11
|
+
*
|
|
12
|
+
* @param {Object} params
|
|
13
|
+
* @param {Object} params.m - message object (Baileys)
|
|
14
|
+
* @param {Object} params.conn - client WA (untuk download media)
|
|
15
|
+
* @param {Function} params.reply - fungsi reply(teks)
|
|
16
|
+
* @param {String} params.botname - nama bot / reporter
|
|
17
|
+
* @param {String} params.apiKey - API key dra-qris-api
|
|
18
|
+
* @param {String} [params.ocrLang="eng"] - bahasa OCR Tesseract
|
|
19
|
+
*
|
|
20
|
+
* @returns {Promise<null | {
|
|
21
|
+
* status: string,
|
|
22
|
+
* ocrAmount: number,
|
|
23
|
+
* ocrText: string,
|
|
24
|
+
* pending: any,
|
|
25
|
+
* apiResult: any
|
|
26
|
+
* }>}
|
|
27
|
+
*/
|
|
28
|
+
async function handlePaymentProof({
|
|
29
|
+
m,
|
|
30
|
+
conn,
|
|
31
|
+
reply,
|
|
32
|
+
botname,
|
|
33
|
+
apiKey,
|
|
34
|
+
ocrLang = "eng",
|
|
35
|
+
}) {
|
|
36
|
+
const pending = globalPending[m.sender];
|
|
37
|
+
if (!pending) return null;
|
|
38
|
+
|
|
39
|
+
const tempDir = path.join(process.cwd(), "temp");
|
|
40
|
+
if (!fs.existsSync(tempDir)) fs.mkdirSync(tempDir, { recursive: true });
|
|
41
|
+
|
|
42
|
+
let buffer;
|
|
43
|
+
let imgPath;
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
buffer = await conn.downloadAndSaveMediaMessage(
|
|
47
|
+
m,
|
|
48
|
+
"buffer",
|
|
49
|
+
{},
|
|
50
|
+
{ reuploadRequest: conn.waUploadToServer }
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
if (!buffer || buffer.length === 0) {
|
|
54
|
+
await reply(
|
|
55
|
+
"❌ Gagal download gambar. Pastikan file terkirim dengan benar."
|
|
56
|
+
);
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
} catch (err) {
|
|
60
|
+
await reply("❌ Gagal download gambar.");
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
imgPath = path.join(tempDir, `${Date.now()}.png`);
|
|
65
|
+
try {
|
|
66
|
+
await sharp(buffer).png().toFile(imgPath);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
await reply("❌ Gagal memproses gambar, pastikan file valid.");
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
await reply("🔍 Membaca bukti transfer kamu...");
|
|
73
|
+
|
|
74
|
+
let text = "";
|
|
75
|
+
try {
|
|
76
|
+
const { data } = await Tesseract.recognize(imgPath, ocrLang);
|
|
77
|
+
text = data.text;
|
|
78
|
+
} catch (err) {
|
|
79
|
+
if (fs.existsSync(imgPath)) fs.unlinkSync(imgPath);
|
|
80
|
+
await reply("❌ Gagal membaca bukti transfer. Pastikan gambar jelas.");
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const parseOcrAmount = (text) => {
|
|
85
|
+
const lines = text.split(/\r?\n/);
|
|
86
|
+
let amountLine = lines.find((l) => /nominal|jumlah total|total/i.test(l));
|
|
87
|
+
if (!amountLine) amountLine = text;
|
|
88
|
+
|
|
89
|
+
let cleaned = (amountLine.match(/[\d.,]+/g) || ["0"]).pop();
|
|
90
|
+
cleaned = cleaned.replace(/\./g, "").replace(/,/g, "");
|
|
91
|
+
return Number(cleaned) || 0;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const ocrAmount = parseOcrAmount(text);
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const apiResult = await submitProof({
|
|
98
|
+
id: pending.id,
|
|
99
|
+
ocrAmount,
|
|
100
|
+
ocrMerchantName: pending.merchantName,
|
|
101
|
+
ocrRawText: text,
|
|
102
|
+
reporter: botname,
|
|
103
|
+
apikey: apiKey,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const status = apiResult.result?.status || "unknown";
|
|
107
|
+
|
|
108
|
+
if (status === "paid") {
|
|
109
|
+
await reply(
|
|
110
|
+
`✅ Pembayaran untuk *${
|
|
111
|
+
pending.description || "transaksi kamu"
|
|
112
|
+
}* sebesar Rp${pending.nominal} terverifikasi.`
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
delete globalPending[m.sender];
|
|
116
|
+
} else {
|
|
117
|
+
await reply(
|
|
118
|
+
`⚠️ Pembayaran belum valid / belum masuk. Status: *${status}*`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (imgPath && fs.existsSync(imgPath)) fs.unlinkSync(imgPath);
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
status,
|
|
126
|
+
ocrAmount,
|
|
127
|
+
ocrText: text,
|
|
128
|
+
pending,
|
|
129
|
+
apiResult,
|
|
130
|
+
};
|
|
131
|
+
} catch (err) {
|
|
132
|
+
console.error("Error submitProof:", err);
|
|
133
|
+
await reply("❌ Gagal submit bukti, coba lagi nanti.");
|
|
134
|
+
|
|
135
|
+
if (imgPath && fs.existsSync(imgPath)) fs.unlinkSync(imgPath);
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
module.exports = { handlePaymentProof };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import crypto from "crypto";
|
|
3
|
+
|
|
4
|
+
async function submitProof({
|
|
5
|
+
id,
|
|
6
|
+
ocrAmount,
|
|
7
|
+
ocrMerchantName,
|
|
8
|
+
ocrRawText,
|
|
9
|
+
reporter,
|
|
10
|
+
apikey,
|
|
11
|
+
requireSignature = true,
|
|
12
|
+
}) {
|
|
13
|
+
const timestamp = Date.now().toString();
|
|
14
|
+
const requestId = crypto.randomBytes(16).toString("hex");
|
|
15
|
+
|
|
16
|
+
const requestBody = {
|
|
17
|
+
id,
|
|
18
|
+
ocrAmount,
|
|
19
|
+
ocrMerchantName,
|
|
20
|
+
ocrRawText,
|
|
21
|
+
reporter,
|
|
22
|
+
apikey,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const signature = requireSignature
|
|
26
|
+
? crypto
|
|
27
|
+
.createHmac("sha256", apikey)
|
|
28
|
+
.update(JSON.stringify(requestBody) + timestamp)
|
|
29
|
+
.digest("hex")
|
|
30
|
+
: undefined;
|
|
31
|
+
|
|
32
|
+
const { data } = await axios.post(
|
|
33
|
+
"https://api.denayrestapi.xyz/api/private/qris?op=submit-proof",
|
|
34
|
+
requestBody,
|
|
35
|
+
{
|
|
36
|
+
headers: {
|
|
37
|
+
"Content-Type": "application/json",
|
|
38
|
+
"x-api-key": apikey,
|
|
39
|
+
...(signature && { "x-signature": signature }),
|
|
40
|
+
"x-timestamp": timestamp,
|
|
41
|
+
"x-request-id": requestId,
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
return data;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { submitProof };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dra-qris-api",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Connector for QRIS API DenayRestAPI ",
|
|
3
|
+
"version": "1.0.4",
|
|
4
|
+
"description": "Connector and modules for QRIS API DenayRestAPI ",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"author": "IrukaIndieDevs",
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"axios": "^1.12.2",
|
|
15
|
-
"crypto": "^1.0.1"
|
|
15
|
+
"crypto": "^1.0.1",
|
|
16
|
+
"tesseract.js": "^6.0.1"
|
|
16
17
|
}
|
|
17
18
|
}
|