@@ -1,30 +1,19 @@
|
||||
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',
|
||||
_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()}` };
|
||||
}
|
||||
|
||||
|
||||
@@ -21,15 +21,6 @@ export class PayWechatProvider implements IPayProvider {
|
||||
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()) {
|
||||
|
||||
@@ -25,15 +25,15 @@ export class AdminOrdersService {
|
||||
private readonly adminRedeemService: AdminRedeemService,
|
||||
) {}
|
||||
|
||||
/** v3.5.1 #1:发布会大屏,返回全部订单(按时间倒序,上限 2000) */
|
||||
/** v3.5.1 #1:发布会大屏,仅已付款订单;按付款时间倒序 */
|
||||
async listBigScreen(limit?: string) {
|
||||
const take = Math.min(Math.max(Number(limit) || 2000, 1), 2000);
|
||||
const orders = await this.prisma.order.findMany({
|
||||
where: {
|
||||
isTest: false,
|
||||
payAmount: { gte: 100 },
|
||||
payStatus: 'PAID',
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
orderBy: [{ paidAt: 'desc' }, { createdAt: 'desc' }],
|
||||
take,
|
||||
include: { user: { select: { phone: true } } },
|
||||
});
|
||||
@@ -41,12 +41,14 @@ export class AdminOrdersService {
|
||||
items: orders.map((o) => {
|
||||
const spec = o.productSpec ? ` ${o.productSpec}` : '';
|
||||
const phone = o.user?.phone || o.receiverPhone;
|
||||
const paidAt = o.paidAt?.toISOString() ?? o.createdAt.toISOString();
|
||||
return {
|
||||
id: o.id.toString(),
|
||||
orderNo: o.orderNo,
|
||||
payAmount: Number(o.payAmount),
|
||||
items: `${o.productName}${spec} × ${o.quantity}瓶`,
|
||||
createdAt: o.createdAt.toISOString(),
|
||||
createdAt: paidAt,
|
||||
paidAt,
|
||||
userPhoneMasked: phone ? maskContactPhone(phone) : null,
|
||||
};
|
||||
}),
|
||||
|
||||
@@ -24,7 +24,7 @@ import { AuthService } from '../iam/auth.service';
|
||||
import { PromoCodeService } from '../promo/promo-code.service';
|
||||
import { TicketService } from '../common/ticket.service';
|
||||
import { PAY_PROVIDER, DELIVERY_PROVIDER } from '../../integrations/integrations.constants';
|
||||
import { IPayProvider } from '../../integrations/pay/pay.interface';
|
||||
import { IPayProvider, type PayOrderResult } from '../../integrations/pay/pay.interface';
|
||||
import { IDeliveryProvider } from '../../integrations/delivery/delivery.interface';
|
||||
import { IpGeoService } from '../../common/geo/ip-geo.service';
|
||||
import { buildOrderClientLocationSnapshot } from '../../common/geo/client-location.util';
|
||||
@@ -2173,6 +2173,9 @@ export class TradeService {
|
||||
throw new BadRequestException('订单已超时未支付');
|
||||
}
|
||||
|
||||
const mockPaid = await this.tryMockPayProxyOrder(order, 'PARTNER_PROXY_MOCK_PAY');
|
||||
if (mockPaid) return mockPaid;
|
||||
|
||||
const lockedMethod = this.parseProxyPayMethod(order.channelSource);
|
||||
if (lockedMethod && lockedMethod !== payMethod) {
|
||||
throw new BadRequestException(
|
||||
@@ -2217,33 +2220,7 @@ export class TradeService {
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (payResult.mode === 'native') {
|
||||
return {
|
||||
mode: 'native' as const,
|
||||
orderId: order.id.toString(),
|
||||
orderNo: order.orderNo,
|
||||
codeUrl: payResult.codeUrl,
|
||||
payExpireAt: order.payExpireAt?.toISOString() ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
if (payResult.mode === 'jsapi') {
|
||||
return {
|
||||
mode: 'jsapi' as const,
|
||||
orderId: order.id.toString(),
|
||||
orderNo: order.orderNo,
|
||||
prepay: payResult.prepay,
|
||||
payExpireAt: order.payExpireAt?.toISOString() ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
await this.markProxyOrderPaid(order.id, payResult.externalNo, 'PARTNER_PROXY_MOCK_PAY');
|
||||
return {
|
||||
mode: 'mock' as const,
|
||||
orderId: order.id.toString(),
|
||||
orderNo: order.orderNo,
|
||||
payExpireAt: order.payExpireAt?.toISOString() ?? null,
|
||||
};
|
||||
return this.finishProxyPayResult(order, payResult, 'PARTNER_PROXY_MOCK_PAY');
|
||||
}
|
||||
|
||||
/** 总部代下单支付:仅 Native 收款码 */
|
||||
@@ -2262,6 +2239,9 @@ export class TradeService {
|
||||
throw new BadRequestException('订单已超时未支付');
|
||||
}
|
||||
|
||||
const mockPaid = await this.tryMockPayProxyOrder(order, 'HQ_PROXY_MOCK_PAY');
|
||||
if (mockPaid) return mockPaid;
|
||||
|
||||
this.payRedeemAnomaly.onPayAttempt(Number(order.payAmount), {
|
||||
orderNo: order.orderNo,
|
||||
userId: order.userId,
|
||||
@@ -2277,20 +2257,15 @@ export class TradeService {
|
||||
});
|
||||
throw e;
|
||||
}
|
||||
if (payResult.mode !== 'native') {
|
||||
const result = await this.finishProxyPayResult(order, payResult, 'HQ_PROXY_MOCK_PAY');
|
||||
if (result.mode !== 'native' && result.mode !== 'mock') {
|
||||
this.payRedeemAnomaly.onPayFail('无法生成收款码', {
|
||||
orderNo: order.orderNo,
|
||||
userId: order.userId,
|
||||
});
|
||||
throw new BadRequestException('无法生成收款码');
|
||||
}
|
||||
return {
|
||||
mode: 'native' as const,
|
||||
orderId: order.id.toString(),
|
||||
orderNo: order.orderNo,
|
||||
codeUrl: payResult.codeUrl,
|
||||
payExpireAt: order.payExpireAt?.toISOString() ?? null,
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Mock 环境确认代下单支付 */
|
||||
@@ -2375,6 +2350,64 @@ export class TradeService {
|
||||
return this.fulfillmentService.getOrderTrack(orderId);
|
||||
}
|
||||
|
||||
private async tryMockPayProxyOrder(
|
||||
order: { id: bigint; orderNo: string; payExpireAt: Date | null },
|
||||
operator: string,
|
||||
) {
|
||||
if (!loadAppConfig().mockPay) return null;
|
||||
await this.markProxyOrderPaid(order.id, `MOCK-${Date.now()}`, operator);
|
||||
return {
|
||||
mode: 'mock' as const,
|
||||
orderId: order.id.toString(),
|
||||
orderNo: order.orderNo,
|
||||
payExpireAt: order.payExpireAt?.toISOString() ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
/** 代下单支付结果:Mock 环境直接落单,真实环境返回收款码/JSAPI */
|
||||
private async finishProxyPayResult(
|
||||
order: { id: bigint; orderNo: string; payExpireAt: Date | null },
|
||||
payResult: PayOrderResult,
|
||||
operator: string,
|
||||
) {
|
||||
const mockPay = loadAppConfig().mockPay;
|
||||
const isMockNative =
|
||||
payResult.mode === 'native' &&
|
||||
(mockPay || payResult.codeUrl.startsWith('mock://'));
|
||||
|
||||
if (payResult.mode === 'mock' || isMockNative) {
|
||||
await this.markProxyOrderPaid(order.id, payResult.externalNo, operator);
|
||||
return {
|
||||
mode: 'mock' as const,
|
||||
orderId: order.id.toString(),
|
||||
orderNo: order.orderNo,
|
||||
payExpireAt: order.payExpireAt?.toISOString() ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
if (payResult.mode === 'native') {
|
||||
return {
|
||||
mode: 'native' as const,
|
||||
orderId: order.id.toString(),
|
||||
orderNo: order.orderNo,
|
||||
codeUrl: payResult.codeUrl,
|
||||
payExpireAt: order.payExpireAt?.toISOString() ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
if (payResult.mode === 'jsapi') {
|
||||
return {
|
||||
mode: 'jsapi' as const,
|
||||
orderId: order.id.toString(),
|
||||
orderNo: order.orderNo,
|
||||
prepay: payResult.prepay,
|
||||
payExpireAt: order.payExpireAt?.toISOString() ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
throw new BadRequestException('支付发起失败');
|
||||
}
|
||||
|
||||
private async markProxyOrderPaid(orderId: bigint, externalNo: string, operator: string) {
|
||||
const order = await this.prisma.order.findUnique({ where: { id: orderId } });
|
||||
if (!order) throw new NotFoundException('订单不存在');
|
||||
|
||||
Reference in New Issue
Block a user