180 lines
5.5 KiB
TypeScript
180 lines
5.5 KiB
TypeScript
import { Injectable, Inject, NotFoundException, BadRequestException } from '@nestjs/common';
|
|
import { PaymentEntity, PaymentMethod, PaymentStatus } from '../domain/entities/payment.entity';
|
|
import { OrderStatus } from '../domain/entities/order.entity';
|
|
import { IPaymentRepository, PAYMENT_REPOSITORY } from '../domain/repositories/payment.repository.interface';
|
|
import { OrderService } from '../order/order.service';
|
|
import { AlipayAdapter } from './adapters/alipay.adapter';
|
|
import { WechatPayAdapter } from './adapters/wechat-pay.adapter';
|
|
import { StripeAdapter } from './adapters/stripe.adapter';
|
|
|
|
export interface CreatePaymentDto {
|
|
orderId: string;
|
|
method: PaymentMethod;
|
|
}
|
|
|
|
export interface PaymentResult {
|
|
paymentId: string;
|
|
orderId: string;
|
|
qrCodeUrl?: string;
|
|
paymentUrl?: string;
|
|
expiresAt: Date;
|
|
method: PaymentMethod;
|
|
amount: number;
|
|
}
|
|
|
|
@Injectable()
|
|
export class PaymentService {
|
|
constructor(
|
|
@Inject(PAYMENT_REPOSITORY)
|
|
private readonly paymentRepo: IPaymentRepository,
|
|
private readonly orderService: OrderService,
|
|
private readonly alipayAdapter: AlipayAdapter,
|
|
private readonly wechatPayAdapter: WechatPayAdapter,
|
|
private readonly stripeAdapter: StripeAdapter,
|
|
) {}
|
|
|
|
async createPayment(dto: CreatePaymentDto): Promise<PaymentResult> {
|
|
const order = await this.orderService.findById(dto.orderId);
|
|
|
|
if (!order.canBePaid()) {
|
|
throw new BadRequestException('Cannot create payment for this order');
|
|
}
|
|
|
|
// Check for existing pending payment
|
|
const existingPayment = await this.paymentRepo.findPendingByOrderId(dto.orderId);
|
|
|
|
if (existingPayment && !existingPayment.isExpired()) {
|
|
return {
|
|
paymentId: existingPayment.id,
|
|
orderId: existingPayment.orderId,
|
|
qrCodeUrl: existingPayment.qrCodeUrl || undefined,
|
|
paymentUrl: existingPayment.paymentUrl || undefined,
|
|
expiresAt: existingPayment.expiresAt,
|
|
method: existingPayment.method,
|
|
amount: existingPayment.amount,
|
|
};
|
|
}
|
|
|
|
// Create payment via adapter
|
|
let qrCodeUrl: string | undefined;
|
|
let paymentUrl: string | undefined;
|
|
|
|
switch (dto.method) {
|
|
case PaymentMethod.ALIPAY:
|
|
const alipayResult = await this.alipayAdapter.createPayment(order);
|
|
qrCodeUrl = alipayResult.qrCodeUrl;
|
|
break;
|
|
|
|
case PaymentMethod.WECHAT:
|
|
const wechatResult = await this.wechatPayAdapter.createPayment(order);
|
|
qrCodeUrl = wechatResult.qrCodeUrl;
|
|
break;
|
|
|
|
case PaymentMethod.CREDIT_CARD:
|
|
const stripeResult = await this.stripeAdapter.createPayment(order);
|
|
paymentUrl = stripeResult.paymentUrl;
|
|
break;
|
|
|
|
default:
|
|
throw new BadRequestException('Unsupported payment method');
|
|
}
|
|
|
|
// Create payment entity
|
|
const payment = PaymentEntity.create({
|
|
orderId: order.id,
|
|
method: dto.method,
|
|
amount: order.amount,
|
|
currency: order.currency,
|
|
qrCodeUrl,
|
|
paymentUrl,
|
|
});
|
|
|
|
const savedPayment = await this.paymentRepo.save(payment);
|
|
|
|
// Update order status
|
|
await this.orderService.updateStatus(order.id, OrderStatus.PENDING_PAYMENT);
|
|
|
|
return {
|
|
paymentId: savedPayment.id,
|
|
orderId: savedPayment.orderId,
|
|
qrCodeUrl: savedPayment.qrCodeUrl || undefined,
|
|
paymentUrl: savedPayment.paymentUrl || undefined,
|
|
expiresAt: savedPayment.expiresAt,
|
|
method: savedPayment.method,
|
|
amount: savedPayment.amount,
|
|
};
|
|
}
|
|
|
|
async findById(paymentId: string): Promise<PaymentEntity> {
|
|
const payment = await this.paymentRepo.findById(paymentId);
|
|
|
|
if (!payment) {
|
|
throw new NotFoundException('Payment not found');
|
|
}
|
|
|
|
return payment;
|
|
}
|
|
|
|
async handleCallback(
|
|
method: PaymentMethod,
|
|
payload: Record<string, unknown>,
|
|
): Promise<void> {
|
|
let orderId: string;
|
|
let transactionId: string;
|
|
let success: boolean;
|
|
|
|
switch (method) {
|
|
case PaymentMethod.ALIPAY:
|
|
const alipayResult = await this.alipayAdapter.handleCallback(payload);
|
|
orderId = alipayResult.orderId;
|
|
transactionId = alipayResult.transactionId;
|
|
success = alipayResult.success;
|
|
break;
|
|
|
|
case PaymentMethod.WECHAT:
|
|
const wechatResult = await this.wechatPayAdapter.handleCallback(payload);
|
|
orderId = wechatResult.orderId;
|
|
transactionId = wechatResult.transactionId;
|
|
success = wechatResult.success;
|
|
break;
|
|
|
|
case PaymentMethod.CREDIT_CARD:
|
|
const stripeResult = await this.stripeAdapter.handleCallback(payload);
|
|
orderId = stripeResult.orderId;
|
|
transactionId = stripeResult.transactionId;
|
|
success = stripeResult.success;
|
|
break;
|
|
|
|
default:
|
|
throw new BadRequestException('Unsupported payment method');
|
|
}
|
|
|
|
// Find payment by order ID
|
|
const payment = await this.paymentRepo.findPendingByOrderId(orderId);
|
|
|
|
if (!payment) {
|
|
throw new NotFoundException('Payment not found');
|
|
}
|
|
|
|
// Update payment
|
|
if (success) {
|
|
payment.markAsCompleted(transactionId, payload);
|
|
await this.paymentRepo.update(payment);
|
|
|
|
// Update order
|
|
await this.orderService.markAsPaid(orderId, payment.id, method);
|
|
} else {
|
|
payment.markAsFailed('Payment failed', payload);
|
|
await this.paymentRepo.update(payment);
|
|
}
|
|
}
|
|
|
|
async checkStatus(paymentId: string): Promise<{ status: PaymentStatus; paidAt?: Date }> {
|
|
const payment = await this.findById(paymentId);
|
|
return {
|
|
status: payment.status,
|
|
paidAt: payment.paidAt || undefined,
|
|
};
|
|
}
|
|
}
|