'FILE', // 文件 '2' => 'FOOD', // 餐饮 '3' => 'CAKE', // 蛋糕 '4' => 'FLOWER', // 鲜花 '5' => 'ELECTRONIC',// 数码 '6' => 'MEDICINAL', // 医药 '8' => 'OTHER', // 其他 // 根据实际情况补充 ]; public function cityList() { // 滴滴快送暂无城市列表接口,返回空 return []; } /** * 构建订单计费请求信息(供并发请求使用) * @param array $data 订单数据 * @param string $pickupTime 取件时间 * @return array 包含 url, data, headers, timeout 的请求信息 */ public function buildPriceRequest($data, $pickupTime) { $sender = $data['sender']; // 滴滴目前主要支持单点对单点,取第一个收件人 $receiver = $data['receiverList'][0] ?? []; if (empty($receiver)) { return []; } // 服务参数列表 (询价时可以传入多个,获取不同服务的价格) // 默认请求快送(BizType=12) $serviceCategoryList = [ [ 'bizType' => 12, // 业务类型 -- 快送 'carType' => 1018, // 车型 -- 两轮车 'serviceLevel' => 0, // 服服务等级 'serviceType' => 1, // 服务类型(快送专用,拉货不需要传) -- 直送 ], [ 'bizType' => 12, // 业务类型 -- 快送 'carType' => 1018, // 车型 -- 两轮车 'serviceLevel' => 0, // 服服务等级 'serviceType' => 0, // 服务类型(快送专用,拉货不需要传) -- 拼送 ], [ 'bizType' => 12, 'carType' => 1017, // 小轿车 'serviceLevel' => 0, 'serviceType' => 1, // 服务类型(快送专用,拉货不需要传) -- 直送 ], [ 'bizType' => 12, 'carType' => 1017, // 小轿车 'serviceLevel' => 0, 'serviceType' => 0, // 服务类型(快送专用,拉货不需要传) -- 拼送 ], // 普通货运业务类型 [ 'bizType' => 0, // 业务类型 -- 普通货运 'carType' => 1020, // 车型 -- 三轮车 'serviceLevel' => 0, //'serviceType' => 1, ], [ 'bizType' => 0, // 业务类型 -- 普通货运 'carType' => 1012, // 车型 -- 微面 'serviceLevel' => 0, //'serviceType' => 1, ], [ 'bizType' => 0, // 业务类型 -- 普通货运 'carType' => 1001, // 车型 -- 小面包 'serviceLevel' => 0, //'serviceType' => 1, ], ]; //$ret = $this->queryCityInfo(null, (float)$receiver['toLongitude'], (float)$receiver['toLatitude']); // 构造起点信息 $fromAddress = [ 'name' => $sender['fromSenderName'], 'phone' => $sender['fromMobile'], 'poiName' => $sender['fromAddress'], 'poiAddress' => $sender['fromAddress'], 'addressInfo' => $sender['fromAddressDetail'], 'latitude' => (float)$sender['fromLatitude'], 'longitude' => (float)$sender['fromLongitude'], 'virtualPhoneType' => 0, // 0-真实号码 ]; // 构造终点信息 $toAddress = [ 'name' => $receiver['toReceiverName'], 'phone' => $receiver['toMobile'], 'poiName' => $receiver['toAddress'], 'poiAddress' => $receiver['toAddress'], 'addressInfo' => $receiver['toAddressDetail'], 'latitude' => (float)$receiver['toLatitude'], 'longitude' => (float)$receiver['toLongitude'], 'virtualPhoneType' => 0, ]; // 物品信息 $weight = (int)($receiver['weight'] ?? 1); // kg $weightGram = $weight * 1000; // 转为克 $goodTypeInt = $receiver['goodType'] ?? '8'; $goodsType = self::GOODS_TYPE_MAP[$goodTypeInt] ?? 'FLOWER'; // 预约单处理 $modeType = 1; // 1-实时单 $useTime = null; if ($pickupTime) { $ts = strtotime($pickupTime); if ($ts > (time() + 300)) { // 5分钟后算预约? 滴滴快送暂不支持预约单,只能应用于普通货运 $modeType = 2;// 变更为2-预约单 $useTime = $ts * 1000; } } // ------------------ 入参构建 ------------------ $businessParams = [ 'thirdUid' => $this->thirdUid, // 使用发件人手机作为接入方用户唯一标识 'serviceCategoryList' => $serviceCategoryList, 'fromAddress' => $fromAddress, 'toAddress' => $toAddress, 'goodsType' => $goodsType, 'weight' => $weightGram, 'needPickupCode' => false, 'needReceiveCode' => false, 'modeType' => $modeType, ]; if ($useTime) { $businessParams['useTime'] = $useTime; } // 构建请求Payload $payload = $this->buildRequestPayload($businessParams); $apiUrl = $this->getApiUrl('freight.open.platform.channel.standard.estimate'); $request = [ 'url' => $apiUrl, 'data' => $payload, 'headers' => ["Content-Type" => "application/x-www-form-urlencoded"], 'timeout' => 10.0, ]; //Yii::info('滴滴构建运费报价请求信息 -- ' .json_encode($request, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); return $request; } /** * 处理询价响应 */ public function processPriceResponse($resp) { if (isset($resp['code']) && $resp['code'] == 200 && isset($resp['data'])) { $data = $resp['data']; $estimateList = $data['estimateList'] ?? []; if (empty($estimateList)) { return [ 'code' => -1, 'platform' => 'didi', 'msg' => '没有预估结果列表', 'data' => null ]; } // 选取第一个结果,或者根据策略选取(比如最便宜) // 这里简单取第一个 $estimate = $estimateList[0]; return [ 'platform' => 'didi', 'estimateList' => $estimateList, 'estimate_id' => $estimate['estimateId'], // 预估ID,发单用 'total_distance' => $estimate['distance'] ?? 0, 'total_amount' => $estimate['fee'] ?? 0, // 分 'total_fee_after_save' => $estimate['fee'] ?? 0, 'coupon_save_fee' => $estimate['discountFee'] ?? 0, 'fee_info_list' => $estimate['feeList'] ?? [], // 补充其他字段以匹配通用结构 'total_weight' => 0, // 响应里没返回重量 ]; } return [ 'code' => $resp['code'] ?? -1, 'platform' => 'didi', 'msg' => $resp['msg'] ?? 'Unknown error', 'data' => null ]; } /** * 转化订单数据 * @param $order * @param $orderType * @param $shop * @param $params * @return array * @throws \Exception */ public function formatOrderData($order, $orderType, $shop, $params) { // 默认实时单,拉货生效,快送暂不支持预约单都按照实时单处理 $timeDiff = strtotime($params['pickupTime']) - time(); $modeType = 1; if ($timeDiff > 300) { $modeType = 2; } $sender = [ 'fromSenderName' => $shop['merchantName'], 'fromMobile' => $shop['mobile'], 'fromAddress' => $shop['fullAddress'], 'fromAddressDetail' => $shop['floor'], 'fromLatitude' => (float)$shop['lat'], 'fromLongitude' => (float)$shop['long'], ]; $receiver = [ 'toReceiverName' => empty($order['showAddress']) ? $order['customName'] : $order['showAddress'], 'toMobile' => Functions::getMobile($order), 'toAddress' => $order['fullAddress'], 'toAddressDetail' => $order['floor'], 'toLatitude' => (float)$order['lat'], 'toLongitude' => (float)$order['long'], 'weight' => round(($order['weight'] ?? 1)) * 1000, 'goodType' => self::GOODS_TYPE_MAP['4'], // const GOODS_TYPE_MAP ]; $apiData = [ 'outOrderNo' => $order['orderSn'], 'estimateId' => $params['estimateId'] ?? null, 'sender' => $sender, 'receiver' => $receiver, 'remark' => $params['remark'] ?? $order['remark'] ?? '', 'orderSequence' => $order['sendNum'] // 取件流水号,方便寻找订单对应的花材 ]; if($modeType == 2){ $apiData['modeType'] = 2; $apiData['useTime'] = strtotime($params['pickupTime']) * 1000; } return $apiData; } /** * 发单 */ public function createOrder($order) { // 解析 $order 数据,注意这里的 $order 结构可能与 buildPriceRequest 的 $data 不同 // 通常 service 层会将 price check 的结果合并进来,或者前端传回 estimateId $estimateId = $order['estimateId'] ?? null; if (!$estimateId) { return [ 'code' => -1, 'platform' => 'didi', 'msg' => '缺少 estimateId', 'data' => [] ]; } $sender = $order['sender']; $fromAddress = [ 'name' => $sender['fromSenderName'], 'phone' => $sender['fromMobile'], 'poiName' => $sender['fromAddress'], 'poiAddress' => $sender['fromAddress'], 'addressInfo' => $sender['fromAddressDetail'], 'latitude' => (float)$sender['fromLatitude'], 'longitude' => (float)$sender['fromLongitude'], 'virtualPhoneType' => 0, ]; $receiver = $order['receiver']; $toAddress = [ 'name' => $receiver['toReceiverName'], 'phone' => $receiver['toMobile'], 'poiName' => $receiver['toAddress'], 'poiAddress' => $receiver['toAddress'], 'addressInfo' => $receiver['toAddressDetail'], 'latitude' => (float)$receiver['toLatitude'], 'longitude' => (float)$receiver['toLongitude'], 'virtualPhoneType' => 0, ]; $weight = (int)($receiver['weight'] ?? 1); $weightGram = $weight * 1000; $goodsType = $receiver['goodType'] ?? 'OTHER'; // ------------------ 入参构建 ------------------ $timeStamp = time(); $businessParams = [ 'estimateIdList' => [$estimateId], // 预估ID 'outOrderNo' => self::generateUniOroder($order['outOrderNo'], $timeStamp), // 接入方订单号 'fromAddress' => $fromAddress, 'toAddress' => $toAddress, 'goodsType' => $goodsType, 'weight' => $weightGram, 'needPickupCode' => false, 'needReceiveCode' => false, 'thirdUid' => $this->thirdUid, // 接入方用户唯一标识。保持在渠道方唯一,会用于安全风控,【聚合模式】必须传 'remark' => $order['remark'] ?? '', 'orderSequence' => $order['orderSequence'] ?? '', ]; if(isset($order['modeType']) && $order['modeType'] == 2){ $businessParams['modeType'] = 2; $businessParams['useTime'] = $order['useTime']; } $payload = $this->buildRequestPayload($businessParams); $url = $this->getApiUrl('freight.open.platform.channel.standard.createOrder'); $headers = ["Content-Type" => "application/x-www-form-urlencoded"]; $resp = HttpClient::post($url, $payload, $headers); if (isset($resp['code']) && $resp['code'] == 200 && isset($resp['data'])) { $respData = $resp['data']; return [ 'code' => 0, 'platform' => 'didi', 'data' => [ 'orderId' => $respData['didiOrderNo'], // 滴滴订单号 'distance' => 0, 'fee' => 0, // 可以附带其他信息 'timeStamp' => $timeStamp, ] ]; } $msg = $resp['msg'] ?? 'Unknown error'; if (isset($resp['code']) && isset($resp['subCode'])) { $msg .= " ({$resp['code']}-{$resp['subCode']})"; } Yii::error('[DidiAdapter] createOrder failed: ' . json_encode($resp, JSON_UNESCAPED_UNICODE)); return [ 'code' => -1, 'platform' => 'didi', 'msg' => $msg, 'data' => [] ]; } /** * 添加小费 */ public function addTip($data) { $outOrderNo = $data['orderId'] ?? null; // 这里假设传入的是接入方订单号,或者需要映射 // 注意:ShansongAdapter用的是 issOrderNo (内部订单号),Didi addTip需要 outOrderNo (接入方订单号) // 假设 $data['orderId'] 就是 outOrderNo $fee = $data['tips'] ?? 0; // 分 if (empty($outOrderNo)) { return ['code' => -1, 'platform' => 'didi', 'msg' => '订单号不能为空']; } if ($fee <= 0) { return ['code' => -1, 'platform' => 'didi', 'msg' => '小费金额必须大于0']; } $businessParams = [ 'outOrderNo' => $outOrderNo, 'fee' => (int)$fee, ]; $payload = $this->buildRequestPayload($businessParams); $url = $this->getApiUrl('freight.open.platform.channel.standard.addTipFee'); $headers = ["Content-Type" => "application/x-www-form-urlencoded"]; $resp = HttpClient::post($url, $payload, $headers); if (isset($resp['code']) && $resp['code'] == 0) { return [ 'code' => 0, 'platform' => 'didi', 'data' => $resp['data'] ?? [] ]; } return [ 'code' => $resp['code'] ?? -1, 'platform' => 'didi', 'msg' => $resp['msg'] ?? 'Unknown error', 'data' => null ]; } /** * 取消订单 * * @param int $didiOrderNo 滴滴订单号 * @param int $post */ public function cancelOrder($didiOrderNo, $post) { $businessParams = [ 'outOrderNo' => (string)$post['outOrderNo'], 'didiOrderNo' => (int)$didiOrderNo, 'cancelSource' => $post['cancelSource'], //'cancelReason' => '暂不需要', ]; $payload = $this->buildRequestPayload($businessParams); $url = $this->getApiUrl('freight.open.platform.channel.standard.cancel'); $headers = ["Content-Type" => "application/x-www-form-urlencoded"]; $resp = HttpClient::post($url, $payload, $headers); if (isset($resp['code']) && $resp['code'] == 200 && isset($resp['data'])) { $data = $resp['data']; return [ 'code' => 0, 'platform' => 'didi', 'data' => [ 'deductionFee' => $data['cancelFee'], // 分 'cancelFee' => $data['cancelFee'], 'cancelResult' => $data['cancelResult'], ] ]; } return [ 'code' => $resp['code'] ?? -1, 'platform' => 'didi', 'msg' => $resp['msg'] ?? 'Unknown error', 'data' => null ]; } /** * 查询订单详情 */ public function selectOrder($orderId, $thirdOrderNo = '', $timeStamp) { $thirdOrderNo = Didi::generateUniOroder($thirdOrderNo, $timeStamp); $businessParams = [ 'didiOrderNo' => (int)$orderId, 'outOrderNo' => $thirdOrderNo, ]; $payload = $this->buildRequestPayload($businessParams); $url = $this->getApiUrl('freight.open.platform.channel.standard.orderInfo'); $headers = ["Content-Type" => "application/x-www-form-urlencoded"]; $resp = HttpClient::post($url, $payload, $headers); if (isset($resp['code']) && $resp['code'] == 200 && isset($resp['data'])) { return [ 'code' => 0, 'platform' => 'didi', 'data' => $resp['data'] ]; } return [ 'code' => $resp['code'] ?? -1, 'platform' => 'didi', 'msg' => $resp['msg'] ?? 'Unknown error', 'data' => null ]; } }