143 lines
4.7 KiB
JavaScript
143 lines
4.7 KiB
JavaScript
import { prisma } from "../lib/prisma.js";
|
|
import { escapeHtml } from "../lib/escape-html.js";
|
|
import { getOtherUploadMaxFileBytes } from "../lib/upload-limits.js";
|
|
import { saveImageBufferToUploads } from "../lib/upload-images.js";
|
|
import { NOTIFICATION_EVENTS } from "../../../shared/constants/notification-events.js";
|
|
|
|
export async function registerUserPaymentRoutes(fastify) {
|
|
fastify.post(
|
|
"/api/me/orders/:id/pay",
|
|
{ preHandler: [fastify.authenticate] },
|
|
async (request, reply) => {
|
|
const userId = request.user.sub;
|
|
const { id } = request.params;
|
|
const order = await prisma.order.findFirst({ where: { id, userId } });
|
|
if (!order) return reply.code(404).send({ error: "Заказ не найден" });
|
|
|
|
const paymentMethod = order.paymentMethod ?? "online";
|
|
if (paymentMethod === "on_pickup") {
|
|
return reply
|
|
.code(409)
|
|
.send({
|
|
error:
|
|
"Для этого заказа оплата при получении — кнопка оплаты не нужна.",
|
|
});
|
|
}
|
|
|
|
if (order.status !== "PENDING_PAYMENT") {
|
|
return reply
|
|
.code(409)
|
|
.send({ error: "Сейчас нельзя выполнить оплату для этого заказа" });
|
|
}
|
|
|
|
if (!request.isMultipart()) {
|
|
return reply
|
|
.code(400)
|
|
.send({
|
|
error:
|
|
"Отправьте multipart/form-data: поле detail и/или файл receipt",
|
|
});
|
|
}
|
|
|
|
let detail = "";
|
|
let receiptBuffer = null;
|
|
let receiptFilename = "";
|
|
try {
|
|
const otherLimit = getOtherUploadMaxFileBytes();
|
|
const parts = request.parts({
|
|
limits: {
|
|
fileSize: otherLimit,
|
|
files: 2,
|
|
},
|
|
});
|
|
for await (const part of parts) {
|
|
if (part.file) {
|
|
if (part.fieldname === "receipt") {
|
|
if (receiptBuffer !== null) {
|
|
return reply
|
|
.code(400)
|
|
.send({ error: "Допускается один файл receipt" });
|
|
}
|
|
receiptBuffer = await part.toBuffer();
|
|
receiptFilename = part.filename ?? "receipt";
|
|
}
|
|
} else if (part.fieldname === "detail") {
|
|
detail = String(part.value ?? "").trim();
|
|
}
|
|
}
|
|
} catch (err) {
|
|
const msg =
|
|
err instanceof Error ? err.message : "Не удалось разобрать форму";
|
|
return reply.code(400).send({ error: msg });
|
|
}
|
|
|
|
const hasDetail = detail.length > 0;
|
|
const hasReceipt = receiptBuffer !== null && receiptBuffer.length > 0;
|
|
|
|
if (!hasDetail && !hasReceipt) {
|
|
return reply
|
|
.code(400)
|
|
.send({
|
|
error: "Укажите текст о платеже и/или прикрепите изображение чека",
|
|
});
|
|
}
|
|
|
|
const maxDetail = 2000;
|
|
if (detail.length > maxDetail) {
|
|
return reply
|
|
.code(400)
|
|
.send({ error: `Текст не длиннее ${maxDetail} символов` });
|
|
}
|
|
|
|
let attachmentUrl = null;
|
|
if (hasReceipt) {
|
|
try {
|
|
attachmentUrl = await saveImageBufferToUploads(
|
|
receiptFilename,
|
|
receiptBuffer,
|
|
);
|
|
} catch (err) {
|
|
const message =
|
|
err instanceof Error ? err.message : "Не удалось сохранить файл";
|
|
const statusCode =
|
|
err &&
|
|
typeof err === "object" &&
|
|
"statusCode" in err &&
|
|
Number.isInteger(err.statusCode)
|
|
? Number(err.statusCode)
|
|
: 400;
|
|
return reply.code(statusCode).send({ error: message });
|
|
}
|
|
}
|
|
|
|
const bodyHtml = hasDetail
|
|
? `<p>${escapeHtml(detail).replace(/\r\n|\n|\r/g, "<br/>")}</p>`
|
|
: "";
|
|
const messageText = `<p><strong>Подтверждение оплаты (перевод ВТБ / Сбербанк)</strong></p>${bodyHtml}`;
|
|
|
|
try {
|
|
await prisma.$transaction(async (tx) => {
|
|
await tx.orderMessage.create({
|
|
data: {
|
|
orderId: id,
|
|
authorType: "user",
|
|
text: messageText,
|
|
attachmentUrl,
|
|
},
|
|
});
|
|
});
|
|
} catch (err) {
|
|
return reply.code(500).send({ error: "Не удалось сохранить оплату" });
|
|
}
|
|
|
|
request.server.eventBus.emit(NOTIFICATION_EVENTS.PAYMENT_STATUS_CHANGED, {
|
|
orderId: id,
|
|
userId,
|
|
paymentStatus: "pending",
|
|
});
|
|
|
|
return { ok: true, status: "PENDING_PAYMENT" };
|
|
},
|
|
);
|
|
}
|