Spaces:
Sleeping
Sleeping
| import express from "express"; | |
| import bodyParser from "body-parser"; | |
| import crypto from "crypto"; | |
| const app = express(); | |
| app.use(bodyParser.json()); | |
| /* ===== ENV ===== */ | |
| const PROJECT = process.env.PAKASIR_PROJECT; | |
| const API_KEY = process.env.PAKASIR_API_KEY; | |
| const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET || "pakasir"; | |
| if (!PROJECT || !API_KEY) { | |
| throw new Error("PAKASIR_PROJECT & PAKASIR_API_KEY wajib diisi"); | |
| } | |
| /* ===== MEMORY STORE ===== */ | |
| const transactions = new Map(); // order_id => { amount, status } | |
| const clients = new Map(); // order_id => [res] | |
| /* ===== SSE HELPER ===== */ | |
| function sendEvent(order_id, payload) { | |
| const arr = clients.get(order_id) || []; | |
| for (const res of arr) { | |
| res.write(`data: ${JSON.stringify(payload)}\n\n`); | |
| } | |
| } | |
| /* ===== CREATE ===== */ | |
| app.post("/create", async (req, res) => { | |
| try { | |
| const { amount } = req.body; | |
| if (!amount || amount < 1000) { | |
| return res.status(400).json({ error: "amount minimal 1000" }); | |
| } | |
| const order_id = `INV-${Date.now()}`; | |
| const r = await fetch("https://app.pakasir.com/api/transactioncreate/qris", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ | |
| project: PROJECT, | |
| api_key: API_KEY, | |
| order_id, | |
| amount | |
| }) | |
| }); | |
| const j = await r.json(); | |
| const payment = j.payment || j; | |
| const qris = payment.payment_number || j.qris_string; | |
| if (!qris) { | |
| return res.status(500).json({ error: "gagal create qris", raw: j }); | |
| } | |
| transactions.set(order_id, { | |
| amount, | |
| status: "PENDING" | |
| }); | |
| res.json({ | |
| success: true, | |
| order_id, | |
| amount, | |
| qr_url: `https://quickchart.io/qr?text=${encodeURIComponent(qris)}&size=500` | |
| }); | |
| } catch (e) { | |
| console.error(e); | |
| res.status(500).json({ error: "create error" }); | |
| } | |
| }); | |
| /* ===== SSE REALTIME ===== */ | |
| app.get("/events/:order_id", (req, res) => { | |
| const { order_id } = req.params; | |
| res.writeHead(200, { | |
| "Content-Type": "text/event-stream", | |
| "Cache-Control": "no-cache", | |
| Connection: "keep-alive" | |
| }); | |
| res.write("\n"); | |
| const arr = clients.get(order_id) || []; | |
| arr.push(res); | |
| clients.set(order_id, arr); | |
| req.on("close", () => { | |
| const left = (clients.get(order_id) || []).filter(r => r !== res); | |
| if (left.length === 0) clients.delete(order_id); | |
| else clients.set(order_id, left); | |
| }); | |
| }); | |
| /* ===== WEBHOOK ===== */ | |
| app.post("/webhook", (req, res) => { | |
| try { | |
| const body = req.body; | |
| const order_id = body.order_id || body.payment?.order_id; | |
| let status = body.status || body.payment?.status; | |
| if (!order_id || !status) { | |
| return res.status(400).send("invalid payload"); | |
| } | |
| status = status.toUpperCase(); | |
| if (transactions.has(order_id)) { | |
| transactions.get(order_id).status = status; | |
| } | |
| sendEvent(order_id, { order_id, status }); | |
| res.json({ ok: true }); | |
| } catch (e) { | |
| console.error(e); | |
| res.status(500).send("webhook error"); | |
| } | |
| }); | |
| /* ===== STATUS (FIXED) ===== */ | |
| app.get("/status/:order_id", async (req, res) => { | |
| try { | |
| const { order_id } = req.params; | |
| const tx = transactions.get(order_id); | |
| if (!tx) { | |
| return res.json({ order_id, status: "NOT_FOUND" }); | |
| } | |
| // kalau sudah PAID dari webhook → langsung return | |
| if (tx.status === "PAID" || tx.status === "SUCCESS") { | |
| return res.json({ order_id, status: tx.status }); | |
| } | |
| // fallback polling (WAJIB amount) | |
| const params = new URLSearchParams({ | |
| project: PROJECT, | |
| api_key: API_KEY, | |
| order_id, | |
| amount: tx.amount | |
| }); | |
| const r = await fetch( | |
| `https://app.pakasir.com/api/transactiondetail?${params}` | |
| ); | |
| const j = await r.json(); | |
| const data = j.transaction || j; | |
| const status = (data.status || "PENDING").toUpperCase(); | |
| tx.status = status; | |
| transactions.set(order_id, tx); | |
| res.json({ order_id, status }); | |
| } catch (e) { | |
| console.error(e); | |
| res.status(500).json({ error: "status error" }); | |
| } | |
| }); | |
| /* ===== HEALTH ===== */ | |
| app.get("/", (req, res) => { | |
| res.json({ | |
| name: "Pakasir Realtime Gateway", | |
| status: "running" | |
| }); | |
| }); | |
| /* ===== START ===== */ | |
| const PORT = process.env.PORT || 7860; | |
| app.listen(PORT, () => console.log("RUNNING ON", PORT)); |