iconsulting/packages/services/payment-service/src/payment/payment.service.ts

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,
};
}
}