643 lines
25 KiB
JavaScript
643 lines
25 KiB
JavaScript
/**
|
||
* @author: huxuejian
|
||
* @description: 蜂鸟配送相关业务代码
|
||
* @doc https://open.ele.me/
|
||
* @date: 2025-12-02
|
||
*/
|
||
const crypto = require('crypto');
|
||
const dayjs = require('dayjs');
|
||
const request = require('../../request');
|
||
const { getDatabase } = require("../../mongodb");
|
||
|
||
const baseUrl = process.env.CONFIG_FENGNIAO_API_DOMAIN;
|
||
const appId = process.env.CONFIG_FENGNIAO_APPID;
|
||
const appSecret = process.env.CONFIG_FENGNIAO_SECRET;
|
||
const merchantId = process.env.CONFIG_FENGNIAO_MERCHANT_ID;
|
||
const outShopCode = process.env.CONFIG_FENGNIAO_OUT_SHOP_CODE;
|
||
const prefix = 'FengNiao'
|
||
let dateBase = null;
|
||
const tokenInfo = {};
|
||
const peddingToken = {};
|
||
|
||
const OrderStatus = {
|
||
0: '订单生成',
|
||
1: '运单生成成功',
|
||
20: '骑手接单',
|
||
80: '骑手到店',
|
||
2: '配送中',
|
||
3: '已完成',
|
||
4: '已取消(非蜂鸟侧取消)',
|
||
5: '配送异常(蜂鸟侧取消)',
|
||
}
|
||
|
||
const cancelErrorCode = {
|
||
B0108: '运单状态不允许取消',
|
||
B0110: '运单已取消',
|
||
B0111: '运单不存在',
|
||
B0113: '使用免责取消权益失败',
|
||
A0100: '参数格式不正确',
|
||
A0101: '缺少必填参数',
|
||
"00001": '蜂鸟系统异常'
|
||
}
|
||
// const appSecret = '1111'
|
||
|
||
/**
|
||
* 安全解析 JSON,将超过安全整数范围的数字转换为字符串,避免精度丢失
|
||
* @param {string} jsonString - 要解析的 JSON 字符串
|
||
* @returns {any} 解析后的对象
|
||
*/
|
||
function safeJsonParse(jsonString) {
|
||
// 在解析前,使用正则表达式将超过安全整数范围的数字替换为字符串
|
||
// MAX_SAFE_INTEGER = 9007199254740991 (16位数字)
|
||
// 匹配 ":数字" 格式,其中数字后面跟着 , } ] 或字符串结束
|
||
const maxSafeInteger = Number.MAX_SAFE_INTEGER; // 9007199254740991
|
||
|
||
// 匹配 JSON 中的数字值:":数字" 后面跟着 , } ] 或空白字符或字符串结束
|
||
// 匹配16位或更多位的整数(可能超过安全整数范围),包括负数
|
||
const bigIntRegex = /":\s*([+-]?\d{16,})([,}\]\s]|$)/g;
|
||
|
||
const processedString = jsonString.replace(bigIntRegex, (match, numberStr, suffix) => {
|
||
// 对于16位或更多位的数字,检查是否超过安全整数范围
|
||
const isNegative = numberStr.startsWith('-');
|
||
const absNumberStr = isNegative ? numberStr.substring(1) : numberStr;
|
||
|
||
if (absNumberStr.length >= 16) {
|
||
// 进一步检查:如果数字确实超过 MAX_SAFE_INTEGER,转换为字符串
|
||
// 使用字符串比较来避免精度丢失
|
||
if (absNumberStr.length > 16 || absNumberStr > String(maxSafeInteger)) {
|
||
return `":"${numberStr}"${suffix}`;
|
||
}
|
||
}
|
||
return match;
|
||
});
|
||
|
||
return JSON.parse(processedString);
|
||
}
|
||
|
||
function getSignText(data) {
|
||
if (Object.prototype.toString.call(data) !== '[object Object]') {
|
||
throw new Error('获取签名文本失败: 参数类型错误')
|
||
}
|
||
const keys = Object.keys(data).sort();
|
||
const str = keys.map(key => `${key}=${data[key]}`).join('&');
|
||
const signBefore = `${appSecret}${str}`;
|
||
// 使用SHA-256加密signBefore
|
||
const messageDigest = crypto.createHash('sha256');
|
||
messageDigest.update(Buffer.from(signBefore, 'utf8'));
|
||
const signText = messageDigest.digest('hex');
|
||
return { sortParam: str, signBefore, signText };
|
||
}
|
||
|
||
async function getDb() {
|
||
if (!dateBase) {
|
||
dateBase = await getDatabase("Internet-hospital");
|
||
}
|
||
if (!dateBase) {
|
||
throw new Error("数据库连接失败");
|
||
}
|
||
return dateBase;
|
||
}
|
||
|
||
async function getToken(corpId) {
|
||
if (tokenInfo[corpId]) {
|
||
return tokenInfo[corpId];
|
||
}
|
||
const pendingToken = await getPendingToken(corpId);
|
||
return pendingToken;
|
||
}
|
||
|
||
async function getPendingToken(corpId) {
|
||
if (peddingToken[corpId]) {
|
||
return await peddingToken[corpId];
|
||
}
|
||
peddingToken[corpId] = getTokenFromDB(corpId);
|
||
const token = await peddingToken[corpId];
|
||
tokenInfo[corpId] = token;
|
||
peddingToken[corpId] = null;
|
||
return token;
|
||
}
|
||
|
||
async function getTokenFromDB(corpId) {
|
||
try {
|
||
const db = await getDb();
|
||
const hlwConfig = require('../hlw-config');
|
||
const { data } = await hlwConfig({ type: 'getHlwConfig', corpId }, db);
|
||
return data;
|
||
} catch (e) {
|
||
return null
|
||
}
|
||
}
|
||
|
||
async function getValidToken() {
|
||
if (!appId || !merchantId) {
|
||
return { success: false, message: 'appId或merchant_id不能为空' };
|
||
}
|
||
try {
|
||
const corpId = `${prefix}_${appId}_${merchantId}`;
|
||
const token = await getToken(corpId);
|
||
if (!token) return { success: false, message: '[蜂鸟配送]token不存在,请联系工作人员重新授权' };
|
||
const { expireTime, refreshTime } = token;
|
||
if (dayjs().isBefore(dayjs(expireTime))) {
|
||
return { success: true, message: '[蜂鸟配送]token未过期', token };
|
||
}
|
||
if (dayjs().isBefore(dayjs(refreshTime))) {
|
||
peddingToken[`refresh_${corpId}`] = peddingToken[`refresh_${corpId}`] || refreshFengniaoToken(token)
|
||
const newToken = await peddingToken[`refresh_${corpId}`];
|
||
peddingToken[`refresh_${corpId}`] = null;
|
||
if (newToken.success) {
|
||
return { success: true, message: '[蜂鸟配送]刷新token成功', token: newToken.data };
|
||
}
|
||
return { success: false, message: newToken.message };
|
||
}
|
||
return { success: false, message: '[蜂鸟配送]token已过期,请联系工作人员重新授权' };
|
||
} catch (e) {
|
||
return { success: true, message: `[蜂鸟配送]获取token错误:${e.message || '未知错误'}` };
|
||
|
||
}
|
||
}
|
||
|
||
async function getFengniaoToken(code = '') {
|
||
try {
|
||
const db = await getDb();
|
||
const params = {
|
||
app_id: appId,
|
||
merchant_id: merchantId,
|
||
grant_type: 'authorization_code',
|
||
timestamp: `${Date.now()}`,
|
||
}
|
||
if (code) {
|
||
params.code = code;
|
||
}
|
||
const { signText: signature } = getSignText(params);
|
||
params.signature = signature;
|
||
const url = `${baseUrl}/anubis-webapi/openapi/token`;
|
||
const res = await request.main(url, params, 'POST');
|
||
if (res && res.code === '200') {
|
||
const business_data = JSON.parse(res.business_data);
|
||
const {
|
||
app_id, // 应用id
|
||
merchant_id,//商户id
|
||
access_token, // 凭证token
|
||
refresh_token, // 刷新token
|
||
expire_in, //access_token剩余有效时间,单位:秒,默认有效期是一年
|
||
re_expire_in, //refresh_token剩余有效时间
|
||
} = business_data;
|
||
const data = {
|
||
app_id,
|
||
merchant_id,
|
||
access_token,
|
||
refresh_token,
|
||
expireTime: dayjs().add(expire_in, 'seconds').format('YYYY-MM-DD HH:mm:ss'),
|
||
refreshTime: dayjs().add(re_expire_in, 'seconds').format('YYYY-MM-DD HH:mm:ss'),
|
||
description: `蜂鸟配送token: 商户ID(${app_id})&门店ID(${merchant_id})`,
|
||
}
|
||
const corpId = `${prefix}_${app_id}_${merchant_id}`;
|
||
tokenInfo[corpId] = data;
|
||
const hlwConfig = require('../hlw-config');
|
||
await hlwConfig({ type: 'changeHlwConfig', corpId, data }, db);
|
||
return { success: true, message: '[蜂鸟配送api]获取token成功', data };
|
||
}
|
||
return { success: false, message: `[蜂鸟配送api]获取token失败:${res?.msg || '未知错误'}` };
|
||
} catch (e) {
|
||
return { success: false, message: `[蜂鸟配送api]获取token错误:${e.message || '未知错误'}` };
|
||
}
|
||
}
|
||
|
||
async function refreshFengniaoToken(token) {
|
||
try {
|
||
const db = await getDb();
|
||
const params = {
|
||
app_id: appId,
|
||
merchant_id: merchantId,
|
||
grant_type: 'refresh_token',
|
||
timestamp: `${Date.now()}`,
|
||
refresh_token: token.refresh_token,
|
||
}
|
||
const { signText: signature } = getSignText(params);
|
||
params.signature = signature;
|
||
const url = `${baseUrl}/anubis-webapi/openapi/refreshToken`;
|
||
const res = await request.main(url, params, 'POST');
|
||
if (res && res.code === '200') {
|
||
const business_data = JSON.parse(res.business_data);
|
||
const {
|
||
app_id, // 应用id
|
||
merchant_id,//商户id
|
||
access_token, // 凭证token
|
||
refresh_token, // 刷新token
|
||
expire_in, //access_token剩余有效时间,单位:秒,默认有效期是一年
|
||
re_expire_in, //refresh_token剩余有效时间
|
||
} = business_data;
|
||
const data = {
|
||
app_id,
|
||
merchant_id,
|
||
access_token,
|
||
refresh_token,
|
||
expireTime: dayjs().add(expire_in, 'seconds').format('YYYY-MM-DD HH:mm:ss'),
|
||
refreshTime: dayjs().add(re_expire_in, 'seconds').format('YYYY-MM-DD HH:mm:ss'),
|
||
}
|
||
const corpId = `${prefix}_${app_id}_${merchant_id}`;
|
||
tokenInfo[corpId] = data;
|
||
const hlwConfig = require('../hlw-config');
|
||
await hlwConfig({ type: 'changeHlwConfig', corpId, data }, db);
|
||
return { success: true, message: '[蜂鸟配送api]刷新token成功', data };
|
||
}
|
||
return { success: false, message: `[蜂鸟配送api]刷新token失败:${res?.msg || '未知错误'}` };
|
||
} catch (e) {
|
||
return { success: false, message: `[蜂鸟配送api]刷新token错误:${e.message || '未知错误'}` };
|
||
}
|
||
}
|
||
|
||
async function preCreateOrder(data) {
|
||
try {
|
||
const { success, message, token } = await getValidToken();
|
||
if (!success) return { success, message };
|
||
const businessData = {
|
||
partner_order_code: data.partner_order_code, // 外部订单号
|
||
goods_count: data.goods_count,// 货物件数
|
||
receiver_latitude: data.receiver_latitude, // 收货人纬度
|
||
goods_total_amount_cent: data.goods_total_amount_cent,//订单商品总金额 单位:分
|
||
order_type: 1,//订单类型(1:即时单,3:预约单)
|
||
// transport_longitude: data.transport_longitude,//取货点经度
|
||
// transport_latitude: data.transport_latitude,//取货点纬度
|
||
// transport_address: data.transport_address,//取货点地址
|
||
out_shop_code: data.out_shop_code,//外部门店id
|
||
goods_weight: data.goods_weight,
|
||
goods_item_list: data.goods_item_list.map(i => ({
|
||
item_actual_amount_cent: i.item_actual_amount_cent, // 商品总金额 (单价 x 数量) 单位:分
|
||
item_amount_cent: i.item_amount_cent, // 商品单价 单位:分
|
||
item_name: i.item_name, // 商品名称
|
||
item_quantity: i.item_quantity, // 商品数量
|
||
})),
|
||
goods_actual_amount_cent: data.goods_actual_amount_cent, // 订单商品实际总金额 单位:分
|
||
receiver_longitude: data.receiver_longitude, // 收货人经度
|
||
receiver_address: data.receiver_address, // 收货人地址
|
||
position_source: 3, // 坐标经纬度来源(1:腾讯地图,2:百度地图, 3:高德地图),蜂鸟建议使用高德地图
|
||
}
|
||
const params = {
|
||
app_id: appId,
|
||
merchant_id: merchantId,
|
||
timestamp: Date.now(),
|
||
version: '1.0',
|
||
business_data: JSON.stringify(businessData),
|
||
access_token: token.access_token,
|
||
}
|
||
const { signText: signature } = getSignText(params);
|
||
params.signature = signature;
|
||
const url = `${baseUrl}/anubis-webapi/v3/invoke/preCreateOrder`;
|
||
const res = await request.main(url, params, 'POST');
|
||
if (res && res.code === '200') {
|
||
const business_data = JSON.parse(res.business_data);
|
||
const goods_infos = Array.isArray(business_data.goods_infos) ? business_data.goods_infos : [];
|
||
const canDelivery = goods_infos.some(i => i && i.is_valid === 1);
|
||
const result = {
|
||
canDelivery,
|
||
distance: typeof business_data.distance === 'number' ? business_data.distance : undefined,
|
||
}
|
||
return { success: true, message: '[蜂鸟配送api]预下单成功', data: result };
|
||
}
|
||
return { success: false, message: `[蜂鸟配送api]预下单失败:${res?.msg || '未知错误'}` };
|
||
} catch (e) {
|
||
return { success: false, message: `[蜂鸟配送api]预下单错误:${e.message || '未知错误'}` };
|
||
}
|
||
}
|
||
|
||
async function createOrder(data) {
|
||
try {
|
||
const { success, message, token } = await getValidToken();
|
||
if (!success) return { success, message };
|
||
const businessData = {
|
||
partner_order_code: data.partner_order_code, // 外部订单号
|
||
receiver_primary_phone: data.receiver_primary_phone,// 收货人主要联系方式
|
||
goods_count: data.goods_count,// 货物件数
|
||
out_shop_code: data.out_shop_code,
|
||
receiver_latitude: data.receiver_latitude, // 收货人纬度
|
||
goods_total_amount_cent: data.goods_total_amount_cent,//订单商品总金额 单位:分
|
||
receiver_name: data.receiver_name,
|
||
order_type: 1,//订单类型(1:即时单,3:预约单)
|
||
goods_weight: data.goods_weight,
|
||
goods_item_list: data.goods_item_list.map(i => ({
|
||
item_actual_amount_cent: i.item_actual_amount_cent, // 商品总金额 (单价 x 数量) 单位:分
|
||
item_amount_cent: i.item_amount_cent, // 商品单价 单位:分
|
||
item_name: i.item_name, // 商品名称
|
||
item_quantity: i.item_quantity, // 商品数量
|
||
})),
|
||
serial_number: data.serial_number, // 商家订单流水号, 方便配送骑手到店取货, 支持数字,字母及#等常见字符. 建议填写。长度<=8。
|
||
goods_actual_amount_cent: data.goods_actual_amount_cent, // 订单商品实际总金额 单位:分
|
||
receiver_longitude: data.receiver_longitude, // 收货人经度
|
||
receiver_address: data.receiver_address, // 收货人地址
|
||
position_source: 3
|
||
}
|
||
const params = {
|
||
app_id: appId,
|
||
merchant_id: merchantId,
|
||
timestamp: Date.now(),
|
||
version: '1.0',
|
||
business_data: JSON.stringify(businessData),
|
||
access_token: token.access_token,
|
||
}
|
||
const { signText: signature } = getSignText(params);
|
||
params.signature = signature;
|
||
const url = `${baseUrl}/anubis-webapi/v3/invoke/createOrder`;
|
||
const res = await request.main(url, params, 'POST');
|
||
if (res && res.code === '200') {
|
||
const business_data = safeJsonParse(res.business_data);
|
||
return { success: true, message: '[蜂鸟配送api]下单成功', shippingNo: business_data.order_id };
|
||
}
|
||
return { success: false, message: `[蜂鸟配送api]下单失败:${res?.msg || '未知错误'}` };
|
||
} catch (e) {
|
||
return { success: false, message: `[蜂鸟配送api]下单错误:${e.message || '未知错误'}` };
|
||
}
|
||
}
|
||
|
||
async function getOrderDetail({ orderNo: partner_order_code, shippingNo: order_id }) {
|
||
try {
|
||
const { success, message, token } = await getValidToken();
|
||
if (!success) return { success, message };
|
||
const businessData = { order_id, partner_order_code };
|
||
const params = {
|
||
app_id: appId,
|
||
merchant_id: merchantId,
|
||
timestamp: Date.now(),
|
||
version: '1.0',
|
||
business_data: JSON.stringify(businessData),
|
||
access_token: token.access_token,
|
||
}
|
||
const { signText: signature } = getSignText(params);
|
||
params.signature = signature;
|
||
const url = `${baseUrl}/anubis-webapi/v3/invoke/getOrderDetail`;
|
||
const res = await request.main(url, params, 'POST');
|
||
if (res && res.code === '200') {
|
||
const business_data = safeJsonParse(res.business_data);
|
||
console.log(business_data)
|
||
const result = {
|
||
shippingNo: business_data.order_id,
|
||
knightName: business_data.carrier_driver_name,//配送员姓名
|
||
knightPhone: business_data.carrier_driver_phone,
|
||
arriveTime: business_data.estimate_arrive_time,//预计送达时间
|
||
partnerOrderCode: business_data.partner_order_code,//外部订单号
|
||
abnormalCode: business_data.abnormal_code,//异常编码
|
||
eventLogDetails: Array.isArray(business_data.event_log_details) ? business_data.event_log_details.map(i => ({
|
||
status: OrderStatus[i.order_status],//订单状态
|
||
orderStatus: i.order_status,//订单状态代码
|
||
time: i.occur_time && dayjs(i.occur_time).isValid() ? dayjs(i.occur_time).format('YYYY-MM-DD HH:mm:ss') : '',//事件时间
|
||
knightName: i.carrier_driver_name,//配送员姓名
|
||
knightPhone: i.carrier_driver_phone,
|
||
})) : [],
|
||
status: OrderStatus[business_data.order_status],//订单状态
|
||
photos: business_data.delivery_fetch_photos,//骑手取货照片地址列表
|
||
};
|
||
return { success: true, message: '[蜂鸟配送api]获取订单详情成功', data: result };
|
||
}
|
||
if (res && res.msg === '订单不存在') {
|
||
return { success: false, message: '[蜂鸟配送api]订单不存在', unexist: true };
|
||
}
|
||
return { success: false, message: `[蜂鸟配送api]获取订单详情失败:${res?.msg || '未知错误'}` };
|
||
} catch (e) {
|
||
return { success: false, message: `[蜂鸟配送api]获取订单详情错误:${e.message || '未知错误'}` };
|
||
}
|
||
}
|
||
|
||
async function cancelOrder(ctx) {
|
||
try {
|
||
const {
|
||
orderNo: partner_order_code,
|
||
role: order_cancel_role, // 1 商户 | 2 用户
|
||
cancelCode: order_cancel_code,
|
||
reason: order_cancel_other_reason = ''
|
||
} = ctx;
|
||
const { success, message, token } = await getValidToken();
|
||
if (!success) return { success, message };
|
||
const businessData = { partner_order_code, order_cancel_role, order_cancel_code, order_cancel_other_reason };
|
||
const params = {
|
||
app_id: appId,
|
||
merchant_id: merchantId,
|
||
timestamp: Date.now(),
|
||
version: '1.0',
|
||
business_data: JSON.stringify(businessData),
|
||
access_token: token.access_token,
|
||
}
|
||
const { signText: signature } = getSignText(params);
|
||
params.signature = signature;
|
||
const url = `${baseUrl}/anubis-webapi/v3/invoke/cancelOrder`;
|
||
const res = await request.main(url, params, 'POST');
|
||
if (res && res.code === '200') {
|
||
const business_data = JSON.parse(res.business_data);
|
||
if (business_data.result === true) {
|
||
return { success: true, message: '[蜂鸟配送api]取消成功' };
|
||
}
|
||
}
|
||
const msg = cancelErrorCode[res?.err_code] || '未知错误';
|
||
return { success: false, message: `[蜂鸟配送api]取消失败:${msg}` };
|
||
} catch (e) {
|
||
return { success: false, message: `[蜂鸟配送api]预取消错误:${e.message || '未知错误'}` };
|
||
}
|
||
}
|
||
|
||
async function preCancelOrder({ orderNo: partner_order_code, reasonCode: order_cancel_code }) {
|
||
try {
|
||
const { success, message, token } = await getValidToken();
|
||
if (!success) return { success, message };
|
||
const businessData = { order_cancel_code, partner_order_code };
|
||
const params = {
|
||
app_id: appId,
|
||
merchant_id: merchantId,
|
||
timestamp: Date.now(),
|
||
version: '1.0',
|
||
business_data: JSON.stringify(businessData),
|
||
access_token: token.access_token,
|
||
}
|
||
const { signText: signature } = getSignText(params);
|
||
params.signature = signature;
|
||
const url = `${baseUrl}/anubis-webapi/v3/invoke/preCancelOrder`;
|
||
const res = await request.main(url, params, 'POST');
|
||
if (res && res.code === '200') {
|
||
const business_data = safeJsonParse(res.business_data);
|
||
const cancelCostCent = business_data.actual_cancel_cost_cent
|
||
return { success: true, message: '[蜂鸟配送api]预取消成功', data: cancelCostCent };
|
||
}
|
||
return { success: false, message: res?.msg || '未知错误' };
|
||
} catch (e) {
|
||
return { success: false, message: e.message || '未知错误' };
|
||
}
|
||
}
|
||
|
||
async function getCancelReasonList({ orderNo: partner_order_code }) {
|
||
try {
|
||
if (!partner_order_code) {
|
||
return { success: false, message: '外部订单号不能为空' };
|
||
}
|
||
const { success, message, token } = await getValidToken();
|
||
if (!success) return { success, message };
|
||
const businessData = { partner_order_code };
|
||
const params = {
|
||
app_id: appId,
|
||
merchant_id: merchantId,
|
||
timestamp: Date.now(),
|
||
version: '1.0',
|
||
business_data: JSON.stringify(businessData),
|
||
access_token: token.access_token,
|
||
}
|
||
const { signText: signature } = getSignText(params);
|
||
params.signature = signature;
|
||
const url = `${baseUrl}/anubis-webapi/v3/invoke/getCancelReasonList`;
|
||
const res = await request.main(url, params, 'POST');
|
||
if (res && res.code === '200') {
|
||
const business_data = safeJsonParse(res.business_data);
|
||
const cancelReasonList = Array.isArray(business_data.cancel_reason_list) ? business_data.cancel_reason_list.map(i => ({
|
||
code: i.order_cancel_code,
|
||
name: i.order_cancel_desc
|
||
})) : [];
|
||
return { success: true, message: '[蜂鸟配送api]取消原因列表成功', data: cancelReasonList };
|
||
}
|
||
return { success: false, message: res?.msg || '未知错误' };
|
||
} catch (e) {
|
||
return { success: false, message: e.message || '未知错误' };
|
||
}
|
||
}
|
||
|
||
async function getStoreDeliveryRange() {
|
||
try {
|
||
const { success, message, token } = await getValidToken();
|
||
if (!success) return { success, message };
|
||
const businessData = { out_shop_code: outShopCode };
|
||
const params = {
|
||
app_id: appId,
|
||
merchant_id: merchantId,
|
||
timestamp: Date.now(),
|
||
version: '1.0',
|
||
business_data: JSON.stringify(businessData),
|
||
access_token: token.access_token,
|
||
}
|
||
const { signText: signature } = getSignText(params);
|
||
params.signature = signature;
|
||
const url = `${baseUrl}/anubis-webapi/v3/invoke/chainstoreRange`;
|
||
const res = await request.main(url, params, 'POST');
|
||
if (res && res.code === '200') {
|
||
const business_data = safeJsonParse(res.business_data);
|
||
return { success: true, message: '[蜂鸟配送api]查询门店配送范围成功', data: business_data };
|
||
}
|
||
return { success: false, message: `[蜂鸟配送api]查询门店配送范围失败:${res?.msg || '未知错误'}` };
|
||
} catch (e) {
|
||
return { success: false, message: `[蜂鸟配送api查询门店配送范围错误:${e.message || '未知错误'}` };
|
||
}
|
||
}
|
||
|
||
function testPreOrder(ctx) {
|
||
const address = typeof ctx.address === 'string' ? ctx.address.trim() : '';
|
||
const longitude = typeof ctx.longitude === 'number' ? ctx.longitude : 0;
|
||
const latitude = typeof ctx.latitude === 'number' ? ctx.latitude : 0;
|
||
if (!address || !latitude || !longitude) {
|
||
return { success: false, message: '地址、经纬度不能为空' };
|
||
}
|
||
|
||
const data = {
|
||
partner_order_code: `FN${Date.now()}${Math.floor(Math.random() * 10000)}`,
|
||
goods_count: 1,
|
||
out_shop_code: outShopCode,
|
||
receiver_latitude: latitude,
|
||
goods_total_amount_cent: 1,
|
||
goods_weight: 1,
|
||
goods_item_list: [
|
||
{
|
||
item_actual_amount_cent: 1,
|
||
item_amount_cent: 1,
|
||
item_name: '测试商品',
|
||
item_quantity: 1
|
||
}
|
||
],
|
||
goods_actual_amount_cent: 1,
|
||
receiver_longitude: longitude,
|
||
receiver_address: address,
|
||
}
|
||
return preCreateOrder(data);
|
||
}
|
||
|
||
exports.main = async (ctx) => {
|
||
if (ctx.type === 'testFengniaoAvailable') {
|
||
return await testPreOrder(ctx);
|
||
} else if (ctx.type === 'createOrder') {
|
||
return await createOrder(ctx.data);
|
||
} else if (ctx.type === 'getOrderDetail') {
|
||
return await getOrderDetail(ctx);
|
||
} else if (ctx.type === 'getCancelReasons') {
|
||
return await getCancelReasonList(ctx);
|
||
} else if (ctx.type === 'cancelOrder') {
|
||
return await cancelOrder(ctx);
|
||
} else if (ctx.type === 'preCancelFengniaoOrder') {
|
||
return await preCancelOrder(ctx);
|
||
}
|
||
return { success: false, message: '未找到接口' };
|
||
}
|
||
|
||
exports.handleFengNiaoAuth = async (req, res) => {
|
||
try {
|
||
const code = typeof req.body?.code === 'string' ? req.body?.code.trim() : '';
|
||
const merchant_id = typeof req.body?.merchant_id === 'string' ? req.body?.merchant_id.trim() : '';
|
||
if (code === '') {
|
||
return res.status(500).send("授权码不能为空");
|
||
}
|
||
if (merchant_id === '') {
|
||
return res.status(500).send("商户ID不能为空");
|
||
}
|
||
const db = await getDb();
|
||
await db.collection('third_merchant_notify').insertOne(
|
||
{
|
||
createTime: new Date(),
|
||
type: 'fengniaoAuthNotify',
|
||
data: { code, merchant_id },
|
||
originData: JSON.stringify(req.body),
|
||
}
|
||
)
|
||
if (merchantId !== merchant_id) {
|
||
return res.status(500).send("商户ID不匹配");
|
||
}
|
||
const { success, message } = await getFengniaoToken(code);
|
||
if (!success) {
|
||
return res.status(500).send(message);
|
||
}
|
||
return res.status(200).send("success");
|
||
} catch (e) {
|
||
res.status(500).send(e.message);
|
||
}
|
||
}
|
||
|
||
exports.notify = async (req, res) => {
|
||
try {
|
||
const {
|
||
app_id,
|
||
timestamp,
|
||
signature,
|
||
business_data
|
||
} = req.body;
|
||
if (app_id !== appId || !timestamp || typeof signature !== 'string' || signature.trim() == '' || typeof business_data !== 'string' || business_data.trim() == '') {
|
||
return res.status(500).send("缺少参数或参数错误");
|
||
}
|
||
const db = await getDb();
|
||
await db.collection('third_merchant_notify').insertOne(
|
||
{
|
||
createTime: new Date(),
|
||
type: 'fengniaoBusinessNotify',
|
||
data: JSON.parse(business_data),
|
||
originData: JSON.stringify(req.body),
|
||
}
|
||
)
|
||
return res.status(200).send("success");
|
||
} catch (e) {
|
||
console.log('蜂鸟推送数据错误: ', e.message)
|
||
res.status(500).send(e.message);
|
||
}
|
||
|
||
}
|
||
|
||
exports.fengniaoTest = async (ctx) => {
|
||
const api = ctx?.api;
|
||
if (api === 'getReasons') {
|
||
return await getCancelReasonList({ orderNo: ctx.orderNo });
|
||
} else if (api === 'preCancelOrder') {
|
||
return await preCancelOrder({ orderNo: ctx.orderNo, reasonCode: ctx.reasonCode });
|
||
} else if (api === 'cancelOrder') {
|
||
return await cancelOrder({ orderNo: ctx.orderNo, role: 1, cancelCode: ctx.cancelCode, reason: ctx.reason });
|
||
} else if (api === 'getStoreDeliveryRange') {
|
||
return await getStoreDeliveryRange();
|
||
}
|
||
} |