| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482 |
- <?php
- namespace common\components\delivery\services\adapter;
- use common\components\delivery\helpers\HttpClient;
- use common\components\delivery\platform\didi\Didi;
- use Yii;
- class DidiAdapter extends Didi implements Adapter
- {
- /**
- * 物品类型映射
- * Didi: FOOD, FLOWER, CAKE, FILE, FRUIT, ELECTRONIC, DRESS, MEDICINAL, TEXTILE, FRESH, CAR_PART, OTHER
- */
- const GOODS_TYPE_MAP = [
- '1' => '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
- ];
- }
- }
|