import { Inject, Injectable, Logger } from '@nestjs/common'; import { loadAppConfig } from '@dukang/shared-types'; import { PrismaService } from '../../common/prisma/prisma.module'; import { WECHAT_PROVIDER } from '../integrations.constants'; import type { IWechatProvider } from '../wechat/wechat.interface'; import type { IPayProvider, PayMethod, PayOrderResult } from './pay.interface'; @Injectable() export class PayWechatProvider implements IPayProvider { private readonly logger = new Logger(PayWechatProvider.name); constructor( private readonly prisma: PrismaService, @Inject(WECHAT_PROVIDER) private readonly wechat: IWechatProvider, ) {} async payOrder( orderId: bigint, openId?: string, platform: 'h5' | 'mini' = 'h5', payMethod: PayMethod = 'JSAPI', ): Promise { if (loadAppConfig().mockPay) { return { mode: 'mock', externalNo: `MOCK-${Date.now()}` }; } if (!this.wechat.isPayEnabled()) { throw new Error('微信支付未配置:请关闭 MOCK_PAY 并配置 WX_MCH_ID 等商户参数'); } const order = await this.prisma.order.findUnique({ where: { id: orderId } }); if (!order) throw new Error('订单不存在'); const amountFen = Math.round(Number(order.payAmount) * 100); const notifyUrl = process.env.WX_PAY_NOTIFY_URL ?? ''; if (payMethod === 'NATIVE') { this.logger.log(`create NATIVE prepay order=${order.orderNo} mchid=${this.wechat.getMchId()}`); const { codeUrl } = await this.wechat.createNativePrepay({ orderNo: order.orderNo, description: `杜康好客订单 ${order.orderNo}`, amountFen, notifyUrl, }); return { mode: 'native', codeUrl, externalNo: `NATIVE-${order.orderNo}`, }; } if (!openId) { throw new Error('微信支付需要用户 openId,请先完成微信授权登录'); } this.logger.log(`create JSAPI prepay order=${order.orderNo} mchid=${this.wechat.getMchId()} platform=${platform}`); const prepay = await this.wechat.createJsapiPrepay({ orderNo: order.orderNo, description: `杜康好客订单 ${order.orderNo}`, amountFen, openId, notifyUrl, platform, }); return { mode: 'jsapi', prepay }; } async refundOrder( orderId: bigint, outRefundNo: string, reason?: string, ) { if (loadAppConfig().mockPay) { return { mode: 'mock' as const, outRefundNo }; } if (!this.wechat.isPayEnabled()) { throw new Error('微信支付未配置:请关闭 MOCK_PAY 并配置 WX_MCH_ID 等商户参数'); } const order = await this.prisma.order.findUnique({ where: { id: orderId } }); if (!order) throw new Error('订单不存在'); const amountFen = Math.round(Number(order.payAmount) * 100); const notifyUrl = process.env.WX_REFUND_NOTIFY_URL ?? ''; this.logger.log(`create refund order=${order.orderNo} outRefundNo=${outRefundNo}`); const result = await this.wechat.createDomesticRefund({ orderNo: order.orderNo, transactionId: order.payExternalNo ?? undefined, outRefundNo, amountFen, totalFen: amountFen, reason, notifyUrl, }); return { mode: 'wechat' as const, outRefundNo: result.outRefundNo, refundId: result.refundId, status: result.status === 'SUCCESS' ? 'SUCCESS' as const : 'PROCESSING' as const, }; } }