@@ -0,0 +1,73 @@
|
||||
import { config } from 'dotenv';
|
||||
import { resolve } from 'path';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
config({ path: resolve('.env'), override: true });
|
||||
|
||||
function mockPayEnabled() {
|
||||
return process.env.MOCK_PAY !== 'false';
|
||||
}
|
||||
|
||||
function wechatPayEnabled() {
|
||||
const e = process.env;
|
||||
const appId = e.WX_MINI_APP_ID?.trim() || e.WX_APP_ID?.trim();
|
||||
return (
|
||||
!mockPayEnabled() &&
|
||||
!!(
|
||||
appId &&
|
||||
e.WX_MCH_ID?.trim() &&
|
||||
e.WX_MCH_SERIAL_NO?.trim() &&
|
||||
e.WX_MCH_PRIVATE_KEY?.trim() &&
|
||||
e.WX_API_V3_KEY?.trim()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
console.log('=== .env only ===');
|
||||
console.log('MOCK_PAY:', process.env.MOCK_PAY);
|
||||
console.log('mockPayEnabled:', mockPayEnabled());
|
||||
|
||||
const rows = await prisma.systemConfig.findMany({
|
||||
where: { configKey: { in: ['MOCK_PAY', 'MOCK_SMS', 'MOCK_WECHAT', 'WX_MCH_ID'] } },
|
||||
});
|
||||
console.log('\n=== system_config (DB) ===');
|
||||
for (const r of rows) {
|
||||
console.log(`${r.configKey} = ${r.configKey.includes('KEY') ? '(redacted)' : r.value}`);
|
||||
process.env[r.configKey] = r.value;
|
||||
}
|
||||
|
||||
console.log('\n=== After DB overlay (API startup) ===');
|
||||
console.log('MOCK_PAY:', process.env.MOCK_PAY);
|
||||
console.log('mockPayEnabled:', mockPayEnabled());
|
||||
console.log('wechatPayEnabled:', wechatPayEnabled());
|
||||
|
||||
const orders = await prisma.order.findMany({
|
||||
where: { orderType: 'PROXY' },
|
||||
orderBy: { id: 'desc' },
|
||||
take: 5,
|
||||
select: {
|
||||
id: true,
|
||||
orderNo: true,
|
||||
status: true,
|
||||
payStatus: true,
|
||||
payExternalNo: true,
|
||||
channelSource: true,
|
||||
createdAt: true,
|
||||
},
|
||||
});
|
||||
console.log('\n=== Recent PROXY orders ===');
|
||||
for (const o of orders) {
|
||||
console.log({
|
||||
id: o.id.toString(),
|
||||
orderNo: o.orderNo,
|
||||
status: o.status,
|
||||
payStatus: o.payStatus,
|
||||
payExternalNo: o.payExternalNo,
|
||||
channelSource: o.channelSource,
|
||||
createdAt: o.createdAt.toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.$disconnect();
|
||||
@@ -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