feat(trade): upload WeChat mini program shipping info
Hook SHIPPING and on-site pickup to upload_shipping_info for settlement unlock. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -12,6 +12,7 @@ import { WechatApiProvider } from './wechat/wechat.api.provider';
|
||||
import { WechatDisabledProvider } from './wechat/wechat.disabled.provider';
|
||||
import { WechatMockProvider } from './wechat/wechat.mock.provider';
|
||||
import { WechatRouterProvider } from './wechat/wechat.router.provider';
|
||||
import { WechatOrderShippingService } from './wechat/wechat-order-shipping.service';
|
||||
import { OssAliyunProvider } from './oss/oss.aliyun.provider';
|
||||
import { TencentLbsProvider } from './map/tencent-lbs.provider';
|
||||
import {
|
||||
@@ -38,6 +39,7 @@ import { DELIVERY_QUEUE } from '../jobs/jobs.constants';
|
||||
WechatMockProvider,
|
||||
WechatRouterProvider,
|
||||
{ provide: WECHAT_PROVIDER, useExisting: WechatRouterProvider },
|
||||
WechatOrderShippingService,
|
||||
PayMockProvider,
|
||||
PayWechatProvider,
|
||||
PayRouterProvider,
|
||||
@@ -55,6 +57,7 @@ import { DELIVERY_QUEUE } from '../jobs/jobs.constants';
|
||||
PAY_PROVIDER,
|
||||
DELIVERY_PROVIDER,
|
||||
WECHAT_PROVIDER,
|
||||
WechatOrderShippingService,
|
||||
OSS_PROVIDER,
|
||||
MAP_PROVIDER,
|
||||
TencentLbsProvider,
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
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.interface';
|
||||
import {
|
||||
formatWechatUploadTime,
|
||||
maskReceiverPhone,
|
||||
resolveExpressCompanyId,
|
||||
} from './wechat-order-shipping.util';
|
||||
|
||||
/**
|
||||
* 小程序发货信息管理:订单发货/自提后向微信录入发货信息,解冻交易资金。
|
||||
* @see https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/order-shipping/order-shipping.html
|
||||
*/
|
||||
@Injectable()
|
||||
export class WechatOrderShippingService {
|
||||
private readonly logger = new Logger(WechatOrderShippingService.name);
|
||||
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
@Inject(WECHAT_PROVIDER) private readonly wechat: IWechatProvider,
|
||||
) {}
|
||||
|
||||
/** 异步安全上报,失败只记日志,不阻断主履约流程 */
|
||||
uploadForOrderSafe(orderId: bigint) {
|
||||
void this.uploadForOrder(orderId).catch((err) => {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
this.logger.error(`upload shipping info failed order=${orderId}: ${msg}`);
|
||||
});
|
||||
}
|
||||
|
||||
async uploadForOrder(orderId: bigint): Promise<{ skipped?: string; ok?: boolean } | void> {
|
||||
const cfg = loadAppConfig();
|
||||
if (cfg.mockPay) {
|
||||
return { skipped: 'MOCK_PAY' };
|
||||
}
|
||||
if (!this.wechat.isPayEnabled() && this.wechat.isMock()) {
|
||||
return { skipped: 'MOCK_WECHAT' };
|
||||
}
|
||||
|
||||
const order = await this.prisma.order.findUnique({
|
||||
where: { id: orderId },
|
||||
include: {
|
||||
delivery: true,
|
||||
user: { select: { wxOpenId: true } },
|
||||
},
|
||||
});
|
||||
if (!order) return { skipped: 'ORDER_NOT_FOUND' };
|
||||
if (order.payStatus !== 'PAID') return { skipped: 'NOT_PAID' };
|
||||
|
||||
const openId = order.user?.wxOpenId?.trim();
|
||||
if (!openId) return { skipped: 'NO_OPENID' };
|
||||
|
||||
const transactionId = order.payExternalNo?.trim();
|
||||
const mchId = this.wechat.getMchId()?.trim();
|
||||
if (!transactionId && !(mchId && order.orderNo)) {
|
||||
return { skipped: 'NO_PAY_REF' };
|
||||
}
|
||||
|
||||
// 已成功上报则跳过(幂等)
|
||||
const existed = await this.prisma.logThirdParty.findFirst({
|
||||
where: {
|
||||
provider: 'WECHAT_PAY',
|
||||
scene: 'UPLOAD_SHIPPING',
|
||||
refType: 'ORDER',
|
||||
refId: orderId,
|
||||
status: 'SUCCESS',
|
||||
},
|
||||
});
|
||||
if (existed) return { skipped: 'ALREADY_UPLOADED' };
|
||||
|
||||
const logisticsType = this.resolveLogisticsType(order.deliveryType, order.delivery);
|
||||
const trackingNo = order.delivery?.trackingNo?.trim() || undefined;
|
||||
let expressCompany = resolveExpressCompanyId(order.delivery?.logisticsCompany);
|
||||
|
||||
if (logisticsType === 1 && trackingNo && !expressCompany) {
|
||||
try {
|
||||
const list = await this.wechat.getDeliveryList();
|
||||
expressCompany = resolveExpressCompanyId(order.delivery?.logisticsCompany, list);
|
||||
} catch (err) {
|
||||
this.logger.warn(
|
||||
`get_delivery_list failed: ${err instanceof Error ? err.message : String(err)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 快递模式缺运力 ID 时降级为同城,避免接口硬失败
|
||||
let finalLogisticsType = logisticsType;
|
||||
if (finalLogisticsType === 1 && (!trackingNo || !expressCompany)) {
|
||||
finalLogisticsType = 2;
|
||||
}
|
||||
|
||||
const itemDesc = `${order.productName}${order.productSpec ? `(${order.productSpec})` : ''}*${order.quantity}`
|
||||
.replace(/\s+/g, ' ')
|
||||
.slice(0, 120);
|
||||
|
||||
const shippingItem: {
|
||||
trackingNo?: string;
|
||||
expressCompany?: string;
|
||||
itemDesc: string;
|
||||
contact?: { receiverContact: string };
|
||||
} = { itemDesc };
|
||||
|
||||
if (finalLogisticsType === 1) {
|
||||
shippingItem.trackingNo = trackingNo;
|
||||
shippingItem.expressCompany = expressCompany;
|
||||
if (expressCompany === 'SF') {
|
||||
shippingItem.contact = {
|
||||
receiverContact: maskReceiverPhone(order.receiverPhone),
|
||||
};
|
||||
}
|
||||
} else if (trackingNo) {
|
||||
// 同城也可附带运单信息
|
||||
shippingItem.trackingNo = trackingNo;
|
||||
if (expressCompany) shippingItem.expressCompany = expressCompany;
|
||||
}
|
||||
|
||||
const input = {
|
||||
orderNumberType: (transactionId ? 2 : 1) as 1 | 2,
|
||||
transactionId: transactionId || undefined,
|
||||
mchId: transactionId ? undefined : mchId,
|
||||
outTradeNo: transactionId ? undefined : order.orderNo,
|
||||
logisticsType: finalLogisticsType,
|
||||
deliveryMode: 1 as const,
|
||||
shippingList: [shippingItem],
|
||||
uploadTime: formatWechatUploadTime(),
|
||||
payerOpenId: openId,
|
||||
};
|
||||
|
||||
const requestUrl = 'https://api.weixin.qq.com/wxa/sec/order/upload_shipping_info';
|
||||
try {
|
||||
const result = await this.wechat.uploadShippingInfo(input);
|
||||
const ok = result.errcode === 0;
|
||||
await this.prisma.logThirdParty.create({
|
||||
data: {
|
||||
provider: 'WECHAT_PAY',
|
||||
scene: 'UPLOAD_SHIPPING',
|
||||
refType: 'ORDER',
|
||||
refId: orderId,
|
||||
requestUrl,
|
||||
requestBody: {
|
||||
orderNo: order.orderNo,
|
||||
logisticsType: finalLogisticsType,
|
||||
trackingNo: shippingItem.trackingNo,
|
||||
expressCompany: shippingItem.expressCompany,
|
||||
orderNumberType: input.orderNumberType,
|
||||
},
|
||||
responseBody: { errcode: result.errcode, errmsg: result.errmsg },
|
||||
externalNo: transactionId || order.orderNo,
|
||||
status: ok ? 'SUCCESS' : 'FAILED',
|
||||
errorMessage: ok ? undefined : `${result.errcode}:${result.errmsg}`.slice(0, 512),
|
||||
},
|
||||
});
|
||||
if (!ok) {
|
||||
this.logger.warn(
|
||||
`WeChat upload_shipping_info order=${order.orderNo} ${result.errcode} ${result.errmsg}`,
|
||||
);
|
||||
return { ok: false };
|
||||
}
|
||||
this.logger.log(`WeChat upload_shipping_info ok order=${order.orderNo}`);
|
||||
return { ok: true };
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
await this.prisma.logThirdParty.create({
|
||||
data: {
|
||||
provider: 'WECHAT_PAY',
|
||||
scene: 'UPLOAD_SHIPPING',
|
||||
refType: 'ORDER',
|
||||
refId: orderId,
|
||||
requestUrl,
|
||||
requestBody: { orderNo: order.orderNo },
|
||||
status: 'FAILED',
|
||||
errorMessage: message.slice(0, 512),
|
||||
externalNo: transactionId || order.orderNo,
|
||||
},
|
||||
});
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
private resolveLogisticsType(
|
||||
deliveryType: string,
|
||||
delivery: { trackingNo: string | null; logisticsCompany: string | null; provider: string } | null,
|
||||
): 1 | 2 | 4 {
|
||||
if (deliveryType === 'ON_SITE_PICKUP') return 4;
|
||||
if (deliveryType === 'LOCAL') return 2;
|
||||
// CROSS_CITY:有运单走快递,否则同城兜底
|
||||
if (delivery?.trackingNo?.trim()) return 1;
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/** 常见快递公司名称 → 微信运力 ID(get_delivery_list 的 delivery_id) */
|
||||
const STATIC_EXPRESS_MAP: Array<{ id: string; aliases: string[] }> = [
|
||||
{ id: 'SF', aliases: ['sf', '顺丰', '顺丰速运', '顺丰快递'] },
|
||||
{ id: 'STO', aliases: ['sto', '申通', '申通快递'] },
|
||||
{ id: 'YTO', aliases: ['yto', '圆通', '圆通速递', '圆通快递'] },
|
||||
{ id: 'ZTO', aliases: ['zto', '中通', '中通快递'] },
|
||||
{ id: 'YD', aliases: ['yd', '韵达', '韵达速递', '韵达快递'] },
|
||||
{ id: 'HTKY', aliases: ['htky', '百世', '百世快递', '汇通'] },
|
||||
{ id: 'EMS', aliases: ['ems', '邮政', '中国邮政', '邮政快递'] },
|
||||
{ id: 'JD', aliases: ['jd', '京东', '京东快递', '京东物流'] },
|
||||
{ id: 'JTSD', aliases: ['jtsd', 'jt', '极兔', '极兔速递', '极兔快递'] },
|
||||
{ id: 'UC', aliases: ['uc', '优速', '优速快递'] },
|
||||
{ id: 'DBL', aliases: ['dbl', '德邦', '德邦快递', '德邦物流'] },
|
||||
{ id: 'ANE', aliases: ['ane', '安能', '安能物流'] },
|
||||
];
|
||||
|
||||
export function maskReceiverPhone(phone: string): string {
|
||||
const digits = phone.replace(/\D/g, '');
|
||||
if (digits.length >= 7) {
|
||||
return `${digits.slice(0, 3)}****${digits.slice(-4)}`;
|
||||
}
|
||||
return phone.trim();
|
||||
}
|
||||
|
||||
/** RFC3339 +08:00,微信发货录入 upload_time 要求 */
|
||||
export function formatWechatUploadTime(date = new Date()): string {
|
||||
const fmt = new Intl.DateTimeFormat('en-CA', {
|
||||
timeZone: 'Asia/Shanghai',
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false,
|
||||
});
|
||||
const parts = Object.fromEntries(fmt.formatToParts(date).map((p) => [p.type, p.value]));
|
||||
const ms = String(date.getMilliseconds()).padStart(3, '0');
|
||||
return `${parts.year}-${parts.month}-${parts.day}T${parts.hour}:${parts.minute}:${parts.second}.${ms}+08:00`;
|
||||
}
|
||||
|
||||
export function resolveExpressCompanyId(
|
||||
logisticsCompany: string | null | undefined,
|
||||
deliveryList?: Array<{ deliveryId: string; deliveryName: string }>,
|
||||
): string | undefined {
|
||||
const raw = (logisticsCompany || '').trim();
|
||||
if (!raw) return undefined;
|
||||
|
||||
const upper = raw.toUpperCase();
|
||||
// 已是运力 ID
|
||||
if (/^[A-Z0-9_()-]{2,32}$/.test(upper) && !/[\u4e00-\u9fff]/.test(raw)) {
|
||||
return upper;
|
||||
}
|
||||
|
||||
const lower = raw.toLowerCase();
|
||||
for (const row of STATIC_EXPRESS_MAP) {
|
||||
if (row.aliases.some((a) => lower.includes(a.toLowerCase()) || a === raw)) {
|
||||
return row.id;
|
||||
}
|
||||
}
|
||||
|
||||
if (deliveryList?.length) {
|
||||
const hit = deliveryList.find(
|
||||
(d) =>
|
||||
d.deliveryId.toUpperCase() === upper ||
|
||||
d.deliveryName === raw ||
|
||||
d.deliveryName.includes(raw) ||
|
||||
raw.includes(d.deliveryName),
|
||||
);
|
||||
if (hit) return hit.deliveryId;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
@@ -8,6 +8,9 @@ import type {
|
||||
WechatCodeSession,
|
||||
WechatOAuthSession,
|
||||
WechatWxaCodeUnlimitedInput,
|
||||
WechatUploadShippingInfoInput,
|
||||
WechatUploadShippingInfoResult,
|
||||
WechatDeliveryCompany,
|
||||
} from './wechat.interface';
|
||||
import { logWechatAuth, type WechatActorRef } from './wechat-log.util';
|
||||
import {
|
||||
@@ -303,6 +306,86 @@ export class WechatApiProvider implements IWechatProvider {
|
||||
return buf;
|
||||
}
|
||||
|
||||
async uploadShippingInfo(input: WechatUploadShippingInfoInput): Promise<WechatUploadShippingInfoResult> {
|
||||
const accessToken = await this.getMiniAccessToken();
|
||||
const orderKey: Record<string, string | number> = {
|
||||
order_number_type: input.orderNumberType,
|
||||
};
|
||||
if (input.orderNumberType === 2) {
|
||||
if (!input.transactionId) {
|
||||
throw new BadRequestException('微信支付单号不能为空');
|
||||
}
|
||||
orderKey.transaction_id = input.transactionId;
|
||||
} else {
|
||||
if (!input.mchId || !input.outTradeNo) {
|
||||
throw new BadRequestException('商户号与商户单号不能为空');
|
||||
}
|
||||
orderKey.mchid = input.mchId;
|
||||
orderKey.out_trade_no = input.outTradeNo;
|
||||
}
|
||||
|
||||
const body = {
|
||||
order_key: orderKey,
|
||||
logistics_type: input.logisticsType,
|
||||
delivery_mode: input.deliveryMode ?? 1,
|
||||
shipping_list: input.shippingList.map((row) => {
|
||||
const item: Record<string, unknown> = {
|
||||
item_desc: row.itemDesc.slice(0, 120),
|
||||
};
|
||||
if (row.trackingNo) item.tracking_no = row.trackingNo;
|
||||
if (row.expressCompany) item.express_company = row.expressCompany;
|
||||
if (row.contact?.consignorContact || row.contact?.receiverContact) {
|
||||
item.contact = {
|
||||
...(row.contact.consignorContact
|
||||
? { consignor_contact: row.contact.consignorContact }
|
||||
: {}),
|
||||
...(row.contact.receiverContact
|
||||
? { receiver_contact: row.contact.receiverContact }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
return item;
|
||||
}),
|
||||
upload_time: input.uploadTime,
|
||||
payer: { openid: input.payerOpenId },
|
||||
};
|
||||
|
||||
const apiUrl = `https://api.weixin.qq.com/wxa/sec/order/upload_shipping_info?access_token=${accessToken}`;
|
||||
const data = await this.fetchJson<{ errcode?: number; errmsg?: string }>(apiUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
return {
|
||||
errcode: data.errcode ?? -1,
|
||||
errmsg: data.errmsg ?? 'unknown',
|
||||
};
|
||||
}
|
||||
|
||||
async getDeliveryList(): Promise<WechatDeliveryCompany[]> {
|
||||
const accessToken = await this.getMiniAccessToken();
|
||||
const apiUrl = `https://api.weixin.qq.com/cgi-bin/express/delivery/open_msg/get_delivery_list?access_token=${accessToken}`;
|
||||
const data = await this.fetchJson<{
|
||||
errcode?: number;
|
||||
errmsg?: string;
|
||||
delivery_list?: Array<{ delivery_id?: string; delivery_name?: string }>;
|
||||
}>(apiUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: '{}',
|
||||
});
|
||||
if (data.errcode && data.errcode !== 0) {
|
||||
this.logger.warn(`get_delivery_list failed: ${data.errcode} ${data.errmsg}`);
|
||||
return [];
|
||||
}
|
||||
return (data.delivery_list ?? [])
|
||||
.filter((row) => row.delivery_id && row.delivery_name)
|
||||
.map((row) => ({
|
||||
deliveryId: row.delivery_id!,
|
||||
deliveryName: row.delivery_name!,
|
||||
}));
|
||||
}
|
||||
|
||||
async getPhoneNumberByCode(code: string, platform: 'mini' | 'h5', actorRef?: WechatActorRef): Promise<string> {
|
||||
if (platform === 'h5') {
|
||||
throw new InternalServerErrorException('H5 请使用短信绑定手机号');
|
||||
|
||||
@@ -58,4 +58,12 @@ export class WechatDisabledProvider implements IWechatProvider {
|
||||
getWxaCodeUnlimited(_input: WechatWxaCodeUnlimitedInput): Promise<Buffer> {
|
||||
return this.disabled();
|
||||
}
|
||||
|
||||
uploadShippingInfo() {
|
||||
return this.disabled();
|
||||
}
|
||||
|
||||
getDeliveryList() {
|
||||
return this.disabled();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,41 @@ export type WechatWxaCodeUnlimitedInput = {
|
||||
isHyaline?: boolean;
|
||||
};
|
||||
|
||||
/** 小程序发货信息管理 — 发货信息录入 */
|
||||
export type WechatUploadShippingInfoInput = {
|
||||
/** 1=商户单号;2=微信支付单号 */
|
||||
orderNumberType: 1 | 2;
|
||||
transactionId?: string;
|
||||
mchId?: string;
|
||||
outTradeNo?: string;
|
||||
/** 1 快递 2 同城 3 虚拟 4 自提 */
|
||||
logisticsType: 1 | 2 | 3 | 4;
|
||||
/** 1 统一发货 2 分拆发货 */
|
||||
deliveryMode?: 1 | 2;
|
||||
shippingList: Array<{
|
||||
trackingNo?: string;
|
||||
/** 微信运力 ID,如 SF / STO */
|
||||
expressCompany?: string;
|
||||
itemDesc: string;
|
||||
contact?: {
|
||||
consignorContact?: string;
|
||||
receiverContact?: string;
|
||||
};
|
||||
}>;
|
||||
uploadTime: string;
|
||||
payerOpenId: string;
|
||||
};
|
||||
|
||||
export type WechatUploadShippingInfoResult = {
|
||||
errcode: number;
|
||||
errmsg: string;
|
||||
};
|
||||
|
||||
export type WechatDeliveryCompany = {
|
||||
deliveryId: string;
|
||||
deliveryName: string;
|
||||
};
|
||||
|
||||
export interface IWechatProvider {
|
||||
isEnabled(): boolean;
|
||||
|
||||
@@ -95,4 +130,13 @@ export interface IWechatProvider {
|
||||
|
||||
/** 获取不限制的小程序码(PNG Buffer),须服务端调用 */
|
||||
getWxaCodeUnlimited(input: WechatWxaCodeUnlimitedInput): Promise<Buffer>;
|
||||
|
||||
/**
|
||||
* 小程序发货信息录入(交易资金解冻前置)
|
||||
* @see https://developers.weixin.qq.com/miniprogram/dev/server/API/order_shipping/api_uploadshippinginfo.html
|
||||
*/
|
||||
uploadShippingInfo(input: WechatUploadShippingInfoInput): Promise<WechatUploadShippingInfoResult>;
|
||||
|
||||
/** 获取运力公司列表(快递公司 delivery_id) */
|
||||
getDeliveryList(): Promise<WechatDeliveryCompany[]>;
|
||||
}
|
||||
|
||||
@@ -94,4 +94,18 @@ export class WechatMockProvider implements IWechatProvider {
|
||||
color: { dark: '#1f1a17', light: '#ffffff' },
|
||||
});
|
||||
}
|
||||
|
||||
async uploadShippingInfo() {
|
||||
return { errcode: 0, errmsg: 'ok' };
|
||||
}
|
||||
|
||||
async getDeliveryList() {
|
||||
return [
|
||||
{ deliveryId: 'SF', deliveryName: '顺丰速运' },
|
||||
{ deliveryId: 'STO', deliveryName: '申通快递' },
|
||||
{ deliveryId: 'YTO', deliveryName: '圆通速递' },
|
||||
{ deliveryId: 'ZTO', deliveryName: '中通快递' },
|
||||
{ deliveryId: 'YD', deliveryName: '韵达速递' },
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,4 +90,12 @@ export class WechatRouterProvider implements IWechatProvider {
|
||||
getWxaCodeUnlimited(input: Parameters<IWechatProvider['getWxaCodeUnlimited']>[0]) {
|
||||
return this.resolve().getWxaCodeUnlimited(input);
|
||||
}
|
||||
|
||||
uploadShippingInfo(input: Parameters<IWechatProvider['uploadShippingInfo']>[0]) {
|
||||
return this.resolve().uploadShippingInfo(input);
|
||||
}
|
||||
|
||||
getDeliveryList() {
|
||||
return this.resolve().getDeliveryList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import { extractClientIp } from '../../common/geo/client-ip.util';
|
||||
import { buildOrderStatusEvent, orderStatusLogWhere } from '../../common/event/event.helpers';
|
||||
import { mapOrderCompat, mapStatusLogCompat } from '../../common/compat/v31-compat';
|
||||
import { FulfillmentService } from '../fulfillment/fulfillment.service';
|
||||
import { WechatOrderShippingService } from '../../integrations/wechat/wechat-order-shipping.service';
|
||||
import type { Request } from 'express';
|
||||
|
||||
@Injectable()
|
||||
@@ -49,6 +50,7 @@ export class TradeService {
|
||||
private readonly promoCodeService: PromoCodeService,
|
||||
@Inject(forwardRef(() => FulfillmentService))
|
||||
private readonly fulfillmentService: FulfillmentService,
|
||||
private readonly wechatOrderShipping: WechatOrderShippingService,
|
||||
) {}
|
||||
|
||||
async preview(
|
||||
@@ -349,6 +351,8 @@ export class TradeService {
|
||||
}
|
||||
|
||||
if (order.deliveryType === 'ON_SITE_PICKUP') {
|
||||
// 现场取货:支付后即向微信录入「用户自提」发货信息
|
||||
this.wechatOrderShipping.uploadForOrderSafe(orderId);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1021,6 +1025,11 @@ export class TradeService {
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
// 发货信息管理:进入 SHIPPING 时向微信录入(解冻结算前置)
|
||||
if (targetStatus === 'SHIPPING' && currentStatus !== 'SHIPPING') {
|
||||
this.wechatOrderShipping.uploadForOrderSafe(orderId);
|
||||
}
|
||||
}
|
||||
|
||||
async getPartnerProxyOrderOptions(partnerAccountId: bigint) {
|
||||
|
||||
Reference in New Issue
Block a user