chore: fix prettier formatting

This commit is contained in:
Kirill
2026-05-20 19:33:13 +05:00
parent faac332138
commit 3177413acd
6 changed files with 127 additions and 145 deletions
+5 -15
View File
@@ -166,9 +166,7 @@ describe('yookassa getPayment', () => {
describe('yookassa buildReceipt', () => { describe('yookassa buildReceipt', () => {
it('builds receipt with order items', () => { it('builds receipt with order items', () => {
const result = buildReceipt({ const result = buildReceipt({
orderItems: [ orderItems: [{ titleSnapshot: 'Test Product', qty: 2, priceCentsSnapshot: 100000 }],
{ titleSnapshot: 'Test Product', qty: 2, priceCentsSnapshot: 100000 },
],
deliveryFeeCents: 0, deliveryFeeCents: 0,
userEmail: 'user@test.ru', userEmail: 'user@test.ru',
}) })
@@ -187,9 +185,7 @@ describe('yookassa buildReceipt', () => {
it('adds delivery item when deliveryFeeCents > 0', () => { it('adds delivery item when deliveryFeeCents > 0', () => {
const result = buildReceipt({ const result = buildReceipt({
orderItems: [ orderItems: [{ titleSnapshot: 'Item A', qty: 1, priceCentsSnapshot: 50000 }],
{ titleSnapshot: 'Item A', qty: 1, priceCentsSnapshot: 50000 },
],
deliveryFeeCents: 35000, deliveryFeeCents: 35000,
userEmail: 'user@test.ru', userEmail: 'user@test.ru',
}) })
@@ -202,9 +198,7 @@ describe('yookassa buildReceipt', () => {
it('passes through taxSystemCode', () => { it('passes through taxSystemCode', () => {
const result = buildReceipt({ const result = buildReceipt({
orderItems: [ orderItems: [{ titleSnapshot: 'Item', qty: 1, priceCentsSnapshot: 1000 }],
{ titleSnapshot: 'Item', qty: 1, priceCentsSnapshot: 1000 },
],
deliveryFeeCents: 0, deliveryFeeCents: 0,
userEmail: 'user@test.ru', userEmail: 'user@test.ru',
taxSystemCode: 3, taxSystemCode: 3,
@@ -241,15 +235,11 @@ describe('yookassa validateWebhook', () => {
}) })
it('throws if missing event', () => { it('throws if missing event', () => {
expect(() => validateWebhook('127.0.0.1', { type: 'notification', object: {} })).toThrow( expect(() => validateWebhook('127.0.0.1', { type: 'notification', object: {} })).toThrow('Missing event or object')
'Missing event or object',
)
}) })
it('throws if missing object', () => { it('throws if missing object', () => {
expect(() => validateWebhook('127.0.0.1', { type: 'notification', event: 'x' })).toThrow( expect(() => validateWebhook('127.0.0.1', { type: 'notification', event: 'x' })).toThrow('Missing event or object')
'Missing event or object',
)
}) })
it('throws for invalid body type', () => { it('throws for invalid body type', () => {
+1 -1
View File
@@ -124,7 +124,7 @@ function isYookassaIp(ip) {
} }
function isTestMode() { function isTestMode() {
return (process.env.YOOKASSA_SECRET_KEY?.startsWith('test_')) ?? false return process.env.YOOKASSA_SECRET_KEY?.startsWith('test_') ?? false
} }
export function validateWebhook(ip, body) { export function validateWebhook(ip, body) {
@@ -1,6 +1,6 @@
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
import Fastify from 'fastify'
import jwt from '@fastify/jwt' import jwt from '@fastify/jwt'
import Fastify from 'fastify'
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
import { prisma } from '../../lib/prisma.js' import { prisma } from '../../lib/prisma.js'
import { registerUserPaymentRoutes } from '../user-payments.js' import { registerUserPaymentRoutes } from '../user-payments.js'
@@ -1,5 +1,5 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import Fastify from 'fastify' import Fastify from 'fastify'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { NOTIFICATION_EVENTS } from '../../../../shared/constants/notification-events.js' import { NOTIFICATION_EVENTS } from '../../../../shared/constants/notification-events.js'
const { mockPrisma } = vi.hoisted(() => ({ const { mockPrisma } = vi.hoisted(() => ({
+117 -125
View File
@@ -3,149 +3,141 @@ import { prisma } from '../lib/prisma.js'
import { createPayment, buildReceipt, getPayment } from '../lib/yookassa.js' import { createPayment, buildReceipt, getPayment } from '../lib/yookassa.js'
export async function registerUserPaymentRoutes(fastify) { export async function registerUserPaymentRoutes(fastify) {
fastify.post( fastify.post('/api/me/orders/:id/pay', { preHandler: [fastify.authenticate] }, async (request, reply) => {
'/api/me/orders/:id/pay', const userId = request.user.sub
{ preHandler: [fastify.authenticate] }, const userEmail = request.user.email
async (request, reply) => {
const userId = request.user.sub
const userEmail = request.user.email
if (!userEmail) { if (!userEmail) {
return reply.code(422).send({ error: 'Для онлайн-оплаты необходим email в профиле' }) return reply.code(422).send({ error: 'Для онлайн-оплаты необходим email в профиле' })
} }
const { id } = request.params const { id } = request.params
const order = await prisma.order.findFirst({ const order = await prisma.order.findFirst({
where: { id, userId }, where: { id, userId },
include: { items: true }, include: { items: true },
})
if (!order) return reply.code(404).send({ error: 'Заказ не найден' })
if (order.paymentMethod === 'on_pickup') {
return reply.code(409).send({
error: 'Для этого заказа оплата при получении — онлайн-оплата недоступна',
}) })
if (!order) return reply.code(404).send({ error: 'Заказ не найден' }) }
if (order.paymentMethod === 'on_pickup') { if (order.status !== 'PENDING_PAYMENT') {
return reply.code(409).send({ return reply.code(409).send({ error: 'Сейчас нельзя выполнить оплату для этого заказа' })
error: 'Для этого заказа оплата при получении — онлайн-оплата недоступна', }
})
}
if (order.status !== 'PENDING_PAYMENT') { if (!order.deliveryFeeLocked) {
return reply.code(409).send({ error: 'Сейчас нельзя выполнить оплату для этого заказа' }) return reply.code(409).send({
} error: 'Стоимость доставки ещё утверждается — оплата станет доступна позже',
if (!order.deliveryFeeLocked) {
return reply.code(409).send({
error: 'Стоимость доставки ещё утверждается — оплата станет доступна позже',
})
}
const existingPayment = await prisma.payment.findFirst({
where: { orderId: id, status: { in: ['pending', 'waiting_for_capture'] } },
orderBy: { createdAt: 'desc' },
}) })
}
if (existingPayment && existingPayment.confirmationUrl) { const existingPayment = await prisma.payment.findFirst({
return { confirmationUrl: existingPayment.confirmationUrl } where: { orderId: id, status: { in: ['pending', 'waiting_for_capture'] } },
} orderBy: { createdAt: 'desc' },
})
const idempotencyKey = `${id}-${Date.now()}` if (existingPayment && existingPayment.confirmationUrl) {
const returnUrl = `${process.env.CLIENT_PUBLIC_URL || 'http://127.0.0.1:5173'}/me/orders/${id}?paid=1` return { confirmationUrl: existingPayment.confirmationUrl }
const clientIp = request.ip }
const amount = { const idempotencyKey = `${id}-${Date.now()}`
value: (order.totalCents / 100).toFixed(2), const returnUrl = `${process.env.CLIENT_PUBLIC_URL || 'http://127.0.0.1:5173'}/me/orders/${id}?paid=1`
const clientIp = request.ip
const amount = {
value: (order.totalCents / 100).toFixed(2),
currency: order.currency,
}
const receipt = buildReceipt({
orderItems: order.items,
deliveryFeeCents: order.deliveryFeeCents,
userEmail: userEmail || 'noemail@example.com',
})
let result
try {
result = await createPayment({
amount,
description: `Оплата заказа №${order.id.slice(-6)}`,
receipt,
confirmation: { type: 'redirect', return_url: returnUrl },
metadata: { orderId: order.id },
idempotencyKey,
clientIp,
})
} catch (err) {
request.log.error({ err, orderId: id }, 'YooKassa createPayment failed')
return reply.code(502).send({
error: 'Не удалось создать платёж. Платёжный сервис временно недоступен.',
})
}
await prisma.payment.create({
data: {
orderId: order.id,
yookassaPaymentId: result.paymentId,
status: result.status,
amountCents: order.totalCents,
currency: order.currency, currency: order.currency,
} confirmationUrl: result.confirmationUrl,
expiresAt: result.expiresAt ? new Date(result.expiresAt) : null,
},
})
const receipt = buildReceipt({ return { confirmationUrl: result.confirmationUrl }
orderItems: order.items, })
deliveryFeeCents: order.deliveryFeeCents,
userEmail: userEmail || 'noemail@example.com',
})
let result fastify.get('/api/me/orders/:orderId/payment', { preHandler: [fastify.authenticate] }, async (request, reply) => {
try { const userId = request.user.sub
result = await createPayment({ const { orderId } = request.params
amount,
description: `Оплата заказа №${order.id.slice(-6)}`, const order = await prisma.order.findFirst({ where: { id: orderId, userId } })
receipt, if (!order) return reply.code(404).send({ error: 'Заказ не найден' })
confirmation: { type: 'redirect', return_url: returnUrl },
metadata: { orderId: order.id }, const payment = await prisma.payment.findFirst({
idempotencyKey, where: { orderId },
clientIp, orderBy: { createdAt: 'desc' },
})
if (!payment) {
return { status: null, paid: false }
}
if (payment.status === 'succeeded' || payment.status === 'canceled') {
return { status: payment.status, paid: payment.status === 'succeeded' }
}
try {
const ykPayment = await getPayment(payment.yookassaPaymentId)
if (ykPayment.status !== payment.status) {
await prisma.payment.update({
where: { id: payment.id },
data: { status: ykPayment.status },
}) })
} catch (err) {
request.log.error({ err, orderId: id }, 'YooKassa createPayment failed')
return reply.code(502).send({
error: 'Не удалось создать платёж. Платёжный сервис временно недоступен.',
})
}
await prisma.payment.create({ if (ykPayment.status === 'succeeded' && order.status === 'PENDING_PAYMENT') {
data: { const updated = await prisma.order.updateMany({
orderId: order.id, where: { id: orderId, status: 'PENDING_PAYMENT' },
yookassaPaymentId: result.paymentId, data: { status: 'PAID' },
status: result.status,
amountCents: order.totalCents,
currency: order.currency,
confirmationUrl: result.confirmationUrl,
expiresAt: result.expiresAt ? new Date(result.expiresAt) : null,
},
})
return { confirmationUrl: result.confirmationUrl }
},
)
fastify.get(
'/api/me/orders/:orderId/payment',
{ preHandler: [fastify.authenticate] },
async (request, reply) => {
const userId = request.user.sub
const { orderId } = request.params
const order = await prisma.order.findFirst({ where: { id: orderId, userId } })
if (!order) return reply.code(404).send({ error: 'Заказ не найден' })
const payment = await prisma.payment.findFirst({
where: { orderId },
orderBy: { createdAt: 'desc' },
})
if (!payment) {
return { status: null, paid: false }
}
if (payment.status === 'succeeded' || payment.status === 'canceled') {
return { status: payment.status, paid: payment.status === 'succeeded' }
}
try {
const ykPayment = await getPayment(payment.yookassaPaymentId)
if (ykPayment.status !== payment.status) {
await prisma.payment.update({
where: { id: payment.id },
data: { status: ykPayment.status },
}) })
if (updated.count > 0) {
if (ykPayment.status === 'succeeded' && order.status === 'PENDING_PAYMENT') { request.server.eventBus.emit(NOTIFICATION_EVENTS.PAYMENT_STATUS_CHANGED, {
const updated = await prisma.order.updateMany({ orderId,
where: { id: orderId, status: 'PENDING_PAYMENT' }, userId: order.userId,
data: { status: 'PAID' }, paymentStatus: 'paid',
}) })
if (updated.count > 0) {
request.server.eventBus.emit(NOTIFICATION_EVENTS.PAYMENT_STATUS_CHANGED, {
orderId,
userId: order.userId,
paymentStatus: 'paid',
})
}
} }
} }
return { status: ykPayment.status, paid: ykPayment.paid }
} catch {
return { status: payment.status, paid: payment.status === 'succeeded' }
} }
},
) return { status: ykPayment.status, paid: ykPayment.paid }
} catch {
return { status: payment.status, paid: payment.status === 'succeeded' }
}
})
} }
+1 -1
View File
@@ -1,5 +1,5 @@
import { prisma } from '../lib/prisma.js'
import { NOTIFICATION_EVENTS } from '../../../shared/constants/notification-events.js' import { NOTIFICATION_EVENTS } from '../../../shared/constants/notification-events.js'
import { prisma } from '../lib/prisma.js'
import { validateWebhook } from '../lib/yookassa.js' import { validateWebhook } from '../lib/yookassa.js'
export async function registerYookassaWebhookRoute(fastify) { export async function registerYookassaWebhookRoute(fastify) {