refactor: simplify order status model — remove DELIVERY_FEE_ADJUSTMENT and PAYMENT_VERIFICATION

- Add deliveryFeeLocked field to Order model
- Remove DELIVERY_FEE_ADJUSTMENT and PAYMENT_VERIFICATION statuses (11→8)
- 3 order paths: delivery+online (locked→unlocked→paid), pickup+online (unlocked→paid), pickup+on_pickup (direct to in_progress)
- Update checkout to use PENDING_PAYMENT + deliveryFeeLocked
- Update payment flow to stay in PENDING_PAYMENT until admin confirms
- Update admin UI to use deliveryFeeLocked instead of status check
- Update client payment UI with new deliveryFeeLocked logic
This commit is contained in:
Kirill
2026-05-15 21:55:14 +05:00
parent 2db6258b33
commit f855568687
18 changed files with 1170 additions and 135 deletions
+78 -99
View File
@@ -18,115 +18,94 @@ export async function registerUserPaymentRoutes(fastify) {
return reply.code(409).send({ error: 'Для этого заказа оплата при получении — кнопка оплаты не нужна.' })
}
if (order.status === 'DELIVERY_FEE_ADJUSTMENT') {
if (order.status !== 'PENDING_PAYMENT') {
return reply.code(409).send({ error: 'Сейчас нельзя выполнить оплату для этого заказа' })
}
if (!request.isMultipart()) {
return reply
.code(409)
.send({
error:
'Оплата станет доступна после корректировки стоимости доставки администратором.',
})
.code(400)
.send({ error: 'Отправьте multipart/form-data: поле detail и/или файл receipt' })
}
let nextStatus = order.status
if (order.status === 'DRAFT') {
await prisma.order.update({ where: { id }, data: { status: 'PENDING_PAYMENT' } })
nextStatus = 'PENDING_PAYMENT'
return { ok: true, status: nextStatus }
}
if (order.status === 'PAYMENT_VERIFICATION') {
return { ok: true, status: nextStatus }
}
if (order.status === 'PENDING_PAYMENT') {
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 })
}
let detail = ''
let receiptBuffer = null
let receiptFilename = ''
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 {
const otherLimit = getOtherUploadMaxFileBytes()
const parts = request.parts({
limits: {
fileSize: otherLimit,
files: 2,
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,
},
})
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.order.update({ where: { id }, data: { status: 'PAYMENT_VERIFICATION' } })
await tx.orderMessage.create({
data: {
orderId: id,
authorType: 'user',
text: messageText,
attachmentUrl,
},
})
})
} catch (err) {
return reply.code(500).send({ error: 'Не удалось сохранить оплату' })
}
return { ok: true, status: 'PAYMENT_VERIFICATION' }
})
} catch (err) {
return reply.code(500).send({ error: 'Не удалось сохранить оплату' })
}
return reply.code(409).send({ error: 'Сейчас нельзя выполнить оплату для этого заказа' })
return { ok: true, status: 'PENDING_PAYMENT' }
},
)
}