handleRiderStatus($data); case 'order_complete': // 订单完成回调 return $this->handleOrderComplete($data); case 'sf_cancel': // 顺丰原因取消回调 return $this->handleSfCancel($data); case 'rider_exception': // 订单异常回调 return $this->handleRiderException($data); case 'rider_recall': // 骑士撤单回调 return $this->handleRiderRecall($data); case 'bindnotify': // 授权状态回调 return $this->handleBindNotify($data); case 'cancelbindnotify': // 取消授权状态回调 return $this->handleCancelBindNotify($data); default: Yii::warning('未知的顺丰回调类型: ' . $urlIndex, 'shunfeng-callback'); return false; } } catch (\Exception $e) { Yii::error('顺丰回调处理异常: ' . $e->getMessage() . ' | ' . $e->getTraceAsString(), 'shunfeng-callback'); return false; } } /** * 通用订单回调处理逻辑 * * @param array $data 回调数据 * @param string $logDesc 日志描述 * @param string $orderIdField 订单ID字段名 * @param callable|null $extraHandler 额外处理逻辑,参数为 ($order, $sendStatus, $isGhs) * @return bool */ private function processOrderCallback($data, $logDesc, $orderIdField = 'sf_order_id', $extraHandler = null) { Yii::info($logDesc . ': ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'shunfeng-callback'); if (empty($data[$orderIdField]) || !isset($data['order_status'])) { Yii::error($logDesc . '参数不完整: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'shunfeng-callback'); return false; } $orderId = $data[$orderIdField]; // 查找配送订单 $deliveryOrder = $this->getDeliveryOrder($orderId); if (!$deliveryOrder) { Yii::error('顺丰配送订单不存在: ' . $orderIdField . '=' . $orderId, 'shunfeng-callback'); return false; } $sendStatus = $this->convertOrderSendStatus($data['order_status']); $orderStatus = $this->convertOrderStatus($data['order_status']); if ($sendStatus === null) { Yii::error('顺丰订单状态转换失败: ' . $data['order_status'], 'shunfeng-callback'); return false; } if($sendStatus == -1){ noticeUtil::push('顺丰跑腿产生订单取消回调'); } // 更新配送订单 $deliveryOrder->orderStatus = $sendStatus; $deliveryOrder->save(); // 更新主订单 if (isset($deliveryOrder->ghsOrderId)) { // 批发 util::checkRepeatCommit('ghs_pt_' . $deliveryOrder->ghsOrderId . '_' . $data['order_status'], 2); $order = OrderClass::getById($deliveryOrder->ghsOrderId, true, 'id,sendType,sendStatus,deliveryId,purchaseId,customId,status'); if ($order) { if ($extraHandler) { call_user_func($extraHandler, $order, $sendStatus, true, $data); } $cg = PurchaseClass::getById($order->purchaseId, true, 'id,sendStatus'); $cg->sendStatus = $sendStatus; $cg->save(); $order->sendStatus = $sendStatus; $order->status = $orderStatus; $order->save(); } } elseif (isset($deliveryOrder->hdOrderId)) { // 零售 util::checkRepeatCommit('hd_pt_' . $deliveryOrder->hdOrderId . '_' . $data['order_status'], 2); $order = HdOrderClass::getById($deliveryOrder->hdOrderId, true, 'id,sendType,sendStatus,deliveryId,customId,status'); if ($order) { if ($extraHandler) { call_user_func($extraHandler, $order, $sendStatus, false, $data); } $order->sendStatus = $sendStatus; $order->status = $orderStatus; $order->save(); } } return true; } /** * 处理配送状态更改回调 * 状态包括:10-配送员接单/改派;12-配送员到店;15-配送员配送中 * * @param array $data 回调数据 * @return bool 处理结果 */ private function handleRiderStatus($data) { return $this->processOrderCallback($data, '顺丰配送状态更改回调', 'sf_order_id'); } /** * 处理订单完成回调 * 订单状态:17-配送员点击完成 * * @param array $data 回调数据 * @return bool 处理结果 */ private function handleOrderComplete($data) { return $this->processOrderCallback($data, '顺丰订单完成回调', 'sf_order_id', function($order, $sendStatus, $isGhs, $data) { if ($isGhs && $sendStatus == 5) { $cg = PurchaseClass::getById($order->purchaseId, true, 'id,status,sendStatus'); if($cg->status == 3){ OrderClass::confirmReach($order, ['staffId' => 0, 'staffName' => '']); }else{ Yii::warning('进行确认送达时,采购单状态不为3,无法进行确认送达。orderId='.$order->id); noticeUtil::push('进行确认送达时,采购单状态不为3,无法进行确认送达。orderId='.$order->id.', purchaseId='.$cg->id); } } }); } /** * 处理顺丰原因取消回调 * 订单状态:2-订单取消 * * @param array $data 回调数据 * @return bool 处理结果 */ private function handleSfCancel($data) { return $this->processOrderCallback($data, '顺丰取消订单回调', 'sf_order_id', function($order, $sendStatus, $isGhs, $data) { if(!in_array($order->sendStatus, [-1, -2]) && in_array($sendStatus, [-1, -2])) { $cancelCode = $data['cancel_code']; $event = $this->cancelFrom($sendStatus, $cancelCode); if($isGhs) { OrderClass::cancelOrderSend($order, ['staffId' => 0, 'staffName' => '']); GhsNotifyClass::ptErrorNotify($order->id, $event); } if(!$isGhs) { HdNotifyClass::ptErrorNotify($order->id, $event); } noticeUtil::push('处理顺丰原因取消回调 -- orderId='.$order->id); } }); } /** * 处理订单异常回调 * 订单状态:91-骑士上报异常 * * @param array $data 回调数据 * @return bool 处理结果 */ private function handleRiderException($data) { return $this->processOrderCallback($data, '顺丰订单异常回调', 'sf_order_id', function($order, $sendStatus, $isGhs, $data) { if(!in_array($order->sendStatus, [-1, -2]) && in_array($sendStatus, [-1, -2])){ $exId = $data['ex_id']; $event = $this->cancelFrom($sendStatus, $exId); if ($isGhs) { OrderClass::cancelOrderSend($order, ['staffId' => 0, 'staffName' => '']); GhsNotifyClass::ptErrorNotify($order->id, $event); } if (!$isGhs) { HdNotifyClass::ptErrorNotify($order->id, $event); } noticeUtil::push('处理订单异常回调 -- orderId='.$order->id); } }); } /** * 处理骑士撤单回调 * 订单状态:22-配送员撤单 * * @param array $data 回调数据 * @return bool 处理结果 */ private function handleRiderRecall($data) { return $this->processOrderCallback($data, '顺丰骑士撤单回调', 'sf_order_id', function($order, $sendStatus, $isGhs, $data) { if (!in_array($order->sendStatus, [-1, -2]) && in_array($sendStatus, [-1, -2])) { $statusDesc = $data['status_desc']; if($isGhs){ GhsNotifyClass::ptErrorNotify($order->id, $statusDesc); } if(!$isGhs){ HdNotifyClass::ptErrorNotify($order->id, $statusDesc); } noticeUtil::push('处理骑士撤单回调 -- orderId='.$order->id); } }); } /** * 处理顺丰授权成功回调 * * @param array $data * @return bool */ private function handleBindNotify($data) { $sign = Yii::$app->request->get('sign'); Yii::info('顺丰授权状态回调(sign=' . ($sign ?? '') . '): ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'shunfeng-callback'); $mainId = isset($data['out_shop_id']) ? (int)$data['out_shop_id'] : 0; $shopId = isset($data['shop_id']) ? $data['shop_id'] : ''; if ($shopId === '') { Yii::error('顺丰授权状态回调参数缺失: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'shunfeng-callback'); return false; } $payload = [ 'mainId' => $mainId, 'platform' => 'shunfeng', 'shopId' => $shopId, 'authType' => 'all_store', 'accessToken' => $data['access_token'] ?? '', 'refreshToken' => $data['refresh_token'] ?? '', 'expiresAt' => time() + (100 * 86400 * 365), ]; $existing = DeliveryAuthTokenClass::getByCondition( ['mainId' => $mainId, 'platform' => 'shunfeng'], false, false, 'id' ); if ($existing && isset($existing['id'])) { DeliveryAuthTokenClass::updateById($existing['id'], [ 'shopId' => $payload['shopId'], 'authType' => $payload['authType'], 'accessToken' => $payload['accessToken'], 'refreshToken' => $payload['refreshToken'], 'expiresAt' => $payload['expiresAt'], ]); } else { DeliveryAuthTokenClass::add($payload); } return true; } /** * 处理顺丰取消授权回调 * * @param array $data * @return bool */ private function handleCancelBindNotify($data) { $sign = Yii::$app->request->get('sign'); Yii::info('顺丰取消授权状态回调(sign=' . ($sign ?? '') . '): ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'shunfeng-callback'); $shopId = isset($data['shop_id']) ? $data['shop_id'] : ''; if ($shopId === '') { Yii::error('顺丰授权状态回调参数缺失: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'shunfeng-callback'); return false; } if (!empty($data['app_id'])) { $auth = new Auth(); if ((string)$auth->getAppKey() !== (string)$data['app_id']) { Yii::warning('顺丰取消授权回调 app_id 不匹配: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'shunfeng-callback'); return false; } } $condition = [ 'platform' => 'shunfeng', 'shopId' => $shopId ]; $deleted = DeliveryAuthTokenClass::deleteByCondition($condition); if ($deleted === 0) { Yii::warning('顺丰取消授权回调未删除任何记录: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'shunfeng-callback'); } return true; } /** * 转换顺丰订单状态到系统配送状态 * * 顺丰状态说明: * - 10: 配送员接单/配送员改派 * - 12: 配送员到店 * - 15: 配送员配送中 * - 17: 配送员点击完成 * - 2: 订单取消 * - 22: 配送员撤单 * - 91: 骑士上报异常 * * 系统状态说明: * - 0: 订单生成 * - 1: 系统已接单 * - 2: 派单中 * - 3: 待取货 * - 4: 配送中 * - 5: 已送达 * - -1: 已取消 * - -2: 异常 * - 10: 改派中 * - 20: 已分配骑手 * - 30: 骑手已到店 * - 40: 申请取消中 * - 50: 客服介入处理中 * * @param int $sfOrderStatus 顺丰订单状态 * @return int|null 系统配送状态,null表示不需要更新主订单状态 */ private function convertOrderSendStatus($sfOrderStatus) { // 顺丰状态映射到系统配送状态 $statusMap = [ 10 => 20, // 配送员接单/改派 -> 已分配骑手 12 => 30, // 配送员到店 -> 骑手已到店 15 => 4, // 配送员配送中 -> 配送中 17 => 5, // 配送完成 -> 已送达 2 => -1, // 订单取消 -> 已取消 22 => 2, // 配送员撤单 -> 派单中(重新派单) 91 => -2, // 骑士上报异常 -> 异常 ]; return $statusMap[$sfOrderStatus] ?? null; } /** * 转换顺丰订单状态到系统订单状态 * * @param int $sfOrderStatus 顺丰订单状态 * @return int|null 系统订单状态,null表示不需要更新主订单状态 */ private function convertOrderStatus($sfOrderStatus) { // 顺丰状态映射到系统订单状态 // 系统订单状态: 1待付款 2待配送 3配送中 4已完成 5已取消 $statusMap = [ 10 => 3, // 配送员接单/改派 -> 配送中 12 => 3, // 配送员到店 -> 配送中 15 => 3, // 配送员配送中 -> 配送中 17 => 4, // 配送完成 -> 已完成 2 => 2, // 订单取消 -> 待配送(配送的取消不等于订单的取消,由于 status 带有配送状态,所以改为:待配送) 22 => 3, // 配送员撤单 -> 配送中 91 => 3, // 骑士上报异常 -> 配送中 ]; return $statusMap[$sfOrderStatus] ?? null; } /** * 根据顺丰订单号查找配送订单 * * @param string $orderId * @return object|null */ private function getDeliveryOrder($orderId) { // 查找批发配送订单 $deliveryOrder = GhsDeliveryOrderClass::getByCondition([ 'deliveryId' => 'shunfeng', 'orderId' => $orderId ], true, 'id DESC'); if ($deliveryOrder) { return $deliveryOrder; } // 查找零售配送订单 return HdDeliveryOrderClass::getByCondition([ 'deliveryId' => 'shunfeng', 'orderId' => $orderId ], true, 'id DESC'); } private function cancelFrom($sendStatus, $cancelCode = 0) { if($sendStatus == -1){ $reasons = [ 100 => '误操作,抢错订单', 101 => '订单距离太远', 102 => '餐箱已满,无法配送', 103 => '物品过大无法配送', 104 => '联系不上寄件人', 105 => '寄方无法提供待配送物品(缺货/出餐慢/已配送等)', 106 => '寄方要求取消(暂时不需要寄件了/重复发单/信息有误)', 107 => '配送车辆故障', 108 => '其他,请注明原因', 303 => '骑士要求取消', 310 => '超出配送范围', 500 => '15分钟无人接单', 501 => '不兜底订单自动取消', 502 => '超期望送达8小时未完成', ]; return $reasons[$cancelCode] ?? '未知'; } if($sendStatus == -2){ $exIds = [ 1001 => '商家出货慢', 1007 => '更改取货地址', 2001 => '顾客电话无法接通', 2004 => '更改期望送达时间', 2005 => '顾客拒收', 2008 => '顾客不在家', 2009 => '更改送货地址', 2010 => '顾客拒绝实名认证', 2013 => '无法联系用户,退回寄件人', 3004 => '实名认证校验失败', 4001 => '配送地址错误', 4002 => '其他', 4003 => '托寄物丢失或损坏', ]; return $exIds[$cancelCode] ?? '未知'; } return '未知'; } }