'FILE', // 文件 '2' => 'FOOD', // 餐饮 '3' => 'CAKE', // 蛋糕 '4' => 'FLOWER', // 鲜花 '5' => 'ELECTRONIC',// 数码 '6' => 'MEDICINAL', // 医药 '8' => 'OTHER', // 其他 // 根据实际情况补充 ]; public function cityList() { // 滴滴快送暂无城市列表接口,返回空 return []; } /** * 构建订单计费请求信息(供并发请求使用) * * @param array $data 订单数据 * @param string $orderTime 配送时间 * @return array 包含 url, data, headers, timeout 的请求信息 */ public function buildPriceRequest($data, $orderTime) { $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' => 1017, // 小轿车 'serviceLevel' => 0, 'serviceType' => 1, ] ]; // 构造起点信息 $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 ($orderTime) { $ts = strtotime($orderTime); if ($ts > time() + 1800) { // 30分钟后算预约? 滴滴快送暂不支持预约单,这里保留逻辑备用 // $modeType = 2; // $useTime = $ts * 1000; // 注意:文档说“快送暂不支持预约单都按照实时单处理”,所以这里可能无需处理,或者传了也没用 } } // ------------------ 入参构建 ------------------ $businessParams = [ 'thirdUid' => $sender['fromMobile'], // 使用发件人手机作为接入方用户唯一标识 'serviceCategoryList' => $serviceCategoryList, 'fromAddress' => $fromAddress, 'toAddress' => $toAddress, 'goodsType' => $goodsType, 'weight' => $weightGram, 'needPickupCode' => false, 'needReceiveCode' => true, // 通常需要收件码 'modeType' => $modeType, ]; if ($useTime) { $businessParams['useTime'] = $useTime; } // 构建请求Payload $payload = $this->buildRequestPayload($businessParams); $apiUrl = $this->getApiUrl('freight.open.platform.channel.standard.estimate'); return [ 'url' => $apiUrl, 'data' => $payload, 'headers' => [], 'timeout' => 10.0, ]; } /** * 处理询价响应 */ public function processPriceResponse($resp) { if (isset($resp['code']) && $resp['code'] == 0 && isset($resp['data'])) { $data = $resp['data']; $estimateList = $data['estimateList'] ?? []; if (empty($estimateList)) { return null; } // 选取第一个结果,或者根据策略选取(比如最便宜) TODO // 这里简单取第一个 $estimate = $estimateList[0]; return [ 'platform' => 'didi', '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 null; } /** * 发单 */ public function createOrder($order) { // 解析 $order 数据,注意这里的 $order 结构可能与 buildPriceRequest 的 $data 不同 // 通常 service 层会将 price check 的结果合并进来,或者前端传回 estimateId $estimateId = $order['estimate_id'] ?? null; if (!$estimateId) { return [ 'code' => -1, 'platform' => 'didi', 'msg' => '缺少estimate_id', 'data' => [] ]; } $sender = $order['sender']; $receiver = $order['receiverList'][0]; $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, ]; $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; $goodTypeInt = $receiver['goodType'] ?? '8'; $goodsType = self::GOODS_TYPE_MAP[$goodTypeInt] ?? 'OTHER'; // ------------------ 入参构建 ------------------ $businessParams = [ 'estimateIdList' => [$estimateId], // 预估ID 'outOrderNo' => $order['issOrderNo'], // 接入方订单号 'fromAddress' => $fromAddress, 'toAddress' => $toAddress, 'goodsType' => $goodsType, 'weight' => $weightGram, 'needPickupCode' => false, 'needReceiveCode' => true, 'thirdUid' => $sender['fromMobile'], 'remark' => $order['remark'] ?? '', ]; $payload = $this->buildRequestPayload($businessParams); $url = $this->getApiUrl('freight.open.platform.channel.standard.createOrder'); $resp = HttpClient::post($url, $payload); if (isset($resp['code']) && $resp['code'] == 0 && isset($resp['data'])) { $respData = $resp['data']; return [ 'code' => 0, 'platform' => 'didi', 'data' => [ 'order_id' => $respData['didiOrderNo'], // 滴滴订单号 // 可以附带其他信息 ] ]; } $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'); $resp = HttpClient::post($url, $payload); 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 ]; } /** * 取消订单 */ public function cancelOrder($orderId) { // $orderId 应该是 outOrderNo (接入方订单号) // Didi API 需要 outOrderNo 和 didiOrderNo。 // 如果我们只有 one of them,可能需要先查询? // 接口文档说: outOrderNo 是必须, didiOrderNo 是必须. // 这有点麻烦。通常 Adapter 的 input 只包含一个 ID。 // 假设 $orderId 是 outOrderNo。 // 此时我们可能没有 didiOrderNo,或者它需要作为第二个参数传入? // 参考 ShansongAdapter,它只传了一个 $orderId (issOrderNo). // 如果必须两个都传,可能需要修改调用方,或者我们只传 outOrderNo 试试,或者 look up mapping. // 文档: outOrderNo (String, 是), didiOrderNo (Long, 是). // 如果无法提供 didiOrderNo,可能无法取消。 // 但通常业务系统会保存 didiOrderNo。 // 这里假设调用方会在参数里传,或者 $orderId 是一个包含多个ID的数组? // 按照 ShansongAdapter: cancelOrder($orderId). // 只能假设 $orderId 结构可能包含 didiOrderNo,或者我们尝试只传 outOrderNo 看是否可行 (文档说是必须)。 // 为了兼容性,如果 $orderId 是数组,则取值;如果是字符串,视为 outOrderNo,且 didiOrderNo 填 0 或空? // 让我们看看能不能只传 outOrderNo. "接口根据接入方订单号幂等...". // 还是严格按照文档吧。如果调用方没传 didiOrderNo,这步会失败。 // 临时处理:检查 $orderId 是否是数组 $didiOrderNo = 0; $outOrderNo = $orderId; if (is_array($orderId)) { $outOrderNo = $orderId['order_no'] ?? ''; $didiOrderNo = $orderId['platform_order_no'] ?? 0; } $businessParams = [ 'outOrderNo' => (string)$outOrderNo, 'didiOrderNo' => (int)$didiOrderNo, 'cancelSource' => 1, // 用户取消 'cancelReason' => '暂不需要', ]; $payload = $this->buildRequestPayload($businessParams); $url = $this->getApiUrl('freight.open.platform.channel.standard.cancel'); $resp = HttpClient::post($url, $payload); if (isset($resp['code']) && $resp['code'] == 0 && isset($resp['data'])) { $data = $resp['data']; return [ 'code' => 0, 'platform' => 'didi', 'data' => [ 'deductionFee' => $data['cancelFee'] ?? 0, // 分 'cancelResult' => $data['cancelResult'], ] ]; } return [ 'code' => $resp['code'] ?? -1, 'platform' => 'didi', 'msg' => $resp['msg'] ?? 'Unknown error', 'data' => null ]; } }