File size: 4,342 Bytes
1538b06
 
 
 
 
 
 
da0af8f
1538b06
 
 
 
da0af8f
 
 
 
 
 
 
1538b06
da0af8f
1538b06
 
 
 
 
 
 
da0af8f
1538b06
 
 
 
da0af8f
1538b06
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
da0af8f
 
 
 
1538b06
 
 
 
 
 
 
 
 
da0af8f
 
1538b06
 
 
da0af8f
1538b06
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
da0af8f
 
 
1538b06
 
 
da0af8f
 
1538b06
 
da0af8f
 
 
 
 
 
1538b06
da0af8f
1538b06
 
da0af8f
1538b06
 
 
 
 
 
 
 
 
 
da0af8f
1538b06
 
 
da0af8f
1538b06
da0af8f
 
 
1538b06
da0af8f
 
 
1538b06
 
da0af8f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1538b06
 
 
 
 
 
 
 
 
da0af8f
1538b06
 
 
 
da0af8f
1538b06
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
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));