396 lines
12 KiB
TypeScript
396 lines
12 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
||
import { HttpService } from '@nestjs/axios';
|
||
import { ConfigService } from '@nestjs/config';
|
||
import { Repository } from 'typeorm';
|
||
import { Logger } from '@nestjs/common';
|
||
import {
|
||
IPaymentAdapter,
|
||
CreatePaymentOrderData,
|
||
PaymentOrderResult,
|
||
PaymentOrderQueryResult,
|
||
PaymentCallbackResult,
|
||
RefundRequestData,
|
||
RefundResult,
|
||
RefundQueryResult,
|
||
BillData,
|
||
PaymentMethod,
|
||
PaymentOrderStatus,
|
||
PaymentBusinessType,
|
||
RefundStatus,
|
||
} from '../interfaces/payment.interface';
|
||
import { PlatformType } from '../../entities/platform-user.entity';
|
||
import {
|
||
PaymentOrderEntity,
|
||
PaymentTransactionEntity,
|
||
RefundRecordEntity,
|
||
} from '../entities';
|
||
import {
|
||
TransactionType,
|
||
TransactionStatus,
|
||
} from '../entities/payment-transaction.entity';
|
||
|
||
/**
|
||
* 基础支付适配器抽象类
|
||
* 提供支付适配器的通用功能实现
|
||
* 包含数据库操作、订单管理、通用业务逻辑等
|
||
* 子类需要实现具体的支付平台接口调用
|
||
*/
|
||
@Injectable()
|
||
export abstract class BasePaymentAdapter implements IPaymentAdapter {
|
||
protected readonly logger = new Logger(this.constructor.name);
|
||
protected isConfigured: boolean = false;
|
||
|
||
abstract platform: PlatformType;
|
||
abstract paymentMethod: PaymentMethod;
|
||
|
||
constructor(
|
||
protected readonly httpService: HttpService,
|
||
protected readonly configService: ConfigService,
|
||
protected readonly paymentOrderRepository: Repository<PaymentOrderEntity>,
|
||
protected readonly paymentTransactionRepository: Repository<PaymentTransactionEntity>,
|
||
protected readonly refundRecordRepository: Repository<RefundRecordEntity>,
|
||
) {}
|
||
|
||
/**
|
||
* 创建支付订单号
|
||
* 生成唯一的系统订单号
|
||
*/
|
||
protected generateOrderNo(): string {
|
||
const timestamp = Date.now();
|
||
const random = Math.random().toString(36).substr(2, 9);
|
||
let platformPrefix: string;
|
||
switch (this.platform) {
|
||
case PlatformType.WECHAT:
|
||
platformPrefix = 'WX';
|
||
break;
|
||
case PlatformType.BYTEDANCE:
|
||
platformPrefix = 'DY';
|
||
break;
|
||
case PlatformType.STRIPE:
|
||
platformPrefix = 'ST';
|
||
break;
|
||
default:
|
||
platformPrefix = 'UN'; // Unknown
|
||
}
|
||
return `${platformPrefix}${timestamp}${random.toUpperCase()}`;
|
||
}
|
||
|
||
/**
|
||
* 创建退款单号
|
||
* 生成唯一的退款单号
|
||
*/
|
||
protected generateRefundNo(): string {
|
||
const timestamp = Date.now();
|
||
const random = Math.random().toString(36).substr(2, 9);
|
||
let platformPrefix: string;
|
||
switch (this.platform) {
|
||
case PlatformType.WECHAT:
|
||
platformPrefix = 'WXR';
|
||
break;
|
||
case PlatformType.BYTEDANCE:
|
||
platformPrefix = 'DYR';
|
||
break;
|
||
case PlatformType.STRIPE:
|
||
platformPrefix = 'STR';
|
||
break;
|
||
default:
|
||
platformPrefix = 'UNR'; // Unknown Refund
|
||
}
|
||
return `${platformPrefix}${timestamp}${random.toUpperCase()}`;
|
||
}
|
||
|
||
/**
|
||
* 保存支付订单到数据库
|
||
* @param orderData 订单数据
|
||
* @param thirdPartyOrderId 第三方订单ID
|
||
* @param paymentParams 支付参数
|
||
* @returns 支付订单实体
|
||
*/
|
||
protected async savePaymentOrder(
|
||
orderData: CreatePaymentOrderData,
|
||
thirdPartyOrderId?: string,
|
||
paymentParams?: any,
|
||
): Promise<PaymentOrderEntity> {
|
||
const paymentOrder = new PaymentOrderEntity();
|
||
|
||
paymentOrder.userId = orderData.userId;
|
||
paymentOrder.platform = this.platform;
|
||
paymentOrder.orderNo = orderData.orderNo;
|
||
paymentOrder.thirdPartyOrderId = thirdPartyOrderId;
|
||
paymentOrder.paymentMethod = this.paymentMethod;
|
||
paymentOrder.status = PaymentOrderStatus.PENDING;
|
||
paymentOrder.businessType = orderData.businessType;
|
||
paymentOrder.businessId = orderData.businessId;
|
||
paymentOrder.description = orderData.description;
|
||
paymentOrder.amount = orderData.amount;
|
||
paymentOrder.currency = orderData.currency;
|
||
paymentOrder.platformUserId = orderData.platformUserId;
|
||
paymentOrder.paymentParams = paymentParams;
|
||
paymentOrder.notifyUrl = orderData.notifyUrl;
|
||
paymentOrder.metadata = orderData.metadata;
|
||
|
||
// 设置过期时间
|
||
if (orderData.expireMinutes) {
|
||
const expiredAt = new Date();
|
||
expiredAt.setMinutes(expiredAt.getMinutes() + orderData.expireMinutes);
|
||
paymentOrder.expiredAt = expiredAt;
|
||
}
|
||
|
||
return await this.paymentOrderRepository.save(paymentOrder);
|
||
}
|
||
|
||
/**
|
||
* 更新支付订单状态
|
||
* @param orderId 订单ID
|
||
* @param status 新状态
|
||
* @param paidAmount 支付金额
|
||
* @param paidAt 支付时间
|
||
* @param errorMessage 错误信息
|
||
*/
|
||
protected async updatePaymentOrderStatus(
|
||
orderId: string,
|
||
status: PaymentOrderStatus,
|
||
paidAmount?: number,
|
||
paidAt?: Date,
|
||
errorMessage?: string,
|
||
): Promise<void> {
|
||
const updateData: Partial<PaymentOrderEntity> = { status };
|
||
|
||
if (paidAmount !== undefined) {
|
||
updateData.paidAmount = paidAmount;
|
||
}
|
||
if (paidAt) {
|
||
updateData.paidAt = paidAt;
|
||
}
|
||
if (errorMessage) {
|
||
updateData.errorMessage = errorMessage;
|
||
}
|
||
|
||
await this.paymentOrderRepository.update(orderId, updateData);
|
||
}
|
||
|
||
/**
|
||
* 保存支付交易记录
|
||
* @param paymentOrderId 支付订单ID
|
||
* @param transactionType 交易类型
|
||
* @param amount 交易金额
|
||
* @param requestData 请求数据
|
||
* @param responseData 响应数据
|
||
* @param status 交易状态
|
||
* @returns 交易记录实体
|
||
*/
|
||
protected async savePaymentTransaction(
|
||
paymentOrderId: string,
|
||
transactionType: TransactionType,
|
||
amount: number,
|
||
requestData?: any,
|
||
responseData?: any,
|
||
status: TransactionStatus = TransactionStatus.PENDING,
|
||
): Promise<PaymentTransactionEntity> {
|
||
const transaction = new PaymentTransactionEntity();
|
||
|
||
transaction.paymentOrderId = paymentOrderId;
|
||
transaction.transactionType = transactionType;
|
||
transaction.status = status;
|
||
transaction.amount = amount;
|
||
transaction.currency = 'CNY';
|
||
transaction.requestData = requestData;
|
||
transaction.responseData = responseData;
|
||
|
||
return await this.paymentTransactionRepository.save(transaction);
|
||
}
|
||
|
||
/**
|
||
* 更新支付交易记录
|
||
* @param transactionId 交易记录ID
|
||
* @param status 交易状态
|
||
* @param thirdPartyTransactionId 第三方交易号
|
||
* @param responseData 响应数据
|
||
* @param errorCode 错误代码
|
||
* @param errorMessage 错误信息
|
||
*/
|
||
protected async updatePaymentTransaction(
|
||
transactionId: string,
|
||
status: TransactionStatus,
|
||
thirdPartyTransactionId?: string,
|
||
responseData?: any,
|
||
errorCode?: string,
|
||
errorMessage?: string,
|
||
): Promise<void> {
|
||
const updateData: Partial<PaymentTransactionEntity> = {
|
||
status,
|
||
lastProcessedAt: new Date(),
|
||
};
|
||
|
||
if (thirdPartyTransactionId) {
|
||
updateData.thirdPartyTransactionId = thirdPartyTransactionId;
|
||
}
|
||
if (responseData) {
|
||
updateData.responseData = responseData;
|
||
}
|
||
if (errorCode) {
|
||
updateData.errorCode = errorCode;
|
||
}
|
||
if (errorMessage) {
|
||
updateData.errorMessage = errorMessage;
|
||
}
|
||
|
||
await this.paymentTransactionRepository.update(transactionId, updateData);
|
||
}
|
||
|
||
/**
|
||
* 保存退款记录
|
||
* @param refundData 退款数据
|
||
* @param paymentOrder 原支付订单
|
||
* @returns 退款记录实体
|
||
*/
|
||
protected async saveRefundRecord(
|
||
refundData: RefundRequestData,
|
||
paymentOrder: PaymentOrderEntity,
|
||
): Promise<RefundRecordEntity> {
|
||
const refundRecord = new RefundRecordEntity();
|
||
|
||
refundRecord.paymentOrderId = refundData.orderId;
|
||
refundRecord.refundNo = refundData.refundNo;
|
||
refundRecord.thirdPartyTransactionId = refundData.thirdPartyTransactionId;
|
||
refundRecord.status = RefundStatus.PENDING;
|
||
refundRecord.refundAmount = refundData.refundAmount;
|
||
refundRecord.originalAmount =
|
||
paymentOrder.paidAmount || paymentOrder.amount;
|
||
refundRecord.currency = paymentOrder.currency;
|
||
refundRecord.reason = refundData.reason;
|
||
refundRecord.requestedBy = paymentOrder.userId;
|
||
|
||
return await this.refundRecordRepository.save(refundRecord);
|
||
}
|
||
|
||
/**
|
||
* 更新退款记录状态
|
||
* @param refundId 退款记录ID
|
||
* @param status 退款状态
|
||
* @param thirdPartyRefundId 第三方退款ID
|
||
* @param refundedAt 退款时间
|
||
* @param errorCode 错误代码
|
||
* @param failureReason 失败原因
|
||
*/
|
||
protected async updateRefundRecord(
|
||
refundId: string,
|
||
status: RefundStatus,
|
||
thirdPartyRefundId?: string,
|
||
refundedAt?: Date,
|
||
errorCode?: string,
|
||
failureReason?: string,
|
||
): Promise<void> {
|
||
const updateData: Partial<RefundRecordEntity> = {
|
||
status,
|
||
lastProcessedAt: new Date(),
|
||
};
|
||
|
||
if (thirdPartyRefundId) {
|
||
updateData.thirdPartyRefundId = thirdPartyRefundId;
|
||
}
|
||
if (refundedAt) {
|
||
updateData.refundedAt = refundedAt;
|
||
}
|
||
if (errorCode) {
|
||
updateData.errorCode = errorCode;
|
||
}
|
||
if (failureReason) {
|
||
updateData.failureReason = failureReason;
|
||
}
|
||
|
||
await this.refundRecordRepository.update(refundId, updateData);
|
||
}
|
||
|
||
/**
|
||
* 统一错误处理
|
||
* @param error 错误对象
|
||
* @param context 错误上下文
|
||
* @returns 标准化错误
|
||
*/
|
||
protected handleError(error: any, context: string): Error {
|
||
this.logger.error(`${context} 错误:`, error);
|
||
|
||
if (error.response?.data) {
|
||
const errorData = error.response.data;
|
||
return new Error(
|
||
`${this.platform}支付错误: ${errorData.message || errorData.err_tips || errorData.errmsg || '未知错误'}`,
|
||
);
|
||
}
|
||
|
||
return new Error(`${this.platform}支付${context}失败: ${error.message}`);
|
||
}
|
||
|
||
/**
|
||
* 验证订单金额
|
||
* @param amount 金额(分)
|
||
* @returns 是否有效
|
||
*/
|
||
protected validateAmount(amount: number): boolean {
|
||
return amount > 0 && amount <= 100000000; // 最大1000万分(10万元)
|
||
}
|
||
|
||
/**
|
||
* 验证货币类型
|
||
* @param currency 货币类型
|
||
* @returns 是否有效
|
||
*/
|
||
protected validateCurrency(currency: string): boolean {
|
||
return ['CNY', 'USD', 'EUR', 'GBP', 'JPY', 'AUD', 'CAD', 'SGD', 'HKD'].includes(currency);
|
||
}
|
||
|
||
/**
|
||
* 检查订单是否可以操作
|
||
* @param paymentOrder 支付订单
|
||
* @param operation 操作类型
|
||
* @returns 检查结果
|
||
*/
|
||
protected validateOrderOperation(
|
||
paymentOrder: PaymentOrderEntity,
|
||
operation: 'pay' | 'refund' | 'query',
|
||
): { valid: boolean; message?: string } {
|
||
if (!paymentOrder) {
|
||
return { valid: false, message: '订单不存在' };
|
||
}
|
||
|
||
switch (operation) {
|
||
case 'pay':
|
||
if (!paymentOrder.canPay()) {
|
||
return { valid: false, message: '订单不能支付' };
|
||
}
|
||
break;
|
||
case 'refund':
|
||
if (!paymentOrder.canRefund()) {
|
||
return { valid: false, message: '订单不能退款' };
|
||
}
|
||
break;
|
||
case 'query':
|
||
// 查询操作不需要特殊验证
|
||
break;
|
||
default:
|
||
return { valid: false, message: '不支持的操作类型' };
|
||
}
|
||
|
||
return { valid: true };
|
||
}
|
||
|
||
// 抽象方法 - 子类必须实现
|
||
abstract createPaymentOrder(
|
||
orderData: CreatePaymentOrderData,
|
||
): Promise<PaymentOrderResult>;
|
||
abstract queryPaymentOrder(
|
||
orderId: string,
|
||
thirdPartyOrderId?: string,
|
||
): Promise<PaymentOrderQueryResult>;
|
||
abstract handlePaymentCallback(
|
||
callbackData: any,
|
||
): Promise<PaymentCallbackResult>;
|
||
abstract refundPayment(refundData: RefundRequestData): Promise<RefundResult>;
|
||
abstract queryRefundStatus(refundId: string): Promise<RefundQueryResult>;
|
||
abstract verifySignature(
|
||
callbackData: any,
|
||
signature: string,
|
||
): Promise<boolean>;
|
||
abstract downloadBill(date: string): Promise<BillData>;
|
||
}
|