a0a6c14758
Receive trade_manage_order_settlement to sync confirm-receive/settle; wait ~65s and retry on 10060001 before upload_shipping_info. Co-authored-by: Cursor <cursoragent@cursor.com>
167 lines
5.6 KiB
TypeScript
167 lines
5.6 KiB
TypeScript
import { Injectable, Logger } from '@nestjs/common';
|
|
import { buildOrderStatusEvent } from '../../common/event/event.helpers';
|
|
import { PrismaService } from '../../common/prisma/prisma.module';
|
|
import { WechatOrderShippingService } from './wechat-order-shipping.service';
|
|
import type { WechatTradeManageEvent } from './wechat-msg-crypto.util';
|
|
|
|
/**
|
|
* 小程序发货信息管理相关消息推送处理。
|
|
* @see https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/order-shipping/order-shipping.html
|
|
*/
|
|
@Injectable()
|
|
export class WechatTradeManageService {
|
|
private readonly logger = new Logger(WechatTradeManageService.name);
|
|
|
|
constructor(
|
|
private readonly prisma: PrismaService,
|
|
private readonly wechatOrderShipping: WechatOrderShippingService,
|
|
) {}
|
|
|
|
async handleEvent(evt: WechatTradeManageEvent): Promise<{ handled: string }> {
|
|
switch (evt.event) {
|
|
case 'trade_manage_order_settlement':
|
|
return this.handleOrderSettlement(evt);
|
|
case 'trade_manage_remind_shipping':
|
|
return this.handleRemindShipping(evt);
|
|
case 'trade_manage_remind_access_api':
|
|
case 'wxa_trade_controlled':
|
|
await this.logEvent(evt, null, 'SUCCESS', evt.msg || evt.event);
|
|
return { handled: evt.event };
|
|
default:
|
|
await this.logEvent(evt, null, 'SUCCESS', `ignored:${evt.event || 'empty'}`);
|
|
return { handled: 'ignored' };
|
|
}
|
|
}
|
|
|
|
private async handleOrderSettlement(evt: WechatTradeManageEvent) {
|
|
const order = await this.findOrder(evt);
|
|
const isConfirmOrSettle =
|
|
evt.confirmReceiveTime != null || evt.settlementTime != null || evt.confirmReceiveMethod != null;
|
|
|
|
if (!isConfirmOrSettle) {
|
|
// 发货时推送:仅有 shipped_time / estimated_settlement_time
|
|
await this.logEvent(evt, order?.id ?? null, 'SUCCESS', 'shipped_notify');
|
|
return { handled: 'settlement_shipped_notify' };
|
|
}
|
|
|
|
if (!order) {
|
|
await this.logEvent(evt, null, 'FAILED', 'ORDER_NOT_FOUND');
|
|
this.logger.warn(
|
|
`trade_manage_order_settlement order not found tradeNo=${evt.merchantTradeNo} tx=${evt.transactionId}`,
|
|
);
|
|
return { handled: 'settlement_order_missing' };
|
|
}
|
|
|
|
if (order.status === 'COMPLETED') {
|
|
await this.logEvent(evt, order.id, 'SUCCESS', 'already_completed');
|
|
return { handled: 'settlement_already_completed' };
|
|
}
|
|
|
|
if (order.payStatus !== 'PAID') {
|
|
await this.logEvent(evt, order.id, 'FAILED', 'NOT_PAID');
|
|
return { handled: 'settlement_not_paid' };
|
|
}
|
|
|
|
const methodLabel =
|
|
evt.confirmReceiveMethod === 2
|
|
? '微信自动确认收货'
|
|
: evt.confirmReceiveMethod === 1
|
|
? '微信手动确认收货'
|
|
: '微信订单结算';
|
|
|
|
const now = new Date();
|
|
const remark = [
|
|
methodLabel,
|
|
evt.confirmReceiveTime ? `confirmAt=${evt.confirmReceiveTime}` : null,
|
|
evt.settlementTime ? `settleAt=${evt.settlementTime}` : null,
|
|
]
|
|
.filter(Boolean)
|
|
.join(' | ')
|
|
.slice(0, 512);
|
|
|
|
await this.prisma.$transaction(async (tx) => {
|
|
await tx.order.update({
|
|
where: { id: order.id },
|
|
data: {
|
|
status: 'COMPLETED',
|
|
completedAt: order.completedAt ?? now,
|
|
},
|
|
});
|
|
await tx.orderDelivery.updateMany({
|
|
where: { orderId: order.id, deliveredAt: null },
|
|
data: { deliveredAt: now },
|
|
});
|
|
await tx.commonEvent.create({
|
|
data: buildOrderStatusEvent({
|
|
orderId: order.id,
|
|
fromStatus: order.status,
|
|
toStatus: 'COMPLETED',
|
|
operator: 'WECHAT_TRADE_MANAGE',
|
|
remark,
|
|
}),
|
|
});
|
|
});
|
|
|
|
await this.logEvent(evt, order.id, 'SUCCESS', methodLabel);
|
|
this.logger.log(
|
|
`WeChat confirm/settle → COMPLETED order=${order.orderNo} method=${evt.confirmReceiveMethod ?? '-'}`,
|
|
);
|
|
return { handled: 'settlement_completed' };
|
|
}
|
|
|
|
private async handleRemindShipping(evt: WechatTradeManageEvent) {
|
|
const order = await this.findOrder(evt);
|
|
await this.logEvent(evt, order?.id ?? null, order ? 'SUCCESS' : 'FAILED', evt.msg || 'remind_shipping');
|
|
if (order) {
|
|
this.wechatOrderShipping.uploadForOrderSafe(order.id);
|
|
}
|
|
return { handled: 'remind_shipping' };
|
|
}
|
|
|
|
private async findOrder(evt: WechatTradeManageEvent) {
|
|
if (evt.merchantTradeNo) {
|
|
const byNo = await this.prisma.order.findUnique({ where: { orderNo: evt.merchantTradeNo } });
|
|
if (byNo) return byNo;
|
|
}
|
|
if (evt.transactionId) {
|
|
return this.prisma.order.findFirst({
|
|
where: { payExternalNo: evt.transactionId },
|
|
orderBy: { id: 'desc' },
|
|
});
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private async logEvent(
|
|
evt: WechatTradeManageEvent,
|
|
orderId: bigint | null,
|
|
status: 'SUCCESS' | 'FAILED',
|
|
note: string,
|
|
) {
|
|
await this.prisma.logThirdParty.create({
|
|
data: {
|
|
provider: 'WECHAT_PAY',
|
|
scene: 'TRADE_MANAGE_PUSH',
|
|
refType: orderId ? 'ORDER' : 'SYSTEM',
|
|
refId: orderId ?? undefined,
|
|
requestUrl: 'callbacks/wechat/message',
|
|
requestBody: {
|
|
event: evt.event,
|
|
merchantTradeNo: evt.merchantTradeNo,
|
|
transactionId: evt.transactionId,
|
|
confirmReceiveMethod: evt.confirmReceiveMethod,
|
|
confirmReceiveTime: evt.confirmReceiveTime,
|
|
settlementTime: evt.settlementTime,
|
|
shippedTime: evt.shippedTime,
|
|
estimatedSettlementTime: evt.estimatedSettlementTime,
|
|
msg: evt.msg,
|
|
},
|
|
responseBody: { note },
|
|
externalNo: evt.transactionId || evt.merchantTradeNo,
|
|
status,
|
|
errorMessage: status === 'FAILED' ? note.slice(0, 512) : undefined,
|
|
},
|
|
});
|
|
}
|
|
}
|