import { Injectable, Logger, BadRequestException, NotFoundException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { ConfigService } from '@nestjs/config'; import { CreatePaymentOrderData, PaymentOrderResult, PaymentOrderQueryResult, PaymentCallbackResult, RefundRequestData, RefundResult, RefundQueryResult, PaymentMethod, PaymentBusinessType, PaymentOrderStatus, } from '../interfaces/payment.interface'; import { PlatformType } from '../../entities/platform-user.entity'; import { UserEntity } from '../../entities/user.entity'; import { UserSubscriptionEntity, SubscriptionPlan, SubscriptionStatus, } from '../../entities/user-subscription.entity'; import { UserCreditEntity, CreditType, CreditSource, } from '../../entities/user-credit.entity'; import { PaymentOrderEntity, PaymentTransactionEntity, RefundRecordEntity, } from '../entities'; import { PaymentAdapterFactory } from './payment-adapter.factory'; /** * 业务配置接口 * 定义不同业务类型的配置信息 */ interface BusinessConfig { /** 业务名称 */ name: string; /** 单价(分) */ unitPrice: number; /** 描述 */ description: string; /** 是否启用 */ enabled: boolean; } /** * 订阅套餐配置 */ interface SubscriptionConfig extends BusinessConfig { /** 套餐类型 */ plan: SubscriptionPlan; /** 有效期(天) */ validDays: number; /** 每月积分 */ monthlyCredits: number; /** 特权功能 */ features: string[]; } /** * 积分包配置 */ interface CreditPackageConfig extends BusinessConfig { /** 积分数量 */ credits: number; /** 赠送积分 */ bonusCredits: number; } /** * 统一支付服务 * 提供跨平台支付的统一业务接口 * 集成订阅服务、积分购买等业务逻辑 * 管理支付订单的完整生命周期 */ @Injectable() export class UnifiedPaymentService { private readonly logger = new Logger(UnifiedPaymentService.name); constructor( private readonly paymentAdapterFactory: PaymentAdapterFactory, private readonly configService: ConfigService, @InjectRepository(UserEntity) private readonly userRepository: Repository, @InjectRepository(UserSubscriptionEntity) private readonly subscriptionRepository: Repository, @InjectRepository(UserCreditEntity) private readonly creditRepository: Repository, @InjectRepository(PaymentOrderEntity) private readonly paymentOrderRepository: Repository, ) {} /** * 创建支付订单 * 根据业务类型和平台创建相应的支付订单 * @param platform 支付平台 * @param userId 用户ID * @param businessType 业务类型 * @param businessId 业务ID * @param platformUserId 平台用户ID * @param currency 货币类型(可选,默认CNY) * @param customAmount 自定义金额(可选,覆盖配置价格) * @returns 支付订单结果 */ async createPaymentOrder( platform: PlatformType, userId: string, businessType: PaymentBusinessType, businessId: string, platformUserId: string, currency: string = 'CNY', customAmount?: number, ): Promise { try { this.logger.log( `创建支付订单: 用户=${userId}, 业务=${businessType}, 业务ID=${businessId}`, ); // 1. 验证用户存在 const user = await this.userRepository.findOne({ where: { id: userId } }); if (!user) { throw new NotFoundException('用户不存在'); } // 2. 获取业务配置 const businessConfig = await this.getBusinessConfig( businessType, businessId, ); if (!businessConfig.enabled) { throw new BadRequestException('该商品暂不可购买'); } // 3. 业务前置检查 await this.validateBusinessOrder(userId, businessType, businessId); // 4. 确定支付方式 const paymentMethod = this.determinePaymentMethod(platform); // 5. 构建订单数据 const orderData: CreatePaymentOrderData = { orderNo: '', // 由适配器生成 amount: customAmount || businessConfig.unitPrice, currency: currency, description: businessConfig.description, userId, platformUserId, businessType, businessId, expireMinutes: 30, // 默认30分钟过期 metadata: { businessName: businessConfig.name, platform, currency, }, notifyUrl: this.buildNotifyUrl(platform), }; // 6. 获取支付适配器并创建订单 const adapter = this.paymentAdapterFactory.getAdapter( platform, paymentMethod, ); console.log(orderData) const result = await adapter.createPaymentOrder(orderData); this.logger.log( `支付订单创建成功: 订单ID=${result.orderId}, 第三方订单ID=${result.thirdPartyOrderId}`, ); return result; } catch (error) { this.logger.error(`创建支付订单失败: ${error.message}`, error.stack); throw error; } } /** * 查询支付订单状态 * @param orderId 订单ID * @returns 订单查询结果 */ async queryPaymentOrder(orderId: string): Promise { try { // 1. 获取本地订单信息 const paymentOrder = await this.paymentOrderRepository.findOne({ where: { id: orderId }, }); if (!paymentOrder) { throw new NotFoundException('支付订单不存在'); } // 2. 获取支付适配器 const adapter = this.paymentAdapterFactory.getAdapter( paymentOrder.platform, paymentOrder.paymentMethod, ); // 3. 查询订单状态 const result = await adapter.queryPaymentOrder( orderId, paymentOrder.thirdPartyOrderId, ); // 4. 如果订单状态变为已支付,触发业务处理 if ( result.status === PaymentOrderStatus.PAID && paymentOrder.status !== PaymentOrderStatus.PAID ) { await this.handlePaymentSuccess(paymentOrder, result); } return result; } catch (error) { this.logger.error(`查询支付订单失败: ${error.message}`, error.stack); throw error; } } /** * 处理支付回调 * @param platform 支付平台 * @param callbackData 回调数据 * @returns 回调处理结果 */ async handlePaymentCallback( platform: PlatformType, callbackData: any, ): Promise { try { this.logger.log(`处理${platform}支付回调:`, JSON.stringify(callbackData)); // 1. 确定支付方式 const paymentMethod = this.determinePaymentMethod(platform); // 2. 获取支付适配器 const adapter = this.paymentAdapterFactory.getAdapter( platform, paymentMethod, ); // 3. 处理回调 const result = await adapter.handlePaymentCallback(callbackData); // 4. 如果支付成功,触发业务处理 if ( result.success && result.status === PaymentOrderStatus.PAID && result.orderId ) { const paymentOrder = await this.paymentOrderRepository.findOne({ where: { id: result.orderId }, }); if (paymentOrder) { await this.handlePaymentSuccess(paymentOrder, result); } } return result; } catch (error) { this.logger.error(`处理支付回调失败: ${error.message}`, error.stack); return { success: false, orderId: '', status: PaymentOrderStatus.FAILED, errorMessage: error.message, }; } } /** * 申请退款 * @param orderId 订单ID * @param refundAmount 退款金额 * @param reason 退款原因 * @returns 退款结果 */ async refundPayment( orderId: string, refundAmount: number, reason: string, ): Promise { try { // 1. 获取原订单信息 const paymentOrder = await this.paymentOrderRepository.findOne({ where: { id: orderId }, }); if (!paymentOrder) { throw new NotFoundException('支付订单不存在'); } // 2. 验证退款条件 if (!paymentOrder.canRefund()) { throw new BadRequestException('订单不支持退款'); } const refundableAmount = paymentOrder.getRefundableAmount(); if (refundAmount > refundableAmount) { throw new BadRequestException( `退款金额不能超过可退金额 ${refundableAmount / 100} 元`, ); } // 3. 获取支付适配器 const adapter = this.paymentAdapterFactory.getAdapter( paymentOrder.platform, paymentOrder.paymentMethod, ); // 4. 构建退款数据 const refundData: RefundRequestData = { orderId, refundAmount, reason, refundNo: this.generateRefundNo(), thirdPartyOrderId: paymentOrder.thirdPartyOrderId || '', thirdPartyTransactionId: '', // 需要从交易记录中获取 }; // 5. 申请退款 const result = await adapter.refundPayment(refundData); this.logger.log( `退款申请成功: 退款ID=${result.refundId}, 金额=${refundAmount / 100}元`, ); return result; } catch (error) { this.logger.error(`申请退款失败: ${error.message}`, error.stack); throw error; } } /** * 查询退款状态 * @param refundId 退款ID * @returns 退款查询结果 */ async queryRefundStatus(refundId: string): Promise { try { // 1. 获取退款记录 const refundRecord = await this.paymentOrderRepository .createQueryBuilder('order') .leftJoinAndSelect('order.refunds', 'refund') .where('refund.id = :refundId', { refundId }) .getOne(); if (!refundRecord) { throw new NotFoundException('退款记录不存在'); } const refund = refundRecord.refunds[0]; const paymentOrder = refundRecord; // 2. 获取支付适配器 const adapter = this.paymentAdapterFactory.getAdapter( paymentOrder.platform, paymentOrder.paymentMethod, ); // 3. 查询退款状态 return await adapter.queryRefundStatus(refundId); } catch (error) { this.logger.error(`查询退款状态失败: ${error.message}`, error.stack); throw error; } } /** * 获取用户支付订单列表 * @param userId 用户ID * @param page 页码 * @param limit 每页数量 * @returns 订单列表 */ async getUserPaymentOrders( userId: string, page: number = 1, limit: number = 20, ): Promise<{ orders: PaymentOrderEntity[]; total: number; page: number; limit: number; }> { const [orders, total] = await this.paymentOrderRepository.findAndCount({ where: { userId }, order: { createdAt: 'DESC' }, skip: (page - 1) * limit, take: limit, relations: ['transactions', 'refunds'], }); return { orders, total, page, limit }; } /** * 处理支付成功的业务逻辑 * @param paymentOrder 支付订单 * @param paymentResult 支付结果 */ private async handlePaymentSuccess( paymentOrder: PaymentOrderEntity, paymentResult: PaymentOrderQueryResult | PaymentCallbackResult, ): Promise { try { this.logger.log( `处理支付成功业务: 订单ID=${paymentOrder.id}, 业务类型=${paymentOrder.businessType}`, ); switch (paymentOrder.businessType) { case PaymentBusinessType.SUBSCRIPTION: await this.handleSubscriptionPayment(paymentOrder); break; case PaymentBusinessType.CREDIT_PURCHASE: await this.handleCreditPurchasePayment(paymentOrder); break; case PaymentBusinessType.TEMPLATE_UNLOCK: await this.handleTemplateUnlockPayment(paymentOrder); break; default: this.logger.warn(`未知的业务类型: ${paymentOrder.businessType}`); } this.logger.log(`支付成功业务处理完成: 订单ID=${paymentOrder.id}`); } catch (error) { this.logger.error(`处理支付成功业务失败: ${error.message}`, error.stack); // 业务处理失败不应该影响支付状态,但需要记录日志并可能需要人工处理 } } /** * 处理订阅支付 * @param paymentOrder 支付订单 */ private async handleSubscriptionPayment( paymentOrder: PaymentOrderEntity, ): Promise { const config = await this.getSubscriptionConfig(paymentOrder.businessId); // 创建或更新订阅 const subscription = new UserSubscriptionEntity(); subscription.userId = paymentOrder.userId; subscription.platform = paymentOrder.platform; subscription.plan = config.plan; subscription.status = SubscriptionStatus.ACTIVE; subscription.startDate = new Date(); subscription.endDate = new Date( Date.now() + config.validDays * 24 * 60 * 60 * 1000, ); subscription.price = paymentOrder.amount / 100; // 转换为元 subscription.monthlyCredits = config.monthlyCredits; subscription.features = config.features; subscription.paymentId = paymentOrder.id; await this.subscriptionRepository.save(subscription); // 发放订阅积分 if (config.monthlyCredits > 0) { await this.addUserCredits( paymentOrder.userId, paymentOrder.platform, config.monthlyCredits, CreditType.REWARD, CreditSource.SUBSCRIPTION, `订阅${config.name}获得积分`, paymentOrder.id, ); } } /** * 处理积分购买支付 * @param paymentOrder 支付订单 */ private async handleCreditPurchasePayment( paymentOrder: PaymentOrderEntity, ): Promise { const config = await this.getCreditPackageConfig(paymentOrder.businessId); // 发放积分 const totalCredits = config.credits + config.bonusCredits; await this.addUserCredits( paymentOrder.userId, paymentOrder.platform, totalCredits, CreditType.PURCHASE, CreditSource.MANUAL, `购买${config.name}获得积分`, paymentOrder.id, ); } /** * 处理模板解锁支付 * @param paymentOrder 支付订单 */ private async handleTemplateUnlockPayment( paymentOrder: PaymentOrderEntity, ): Promise { // 模板解锁逻辑,可能需要更新用户的模板权限 // 这里需要根据具体业务逻辑实现 this.logger.log( `模板解锁支付处理: 用户=${paymentOrder.userId}, 模板=${paymentOrder.businessId}`, ); } /** * 添加用户积分 * @param userId 用户ID * @param platform 平台 * @param amount 积分数量 * @param type 积分类型 * @param source 积分来源 * @param description 描述 * @param referenceId 关联ID */ private async addUserCredits( userId: string, platform: PlatformType, amount: number, type: CreditType, source: CreditSource, description: string, referenceId: string, ): Promise { // 计算新余额 const lastCredit = await this.creditRepository.findOne({ where: { userId }, order: { createdAt: 'DESC' }, }); const newBalance = (lastCredit?.balance || 0) + amount; // 创建积分记录 const credit = new UserCreditEntity(); credit.userId = userId; credit.platform = platform; credit.type = type; credit.source = source; credit.amount = amount; credit.balance = newBalance; credit.description = description; credit.referenceId = referenceId; await this.creditRepository.save(credit); } /** * 业务前置检查 * @param userId 用户ID * @param businessType 业务类型 * @param businessId 业务ID */ private async validateBusinessOrder( userId: string, businessType: PaymentBusinessType, businessId: string, ): Promise { switch (businessType) { case PaymentBusinessType.SUBSCRIPTION: await this.validateSubscriptionOrder(userId, businessId); break; case PaymentBusinessType.CREDIT_PURCHASE: // 积分购买一般无特殊限制 break; case PaymentBusinessType.TEMPLATE_UNLOCK: await this.validateTemplateUnlockOrder(userId, businessId); break; } } /** * 验证订阅订单 */ private async validateSubscriptionOrder( userId: string, businessId: string, ): Promise { // 检查用户是否已有有效订阅 const existingSubscription = await this.subscriptionRepository.findOne({ where: { userId, status: SubscriptionStatus.ACTIVE, }, }); if (existingSubscription) { throw new BadRequestException('您已有有效订阅,请等待到期后再次购买'); } } /** * 验证模板解锁订单 */ private async validateTemplateUnlockOrder( userId: string, templateId: string, ): Promise { // 检查用户是否已解锁该模板 // 这里需要根据具体业务逻辑实现 } /** * 确定支付方式 * @param platform 平台类型 * @returns 支付方式 */ private determinePaymentMethod(platform: PlatformType): PaymentMethod { switch (platform) { case PlatformType.WECHAT: return PaymentMethod.WECHAT_MINIPROGRAM; case PlatformType.BYTEDANCE: return PaymentMethod.BYTEDANCE_MINIPROGRAM; case PlatformType.STRIPE: return PaymentMethod.STRIPE_CARD; default: throw new BadRequestException('不支持的支付平台'); } } /** * 获取业务配置 * @param businessType 业务类型 * @param businessId 业务ID * @returns 业务配置 */ private async getBusinessConfig( businessType: PaymentBusinessType, businessId: string, ): Promise { switch (businessType) { case PaymentBusinessType.SUBSCRIPTION: return await this.getSubscriptionConfig(businessId); case PaymentBusinessType.CREDIT_PURCHASE: return await this.getCreditPackageConfig(businessId); case PaymentBusinessType.TEMPLATE_UNLOCK: return await this.getTemplateConfig(businessId); default: throw new BadRequestException('不支持的业务类型'); } } /** * 获取订阅配置 */ private async getSubscriptionConfig( businessId: string, ): Promise { const configs: Record = { basic_monthly: { name: '基础版月度订阅', plan: SubscriptionPlan.BASIC, unitPrice: 1980, // 19.8元 description: '基础版会员月度订阅', validDays: 30, monthlyCredits: 1000, features: ['basic_templates', 'standard_generation'], enabled: true, }, premium_monthly: { name: '高级版月度订阅', plan: SubscriptionPlan.PREMIUM, unitPrice: 2980, // 29.8元 description: '高级版会员月度订阅', validDays: 30, monthlyCredits: 2000, features: ['premium_templates', 'fast_generation', 'batch_processing'], enabled: true, }, pro_monthly: { name: '专业版月度订阅', plan: SubscriptionPlan.PRO, unitPrice: 4980, // 49.8元 description: '专业版会员月度订阅', validDays: 30, monthlyCredits: 5000, features: [ 'all_templates', 'fastest_generation', 'unlimited_processing', 'priority_support', ], enabled: true, }, }; const config = configs[businessId]; if (!config) { throw new BadRequestException('无效的订阅套餐ID'); } return config; } /** * 获取积分包配置 */ private async getCreditPackageConfig( businessId: string, ): Promise { const configs: Record = { credits_100: { name: '100积分包', unitPrice: 980, // 9.8元 description: '购买100积分', credits: 100, bonusCredits: 0, enabled: true, }, credits_500: { name: '500积分包', unitPrice: 4800, // 48元 description: '购买500积分', credits: 500, bonusCredits: 50, // 赠送50积分 enabled: true, }, credits_1000: { name: '1000积分包', unitPrice: 8800, // 88元 description: '购买1000积分', credits: 1000, bonusCredits: 200, // 赠送200积分 enabled: true, }, }; const config = configs[businessId]; if (!config) { throw new BadRequestException('无效的积分包ID'); } return config; } /** * 获取模板配置 */ private async getTemplateConfig(businessId: string): Promise { // 这里可以从数据库或配置文件中获取模板价格 return { name: `模板解锁-${businessId}`, unitPrice: 1980, // 19.8元 description: '解锁高级模板', enabled: true, }; } /** * 生成退款单号 */ private generateRefundNo(): string { const timestamp = Date.now(); const random = Math.random().toString(36).substr(2, 9); return `RF${timestamp}${random.toUpperCase()}`; } /** * 构建回调通知URL * @param platform 支付平台 * @returns 回调URL */ private buildNotifyUrl(platform: PlatformType): string { const baseUrl = this.configService.get('SERVER_URL') || 'http://localhost:3000'; switch (platform) { case PlatformType.WECHAT: return `${baseUrl}/api/v1/payment/wechat/callback`; case PlatformType.BYTEDANCE: return `${baseUrl}/api/v1/payment/bytedance/callback`; case PlatformType.STRIPE: return `${baseUrl}/api/v1/payment/stripe/webhook`; default: return `${baseUrl}/api/v1/payment/${platform.toLowerCase()}/callback`; } } }