api-pakasir / app.js
FarelDeveloper's picture
Update app.js
da0af8f verified
Raw
History Blame Contribute Delete
4.34 kB
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));