refactor order craetion
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { PaymentMethodEnum, PaymentStatusEnum } from '../interface/payment';
|
||||
import { PaymentGatewayEnum, PaymentMethodEnum, PaymentStatusEnum } from '../interface/payment';
|
||||
import { Payment } from '../entities/payment.entity';
|
||||
import { EntityManager } from '@mikro-orm/core';
|
||||
import { Order } from '../../orders/entities/order.entity';
|
||||
@@ -19,9 +19,13 @@ export class PaymentsService {
|
||||
|
||||
async payOrder(
|
||||
orderId: string,
|
||||
restId: string,
|
||||
): Promise<{ paymentUrl: string | null }> {
|
||||
const ctx = await this.loadAndValidateOrder(orderId, restId);
|
||||
const ctx = await this.loadAndValidateOrder(orderId);
|
||||
|
||||
// Idempotency: avoid creating/charging again for already-paid orders
|
||||
if (ctx.order.paymentStatus === PaymentStatusEnum.Paid) {
|
||||
return { paymentUrl: null };
|
||||
}
|
||||
|
||||
switch (ctx.method) {
|
||||
case PaymentMethodEnum.Cash:
|
||||
@@ -42,12 +46,11 @@ export class PaymentsService {
|
||||
|
||||
private async loadAndValidateOrder(
|
||||
orderId: string,
|
||||
restId: string,
|
||||
): Promise<OrderPaymentContext> {
|
||||
const order = await this.em.findOne(
|
||||
Order,
|
||||
{ id: orderId, restaurant: { id: restId } },
|
||||
{ populate: ['user', 'paymentMethod', 'paymentMethod.restaurant'] },
|
||||
{ id: orderId },
|
||||
{ populate: ['user', 'restaurant', 'paymentMethod', 'paymentMethod.restaurant'] },
|
||||
);
|
||||
|
||||
if (!order) {
|
||||
@@ -63,8 +66,10 @@ export class PaymentsService {
|
||||
throw new NotFoundException('Payment method not found');
|
||||
}
|
||||
|
||||
if (pm.method === PaymentMethodEnum.Online && !pm.merchantId) {
|
||||
throw new BadRequestException('Merchant ID is required for online payment');
|
||||
if (pm.method === PaymentMethodEnum.Online) {
|
||||
if (!pm.gateway) throw new BadRequestException('Gateway is required for online payment');
|
||||
if (!pm.merchantId) throw new BadRequestException('Merchant ID is required for online payment');
|
||||
if (!pm.restaurant?.domain) throw new BadRequestException('Restaurant domain is required for online payment');
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -78,23 +83,25 @@ export class PaymentsService {
|
||||
}
|
||||
|
||||
private async handleCashPayment(ctx: OrderPaymentContext): Promise<void> {
|
||||
const payment = this.em.create(Payment, {
|
||||
await this.getOrCreateLatestPendingPayment(ctx.order.id, {
|
||||
amount: ctx.amount,
|
||||
order: ctx.order,
|
||||
method: PaymentMethodEnum.Cash,
|
||||
gateway: null,
|
||||
transactionId: null,
|
||||
status: PaymentStatusEnum.Pending,
|
||||
});
|
||||
|
||||
await this.em.persistAndFlush(payment);
|
||||
}
|
||||
|
||||
private async handleWalletPayment(ctx: OrderPaymentContext): Promise<void> {
|
||||
await this.em.transactional(async em => {
|
||||
const order = await em.findOne(Order, { id: ctx.order.id }, { populate: ['user', 'restaurant'] });
|
||||
if (!order) throw new NotFoundException('Order not found');
|
||||
|
||||
if (order.paymentStatus === PaymentStatusEnum.Paid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const wallet = await em.findOne(UserWallet, {
|
||||
user: { id: ctx.order.user.id },
|
||||
restaurant: { id: ctx.order.restaurant.id },
|
||||
user: { id: order.user.id },
|
||||
restaurant: { id: order.restaurant.id },
|
||||
});
|
||||
|
||||
if (!wallet) {
|
||||
@@ -107,18 +114,18 @@ export class PaymentsService {
|
||||
|
||||
wallet.wallet -= ctx.amount;
|
||||
|
||||
const payment = em.create(Payment, {
|
||||
const payment = await this.getOrCreateLatestPendingPayment(order.id, {
|
||||
em,
|
||||
amount: ctx.amount,
|
||||
order: ctx.order,
|
||||
method: PaymentMethodEnum.Wallet,
|
||||
gateway: null,
|
||||
status: PaymentStatusEnum.Paid,
|
||||
paidAt: new Date(),
|
||||
});
|
||||
|
||||
ctx.order.paymentStatus = PaymentStatusEnum.Paid;
|
||||
payment.status = PaymentStatusEnum.Paid;
|
||||
payment.paidAt = new Date();
|
||||
order.paymentStatus = PaymentStatusEnum.Paid;
|
||||
|
||||
em.persist([wallet, payment, ctx.order]);
|
||||
em.persist([wallet, payment, order]);
|
||||
await em.flush();
|
||||
});
|
||||
}
|
||||
@@ -129,6 +136,17 @@ export class PaymentsService {
|
||||
): Promise<{ paymentUrl: string }> {
|
||||
const gateway = this.gatewayManager.get(ctx.gateway!);
|
||||
|
||||
const payment = await this.getOrCreateLatestPendingPayment(ctx.order.id, {
|
||||
amount: ctx.amount,
|
||||
method: PaymentMethodEnum.Online,
|
||||
gateway: ctx.gateway!,
|
||||
});
|
||||
|
||||
// If we already requested a gateway transaction, just return the same URL (idempotent)
|
||||
if (payment.transactionId) {
|
||||
return { paymentUrl: gateway.getPaymentUrl(payment.transactionId) };
|
||||
}
|
||||
|
||||
const { transactionId } = await gateway.requestPayment({
|
||||
amount: ctx.amount,
|
||||
orderId: ctx.order.id,
|
||||
@@ -136,14 +154,9 @@ export class PaymentsService {
|
||||
domain: ctx.restaurantDomain!,
|
||||
});
|
||||
|
||||
const payment = this.em.create(Payment, {
|
||||
amount: ctx.amount,
|
||||
order: ctx.order,
|
||||
method: PaymentMethodEnum.Online,
|
||||
gateway: ctx.gateway,
|
||||
transactionId,
|
||||
status: PaymentStatusEnum.Pending,
|
||||
});
|
||||
payment.gateway = ctx.gateway;
|
||||
payment.transactionId = transactionId;
|
||||
payment.status = PaymentStatusEnum.Pending;
|
||||
|
||||
await this.em.persistAndFlush(payment);
|
||||
|
||||
@@ -212,4 +225,43 @@ export class PaymentsService {
|
||||
);
|
||||
}
|
||||
|
||||
private async getOrCreateLatestPendingPayment(
|
||||
orderId: string,
|
||||
params: {
|
||||
amount: number;
|
||||
method: PaymentMethodEnum;
|
||||
gateway: PaymentGatewayEnum | null;
|
||||
em?: EntityManager;
|
||||
},
|
||||
): Promise<Payment> {
|
||||
const em = params.em ?? this.em;
|
||||
|
||||
const existing = await em.findOne(
|
||||
Payment,
|
||||
{ order: { id: orderId }, status: PaymentStatusEnum.Pending },
|
||||
{ orderBy: { createdAt: 'DESC' } },
|
||||
);
|
||||
|
||||
if (existing) {
|
||||
if (existing.method !== params.method) {
|
||||
throw new BadRequestException('Existing pending payment method does not match order payment method');
|
||||
}
|
||||
|
||||
return existing;
|
||||
}
|
||||
|
||||
const orderRef = em.getReference(Order, orderId);
|
||||
const payment = em.create(Payment, {
|
||||
amount: params.amount,
|
||||
order: orderRef,
|
||||
method: params.method,
|
||||
gateway: params.gateway,
|
||||
transactionId: null,
|
||||
status: PaymentStatusEnum.Pending,
|
||||
});
|
||||
|
||||
em.persist(payment);
|
||||
await em.flush();
|
||||
return payment;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user