const express = require('express'); const path = require('path'); const app = express(); app.use(express.json()); // Tempat menyimpan status transaksi sementara di memory server let databaseTransaksi = {}; const QRIS_BISNIS_BASE = "00020101021226570011ID.DANA.WWW011893600915092050870402099205087040303UMI51440014ID.CO.QRIS.WWW0215ID10254080885610303UMI520448145303360540115802ID5910FAREL SHOP6012Kota Cirebon610545122621960150011ID.DANA.WWW6304"; function hitungCRC16(str) { let crc = 0xFFFF; for (let c = 0; c < str.length; c++) { let code = str.charCodeAt(c); crc ^= (code << 8); for (let i = 0; i < 8; i++) { if ((crc & 0x8000) !== 0) { crc = ((crc << 1) ^ 0x1021) & 0xFFFF; } else { crc = (crc << 1) & 0xFFFF; } } } let hex = crc.toString(16).toUpperCase(); return hex.padStart(4, '0'); } // 1. ENDPOINT GENERATE QRIS DYNAMIC app.get('/create', (req, res) => { const nominal = req.query.nominal; if (!nominal || isNaN(nominal) || parseInt(nominal) <= 0) { return res.status(400).json({ error: 'Nominal harus berupa angka!' }); } // Pastikan kunci disimpan sebagai String Angka Murni yang Bersih (Tanpa Spasi/Titik) const valueUang = parseInt(nominal, 10).toString(); const lengthUang = valueUang.length.toString().padStart(2, '0'); const tagNominalBaru = `54${lengthUang}${valueUang}`; let qrisTanpaCRC = ""; if (QRIS_BISNIS_BASE.includes('54011')) { qrisTanpaCRC = QRIS_BISNIS_BASE.replace('54011', tagNominalBaru); } else { const posisiID58 = QRIS_BISNIS_BASE.indexOf('5802ID'); const bagianAwal = QRIS_BISNIS_BASE.substring(0, posisiID58); const bagianAkhir = QRIS_BISNIS_BASE.substring(posisiID58); qrisTanpaCRC = bagianAwal + tagNominalBaru + bagianAkhir; } const hasilCRC = hitungCRC16(qrisTanpaCRC); const qrisDynamicString = qrisTanpaCRC + hasilCRC; // Daftarkan nominal ke database dengan key string angka bersih databaseTransaksi[valueUang] = { status: "PENDING", waktu: new Date() }; return res.json({ status: 'success', nominal: parseInt(valueUang, 10), qris_string: qrisDynamicString }); }); // 2. ENDPOINT CHECK STATUS (Real-time & Manual lewat satu pintu) app.get('/check-status', (req, res) => { if (!req.query.nominal) return res.status(400).json({ status: "ERROR" }); const nominal = parseInt(req.query.nominal, 10).toString(); // Normalisasi input key if (databaseTransaksi[nominal]) { return res.json({ status: databaseTransaksi[nominal].status }); } return res.json({ status: "NOT_FOUND" }); }); // 3. ENDPOINT BATALKAN TRANSAKSI app.get('/cancel', (req, res) => { if (!req.query.nominal) return res.status(400).json({ status: "ERROR" }); const nominal = parseInt(req.query.nominal, 10).toString(); if (databaseTransaksi[nominal]) { delete databaseTransaksi[nominal]; // Hapus antrean di server return res.json({ status: "CANCELLED" }); } return res.json({ status: "NOT_FOUND" }); }); // Ganti bagian app.post('/notifdana') di index.js Hugging Face Anda dengan versi Super-Fix ini: app.post('/notifdana', (req, res) => { const { message } = req.body; console.log(`[LOG MASUK] Pesan dari HP: "${message}"`); if (message) { try { // STRATEGI BARU: // Ambil bagian teks yang ada di depan sebelum kata "dari" (karena nominal Rp pasti di depan) // Contoh: "Rp1 dari Gopay..." diambil "Rp1" // Contoh: "Rp50.000 dari..." diambil "Rp50.000" const bagianDepan = message.split(/dari/i)[0]; // Ambil semua karakter angka saja dari bagian depan tersebut const angkaSaja = bagianDepan.replace(/\D/g, ''); // Menghapus semua yang BUKAN angka (Rp, titik, spasi hilang) if (angkaSaja) { const keyTransaksi = parseInt(angkaSaja, 10).toString(); console.log(`[HASIL EKSTRAKSI] Mencari nominal antrean: "${keyTransaksi}"`); if (databaseTransaksi[keyTransaksi]) { databaseTransaksi[keyTransaksi].status = "SUCCESS"; console.log(`[SUKSES TOTAL] Status untuk Rp ${keyTransaksi} diubah menjadi SUCCESS!`); } else { // Jika tidak cocok, kita cek apakah ada spasi atau kecocokan parsial console.log(`[BELUM COCOK] Angka "${keyTransaksi}" didapat, tapi tidak ada di antrean web.`); console.log(`Daftar antrean aktif saat ini:`, Object.keys(databaseTransaksi)); } } else { console.log(`[EROR PARSING] Tidak ditemukan karakter angka di bagian depan notifikasi.`); } } catch (error) { console.error("Gagal total memproses ekstraksi teks:", error); } } // Tetap kirim 200 ke MacroDroid agar tidak dianggap eror oleh HP return res.status(200).json({ status: 'success' }); }); // 5. FRONTEND HALAMAN UTAMA (Dengan tombol Manual Cek dan Cancel) app.get('/', (req, res) => { res.send(`
Generate QRIS & Cek Otomatis