bw-mini-app-server/src/payment/adapters/base-payment.adapter.ts

396 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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