add: gui sep requests

This commit is contained in:
2026-02-01 08:32:36 +03:30
parent 2898086499
commit c5aad8deca
3 changed files with 480 additions and 46 deletions

293
index.js
View File

@@ -57,6 +57,27 @@ const {
} = require("./lib/taavon-send-data");
const { getAllCities } = require("./lib/getAllCities");
const { getAllProvinces } = require("./lib/getAllProvinces");
const { MongoClient, ObjectId } = require("mongodb");
// MongoDB for SEP pay requests (use MONGODB_URI env to override)
const MONGODB_URI =
process.env.MONGODB_URI ||
"mongodb://root:2pCCFs4wrsLDsO1pjQVA9jORT2WCjLNO5uauS6FUUaGLXCcfjw28IJmAO8RxlEJN@31.7.78.133:14365/?authSource=admin";
const SEP_DB_NAME = "rasadyar";
const SEP_COLLECTION = "sepPayRequests";
let mongoClient = null;
async function getMongoClient() {
if (!mongoClient) {
mongoClient = new MongoClient(MONGODB_URI);
await mongoClient.connect();
}
return mongoClient;
}
async function getSepPayCollection() {
const client = await getMongoClient();
return client.db(SEP_DB_NAME).collection(SEP_COLLECTION);
}
// var _soap2 = _interopRequireDefault(_soap);
@@ -98,7 +119,7 @@ app.post("/cumulative-dynamic-pay-request", async (req, res) => {
parseInt(orderId),
amount,
additionalData,
"https://rasadyar.net/verify-payment"
"https://rasadyar.net/verify-payment",
);
const payRequestRes = payRequestResult.return.split(",");
const responseCode = payRequestRes[0];
@@ -168,7 +189,7 @@ async function reversePay(orderId, saleOrderId, saleReferenceId) {
let resultReversePay = await bpReversalRequest(
orderId,
saleOrderId,
saleReferenceId
saleReferenceId,
);
resultReversePay = resultReversePay.return;
console.log(resultReversePay);
@@ -329,7 +350,7 @@ app.post("/verify-payment", async (req, res) => {
resultCode_bpVerifyRequest = await bpVerifyRequest(
saleOrderId,
saleOrderId,
saleReferenceId
saleReferenceId,
);
resultCode_bpVerifyRequest = resultCode_bpVerifyRequest.return;
console.log("bpVerifyRequest:" + resultCode_bpVerifyRequest);
@@ -342,10 +363,10 @@ app.post("/verify-payment", async (req, res) => {
resultCode_bpinquiryRequest = await bpInquiryRequest(
saleOrderId,
saleOrderId,
saleReferenceId
saleReferenceId,
);
resultCode_bpinquiryRequest = parseInt(
resultCode_bpinquiryRequest.return
resultCode_bpinquiryRequest.return,
);
console.log("bpinquiryRequest" + resultCode_bpinquiryRequest);
@@ -366,7 +387,7 @@ app.post("/verify-payment", async (req, res) => {
resultCode_bpSettleRequest = await bpSettleRequest(
saleOrderId,
saleOrderId,
saleReferenceId
saleReferenceId,
);
resultCode_bpSettleRequest = parseInt(resultCode_bpSettleRequest.return);
@@ -492,7 +513,7 @@ app.post("/zarinpay", async (req, res) => {
"Content-Type": "application/json",
accept: "application/json",
},
}
},
);
if (response.data.data.authority) {
return res.status(201).json(response.data.data);
@@ -530,7 +551,7 @@ app.get("/zarinverify", async (req, res) => {
"Content-Type": "application/json",
accept: "application/json",
},
}
},
);
console.log("Verification response in verify: ", response.data);
@@ -634,7 +655,7 @@ app.post("/sadad-get-token", async (req, res) => {
"Content-Type": "application/json",
Referer: "https://rasadyar.net/",
},
}
},
);
res.status(201).send(response.data);
@@ -691,7 +712,7 @@ app.post("/sadad-request-payment", async (req, res) => {
"Content-Type": "application/json",
Referer: "https://rasadyar.net/",
},
}
},
);
const responseData = response.data;
@@ -750,7 +771,7 @@ app.post("/asanverify", async (req, res) => {
}
const encryptedCredintials = await encryptWS(
`${config.username},${config.password}`
`${config.username},${config.password}`,
);
const soapClient = await soap.createClientAsync(config.WebServiceUrl);
const verifyArgs = {
@@ -764,7 +785,7 @@ app.post("/asanverify", async (req, res) => {
return res
.status(500)
.send(
`خطای شماره: ${verifyResult.RequestVerificationResult} در هنگام Verify`
`خطای شماره: ${verifyResult.RequestVerificationResult} در هنگام Verify`,
);
}
@@ -773,11 +794,11 @@ app.post("/asanverify", async (req, res) => {
return res
.status(500)
.send(
`خطای شماره: ${settlementResult.RequestReconciliationResult} در هنگام Settlement`
`خطای شماره: ${settlementResult.RequestReconciliationResult} در هنگام Settlement`,
);
}
res.send(
'<div style="width:250px; margin:100px auto; direction:rtl; font:bold 14px Tahoma">تراکنش با موفقیت انجام پذیرفت.</div>'
'<div style="width:250px; margin:100px auto; direction:rtl; font:bold 14px Tahoma">تراکنش با موفقیت انجام پذیرفت.</div>',
);
});
});
@@ -824,6 +845,9 @@ app.post("/sep-pay-request", async (req, res) => {
return res.status(400).send("Invalid amount");
}
const resNum = generateRandomString();
const redirectUrl = `https://pay.rasadyar.net/sepverify/?Amount=${amount}&province=${provincecode}&isLink=${isLink}`;
try {
const response = await axios.post(
SEP_API,
@@ -831,8 +855,8 @@ app.post("/sep-pay-request", async (req, res) => {
action: "token",
TerminalId: SEP_TERMINAL_ID,
Amount: parsedAmount,
ResNum: generateRandomString(),
RedirectUrl: `https://pay.rasadyar.net/sepverify/?Amount=${amount}&province=${provincecode}&isLink=${isLink}`,
ResNum,
RedirectUrl: redirectUrl,
CellNumber: phone,
SettlementIBANInfo: wages,
// TranType: "Government",
@@ -842,8 +866,35 @@ app.post("/sep-pay-request", async (req, res) => {
"Content-Type": "application/json",
accept: "application/json",
},
}
},
);
// Save to MongoDB before returning
try {
const coll = await getSepPayCollection();
const token = response.data?.Token ?? response.data?.token ?? null;
await coll.insertOne({
amount: parsedAmount,
amountRaw: amount,
phone: phone || null,
provincecode: provincecode || null,
isLink: isLink || null,
wages: wages || null,
resNum,
redirectUrl,
token,
rawResponse: response.data,
verified: false,
refNum: null,
traceNo: null,
securePan: null,
createdAt: new Date(),
updatedAt: new Date(),
});
} catch (dbErr) {
console.error("SEP pay request: failed to save to DB", dbErr);
}
return res.status(201).json(response.data);
} catch (error) {
return res
@@ -870,12 +921,31 @@ app.post("/sepverify", async (req, res) => {
"Content-Type": "application/json",
accept: "application/json",
},
}
},
);
if (response.data.ResultCode === 0) {
let redirectUrl = `https://rasadyar.net/payment?finalAmount=${Amount}&cardHolderPan=${SecurePan}&date=${new Date()}&saleReferenceId=${TraceNo}`;
let subDomain = "";
// Update DB with verify result for manual submit in GUI
try {
const coll = await getSepPayCollection();
await coll.updateOne(
{ token: Token },
{
$set: {
verified: true,
refNum: RefNum,
traceNo: TraceNo,
securePan: SecurePan,
verifyAmount: Amount,
updatedAt: new Date(),
},
},
);
} catch (dbErr) {
console.error("sepverify: failed to update DB", dbErr);
}
if (isLink) {
await taavonSendDataZarinPalLink(province, {
@@ -911,6 +981,149 @@ app.post("/sepverify", async (req, res) => {
}
});
app.get("/sep-pay-requests/gui", async (req, res) => {
const basePath =
(req.baseUrl || "").replace(/\/sep-pay-requests\/gui$/, "") || "";
const listPath = basePath
? basePath + "/sep-pay-requests"
: "/sep-pay-requests";
const submitPathPrefix = basePath
? basePath + "/sep-pay-request/"
: "/sep-pay-request/";
const html = `<!DOCTYPE html>
<html dir="rtl" lang="fa">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SEP Pay Requests</title>
<style>
* { box-sizing: border-box; }
body { font-family: Tahoma, Arial, sans-serif; margin: 0; padding: 16px; background: #f5f5f5; }
h1 { color: #333; margin-bottom: 16px; }
.card { background: #fff; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); padding: 16px; margin-bottom: 12px; }
.row { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; }
.label { font-weight: bold; color: #555; min-width: 100px; }
.value { color: #333; }
.verified { color: #2e7d32; }
.unverified { color: #c62828; }
button { background: #1976d2; color: #fff; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: 14px; }
button:hover { background: #1565c0; }
button:disabled { background: #9e9e9e; cursor: not-allowed; }
.msg { margin-top: 12px; padding: 8px; border-radius: 6px; }
.msg.success { background: #e8f5e9; color: #2e7d32; }
.msg.error { background: #ffebee; color: #c62828; }
#list { margin-top: 16px; }
</style>
</head>
<body>
<h1>درخواست‌های پرداخت SEP</h1>
<p><a href="${listPath}">API لیست (JSON)</a></p>
<div id="list">در حال بارگذاری...</div>
<script>
const listEl = document.getElementById('list');
const listPath = ${JSON.stringify(listPath)};
const submitPathPrefix = ${JSON.stringify(submitPathPrefix)};
const submitPath = (id) => submitPathPrefix + id + '/submit';
async function load() {
try {
const r = await fetch(listPath);
const data = await r.json();
if (!Array.isArray(data)) { listEl.innerHTML = '<p class="msg error">پاسخ نامعتبر</p>'; return; }
if (data.length === 0) { listEl.innerHTML = '<p>موردی یافت نشد.</p>'; return; }
listEl.innerHTML = data.map(item => {
const id = item._id;
const verified = item.verified === true;
const hasData = verified || (item.token && (item.traceNo || item.securePan));
return '<div class="card" data-id="' + id + '">' +
'<div class="row"><span class="label">مبلغ:</span><span class="value">' + (item.amountRaw || item.amount) + '</span></div>' +
'<div class="row"><span class="label">استان:</span><span class="value">' + (item.provincecode || '-') + '</span></div>' +
'<div class="row"><span class="label">لینک:</span><span class="value">' + (item.isLink ? 'بله' : 'خیر') + '</span></div>' +
'<div class="row"><span class="label">وضعیت:</span><span class="' + (verified ? 'verified' : 'unverified') + '">' + (verified ? 'تایید شده' : 'تایید نشده') + '</span></div>' +
(item.traceNo ? '<div class="row"><span class="label">TraceNo:</span><span class="value">' + item.traceNo + '</span></div>' : '') +
'<div class="row"><span class="label">تاریخ:</span><span class="value">' + (item.createdAt ? new Date(item.createdAt).toLocaleString('fa-IR') : '-') + '</span></div>' +
'<button onclick="submit(\'' + id + '\', this)" ' + (!hasData ? 'disabled title="نیاز به authority/refId/cardHolderPan دارد"' : '') + '>ارسال به تعاون</button>' +
'<span class="msg" id="msg-' + id + '"></span>' +
'</div>';
}).join('');
} catch (e) {
listEl.innerHTML = '<p class="msg error">خطا: ' + e.message + '</p>';
}
}
async function submit(id, btn) {
const msgEl = document.getElementById('msg-' + id);
if (msgEl) { msgEl.textContent = ''; msgEl.className = 'msg'; }
btn.disabled = true;
try {
const r = await fetch(submitPath(id), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' });
const j = await r.json();
if (msgEl) { msgEl.textContent = j.error || (j.message || 'ارسال شد'); msgEl.className = 'msg ' + (j.error ? 'error' : 'success'); }
} catch (e) {
if (msgEl) { msgEl.textContent = e.message; msgEl.className = 'msg error'; }
}
btn.disabled = false;
}
load();
</script>
</body>
</html>`;
res.setHeader("Content-Type", "text/html; charset=utf-8");
res.send(html);
});
// List all SEP pay requests (API)
app.get("/sep-pay-requests", async (req, res) => {
try {
const coll = await getSepPayCollection();
const list = await coll
.find({})
.sort({ createdAt: -1 })
.limit(500)
.toArray();
// Ensure _id is string for GUI
const listWithIds = list.map((doc) => ({
...doc,
_id: doc._id ? doc._id.toString() : doc._id,
}));
return res.json(listWithIds);
} catch (err) {
console.error("sep-pay-requests list error", err);
return res.status(500).json({ error: err.message });
}
});
// Manual submit to Taavon (like sepverify does) - use stored or body: authority, refId, cardHolderPan
app.post("/sep-pay-request/:id/submit", async (req, res) => {
const id = req.params.id;
const { authority, refId, cardHolderPan } = req.body;
try {
const coll = await getSepPayCollection();
const doc = await coll.findOne({ _id: new ObjectId(id) });
if (!doc) {
return res.status(404).json({ error: "Record not found" });
}
const province = (doc.provincecode || "").toString().substring(0, 2);
const isLink =
doc.isLink === true || doc.isLink === "true" || doc.isLink === "1";
const data = {
authority: authority ?? doc.token,
refId: refId ?? doc.traceNo,
cardHolderPan: cardHolderPan ?? doc.securePan,
};
if (isLink) {
await taavonSendDataZarinPalLink(province, data);
} else {
await taavonSendDataZarinPal(province, data);
}
return res.json({ ok: true, message: "Submitted to Taavon" });
} catch (err) {
console.error("sep-pay-request submit error", err);
return res.status(500).json({ error: err.message });
}
});
//end sep ---------------------------------------------------------------------------------------------
// samasat crack
@@ -922,12 +1135,12 @@ const getPersianDate = (daysOffset = 0) => {
const jalaaliDate = toJalaali(
currentDate.getFullYear(),
currentDate.getMonth() + 1,
currentDate.getDate()
currentDate.getDate(),
);
const { jy, jm, jd } = jalaaliDate;
const formattedDate = `${jy}/${String(jm).padStart(2, "0")}/${String(
jd
jd,
).padStart(2, "0")}`;
return formattedDate;
@@ -940,13 +1153,13 @@ const getPersianDateForHatching = (daysOffset = 0) => {
const jalaaliDate = toJalaali(
currentDate.getFullYear(),
currentDate.getMonth() + 1,
currentDate.getDate()
currentDate.getDate(),
);
const { jy, jm, jd } = jalaaliDate;
const formattedDate = `${String(jd).padStart(2, "0")}/${String(jm).padStart(
2,
"0"
"0",
)}/${jy}`;
return formattedDate;
@@ -1265,14 +1478,14 @@ app.post("/samasat-users", async (req, res) => {
...updatedUnit,
PId: Id,
Province: getAllProvinces().find(
(p) => parseInt(p.id) === parseInt(province)
(p) => parseInt(p.id) === parseInt(province),
)?.name,
City: getAllCities().find(
(city) =>
parseInt(city.id) === parseInt(updatedUnit?.LocationIdCity)
parseInt(city.id) === parseInt(updatedUnit?.LocationIdCity),
)?.name,
};
}
},
);
res.json(transformedUsers);
@@ -1586,8 +1799,8 @@ app.post("/samasat-good-sum", async (req, res) => {
const jsonData = JSON.parse(data);
res.json(
jsonData.Data?.filter(
(option) => option?.TrackingStatus === 2
)?.reduce((acc, item) => acc + Number(item?.GoodAmount || 0), 0)
(option) => option?.TrackingStatus === 2,
)?.reduce((acc, item) => acc + Number(item?.GoodAmount || 0), 0),
);
} catch (error) {
console.error("Error parsing JSON:", error.message);
@@ -1660,7 +1873,7 @@ app.post("/samasat-evacuation", async (req, res) => {
// jsonData.SumLosses +
jsonData.SumIllness +
jsonData.SumNaturalOccurrence +
jsonData.SumFire
jsonData.SumFire,
);
}
} else {
@@ -1877,7 +2090,7 @@ async function performLogin() {
response.on("end", () => {
const csrfMatch = data.match(
/<input name="__RequestVerificationToken" type="hidden" value="([^"]+)"/
/<input name="__RequestVerificationToken" type="hidden" value="([^"]+)"/,
);
const csrfToken = csrfMatch ? csrfMatch[1] : null;
@@ -1973,12 +2186,12 @@ async function makeInquiryRequest(info, type, cookie) {
birthDateString: "1404/08/12",
})
: type === "unit"
? querystring.stringify({
NationalCode: info,
})
: JSON.stringify({
NationaId: info,
});
? querystring.stringify({
NationalCode: info,
})
: JSON.stringify({
NationaId: info,
});
const requestOptions = {
hostname: "ba124.ir",
@@ -1986,8 +2199,8 @@ async function makeInquiryRequest(info, type, cookie) {
type === "person"
? "/Inquiries/PersonInfo"
: type === "unit"
? "/Inquiries/CallGetLegalPersonInfoByNationalCode"
: "/Inquiries/AsnafGWLicenseInquiry",
? "/Inquiries/CallGetLegalPersonInfoByNationalCode"
: "/Inquiries/AsnafGWLicenseInquiry",
method: "POST",
headers: {
"Content-Type":
@@ -2008,8 +2221,8 @@ async function makeInquiryRequest(info, type, cookie) {
type === "person"
? "https://ba124.ir/Inquiries/PersonInfo"
: type === "unit"
? "https://ba124.ir/Inquiries/GetLegalPersonInfoByNationalCode"
: "https://ba124.ir/Inquiries/AsnafGWLicenseInquiry",
? "https://ba124.ir/Inquiries/GetLegalPersonInfoByNationalCode"
: "https://ba124.ir/Inquiries/AsnafGWLicenseInquiry",
"Sec-Ch-Ua":
'"Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"',
"Sec-Ch-Ua-Mobile": "?0",