server/routes/payments.js Repository wynajem_motorowek Original path server/routes/payments.jsRole SOURCE Size 6786 bytes Lines 231 SHA-256 28f2a915cb0a55b6512356ea5c62b7b9de1945ce5ee1d3aa271ebc73c74e7a52Displayed range 1–231 Previous file/page · Project index · Next file/page
const express = require("express");
const { buildCancelToken, buildOrderNumber } = require("../lib/orders");
const { sendReservationConfirmedEmails } = require("../lib/notifications");
function toClientIso(value) {
if (!value) return null;
if (value instanceof Date) return value.toISOString();
return String(value).replace(" ", "T");
}
module.exports = function paymentsRoute({ db }) {
const router = express.Router();
function nowIso() {
return new Date().toISOString();
}
async function getHoldById(holdId) {
const holdResult = await db.query(
`SELECT rv.id,
rv.resource_id,
rv.start_time,
rv.end_time,
rv.status,
rv.expires_at,
rv.payment_status,
rv.payment_method,
rv.payment_ref,
rv.customer_email,
rv.order_number,
rv.cancel_token,
r.name AS resource_name
FROM reservations rv
JOIN resources r ON r.id = rv.resource_id
WHERE rv.id = ?
LIMIT 1`,
[holdId]
);
return holdResult.rows[0] || null;
}
async function finalizeReservation({ holdId, paymentStatus, paymentRef, paymentMethod }) {
await db.query(
`UPDATE reservations
SET status = 'CONFIRMED',
payment_status = ?,
payment_method = ?,
payment_ref = ?
WHERE id = ?`,
[paymentStatus, paymentMethod, paymentRef, holdId]
);
const current = await getHoldById(holdId);
if (!current) return null;
let orderNumber = String(current.order_number || "");
let cancelToken = String(current.cancel_token || "");
if (!orderNumber) {
orderNumber = buildOrderNumber(current.id);
}
if (!cancelToken) {
cancelToken = buildCancelToken(current.id);
}
await db.query(
`UPDATE reservations
SET order_number = ?,
cancel_token = ?
WHERE id = ?`,
[orderNumber, cancelToken, holdId]
);
const updated = await getHoldById(holdId);
if (updated) {
try {
await sendReservationConfirmedEmails({ db, reservation: updated, resourceName: updated.resource_name });
} catch (mailError) {
console.error("[payments] confirmation email failed", String(mailError && mailError.message ? mailError.message : mailError));
}
}
return updated;
}
function testPaymentsEnabled() {
return String(process.env.ENABLE_TEST_PAYMENTS || "false").toLowerCase() === "true";
}
router.post("/test/confirm", async (req, res, next) => {
try {
if (!testPaymentsEnabled()) {
return res.status(404).json({ ok: false, reason: "TEST_PAYMENTS_DISABLED" });
}
await db.query(
`UPDATE reservations
SET status = 'EXPIRED', payment_status = COALESCE(payment_status, 'EXPIRED')
WHERE status = 'HOLD' AND expires_at < ?`,
[nowIso()]
);
const holdId = Number(req.body?.holdId || 0);
if (!Number.isInteger(holdId) || holdId <= 0) {
return res.status(400).json({ ok: false, reason: "INVALID_HOLD_ID" });
}
const hold = await getHoldById(holdId);
if (!hold) {
return res.status(404).json({ ok: false, reason: "HOLD_NOT_FOUND" });
}
if (hold.status !== "HOLD") {
return res.status(409).json({ ok: false, reason: hold.status === "EXPIRED" ? "EXPIRED" : "HOLD_NOT_ACTIVE" });
}
if (hold.expires_at && new Date(toClientIso(hold.expires_at)).getTime() <= Date.now()) {
await db.query(
`UPDATE reservations
SET status = 'EXPIRED', payment_status = 'EXPIRED'
WHERE id = ?`,
[holdId]
);
return res.status(409).json({ ok: false, reason: "EXPIRED" });
}
const updated = await finalizeReservation({
holdId,
paymentStatus: "PAID",
paymentMethod: hold.payment_method || "ONLINE",
paymentRef: `TEST_CONFIRM_${holdId}`,
});
return res.json({ ok: true, status: "CONFIRMED", orderNumber: updated?.order_number || null, paymentRef: updated?.payment_ref || null });
} catch (err) {
next(err);
}
});
router.post("/cash/confirm", async (req, res, next) => {
try {
await db.query(
`UPDATE reservations
SET status = 'EXPIRED', payment_status = COALESCE(payment_status, 'EXPIRED')
WHERE status = 'HOLD' AND expires_at < ?`,
[nowIso()]
);
const holdId = Number(req.body?.holdId || 0);
if (!Number.isInteger(holdId) || holdId <= 0) {
return res.status(400).json({ ok: false, reason: "INVALID_HOLD_ID" });
}
const hold = await getHoldById(holdId);
if (!hold) {
return res.status(404).json({ ok: false, reason: "HOLD_NOT_FOUND" });
}
if (hold.status !== "HOLD") {
return res.status(409).json({ ok: false, reason: hold.status === "EXPIRED" ? "EXPIRED" : "HOLD_NOT_ACTIVE" });
}
if (hold.expires_at && new Date(toClientIso(hold.expires_at)).getTime() <= Date.now()) {
await db.query(
`UPDATE reservations
SET status = 'EXPIRED', payment_status = 'EXPIRED'
WHERE id = ?`,
[holdId]
);
return res.status(409).json({ ok: false, reason: "EXPIRED" });
}
const updated = await finalizeReservation({
holdId,
paymentStatus: "PAID",
paymentMethod: "CASH",
paymentRef: `CASH_${holdId}`,
});
return res.json({ ok: true, status: "CONFIRMED", orderNumber: updated?.order_number || null, paymentRef: updated?.payment_ref || null });
} catch (err) {
next(err);
}
});
router.post("/test/fail", async (req, res, next) => {
try {
if (!testPaymentsEnabled()) {
return res.status(404).json({ ok: false, reason: "TEST_PAYMENTS_DISABLED" });
}
await db.query(
`UPDATE reservations
SET status = 'EXPIRED', payment_status = COALESCE(payment_status, 'EXPIRED')
WHERE status = 'HOLD' AND expires_at < ?`,
[nowIso()]
);
const holdId = Number(req.body?.holdId || 0);
if (!Number.isInteger(holdId) || holdId <= 0) {
return res.status(400).json({ ok: false, reason: "INVALID_HOLD_ID" });
}
const holdResult = await db.query(
`SELECT id, status
FROM reservations
WHERE id = ?
LIMIT 1`,
[holdId]
);
const hold = holdResult.rows[0];
if (!hold) {
return res.status(404).json({ ok: false, reason: "HOLD_NOT_FOUND" });
}
if (hold.status === "CONFIRMED") {
return res.status(409).json({ ok: false, reason: "ALREADY_CONFIRMED" });
}
await db.query(
`UPDATE reservations
SET status = 'EXPIRED',
payment_status = 'FAILED',
payment_ref = 'TEST_FAIL_' || id
WHERE id = ?`,
[holdId]
);
return res.json({ ok: true, status: "EXPIRED" });
} catch (err) {
next(err);
}
});
return router;
};