import { actionDefDict } from '../oak-app-domain'; 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 { 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 date = new Date(); const isoString = date.toISOString().replace('Z', ''); // 移除UTC的'Z' // 手动添加时区偏移(例如 +08:00) const finalFormat = `${isoString}+08:00`; // 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, }, logistics_type: 3, delivery_mode: 1, shipping_list: [ { item_desc: '账户充值', } ], upload_time: finalFormat, 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); } } } 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, }, logistics_type: shipType === 'express' ? 1 : 4, delivery_mode: 1, shipping_list: shippingList, upload_time: finalFormat, 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); } } } } } /** * 获取小程序订单状态 * @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, type } = 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 = 'unknow'; // let action = undefined; switch (orderState) { case 1: //待发货 state = 'unshipped'; break; case 2: //已发货 if (['pickup', 'virtual'].includes(type)) { state = 'receiving'; } else { state = 'shipping'; } // action = 'ship'; break; case 3: //确认收货 if (type === 'express') { state = 'receiving'; } break; case 4: //交易完成 state = 'received'; // action = 'receive'; break; } return state; } } } /** * 刷新小程序订单状态(自动确认收货) * @param context * @param shipId * @returns */ export async function refreshMpOrderShipState(shipId, context) { const [ship] = await context.select('ship', { data: { id: 1, iState: 1, }, filter: { id: shipId, } }, { blockTrigger: true, forUpdate: true }); const currentState = ship.iState; const mpState = await getOrderShipState(context, shipId); if (currentState !== mpState) { let action = undefined; const shipActionDef = actionDefDict.ship.iState.stm; for (const a of Object.keys(shipActionDef)) { const pState = shipActionDef[a][0]; const nState = shipActionDef[a][1]; if (mpState === nState && pState.includes(currentState)) { action = a; break; } } if (action) { return await context.operate('ship', { id: await generateNewIdAsync(), action, data: {}, filter: { id: ship.id, } }, {}); } } } /** * 刷新ship的物流状态 * @param ship * @param context * @returns */ export async function refreshShipState(shipId, context) { const ships = await context.select('ship', { data: shipProjection, filter: { id: shipId, } }, { blockTrigger: true, forUpdate: true, }); const ship = ships[0]; const { entity, entityId } = ship; if (entity && entityId) { const clazz = await getShipClazz(entity, entityId, context); const { state, time } = await clazz.syncState(ship.id, context); const wpPayIds = ship.shipOrder$ship?.map((ele) => ele.order).map((order) => order.pay$order).flat().filter((pay) => pay?.entity === 'wpProduct').map((ele) => ele?.id); let needReceiving = false; if (wpPayIds && wpPayIds.length > 0) { const cnt = await context.count('pay', { filter: { id: { $in: wpPayIds, }, iState: { $ne: 'closed', }, wpProduct: { needReceiving: true, } } }, {}); if (cnt > 0) { needReceiving = true; } } let action = undefined, updateData = {}; switch (state) { case 'shipping': //已发货 action = 'ship'; break; case 'received': //已收货 action = needReceiving ? 'startReceiving' : 'receive'; updateData = { receiveAt: time }; break; case 'unknow': //不明 action = 'unknow'; break; default: action = undefined; } if (action && ship.iState !== state) { return await context.operate('ship', { id: await generateNewIdAsync(), action, data: updateData, filter: { id: ship.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); } } }