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 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
- // file: denay-qris.js
2
- import axios from "axios";
3
- import crypto from "crypto";
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
- export async function createQris({
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,3 @@
1
+ const globalPending = Object.create(null);
2
+
3
+ module.exports = globalPending;
@@ -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.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
  }