微信JSSDK对接,代下单支付
This commit is contained in:
@@ -1257,7 +1257,7 @@ export class TradeService {
|
||||
}
|
||||
|
||||
const orderNo = generateOrderNo();
|
||||
const now = new Date();
|
||||
const payExpireAt = new Date(Date.now() + 30 * 60 * 1000);
|
||||
const location = buildOrderClientLocationSnapshot(
|
||||
req,
|
||||
this.ipGeoService.resolve(extractClientIp(req)),
|
||||
@@ -1271,10 +1271,10 @@ export class TradeService {
|
||||
orderType: 'PROXY',
|
||||
userId: user.id,
|
||||
cityId: city.id,
|
||||
status: 'COMPLETED',
|
||||
payStatus: 'PAID',
|
||||
status: 'PENDING_PAY',
|
||||
payStatus: 'UNPAID',
|
||||
deliveryType: preview.deliveryType,
|
||||
channelSource: 'OFFLINE_PROXY',
|
||||
channelSource: 'PROXY_ONLINE',
|
||||
promoCodeId,
|
||||
productId: product.id,
|
||||
barcode69: product.barcode69,
|
||||
@@ -1299,9 +1299,7 @@ export class TradeService {
|
||||
ipProvince: location.ipProvince,
|
||||
ipCity: location.ipCity,
|
||||
ipDistrict: location.ipDistrict,
|
||||
paidAt: now,
|
||||
shippedAt: now,
|
||||
completedAt: now,
|
||||
payExpireAt,
|
||||
partnerAccountIdAtPay: paySnapshot?.partnerAccountId ?? primary.id,
|
||||
orderCommissionRateAtPay: paySnapshot?.orderCommissionRate ?? null,
|
||||
proxyPartnerAccountId: primary.id,
|
||||
@@ -1312,23 +1310,13 @@ export class TradeService {
|
||||
include: { product: true, imageResource: true },
|
||||
});
|
||||
|
||||
await tx.orderDelivery.create({
|
||||
data: {
|
||||
orderId: created.id,
|
||||
provider: 'MANUAL',
|
||||
outWarehouseAt: now,
|
||||
shippingAt: now,
|
||||
deliveredAt: now,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.commonEvent.create({
|
||||
data: buildOrderStatusEvent({
|
||||
orderId: created.id,
|
||||
fromStatus: 'PENDING_PAY',
|
||||
toStatus: 'COMPLETED',
|
||||
toStatus: 'PENDING_PAY',
|
||||
operator: 'PARTNER_PROXY',
|
||||
remark: `合伙人线下代下单 mode=${body.deliveryMode} customer=${normalizedPhone} partner=${primary.id}`,
|
||||
remark: `合伙人代下单待支付 mode=${body.deliveryMode} customer=${normalizedPhone} partner=${primary.id}`,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1342,8 +1330,6 @@ export class TradeService {
|
||||
return created;
|
||||
});
|
||||
|
||||
await this.benefitService.grantOnOrderPaid(order.id);
|
||||
|
||||
this.analyticsService.trackPartnerOneSafe(partnerAccountId, 'PARTNER_H5', {
|
||||
partnerAccountId: primary.id,
|
||||
eventName: 'partner_proxy_order_create',
|
||||
@@ -1359,7 +1345,17 @@ export class TradeService {
|
||||
},
|
||||
});
|
||||
|
||||
return serializeBigInt(mapOrderCompat(order));
|
||||
return {
|
||||
id: order.id.toString(),
|
||||
orderNo: order.orderNo,
|
||||
status: order.status,
|
||||
payStatus: order.payStatus,
|
||||
payAmount: Number(order.payAmount),
|
||||
benefitAmount: Number(order.benefitAmount),
|
||||
deliveryType: order.deliveryType,
|
||||
payExpireAt: order.payExpireAt?.toISOString() ?? null,
|
||||
proxyPartnerName: primary.name,
|
||||
};
|
||||
}
|
||||
|
||||
/** 合伙人代下单列表:按 proxyPartnerAccountId 归属,与管仓订单无关 */
|
||||
@@ -1547,7 +1543,7 @@ export class TradeService {
|
||||
}
|
||||
|
||||
const orderNo = generateOrderNo();
|
||||
const now = new Date();
|
||||
const payExpireAt = new Date(Date.now() + 30 * 60 * 1000);
|
||||
const location = buildOrderClientLocationSnapshot(
|
||||
req,
|
||||
this.ipGeoService.resolve(extractClientIp(req)),
|
||||
@@ -1561,10 +1557,10 @@ export class TradeService {
|
||||
orderType: 'PROXY',
|
||||
userId: user.id,
|
||||
cityId: city.id,
|
||||
status: 'COMPLETED',
|
||||
payStatus: 'PAID',
|
||||
status: 'PENDING_PAY',
|
||||
payStatus: 'UNPAID',
|
||||
deliveryType: preview.deliveryType,
|
||||
channelSource: 'OFFLINE_PROXY',
|
||||
channelSource: 'PROXY_ONLINE',
|
||||
promoCodeId,
|
||||
productId: product.id,
|
||||
barcode69: product.barcode69,
|
||||
@@ -1589,9 +1585,7 @@ export class TradeService {
|
||||
ipProvince: location.ipProvince,
|
||||
ipCity: location.ipCity,
|
||||
ipDistrict: location.ipDistrict,
|
||||
paidAt: now,
|
||||
shippedAt: now,
|
||||
completedAt: now,
|
||||
payExpireAt,
|
||||
partnerAccountIdAtPay: paySnapshot?.partnerAccountId ?? null,
|
||||
orderCommissionRateAtPay: paySnapshot?.orderCommissionRate ?? null,
|
||||
proxyPartnerAccountId: null,
|
||||
@@ -1602,23 +1596,13 @@ export class TradeService {
|
||||
include: { product: true, imageResource: true },
|
||||
});
|
||||
|
||||
await tx.orderDelivery.create({
|
||||
data: {
|
||||
orderId: created.id,
|
||||
provider: 'MANUAL',
|
||||
outWarehouseAt: now,
|
||||
shippingAt: now,
|
||||
deliveredAt: now,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.commonEvent.create({
|
||||
data: buildOrderStatusEvent({
|
||||
orderId: created.id,
|
||||
fromStatus: 'PENDING_PAY',
|
||||
toStatus: 'COMPLETED',
|
||||
toStatus: 'PENDING_PAY',
|
||||
operator: 'HQ_PROXY',
|
||||
remark: `总部线下代下单 mode=${body.deliveryMode} customer=${normalizedPhone} hq=${hq.id}`,
|
||||
remark: `总部代下单待支付 mode=${body.deliveryMode} customer=${normalizedPhone} hq=${hq.id}`,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1632,15 +1616,245 @@ export class TradeService {
|
||||
return created;
|
||||
});
|
||||
|
||||
await this.benefitService.grantOnOrderPaid(order.id);
|
||||
|
||||
return serializeBigInt({
|
||||
id: order.id,
|
||||
return {
|
||||
id: order.id.toString(),
|
||||
orderNo: order.orderNo,
|
||||
status: order.status,
|
||||
payStatus: order.payStatus,
|
||||
payAmount: Number(order.payAmount),
|
||||
benefitAmount: Number(order.benefitAmount),
|
||||
deliveryType: order.deliveryType,
|
||||
payExpireAt: order.payExpireAt?.toISOString() ?? null,
|
||||
proxyPartnerName: proxyDisplayName,
|
||||
};
|
||||
}
|
||||
|
||||
/** 合伙人代下单支付:NATIVE 商家码 / JSAPI 合伙人微信代付 */
|
||||
async payPartnerProxyOrder(
|
||||
partnerAccountId: bigint,
|
||||
orderId: bigint,
|
||||
payMethod: 'NATIVE' | 'JSAPI',
|
||||
) {
|
||||
const primary = await this.partnerCityService.resolvePrimaryAccount(partnerAccountId);
|
||||
const order = await this.prisma.order.findFirst({
|
||||
where: {
|
||||
id: orderId,
|
||||
orderType: 'PROXY',
|
||||
proxyPartnerAccountId: primary.id,
|
||||
},
|
||||
});
|
||||
if (!order) throw new NotFoundException('代下单不存在');
|
||||
if (order.status !== 'PENDING_PAY' || order.payStatus !== 'UNPAID') {
|
||||
throw new BadRequestException('订单状态不可支付');
|
||||
}
|
||||
if (order.payExpireAt && order.payExpireAt.getTime() < Date.now()) {
|
||||
throw new BadRequestException('订单已超时未支付');
|
||||
}
|
||||
|
||||
let openId: string | undefined;
|
||||
if (payMethod === 'JSAPI') {
|
||||
openId = primary.wxOpenId ?? undefined;
|
||||
const appConfig = loadAppConfig();
|
||||
if (!appConfig.mockPay && !openId) {
|
||||
throw new BadRequestException('请先在微信内登录并绑定微信后再代付');
|
||||
}
|
||||
}
|
||||
|
||||
const payResult = await this.payProvider.payOrder(orderId, openId, 'h5', payMethod);
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
/** 总部代下单支付:仅 Native 收款码 */
|
||||
async payHqProxyOrder(orderId: bigint, payMethod: 'NATIVE' | 'JSAPI' = 'NATIVE') {
|
||||
if (payMethod !== 'NATIVE') {
|
||||
throw new BadRequestException('总部代下单仅支持收款码支付');
|
||||
}
|
||||
const order = await this.prisma.order.findFirst({
|
||||
where: { id: orderId, orderType: 'PROXY' },
|
||||
});
|
||||
if (!order) throw new NotFoundException('代下单不存在');
|
||||
if (order.status !== 'PENDING_PAY' || order.payStatus !== 'UNPAID') {
|
||||
throw new BadRequestException('订单状态不可支付');
|
||||
}
|
||||
if (order.payExpireAt && order.payExpireAt.getTime() < Date.now()) {
|
||||
throw new BadRequestException('订单已超时未支付');
|
||||
}
|
||||
|
||||
const payResult = await this.payProvider.payOrder(orderId, undefined, 'h5', 'NATIVE');
|
||||
if (payResult.mode !== 'native') {
|
||||
throw new BadRequestException('无法生成收款码');
|
||||
}
|
||||
return {
|
||||
mode: 'native' as const,
|
||||
orderId: order.id.toString(),
|
||||
orderNo: order.orderNo,
|
||||
codeUrl: payResult.codeUrl,
|
||||
payExpireAt: order.payExpireAt?.toISOString() ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
/** Mock 环境确认代下单支付 */
|
||||
async mockConfirmProxyPay(
|
||||
orderId: bigint,
|
||||
opts: { partnerAccountId?: bigint; hq?: boolean },
|
||||
) {
|
||||
const appConfig = loadAppConfig();
|
||||
if (!appConfig.mockPay) {
|
||||
throw new BadRequestException('仅 MOCK_PAY 环境可用');
|
||||
}
|
||||
|
||||
let order;
|
||||
if (opts.partnerAccountId != null) {
|
||||
const primary = await this.partnerCityService.resolvePrimaryAccount(opts.partnerAccountId);
|
||||
order = await this.prisma.order.findFirst({
|
||||
where: {
|
||||
id: orderId,
|
||||
orderType: 'PROXY',
|
||||
proxyPartnerAccountId: primary.id,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
order = await this.prisma.order.findFirst({
|
||||
where: { id: orderId, orderType: 'PROXY' },
|
||||
});
|
||||
}
|
||||
if (!order) throw new NotFoundException('代下单不存在');
|
||||
if (order.payStatus === 'PAID') {
|
||||
return this.getProxyPayStatus(order.id);
|
||||
}
|
||||
if (order.status !== 'PENDING_PAY') {
|
||||
throw new BadRequestException('订单状态不可支付');
|
||||
}
|
||||
|
||||
await this.markProxyOrderPaid(
|
||||
order.id,
|
||||
`MOCK-CONFIRM-${Date.now()}`,
|
||||
opts.hq ? 'HQ_PROXY_MOCK_PAY' : 'PARTNER_PROXY_MOCK_PAY',
|
||||
);
|
||||
return this.getProxyPayStatus(order.id);
|
||||
}
|
||||
|
||||
async getProxyPayStatus(orderId: bigint) {
|
||||
const order = await this.prisma.order.findUnique({
|
||||
where: { id: orderId },
|
||||
select: {
|
||||
id: true,
|
||||
orderNo: true,
|
||||
status: true,
|
||||
payStatus: true,
|
||||
payAmount: true,
|
||||
deliveryType: true,
|
||||
payExpireAt: true,
|
||||
paidAt: true,
|
||||
},
|
||||
});
|
||||
if (!order) throw new NotFoundException('订单不存在');
|
||||
return {
|
||||
id: order.id.toString(),
|
||||
orderNo: order.orderNo,
|
||||
status: order.status,
|
||||
payStatus: order.payStatus,
|
||||
payAmount: Number(order.payAmount),
|
||||
deliveryType: order.deliveryType,
|
||||
payExpireAt: order.payExpireAt?.toISOString() ?? null,
|
||||
paidAt: order.paidAt?.toISOString() ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
async getPartnerProxyOrderTrack(partnerAccountId: bigint, orderId: bigint) {
|
||||
const primary = await this.partnerCityService.resolvePrimaryAccount(partnerAccountId);
|
||||
const order = await this.prisma.order.findFirst({
|
||||
where: {
|
||||
id: orderId,
|
||||
orderType: 'PROXY',
|
||||
proxyPartnerAccountId: primary.id,
|
||||
},
|
||||
select: { id: true, deliveryType: true },
|
||||
});
|
||||
if (!order) throw new NotFoundException('代下单不存在');
|
||||
return this.fulfillmentService.getOrderTrack(orderId);
|
||||
}
|
||||
|
||||
private async markProxyOrderPaid(orderId: bigint, externalNo: string, operator: string) {
|
||||
const order = await this.prisma.order.findUnique({ where: { id: orderId } });
|
||||
if (!order) throw new NotFoundException('订单不存在');
|
||||
if (order.payStatus === 'PAID') return;
|
||||
|
||||
const now = new Date();
|
||||
const paySnapshot = await this.partnerCityService.resolveForOrder(
|
||||
order.cityId,
|
||||
order.receiverDistrict,
|
||||
);
|
||||
const toStatus =
|
||||
order.deliveryType === 'ON_SITE_PICKUP' ? 'PENDING_RECEIVE' : 'PENDING_SHIP';
|
||||
|
||||
await this.prisma.$transaction(async (tx) => {
|
||||
await tx.order.update({
|
||||
where: { id: order.id },
|
||||
data: {
|
||||
status: toStatus,
|
||||
payStatus: 'PAID',
|
||||
paidAt: now,
|
||||
payExternalNo: externalNo,
|
||||
partnerAccountIdAtPay: paySnapshot?.partnerAccountId ?? order.partnerAccountIdAtPay,
|
||||
orderCommissionRateAtPay:
|
||||
paySnapshot?.orderCommissionRate ?? order.orderCommissionRateAtPay,
|
||||
},
|
||||
});
|
||||
await tx.logThirdParty.create({
|
||||
data: {
|
||||
provider: 'WECHAT_PAY',
|
||||
scene: 'ORDER_PAY',
|
||||
refType: 'ORDER',
|
||||
refId: order.id,
|
||||
externalNo,
|
||||
amount: order.payAmount,
|
||||
status: 'SUCCESS',
|
||||
},
|
||||
});
|
||||
await tx.commonEvent.create({
|
||||
data: buildOrderStatusEvent({
|
||||
orderId: order.id,
|
||||
fromStatus: 'PENDING_PAY',
|
||||
toStatus,
|
||||
operator,
|
||||
}),
|
||||
});
|
||||
const delivery = await tx.orderDelivery.findUnique({ where: { orderId: order.id } });
|
||||
if (!delivery) {
|
||||
await tx.orderDelivery.create({
|
||||
data: { orderId: order.id, provider: 'MANUAL' },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await this.afterOrderPaid(order.id);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user