verifySignature($post)) { Yii::error('滴滴回调签名验证失败', 'didi-callback'); return false; } // 签名验证通过后会判断请求有效期, now - 2分钟 < timestamp < now + 2分钟 为有效区间内的请求 $timeStamp = (int)($post['timeStamp']/1000); if ($timeStamp < time() - 120 || $timeStamp > time() + 120) { if(getenv('YII_ENV') == 'production'){ Yii::error('滴滴回调请求有效期验证失败', 'didi-callback'); noticeUtil::push('滴滴回调请求有效期验证失败 -- '.$post['logisticsParam']); return false; } } $data = json_decode($post['logisticsParam'], true); // ----- 区分业务类型 ----- // 订单详情查询: 包含 orderNo 和 outOrderNo (且不包含 status/updateTime) if (isset($data['orderNo']) && isset($data['outOrderNo'])) { return $this->queryOrderDetails($data); } // 订单状态变更: 包含 didiOrderNo, status, updateTime if (isset($data['didiOrderNo']) && isset($data['status'])) { return $this->processOrderCallback($data); } // 同步授权结果 if (isset($data['authResult']) && isset($data['token']) && isset($data['thirdUid'])) { return $this->processAuthResult($data); } Yii::warning('滴滴回调未知类型: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'didi-callback'); return false; } catch (\Exception $e) { Yii::error('滴滴回调处理异常: ' . $e->getMessage() . ' | ' . $e->getTraceAsString(), 'didi-callback'); return false; } } /** * 查询订单详情 (返回给滴滴) * * @param array $data * @return array */ private function queryOrderDetails($data) { $didiOrderNo = $data['orderNo']; $outOrderNo = $data['outOrderNo']; $outOrderNo = Didi::getSystemOrder($outOrderNo); //还原为数据库中的订单号 // 查找主订单 $order = OrderClass::getByCondition(['orderSn' => $outOrderNo], true); $isGhs = true; if (!$order) { $order = HdOrderClass::getByCondition(['orderSn' => $outOrderNo], true); $isGhs = false; } if (!$order) { Yii::error('滴滴查询详情未找到对应订单: ' . $outOrderNo, 'didi-callback'); // 返回空或错误信息? 根据协议,这里应该返回错误,但方法定义返回数组。 // 暂时返回空数组或抛异常 return []; } // 查找配送订单以获取 shopId $deliveryOrder = $this->getDeliveryOrder($order, $isGhs); if (!$deliveryOrder) { Yii::error('滴滴查询详情未找到配送订单: ' . $outOrderNo, 'didi-callback'); return []; } // 获取店铺信息 $shopId = isset($deliveryOrder->shopId) ? $deliveryOrder->shopId : 0; if (empty($shopId) && isset($order->shopId)) { // 尝试从订单获取 $shopId = $order->shopId; } $shop = ShopClass::getById($shopId, false, 'id,merchantName,fullAddress,lat,long,city,dist,floor,mobile,address'); if (!$shop) { Yii::error('滴滴查询详情未找到店铺: ' . $shopId, 'didi-callback'); // 构造一个兜底的 shop 信息? $shop = [ 'merchantName' => '未知店铺', 'mobile' => '', 'fullAddress' => '', 'lat' => 0, 'long' => 0, 'address' => '', 'floor' => '' ]; } // 构造返回数据 // 1. 发货人信息 (Location Info) $fromAddress = [ 'name' => $shop['merchantName'], //联系名称 'poiName' => '', //地图获取的poi名称 'poiAddress' => $shop['fullAddress'],//地图获取的地址信息 'addressInfo' => $shop['floor'], //用户补充地址信息(门牌号等) 'latitude' => (float)$shop['lat'], 'longitude' => (float)$shop['long'], 'phone' => $shop['mobile'], 'virtualPhoneType' => 0, ]; // 2. 收货人信息 (Location Info) $toAddress = [ 'name' => $order['customName'], 'poiName' => '', //地图获取的poi名称 'poiAddress' => $order['fullAddress'], 'addressInfo' => $order['floor'], 'latitude' => (float)$order['lat'], 'longitude' => (float)$order['long'], 'phone' => Functions::getMobile($order), 'virtualPhoneType' => 0, ]; // 3. 计算费用 // 订单金额 (单位:分) - 这里取实际支付金额/商品金额 $fee = (int)($order['actPrice'] * 100); // 优惠金额 (单位:分) $prePrice = isset($order['prePrice']) ? $order['prePrice'] : $order['actPrice']; $discountFee = (int)(($prePrice - $order['actPrice']) * 100); if ($discountFee < 0) $discountFee = 0; // 重量 (单位:克) $weight = isset($order['weight']) ? (int)($order['weight'] * 1000) : 1000; Yii::info('滴滴订单返回成功', 'didi-callback'); return [ 'didiOrderNo' => (int)$didiOrderNo, // Long 'outOrderNo' => (string)$outOrderNo, 'status' => $this->convertStatusToDidi($deliveryOrder->orderStatus), // 转换回滴滴状态? 或者直接返回当前状态 'fromAddress' => $fromAddress, 'toAddress' => $toAddress, 'weight' => $weight, // 'size' => 0, // 非必填 'goodsType' => 'FLOWER', 'goodsPrice' => (int)($order['actPrice'] * 100), // 保价费物品价值 'remark' => $order['remark'] ?? '', 'fee' => $fee, 'discountFee' => $discountFee, ]; } /** * 处理订单回调 */ private function processOrderCallback($data) { $didiOrderNo = isset($data['didiOrderNo']) ? $data['didiOrderNo'] : ''; $outOrderNo = isset($data['outOrderNo']) ? Didi::getSystemOrder($data['outOrderNo']) : ''; //还原为数据库中的订单号 $didiStatus = isset($data['status']) ? $data['status'] : ''; if (empty($outOrderNo)) { Yii::error('滴滴回调缺少outOrderNo', 'didi-callback'); return false; } // 查找主订单 (假设 outOrderNo 为 orderSn) $order = OrderClass::getByCondition(['orderSn' => $outOrderNo], true); $isGhs = true; if (!$order) { $order = HdOrderClass::getByCondition(['orderSn' => $outOrderNo], true); $isGhs = false; } if (!$order) { Yii::error('滴滴回调未找到对应订单: ' . $outOrderNo, 'didi-callback'); return false; } // 查找配送订单 $deliveryOrder = $this->getDeliveryOrder($order, $isGhs); if (!$deliveryOrder) { Yii::error("滴滴配送订单不存在: orderSn={$outOrderNo}, isGhs={$isGhs}, ghsOrderId={$order->id}", 'didi-callback'); return false; } // 更新 didiOrderNo if (!empty($didiOrderNo) && empty($deliveryOrder->orderId)) { $deliveryOrder->orderId = (string)$didiOrderNo; } $sendStatus = $this->convertOrderSendStatus($didiStatus); // 如果状态无法映射,则忽略 if ($sendStatus === null) { return true; } // 更新配送订单状态 if ($didiStatus == 'SUBMIT_CHARGE') { $deliveryOrder->fee = $data['realTotalFee']; //真实支付总金额 -- 总金额-抵扣金额(status = SUBMIT_CHARGE时返回) } $deliveryOrder->orderStatus = $sendStatus; // 更新主订单 if ($isGhs) { util::checkRepeatCommit('ghs_pt_' . $order->id . '_' . $didiStatus, 2); if ($order) { $cg = PurchaseClass::getById($order->purchaseId, true, 'id,status,sendStatus'); // 已送达 if ($sendStatus == 5) { 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); } } // 取消/异常 (且当前状态未取消) if (!in_array($order->sendStatus, [-2, -1]) && in_array($sendStatus, [-1, -2])) { OrderClass::cancelOrderSend($order, ['staffId' => 0, 'staffName' => '']); // 取消费用设置与保存 if($sendStatus == -1 && isset($data['cancelInfo']) && isset($data['cancelInfo']['cancelFee'])){ $deliveryOrder->cancelCost = $data['cancelCost']['cancelFee']; } $event = $this->cancelFrom($data); GhsNotifyClass::ptErrorNotify($order->id, $event); } if ($cg) { $cg->sendStatus = $sendStatus; $cg->save(); } $order->sendStatus = $sendStatus; // 转换主订单状态 $orderStatus = $this->convertOrderStatus($didiStatus); if ($orderStatus !== null) { $order->status = $orderStatus; } $order->save(); } } else { // 零售订单处理 util::checkRepeatCommit('hd_pt_' . $order->id . '_' . $didiStatus, 2); if (!in_array($order->sendStatus, [-2, -1]) && in_array($sendStatus, [-1, -2])) { // 取消费用设置与保存 if($sendStatus == -1 && isset($data['cancelInfo']) && isset($data['cancelInfo']['cancelFee'])){ $deliveryOrder->cancelCost = $data['cancelCost']['cancelFee']; } $event = $this->cancelFrom($data); HdNotifyClass::ptErrorNotify($order->id, $event); } if ($order) { $order->sendStatus = $sendStatus; $orderStatus = $this->convertOrderStatus($didiStatus); if ($orderStatus !== null) { $order->status = $orderStatus; } $order->save(); } } $deliveryOrder->save(); Yii::info('滴滴回调处理成功', 'didi-callback'); return true; } /** * 同步授权结果 * 使用场景 * 当订单结算模式为用户余额时,需要接入该接口 * 用户授权后,将用户token发送给接入方,后续接口调用都需要在参数中传递token * @param $data */ private function processAuthResult($data) { // 1-成功 暂时只有成功时会回调 if (!isset($data['authResult']) || $data['authResult'] != 1) { Yii::error('滴滴授权回调失败: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'didi-callback'); noticeUtil::push('滴滴授权回调失败: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'didi-callback'); return false; } $token = isset($data['token']) ? $data['token'] : ''; $thirdUid = isset($data['thirdUid']) ? $data['thirdUid'] : ''; if (empty($token) || empty($thirdUid)) { Yii::error('滴滴授权回调参数缺失: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'didi-callback'); return false; } $mainId = (int)$thirdUid; // 查找或更新Token $condition = ['mainId' => $mainId, 'platform' => 'didi']; $dat = DeliveryAuthTokenClass::getByCondition($condition, true); if ($dat) { //DeliveryAuthTokenClass::updateById($existing['id'], $saveData); $dat->accessToken = $token; $dat->save(); } else { // $saveData = [ // 'mainId' => $mainId, // 'platform' => 'didi', // 'accessToken' => $token, // ]; // $saveData['createTime'] = date('Y-m-d H:i:s'); // DeliveryAuthTokenClass::add($saveData); Yii::error('滴滴授权回调未找到对应授权信息: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'didi-callback'); noticeUtil::push('滴滴授权回调未找到对应授权信息: ' . json_encode($data, JSON_UNESCAPED_UNICODE), 'didi-callback'); return false; } Yii::info("滴滴授权成功 mainId:{$mainId}", 'didi-callback'); // 获取店铺手机号 $shop = ShopClass::getByCondition(['mainId'=>$mainId, 'ptStyle'=>2], false, false, 'id,mobile,telephone'); $phone = ($shop) ? $shop['mobile'] : $shop['telephone']; return [ 'phone' => (string)$phone ]; } /** * @param $order * @param $isGhs * @return mixed|\yii\db\ActiveRecord|null */ private function getDeliveryOrder($order, $isGhs) { if ($isGhs) { $gdos = GhsDeliveryOrderClass::getAllByCondition([ 'deliveryId' => 'didi', 'ghsOrderId' => $order->id, 'orderStatus' => ['not in', [-1, -2]], ], 'id DESC', '*', null, true); if(count($gdos)>=2){ noticeUtil::push("xhGhsDeliveryOrder ghsOrderId={$order->id} 有两笔以上是在配送中"); $gdo = $gdos[0]; }elseif(count($gdos)==1){ $gdo = $gdos[0]; }else{ $gdo = null; } return $gdo; } else { $hdos = HdDeliveryOrderClass::getAllByCondition([ 'deliveryId' => 'didi', 'hdOrderId' => $order->id, 'orderStatus!=' => -1 ], 'id DESC', '*', null, true); if(count($hdos)>=2){ noticeUtil::push("xhHdDeliveryOrder hdOrderId={$order->id} 有两笔以上是在配送中"); $hdo = $hdos[0]; }elseif(count($hdos)==1){ $hdo = $hdos[0]; }else{ $hdo = null; } return $hdo; } } private function verifySignature($data) { $didi = new Didi(); $logisticsParam = $data['logisticsParam']; $sign = $didi->generateSignature($logisticsParam, $data['timeStamp']); return $sign === $data['appSign']; } private function convertOrderSendStatus($didiStatus) { $map = [ 'DRIVER_ACCEPT' => 3, // 司机接单 -> 待取货 'PAIRED_DRIVER' => 20, // 发单成功,匹配司机中 -> 已分配骑手 'ARRIVED_FROM_ADDRESS' => 30, // 到达起点 -> 骑手已到店 'LEAVE_FROM_ADDRESS' => 4, // 已取货 -> 配送中 'ARRIVED_TO_ADDRESS' => 4, // 到达终点 -> 配送中 'SERVICE_FINISH' => 4, // 结束服务 'SUBMIT_CHARGE' => 4, // 司机提交费用 'COMPLETED' => 5, // 已完成 -> 已送达 'CANCEL' => -1, // 已取消 ]; return isset($map[$didiStatus]) ? $map[$didiStatus] : null; } private function convertOrderStatus($didiStatus) { // 对应 OrderClass 的 status // 3: 配送中, 4: 已完成 $map = [ 'PAIRED_DRIVER' => 3, //发单成功,匹配司机中 'DRIVER_ACCEPT' => 3, //司机接单 'ARRIVED_FROM_ADDRESS' => 3, 'LEAVE_FROM_ADDRESS' => 3, 'ARRIVED_TO_ADDRESS' => 3, //'SERVICE_FINISH' => 4, //'SUBMIT_CHARGE' => 4, 'COMPLETED' => 4, ]; return isset($map[$didiStatus]) ? $map[$didiStatus] : null; } /** * 将系统状态转换为滴滴状态 (用于订单详情查询) * 这里只能做近似映射,因为系统状态比滴滴状态少 */ private function convertStatusToDidi($sendStatus) { $map = [ 20 => 'DRIVER_ACCEPT', 2 => 'PAIRED_DRIVER', 30 => 'ARRIVED_FROM_ADDRESS', 4 => 'LEAVE_FROM_ADDRESS', // or ARRIVED_TO_ADDRESS 5 => 'COMPLETED', -1 => 'CANCEL', -2 => 'CANCEL', // 异常也暂视为取消? 或没有对应 ]; return isset($map[$sendStatus]) ? $map[$sendStatus] : 'WAIT_ACCEPT'; } private function cancelFrom($data) { if (!isset($data['cancelInfo'])) { return '未知原因'; } $info = $data['cancelInfo']; $sourceMap = [ 1 => '用户', 2 => '司机', 3 => '客服', 4 => '系统' ]; $source = isset($sourceMap[$info['cancelSource']]) ? $sourceMap[$info['cancelSource']] : '未知'; $reason = isset($info['cancelReason']) ? $info['cancelReason'] : ''; return "{$source}-取消: {$reason}"; } }