feat: 技术支持工单/企微权限/开发版本管理/消息推送等迭代

This commit is contained in:
2026-08-04 21:32:13 +08:00
parent c8ea5a3119
commit 9d96c73246
1341 changed files with 0 additions and 195605 deletions
@@ -1,22 +0,0 @@
import type { WechatJsapiPrepayParams } from '@dukang/shared-types';
export type PayMethod = 'JSAPI' | 'NATIVE';
export type PayOrderResult =
| { mode: 'mock'; externalNo: string }
| { mode: 'jsapi'; prepay: WechatJsapiPrepayParams }
| { mode: 'native'; codeUrl: string; externalNo: string };
export type RefundOrderResult =
| { mode: 'mock'; outRefundNo: string }
| { mode: 'wechat'; outRefundNo: string; refundId?: string; status: 'PROCESSING' | 'SUCCESS' };
export interface IPayProvider {
payOrder(
orderId: bigint,
openId?: string,
platform?: 'h5' | 'mini',
payMethod?: PayMethod,
): Promise<PayOrderResult>;
refundOrder(orderId: bigint, outRefundNo: string, reason?: string): Promise<RefundOrderResult>;
}
@@ -1,41 +0,0 @@
import { Injectable } from '@nestjs/common';
import { loadAppConfig } from '@dukang/shared-types';
import { PrismaService } from '../../common/prisma/prisma.module';
import type { IPayProvider, PayMethod, PayOrderResult, RefundOrderResult } from './pay.interface';
@Injectable()
export class PayMockProvider implements IPayProvider {
constructor(private readonly prisma: PrismaService) {}
async payOrder(
orderId: bigint,
_openId?: string,
_platform?: 'h5' | 'mini',
payMethod: PayMethod = 'JSAPI',
): Promise<PayOrderResult> {
if (!loadAppConfig().mockPay) {
throw new Error('Real WeChat pay requires PayWechatProvider');
}
if (payMethod === 'NATIVE') {
const order = await this.prisma.order.findUnique({ where: { id: orderId } });
const orderNo = order?.orderNo ?? orderId.toString();
return {
mode: 'native',
codeUrl: `mock://wechat-pay/native?orderNo=${encodeURIComponent(orderNo)}`,
externalNo: `MOCK-NATIVE-${Date.now()}`,
};
}
return { mode: 'mock', externalNo: `MOCK-${Date.now()}` };
}
async refundOrder(
_orderId: bigint,
outRefundNo: string,
_reason?: string,
): Promise<RefundOrderResult> {
if (!loadAppConfig().mockPay) {
throw new Error('Real WeChat refund requires PayWechatProvider');
}
return { mode: 'mock', outRefundNo };
}
}
@@ -1,35 +0,0 @@
import { Injectable } from '@nestjs/common';
import { loadAppConfig } from '@dukang/shared-types';
import type { IPayProvider, PayMethod, PayOrderResult, RefundOrderResult } from './pay.interface';
import { PayMockProvider } from './pay.mock.provider';
import { PayWechatProvider } from './pay.wechat.provider';
/** 按当前 process.env 动态选择 Mock / 微信支付 */
@Injectable()
export class PayRouterProvider implements IPayProvider {
constructor(
private readonly mock: PayMockProvider,
private readonly wechat: PayWechatProvider,
) {}
private resolve(): IPayProvider {
return loadAppConfig().mockPay ? this.mock : this.wechat;
}
payOrder(
orderId: bigint,
openId?: string,
platform?: 'h5' | 'mini',
payMethod?: PayMethod,
): Promise<PayOrderResult> {
return this.resolve().payOrder(orderId, openId, platform, payMethod);
}
refundOrder(
orderId: bigint,
outRefundNo: string,
reason?: string,
): Promise<RefundOrderResult> {
return this.resolve().refundOrder(orderId, outRefundNo, reason);
}
}
@@ -1,111 +0,0 @@
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<PayOrderResult> {
if (loadAppConfig().mockPay) {
if (payMethod === 'NATIVE') {
const order = await this.prisma.order.findUnique({ where: { id: orderId } });
const orderNo = order?.orderNo ?? orderId.toString();
return {
mode: 'native',
codeUrl: `mock://wechat-pay/native?orderNo=${encodeURIComponent(orderNo)}`,
externalNo: `MOCK-NATIVE-${Date.now()}`,
};
}
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,
};
}
}