CallBackHandler.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. <?php
  2. namespace common\components\delivery\platform\shunfeng;
  3. use bizGhs\express\classes\DeliveryAuthTokenClass;
  4. use bizGhs\express\classes\GhsDeliveryOrderClass;
  5. use bizHd\express\classes\HdDeliveryOrderClass;
  6. use bizGhs\order\classes\OrderClass;
  7. use bizHd\order\classes\OrderClass as HdOrderClass;
  8. use Yii;
  9. /**
  10. * 顺丰同城回调处理类
  11. * 处理顺丰平台的各种回调通知
  12. *
  13. * Class CallBackHandler
  14. * @package common\components\delivery\platform\shunfeng
  15. */
  16. class CallBackHandler
  17. {
  18. /**
  19. * 处理顺丰回调
  20. * 根据 url_index 参数判断回调类型并分发到对应的处理方法
  21. *
  22. * @param array $data 回调数据
  23. * @return bool 处理结果
  24. */
  25. public function handle($data)
  26. {
  27. try {
  28. // 记录回调原始数据
  29. Yii::info('顺丰同城回调 - 原始数据: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'shunfeng-callback');
  30. // 验证必要参数
  31. if (empty($data['url_index'])) {
  32. Yii::error('顺丰回调缺少 url_index 参数: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'shunfeng-callback');
  33. return false;
  34. }
  35. $urlIndex = $data['url_index'];
  36. // 根据回调类型分发处理
  37. switch ($urlIndex) {
  38. case 'rider_status':
  39. // 配送状态更改回调
  40. return $this->handleRiderStatus($data);
  41. case 'order_complete':
  42. // 订单完成回调
  43. return $this->handleOrderComplete($data);
  44. case 'sf_cancel':
  45. // 顺丰原因取消回调
  46. return $this->handleSfCancel($data);
  47. case 'rider_exception':
  48. // 订单异常回调
  49. return $this->handleRiderException($data);
  50. case 'rider_recall':
  51. // 骑士撤单回调
  52. return $this->handleRiderRecall($data);
  53. case 'bindnotify':
  54. // 授权状态回调
  55. return $this->handleBindNotify($data);
  56. case 'cancelbindnotify':
  57. // 取消授权状态回调
  58. return $this->handleCancelBindNotify($data);
  59. default:
  60. Yii::warning('未知的顺丰回调类型: ' . $urlIndex, 'shunfeng-callback');
  61. return false;
  62. }
  63. } catch (\Exception $e) {
  64. Yii::error('顺丰回调处理异常: ' . $e->getMessage() . ' | ' . $e->getTraceAsString(), 'shunfeng-callback');
  65. return false;
  66. }
  67. }
  68. /**
  69. * 处理配送状态更改回调
  70. * 状态包括:10-配送员接单/改派;12-配送员到店;15-配送员配送中
  71. *
  72. * @param array $data 回调数据
  73. * @return bool 处理结果
  74. */
  75. private function handleRiderStatus($data)
  76. {
  77. Yii::info('顺丰配送状态更改回调: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'shunfeng-callback');
  78. // 验证必要参数
  79. if (empty($data['sf_order_id']) || !isset($data['order_status'])) {
  80. Yii::error('顺丰配送状态回调参数不完整: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'shunfeng-callback');
  81. return false;
  82. }
  83. $orderId = $data['sf_order_id'];
  84. // 查找配送订单
  85. $deliveryOrder = $this->getDeliveryOrder($orderId);
  86. if (!$deliveryOrder) {
  87. Yii::error('顺丰配送订单不存在: sf_order_id=' . $orderId, 'shunfeng-callback');
  88. return false;
  89. }
  90. // 更新配送订单信息
  91. $updateData = [];
  92. // 更新配送员信息
  93. $updateData['riderName'] = $data['operator_name'];
  94. $updateData['riderPhone'] = $data['operator_phone'];
  95. $updateData['riderLng'] = $data['rider_lng'];
  96. $updateData['riderLat'] = $data['rider_lat'];
  97. $updateData['statusDesc'] = $data['status_desc'];
  98. $updateData['pushTime'] = date('Y-m-d H:i:s', $data['push_time']);
  99. $orderStatus = $data['order_status'];
  100. // 转换顺丰订单状态到系统状态
  101. $sendStatus = $this->convertOrderSendStatus($orderStatus);
  102. $orderStatus = $this->convertOrderStatus($orderStatus);
  103. if ($sendStatus !== null) {
  104. // 更新配送订单
  105. $deliveryOrder->orderStatus = $sendStatus;
  106. $deliveryOrder->save();
  107. // 更新主订单的配送状态
  108. if(isset($deliveryOrder->ghsOrderId)){ // 批发
  109. $order = OrderClass::getById($deliveryOrder->ghsOrderId, true);
  110. if ($order) {
  111. $order->sendStatus = $sendStatus;
  112. $order->status = $orderStatus;
  113. $order->save();
  114. }
  115. }elseif(isset($deliveryOrder->hdOrderId)){ // 零售
  116. $order = HdOrderClass::getById($deliveryOrder->hdOrderId, true);
  117. if ($order) {
  118. $order->sendStatus = $sendStatus;
  119. $order->status = $orderStatus;
  120. $order->save();
  121. }
  122. }
  123. } else {
  124. return false;
  125. }
  126. return true;
  127. }
  128. /**
  129. * 处理订单完成回调
  130. * 订单状态:17-配送员点击完成
  131. *
  132. * @param array $data 回调数据
  133. * @return bool 处理结果
  134. */
  135. private function handleOrderComplete($data)
  136. {
  137. Yii::info('顺丰订单完成回调: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'shunfeng-callback');
  138. // 验证必要参数
  139. if (empty($data['sf_order_id'])) {
  140. Yii::error('顺丰订单完成回调参数不完整: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'shunfeng-callback');
  141. return false;
  142. }
  143. $orderId = $data['sf_order_id'];
  144. // 查找配送订单
  145. $deliveryOrder = $this->getDeliveryOrder($orderId);
  146. if (!$deliveryOrder) {
  147. Yii::error('顺丰配送订单不存在: sf_order_id=' . $orderId, 'shunfeng-callback');
  148. return false;
  149. }
  150. $sendStatus = $this->convertOrderSendStatus($data['order_status']);
  151. $orderStatus = $this->convertOrderStatus($data['order_status']);
  152. if ($sendStatus === null) {
  153. Yii::error('顺丰订单状态转换失败: ' . $data['order_status'], 'shunfeng-callback');
  154. return false;
  155. }
  156. $deliveryOrder->orderStatus = $sendStatus;
  157. $deliveryOrder->save();
  158. // 更新主订单状态为已送达
  159. if(isset($deliveryOrder->ghsOrderId)){ // 批发
  160. $order = OrderClass::getById($deliveryOrder->ghsOrderId, true, 'id,sendStatus,deliveryId,purchaseId,customId,status');
  161. if ($order) {
  162. $order->sendStatus = $sendStatus;
  163. $order->status = $orderStatus;
  164. $order->save();
  165. // 确认送达,更新主订单状态
  166. if ($sendStatus == 5) {
  167. OrderClass::confirmReach($order, ['staffId' => 0, 'staffName' => '']);
  168. }
  169. }
  170. }elseif(isset($deliveryOrder->hdOrderId)){ // 零售
  171. $order = HdOrderClass::getById($deliveryOrder->hdOrderId, true);
  172. if ($order) {
  173. $order->sendStatus = $sendStatus;
  174. $order->status = $orderStatus;
  175. $order->save();
  176. }
  177. }
  178. return true;
  179. }
  180. /**
  181. * 处理顺丰原因取消回调
  182. * 订单状态:2-订单取消
  183. *
  184. * @param array $data 回调数据
  185. * @return bool 处理结果
  186. */
  187. private function handleSfCancel($data)
  188. {
  189. Yii::info('顺丰取消订单回调: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'shunfeng-callback');
  190. // 验证必要参数
  191. if (empty($data['sf_order_id'])) {
  192. Yii::error('顺丰取消回调参数不完整: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'shunfeng-callback');
  193. return false;
  194. }
  195. $orderId = $data['sf_order_id'];
  196. // 查找配送订单
  197. $deliveryOrder = $this->getDeliveryOrder($orderId);
  198. if (!$deliveryOrder) {
  199. Yii::error('顺丰配送订单不存在: sf_order_id=' . $orderId, 'shunfeng-callback');
  200. return false;
  201. }
  202. $sendStatus = $this->convertOrderSendStatus($data['order_status']);
  203. $orderStatus = $this->convertOrderStatus($data['order_status']);
  204. if ($sendStatus === null) {
  205. Yii::error('顺丰订单状态转换失败: ' . $data['order_status'], 'shunfeng-callback');
  206. return false;
  207. }
  208. $deliveryOrder->orderStatus = $sendStatus;
  209. $deliveryOrder->save();
  210. // 更新主订单状态为取消
  211. if(isset($deliveryOrder->ghsOrderId)) { // 批发
  212. $order = OrderClass::getById($deliveryOrder->ghsOrderId, true, 'id,sendStatus,deliveryId,purchaseId,customId,status');
  213. if($order) {
  214. $order->sendStatus = $sendStatus;
  215. $order->status = $orderStatus;
  216. $order->save();
  217. // 更新主订单状态为取消
  218. if($sendStatus == -1) {
  219. OrderClass::cancelOrderSend($order, ['staffId' => 0, 'staffName' => '']);
  220. }
  221. }
  222. }elseif(isset($deliveryOrder->hdOrderId)){ // 零售
  223. $order = HdOrderClass::getById($deliveryOrder->hdOrderId, true);
  224. if ($order) {
  225. $order->sendStatus = $sendStatus;
  226. $order->status = $orderStatus;
  227. $order->save();
  228. }
  229. }
  230. return true;
  231. }
  232. /**
  233. * 处理订单异常回调
  234. * 订单状态:91-骑士上报异常
  235. *
  236. * @param array $data 回调数据
  237. * @return bool 处理结果
  238. */
  239. private function handleRiderException($data)
  240. {
  241. Yii::info('顺丰订单异常回调: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'shunfeng-callback');
  242. // 验证必要参数
  243. if (empty($data['sf_order_id'])) {
  244. Yii::error('顺丰异常回调参数不完整: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'shunfeng-callback');
  245. return false;
  246. }
  247. $orderId = $data['sf_order_id'];
  248. // 查找配送订单
  249. $deliveryOrder = $this->getDeliveryOrder($orderId);
  250. if (!$deliveryOrder) {
  251. Yii::error('顺丰配送订单不存在: sf_order_id=' . $orderId, 'shunfeng-callback');
  252. return false;
  253. }
  254. $sendStatus = $this->convertOrderSendStatus($data['order_status']);
  255. $orderStatus = $this->convertOrderStatus($data['order_status']);
  256. if ($sendStatus === null) {
  257. Yii::error('顺丰订单状态转换失败: ' . $data['order_status'], 'shunfeng-callback');
  258. return false;
  259. }
  260. $deliveryOrder->orderStatus = $sendStatus;
  261. $deliveryOrder->save();
  262. // 更新主订单状态为异常
  263. if(isset($deliveryOrder->ghsOrderId)) { // 批发
  264. $order = OrderClass::getById($deliveryOrder->ghsOrderId, true, 'id,sendStatus,deliveryId,purchaseId,customId,status');
  265. if($order) {
  266. $order->sendStatus = $sendStatus;
  267. $order->status = $orderStatus;
  268. $order->save();
  269. // 更新主订单状态为异常
  270. if($sendStatus == -2) {
  271. OrderClass::cancelOrderSend($order, ['staffId' => 0, 'staffName' => '']);
  272. }
  273. }
  274. }elseif(isset($deliveryOrder->hdOrderId)){ // 零售
  275. $order = HdOrderClass::getById($deliveryOrder->hdOrderId, true);
  276. if ($order) {
  277. $order->sendStatus = $sendStatus;
  278. $order->status = $orderStatus;
  279. $order->save();
  280. }
  281. }
  282. return true;
  283. }
  284. /**
  285. * 处理骑士撤单回调
  286. * 订单状态:22-配送员撤单
  287. *
  288. * @param array $data 回调数据
  289. * @return bool 处理结果
  290. */
  291. private function handleRiderRecall($data)
  292. {
  293. Yii::info('顺丰骑士撤单回调: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'shunfeng-callback');
  294. // 验证必要参数
  295. if (empty($data['shop_order_id'])) {
  296. Yii::error('顺丰撤单回调参数不完整: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'shunfeng-callback');
  297. return false;
  298. }
  299. $shopOrderId = $data['shop_order_id'];
  300. // 查找配送订单
  301. $deliveryOrder = $this->getDeliveryOrder($shopOrderId);
  302. if (!$deliveryOrder) {
  303. Yii::error('顺丰配送订单不存在: shop_order_id=' . $shopOrderId, 'shunfeng-callback');
  304. return false;
  305. }
  306. $sendStatus = $this->convertOrderSendStatus($data['order_status']);
  307. $orderStatus = $this->convertOrderStatus($data['order_status']);
  308. if ($sendStatus === null) {
  309. Yii::error('顺丰订单状态转换失败: ' . $data['order_status'], 'shunfeng-callback');
  310. return false;
  311. }
  312. $deliveryOrder->orderStatus = $sendStatus;
  313. $deliveryOrder->save();
  314. // 更新主订单状态为派单中
  315. if(isset($deliveryOrder->ghsOrderId)) { // 批发
  316. $order = OrderClass::getById($deliveryOrder->ghsOrderId, true);
  317. if($order) {
  318. $order->sendStatus = $sendStatus;
  319. $order->status = $orderStatus;
  320. $order->save();
  321. }
  322. }elseif(isset($deliveryOrder->hdOrderId)){ // 零售
  323. $order = HdOrderClass::getById($deliveryOrder->hdOrderId, true);
  324. if ($order) {
  325. $order->sendStatus = $sendStatus;
  326. $order->status = $orderStatus;
  327. $order->save();
  328. }
  329. }
  330. return true;
  331. }
  332. /**
  333. * 处理顺丰授权成功回调
  334. *
  335. * @param array $data
  336. * @return bool
  337. */
  338. private function handleBindNotify($data)
  339. {
  340. $sign = Yii::$app->request->get('sign');
  341. Yii::info('顺丰授权状态回调(sign=' . ($sign ?? '') . '): ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'shunfeng-callback');
  342. $mainId = isset($data['out_shop_id']) ? (int)$data['out_shop_id'] : 0;
  343. $shopId = isset($data['shop_id']) ? $data['shop_id'] : '';
  344. if ($shopId === '') {
  345. Yii::error('顺丰授权状态回调参数缺失: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'shunfeng-callback');
  346. return false;
  347. }
  348. $payload = [
  349. 'mainId' => $mainId,
  350. 'platform' => 'shunfeng',
  351. 'shopId' => $shopId,
  352. 'authType' => 'all_store',
  353. 'accessToken' => $data['access_token'] ?? '',
  354. 'refreshToken' => $data['refresh_token'] ?? '',
  355. 'expiresAt' => time() + (100 * 86400 * 365),
  356. ];
  357. $existing = DeliveryAuthTokenClass::getByCondition(
  358. ['mainId' => $mainId, 'platform' => 'shunfeng'],
  359. false,
  360. false,
  361. 'id'
  362. );
  363. if ($existing && isset($existing['id'])) {
  364. DeliveryAuthTokenClass::updateById($existing['id'], [
  365. 'shopId' => $payload['shopId'],
  366. 'authType' => $payload['authType'],
  367. 'accessToken' => $payload['accessToken'],
  368. 'refreshToken' => $payload['refreshToken'],
  369. 'expiresAt' => $payload['expiresAt'],
  370. ]);
  371. } else {
  372. DeliveryAuthTokenClass::add($payload);
  373. }
  374. return true;
  375. }
  376. /**
  377. * 处理顺丰取消授权回调
  378. *
  379. * @param array $data
  380. * @return bool
  381. */
  382. private function handleCancelBindNotify($data)
  383. {
  384. $sign = Yii::$app->request->get('sign');
  385. Yii::info('顺丰取消授权状态回调(sign=' . ($sign ?? '') . '): ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'shunfeng-callback');
  386. $shopId = isset($data['shop_id']) ? $data['shop_id'] : '';
  387. if ($shopId === '') {
  388. Yii::error('顺丰授权状态回调参数缺失: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'shunfeng-callback');
  389. return false;
  390. }
  391. if (!empty($data['app_id'])) {
  392. $auth = new Auth();
  393. if ((string)$auth->getAppKey() !== (string)$data['app_id']) {
  394. Yii::warning('顺丰取消授权回调 app_id 不匹配: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'shunfeng-callback');
  395. return false;
  396. }
  397. }
  398. $condition = [
  399. 'platform' => 'shunfeng',
  400. 'shopId' => $shopId
  401. ];
  402. $deleted = DeliveryAuthTokenClass::deleteByCondition($condition);
  403. if ($deleted === 0) {
  404. Yii::warning('顺丰取消授权回调未删除任何记录: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'shunfeng-callback');
  405. }
  406. return true;
  407. }
  408. /**
  409. * 转换顺丰订单状态到系统配送状态
  410. *
  411. * 顺丰状态说明:
  412. * - 10: 配送员接单/配送员改派
  413. * - 12: 配送员到店
  414. * - 15: 配送员配送中
  415. * - 17: 配送员点击完成
  416. * - 2: 订单取消
  417. * - 22: 配送员撤单
  418. * - 91: 骑士上报异常
  419. *
  420. * 系统状态说明:
  421. * - 0: 订单生成
  422. * - 1: 系统已接单
  423. * - 2: 派单中
  424. * - 3: 待取货
  425. * - 4: 配送中
  426. * - 5: 已送达
  427. * - -1: 已取消
  428. * - -2: 异常
  429. * - 10: 改派中
  430. * - 20: 已分配骑手
  431. * - 30: 骑手已到店
  432. * - 40: 申请取消中
  433. * - 50: 客服介入处理中
  434. *
  435. * @param int $sfOrderStatus 顺丰订单状态
  436. * @return int|null 系统配送状态,null表示不需要更新主订单状态
  437. */
  438. private function convertOrderSendStatus($sfOrderStatus)
  439. {
  440. // 顺丰状态映射到系统配送状态
  441. $statusMap = [
  442. 10 => 20, // 配送员接单/改派 -> 已分配骑手
  443. 12 => 30, // 配送员到店 -> 骑手已到店
  444. 15 => 4, // 配送员配送中 -> 配送中
  445. 17 => 5, // 配送完成 -> 已送达
  446. 2 => -1, // 订单取消 -> 已取消
  447. 22 => 2, // 配送员撤单 -> 派单中(重新派单)
  448. 91 => -2, // 骑士上报异常 -> 异常
  449. ];
  450. return $statusMap[$sfOrderStatus] ?? null;
  451. }
  452. /**
  453. * 转换顺丰订单状态到系统订单状态
  454. *
  455. * @param int $sfOrderStatus 顺丰订单状态
  456. * @return int|null 系统订单状态,null表示不需要更新主订单状态
  457. */
  458. private function convertOrderStatus($sfOrderStatus)
  459. {
  460. // 顺丰状态映射到系统订单状态
  461. // 系统订单状态: 1待付款 2待配送 3配送中 4已完成 5已取消
  462. $statusMap = [
  463. 10 => 2, // 配送员接单/改派 -> 待配送
  464. 12 => 2, // 配送员到店 -> 待配送
  465. 15 => 3, // 配送员配送中 -> 配送中
  466. 17 => 4, // 配送完成 -> 已完成
  467. 2 => 2, // 订单取消 -> 待配送(配送的取消不等于订单的取消,由于 status 带有配送状态,所以改为:待配送)
  468. 22 => 2, // 配送员撤单 -> 待配送 (重新派单)
  469. 91 => 2, // 骑士上报异常 -> 异常 (不更新主状态)
  470. ];
  471. return $statusMap[$sfOrderStatus] ?? null;
  472. }
  473. /**
  474. * 根据顺丰订单号查找配送订单
  475. *
  476. * @param string $orderId
  477. * @return object|null
  478. */
  479. private function getDeliveryOrder($orderId)
  480. {
  481. // 查找批发配送订单
  482. $deliveryOrder = GhsDeliveryOrderClass::getByCondition([
  483. 'deliveryId' => 'shunfeng',
  484. 'orderId' => $orderId
  485. ], true);
  486. if ($deliveryOrder) {
  487. return $deliveryOrder;
  488. }
  489. // 查找零售配送订单
  490. return HdDeliveryOrderClass::getByCondition([
  491. 'deliveryId' => 'shunfeng',
  492. 'orderId' => $orderId
  493. ], true);
  494. }
  495. }