435 lines
13 KiB
JavaScript
435 lines
13 KiB
JavaScript
import { WechatSDK } from 'oak-external-sdk';
|
|
import { assert } from 'oak-domain/lib/utils/assert';
|
|
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
|
|
import { UploadShipException } from '../types/Exception';
|
|
import dayjs from 'dayjs';
|
|
import { isMobile } from 'oak-domain/lib/utils/validator';
|
|
import { getShipClazz } from './shipClazz';
|
|
export const shipProjection = {
|
|
id: 1,
|
|
type: 1,
|
|
iState: 1,
|
|
entity: 1,
|
|
entityId: 1,
|
|
extraShipId: 1,
|
|
shipService: {
|
|
id: 1,
|
|
name: 1,
|
|
shipCompany: {
|
|
id: 1,
|
|
name: 1,
|
|
abbr: 1,
|
|
wechatMpName: 1,
|
|
}
|
|
},
|
|
deposit$ship: {
|
|
$entity: 'deposit',
|
|
data: {
|
|
id: 1,
|
|
iState: 1,
|
|
accountId: 1,
|
|
pay$deposit: {
|
|
$entity: 'pay',
|
|
data: {
|
|
id: 1,
|
|
applicationId: 1,
|
|
application: {
|
|
id: 1,
|
|
type: 1,
|
|
config: 1,
|
|
},
|
|
price: 1,
|
|
meta: 1,
|
|
iState: 1,
|
|
entity: 1,
|
|
entityId: 1,
|
|
},
|
|
filter: {
|
|
iState: 'paid'
|
|
}
|
|
}
|
|
}
|
|
},
|
|
shipOrder$ship: {
|
|
$entity: 'shipOrder',
|
|
data: {
|
|
id: 1,
|
|
orderId: 1,
|
|
order: {
|
|
id: 1,
|
|
iState: 1,
|
|
pay$order: {
|
|
$entity: 'pay',
|
|
data: {
|
|
id: 1,
|
|
applicationId: 1,
|
|
application: {
|
|
id: 1,
|
|
type: 1,
|
|
config: 1,
|
|
},
|
|
price: 1,
|
|
meta: 1,
|
|
iState: 1,
|
|
entity: 1,
|
|
entityId: 1,
|
|
},
|
|
filter: {
|
|
iState: 'paid'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
from: {
|
|
id: 1,
|
|
detail: 1,
|
|
name: 1,
|
|
phone: 1,
|
|
area: {
|
|
id: 1,
|
|
name: 1,
|
|
parent: {
|
|
id: 1,
|
|
name: 1,
|
|
parent: {
|
|
id: 1,
|
|
name: 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
to: {
|
|
id: 1,
|
|
detail: 1,
|
|
name: 1,
|
|
phone: 1,
|
|
area: {
|
|
id: 1,
|
|
name: 1,
|
|
parent: {
|
|
id: 1,
|
|
name: 1,
|
|
parent: {
|
|
id: 1,
|
|
name: 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
/**
|
|
* 手机号掩码(130****0000)
|
|
* @param phone
|
|
*/
|
|
export function maskPhone(phone) {
|
|
assert(isMobile(phone));
|
|
const start = phone.slice(3);
|
|
const end = phone.slice(-4);
|
|
return start + '****' + end;
|
|
}
|
|
/**
|
|
* 小程序发货信息录入
|
|
* @param shipInfo
|
|
* @param context
|
|
* @returns
|
|
*/
|
|
export async function uploadShippingInfo(shipId, context) {
|
|
const [ship] = await context.select('ship', {
|
|
data: shipProjection,
|
|
filter: {
|
|
id: shipId,
|
|
}
|
|
}, { forUpdate: true });
|
|
const { deposit$ship: deposits, shipOrder$ship, type: shipType, extraShipId, shipService, from, to } = ship;
|
|
// assert(iState === 'unshipped'); //可更改一次发货信息
|
|
//发货信息录入前检查小程序订单发货状态
|
|
const shipState = await getOrderShipState(context, shipId);
|
|
const now = dayjs().format();
|
|
// const application = context.getApplication();
|
|
// const { type, config } = application!;
|
|
// assert(type === 'wechatMp');
|
|
// const { appId, appSecret } = config as WechatMpConfig;
|
|
// const wechatInstance = WechatSDK.getInstance(
|
|
// appId,
|
|
// 'wechatMp',
|
|
// appSecret,
|
|
// ) as WechatMpInstance;
|
|
if (deposits && deposits.length > 0 && shipState === 'unshipped') {
|
|
//充值
|
|
assert(deposits.length === 1);
|
|
const [deposit] = deposits;
|
|
const { pay$deposit: pays, } = deposit;
|
|
const pay = pays?.[0];
|
|
const meta = pay?.meta;
|
|
const shipInfo = {
|
|
order_key: {
|
|
order_number_type: 2,
|
|
transaction_id: meta?.transaction_id,
|
|
},
|
|
logistic_type: 3,
|
|
delivery_mode: 1,
|
|
shipping_list: [
|
|
{
|
|
item_desc: '账户充值',
|
|
}
|
|
],
|
|
upload_time: now,
|
|
payer: {
|
|
openid: meta?.payer?.openid,
|
|
},
|
|
};
|
|
const { type, config } = pay?.application;
|
|
if (type === 'wechatMp') {
|
|
const { appId, appSecret } = config;
|
|
const wechatInstance = WechatSDK.getInstance(appId, 'wechatMp', appSecret);
|
|
const result = await wechatInstance.uploadShippingInfo(shipInfo);
|
|
if (result?.errcode !== 0) {
|
|
console.error(JSON.stringify(result));
|
|
throw new UploadShipException(result?.message);
|
|
}
|
|
}
|
|
}
|
|
else if (shipOrder$ship && shipOrder$ship.length > 0 && shipState && ['unshipped', 'shipping'].includes(shipState)) {
|
|
//订单 每笔微信支付调用一次接口
|
|
//当已发货的订单再次调用小程序发货信息录入接口视为重新发货(仅可重新发货一次)
|
|
const orders = shipOrder$ship.map((ele) => ele.order);
|
|
const fromPhoneStr = maskPhone(from.phone);
|
|
const toPhoneStr = maskPhone(to.phone);
|
|
for (const order of orders) {
|
|
const { pay$order, desc } = order;
|
|
const wechatPay = pay$order.find((ele) => ele.entity === 'wpProduct');
|
|
const meta = wechatPay?.meta;
|
|
let shippingList = [];
|
|
if (shipType === 'express') {
|
|
shippingList = [
|
|
{
|
|
tracking_no: extraShipId,
|
|
express_company: shipService?.shipCompany?.wechatMpName,
|
|
item_desc: desc,
|
|
contact: {
|
|
consignor_contact: fromPhoneStr,
|
|
receiver_contact: toPhoneStr,
|
|
}
|
|
}
|
|
];
|
|
}
|
|
else if (shipType === 'pickup') {
|
|
shippingList = [{
|
|
item_desc: desc,
|
|
}];
|
|
}
|
|
const shipInfo = {
|
|
order_key: {
|
|
order_number_type: 2,
|
|
transaction_id: meta?.transaction_id,
|
|
},
|
|
logistic_type: shipType === 'express' ? 1 : 4,
|
|
delivery_mode: 1,
|
|
shipping_list: shippingList,
|
|
upload_time: now,
|
|
payer: {
|
|
openid: meta?.payer?.openid,
|
|
},
|
|
};
|
|
const { type, config } = wechatPay?.application;
|
|
if (type === 'wechatMp') {
|
|
const { appId, appSecret } = config;
|
|
const wechatInstance = WechatSDK.getInstance(appId, 'wechatMp', appSecret);
|
|
const result = await wechatInstance.uploadShippingInfo(shipInfo);
|
|
if (result?.errcode !== 0) {
|
|
console.error(JSON.stringify(result));
|
|
throw new UploadShipException(result?.message);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* 获取小程序物流状态
|
|
* @param context
|
|
* @param shipId
|
|
* @returns
|
|
*/
|
|
export async function getOrderShipState(context, shipId) {
|
|
const [ship] = await context.select('ship', {
|
|
data: shipProjection,
|
|
filter: {
|
|
id: shipId,
|
|
}
|
|
}, {
|
|
blockTrigger: true,
|
|
forUpdate: true
|
|
});
|
|
assert(ship);
|
|
const { deposit$ship: deposits, shipOrder$ship } = ship;
|
|
let info = undefined, wechatInstance = undefined;
|
|
if (deposits && deposits.length > 0) {
|
|
//充值
|
|
const [deposit] = deposits;
|
|
const { pay$deposit: pays, } = deposit;
|
|
const pay = pays?.[0];
|
|
if (shipId && pay) {
|
|
const { config } = pay.application;
|
|
const { appId, appSecret } = config;
|
|
wechatInstance = WechatSDK.getInstance(appId, 'wechatMp', appSecret);
|
|
info = {
|
|
transaction_id: pay?.meta?.transaction_id
|
|
};
|
|
}
|
|
}
|
|
else if (shipOrder$ship && shipOrder$ship.length > 0) {
|
|
const order = shipOrder$ship[0].order;
|
|
const { pay$order: pays, } = order;
|
|
const pay = pays?.find((ele) => ele.entity === 'wpProduct');
|
|
if (shipId && pay) {
|
|
const { config } = pay.application;
|
|
const { appId, appSecret } = config;
|
|
wechatInstance = WechatSDK.getInstance(appId, 'wechatMp', appSecret);
|
|
info = {
|
|
transaction_id: pay?.meta?.transaction_id
|
|
};
|
|
}
|
|
}
|
|
if (info && wechatInstance) {
|
|
const result = await wechatInstance.getOrderState(info);
|
|
const { orderState, inComplaint } = result;
|
|
if (!inComplaint) {
|
|
//未处于纠纷中
|
|
let state = undefined;
|
|
// let action = undefined;
|
|
switch (orderState) {
|
|
case 1: //待发货
|
|
state = 'unshipped';
|
|
break;
|
|
case 2: //已发货
|
|
state = 'shipping';
|
|
// action = 'ship';
|
|
break;
|
|
case 3: //确认收货
|
|
state = 'received';
|
|
// action = 'receive';
|
|
break;
|
|
}
|
|
return state;
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* 刷新ship的物流状态
|
|
* @param ship
|
|
* @param context
|
|
* @returns
|
|
*/
|
|
export async function refreshtShipState(ship, context) {
|
|
let ship2 = ship;
|
|
if (!ship2.iState || !ship2.entity || !ship2.entityId) {
|
|
const ships = await context.select('ship', {
|
|
data: shipProjection,
|
|
filter: {
|
|
id: ship.id,
|
|
}
|
|
}, {
|
|
blockTrigger: true,
|
|
forUpdate: true,
|
|
});
|
|
ship2 = ships[0];
|
|
}
|
|
const { entity, entityId } = ship2;
|
|
const clazz = await getShipClazz(entity, entityId, context);
|
|
const { state, time } = await clazz.syncState(ship2.id, context);
|
|
let action = undefined, updateData = {};
|
|
switch (state) {
|
|
case 'shipping': //已发货
|
|
action = 'ship';
|
|
break;
|
|
case 'received': //已收货
|
|
action = 'receive';
|
|
updateData = {
|
|
receiveAt: time
|
|
};
|
|
break;
|
|
case 'unknow': //不明
|
|
action = 'unknow';
|
|
break;
|
|
default:
|
|
action = undefined;
|
|
}
|
|
if (action && ship2.iState !== state) {
|
|
return await context.operate('ship', {
|
|
id: await generateNewIdAsync(),
|
|
action,
|
|
data: updateData,
|
|
filter: {
|
|
id: ship2.id,
|
|
}
|
|
}, {});
|
|
}
|
|
}
|
|
/**
|
|
* 小程序确认收货提醒
|
|
*/
|
|
export async function notifyConfirmReceive(shipId, context) {
|
|
const [ship] = await context.select('ship', {
|
|
data: {
|
|
id: 1,
|
|
receiveAt: 1,
|
|
shipOrder$ship: {
|
|
$entity: 'shipOrder',
|
|
data: {
|
|
id: 1,
|
|
orderId: 1,
|
|
order: {
|
|
id: 1,
|
|
iState: 1,
|
|
pay$order: {
|
|
$entity: 'pay',
|
|
data: {
|
|
id: 1,
|
|
applicationId: 1,
|
|
application: {
|
|
id: 1,
|
|
type: 1,
|
|
config: 1,
|
|
},
|
|
price: 1,
|
|
meta: 1,
|
|
iState: 1,
|
|
entity: 1,
|
|
entityId: 1,
|
|
},
|
|
filter: {
|
|
iState: 'paid'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
},
|
|
filter: {
|
|
id: shipId,
|
|
}
|
|
}, {
|
|
blockTrigger: true,
|
|
forUpdate: true
|
|
});
|
|
assert(ship);
|
|
const { shipOrder$ship, receiveAt } = ship;
|
|
assert(!!receiveAt && shipOrder$ship && shipOrder$ship.length > 0);
|
|
const orders = shipOrder$ship.map((ele) => ele.order);
|
|
for (const order of orders) {
|
|
const wechatPay = order.pay$order.find((ele) => ele.entity === 'wpProduct');
|
|
const meta = wechatPay?.meta;
|
|
const notifyInfo = {
|
|
transaction_id: meta.transaction_id,
|
|
received_time: receiveAt.valueOf(),
|
|
};
|
|
const { type, config } = wechatPay?.application;
|
|
if (type === 'wechatMp') {
|
|
const { appId, appSecret } = config;
|
|
const wechatInstance = WechatSDK.getInstance(appId, 'wechatMp', appSecret);
|
|
await wechatInstance.notifyConfirmReceive(notifyInfo);
|
|
}
|
|
}
|
|
}
|