fix: email validation, conditional order update, improved tests for payment routes
This commit is contained in:
@@ -1,20 +1,20 @@
|
|||||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest'
|
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
import Fastify from 'fastify'
|
import Fastify from 'fastify'
|
||||||
import jwt from '@fastify/jwt'
|
import jwt from '@fastify/jwt'
|
||||||
import { prisma } from '../../lib/prisma.js'
|
import { prisma } from '../../lib/prisma.js'
|
||||||
import { registerUserPaymentRoutes } from '../user-payments.js'
|
import { registerUserPaymentRoutes } from '../user-payments.js'
|
||||||
|
|
||||||
const JWT_SECRET = 'test-secret'
|
const JWT_SECRET = 'test-secret'
|
||||||
const TEST_USER_EMAIL = 'test-pay-user@example.com'
|
const TEST_USER_EMAIL = `test-pay-${Date.now()}@example.com`
|
||||||
|
|
||||||
let testUserId
|
let testUserId
|
||||||
let testOrderId
|
let testOrderId
|
||||||
|
|
||||||
async function signToken(userId) {
|
async function signToken(userId, email = TEST_USER_EMAIL) {
|
||||||
const fastify = Fastify()
|
const fastify = Fastify()
|
||||||
await fastify.register(jwt, { secret: JWT_SECRET })
|
await fastify.register(jwt, { secret: JWT_SECRET })
|
||||||
await fastify.ready()
|
await fastify.ready()
|
||||||
return fastify.jwt.sign({ sub: userId, email: TEST_USER_EMAIL })
|
return fastify.jwt.sign({ sub: userId, email })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function buildApp() {
|
async function buildApp() {
|
||||||
@@ -37,6 +37,10 @@ describe('POST /api/me/orders/:id/pay', () => {
|
|||||||
let app
|
let app
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
|
await prisma.payment.deleteMany()
|
||||||
|
await prisma.order.deleteMany({ where: { user: { email: TEST_USER_EMAIL } } })
|
||||||
|
await prisma.user.deleteMany({ where: { email: TEST_USER_EMAIL } })
|
||||||
|
|
||||||
const user = await prisma.user.create({
|
const user = await prisma.user.create({
|
||||||
data: { email: TEST_USER_EMAIL },
|
data: { email: TEST_USER_EMAIL },
|
||||||
})
|
})
|
||||||
@@ -57,6 +61,7 @@ describe('POST /api/me/orders/:id/pay', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
await prisma.payment.deleteMany({ where: { orderId: testOrderId } })
|
||||||
await prisma.order.deleteMany({ where: { userId: testUserId } })
|
await prisma.order.deleteMany({ where: { userId: testUserId } })
|
||||||
await prisma.user.deleteMany({ where: { email: TEST_USER_EMAIL } })
|
await prisma.user.deleteMany({ where: { email: TEST_USER_EMAIL } })
|
||||||
})
|
})
|
||||||
@@ -75,6 +80,7 @@ describe('POST /api/me/orders/:id/pay', () => {
|
|||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await app.close()
|
await app.close()
|
||||||
|
vi.restoreAllMocks()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns 401 without auth', async () => {
|
it('returns 401 without auth', async () => {
|
||||||
@@ -136,4 +142,104 @@ describe('POST /api/me/orders/:id/pay', () => {
|
|||||||
})
|
})
|
||||||
expect(res.statusCode).toBe(409)
|
expect(res.statusCode).toBe(409)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('returns 422 when user has no email', async () => {
|
||||||
|
const noEmailUser = await prisma.user.create({
|
||||||
|
data: { email: `noemail-${Date.now()}@test.com` },
|
||||||
|
})
|
||||||
|
const noEmailOrder = await prisma.order.create({
|
||||||
|
data: {
|
||||||
|
userId: noEmailUser.id,
|
||||||
|
status: 'PENDING_PAYMENT',
|
||||||
|
paymentMethod: 'online',
|
||||||
|
deliveryFeeLocked: true,
|
||||||
|
totalCents: 100000,
|
||||||
|
currency: 'RUB',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const fastify = Fastify()
|
||||||
|
await fastify.register(jwt, { secret: JWT_SECRET })
|
||||||
|
const token = fastify.jwt.sign({ sub: noEmailUser.id })
|
||||||
|
await fastify.close()
|
||||||
|
|
||||||
|
const res = await app.inject({
|
||||||
|
method: 'POST',
|
||||||
|
url: `/api/me/orders/${noEmailOrder.id}/pay`,
|
||||||
|
headers: { authorization: `Bearer ${token}` },
|
||||||
|
})
|
||||||
|
expect(res.statusCode).toBe(422)
|
||||||
|
|
||||||
|
await prisma.order.deleteMany({ where: { userId: noEmailUser.id } })
|
||||||
|
await prisma.user.deleteMany({ where: { id: noEmailUser.id } })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('GET /api/me/orders/:orderId/payment', () => {
|
||||||
|
let app
|
||||||
|
let getTestUserId
|
||||||
|
let getTestOrderId
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const getEmail = `get-pay-${Date.now()}@example.com`
|
||||||
|
const user = await prisma.user.create({ data: { email: getEmail } })
|
||||||
|
getTestUserId = user.id
|
||||||
|
|
||||||
|
const order = await prisma.order.create({
|
||||||
|
data: {
|
||||||
|
userId: getTestUserId,
|
||||||
|
status: 'PENDING_PAYMENT',
|
||||||
|
paymentMethod: 'online',
|
||||||
|
deliveryFeeLocked: true,
|
||||||
|
totalCents: 100000,
|
||||||
|
currency: 'RUB',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
getTestOrderId = order.id
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await prisma.payment.deleteMany({ where: { orderId: getTestOrderId } })
|
||||||
|
await prisma.order.deleteMany({ where: { userId: getTestUserId } })
|
||||||
|
await prisma.user.deleteMany({ where: { id: getTestUserId } })
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
app = await buildApp()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await app.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns 401 without auth', async () => {
|
||||||
|
const res = await app.inject({
|
||||||
|
method: 'GET',
|
||||||
|
url: `/api/me/orders/${getTestOrderId}/payment`,
|
||||||
|
})
|
||||||
|
expect(res.statusCode).toBe(401)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns 404 when order not found', async () => {
|
||||||
|
const token = await signToken(getTestUserId)
|
||||||
|
const res = await app.inject({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/api/me/orders/nonexistent-id/payment',
|
||||||
|
headers: { authorization: `Bearer ${token}` },
|
||||||
|
})
|
||||||
|
expect(res.statusCode).toBe(404)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns status null when no payment exists', async () => {
|
||||||
|
const token = await signToken(getTestUserId)
|
||||||
|
const res = await app.inject({
|
||||||
|
method: 'GET',
|
||||||
|
url: `/api/me/orders/${getTestOrderId}/payment`,
|
||||||
|
headers: { authorization: `Bearer ${token}` },
|
||||||
|
})
|
||||||
|
expect(res.statusCode).toBe(200)
|
||||||
|
const body = JSON.parse(res.payload)
|
||||||
|
expect(body.status).toBeNull()
|
||||||
|
expect(body.paid).toBe(false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ export async function registerUserPaymentRoutes(fastify) {
|
|||||||
async (request, reply) => {
|
async (request, reply) => {
|
||||||
const userId = request.user.sub
|
const userId = request.user.sub
|
||||||
const userEmail = request.user.email
|
const userEmail = request.user.email
|
||||||
|
|
||||||
|
if (!userEmail) {
|
||||||
|
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({
|
||||||
@@ -42,7 +47,7 @@ export async function registerUserPaymentRoutes(fastify) {
|
|||||||
return { confirmationUrl: existingPayment.confirmationUrl }
|
return { confirmationUrl: existingPayment.confirmationUrl }
|
||||||
}
|
}
|
||||||
|
|
||||||
const idempotencyKey = `${id}-v1`
|
const idempotencyKey = `${id}-${Date.now()}`
|
||||||
const returnUrl = `${process.env.CLIENT_PUBLIC_URL || 'http://127.0.0.1:5173'}/me/orders/${id}?paid=1`
|
const returnUrl = `${process.env.CLIENT_PUBLIC_URL || 'http://127.0.0.1:5173'}/me/orders/${id}?paid=1`
|
||||||
const clientIp = request.ip
|
const clientIp = request.ip
|
||||||
|
|
||||||
@@ -123,15 +128,17 @@ export async function registerUserPaymentRoutes(fastify) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (ykPayment.status === 'succeeded' && order.status === 'PENDING_PAYMENT') {
|
if (ykPayment.status === 'succeeded' && order.status === 'PENDING_PAYMENT') {
|
||||||
await prisma.order.update({
|
const updated = await prisma.order.updateMany({
|
||||||
where: { id: orderId },
|
where: { id: orderId, status: 'PENDING_PAYMENT' },
|
||||||
data: { status: 'PAID' },
|
data: { status: 'PAID' },
|
||||||
})
|
})
|
||||||
request.server.eventBus.emit(NOTIFICATION_EVENTS.PAYMENT_STATUS_CHANGED, {
|
if (updated.count > 0) {
|
||||||
orderId,
|
request.server.eventBus.emit(NOTIFICATION_EVENTS.PAYMENT_STATUS_CHANGED, {
|
||||||
userId: order.userId,
|
orderId,
|
||||||
paymentStatus: 'paid',
|
userId: order.userId,
|
||||||
})
|
paymentStatus: 'paid',
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user