|
|
@@ -2,7 +2,7 @@
|
|
|
namespace common\components\delivery\services\adapter;
|
|
|
|
|
|
use common\components\delivery\helpers\HttpClient;
|
|
|
-use common\components\delivery\helpers\SignHelper;
|
|
|
+use common\components\delivery\platform\huolala\Huolala;
|
|
|
|
|
|
/**
|
|
|
* 货拉拉适配器
|
|
|
@@ -10,66 +10,14 @@ use common\components\delivery\helpers\SignHelper;
|
|
|
* 基于货拉拉开放平台API进行封装
|
|
|
* 文档参考:货拉拉API调用协议与签名算法
|
|
|
*/
|
|
|
-class HuolalaAdapter implements Adapter
|
|
|
+class HuolalaAdapter extends Huolala implements Adapter
|
|
|
{
|
|
|
- protected $baseUrl;
|
|
|
- protected $appSecret;
|
|
|
- protected $appKey;
|
|
|
- protected $accessToken;
|
|
|
- protected $apiVersion = '1.0';
|
|
|
- /**
|
|
|
- * @var string 订单消息回调地址
|
|
|
- */
|
|
|
- protected $notifyUrl;
|
|
|
-
|
|
|
- public function __construct($accessToken)
|
|
|
- {
|
|
|
- if (getenv('YII_ENV') == 'production') {
|
|
|
- $cfg = [
|
|
|
- 'base_url' => 'https://openapi.huolala.cn/v1',
|
|
|
- 'app_key' => '8S2uZQrWwM1mkG2Cj576PsMiNIe25UNT',
|
|
|
- 'secret' => 'olzor07UgmUWaagahNGQAKQKXRtvhK8j',
|
|
|
- 'notify_url' => 'https://api.shop.wixhb.com/delivery/huolala-callback',
|
|
|
- ];
|
|
|
- } else {
|
|
|
- $cfg = [
|
|
|
- 'base_url' => 'https://openapi-pre.huolala.cn/v1',
|
|
|
- 'app_key' => '8S2uZQrWwM1mkG2Cj576PsMiNIe25UNT',
|
|
|
- 'secret' => 'olzor07UgmUWaagahNGQAKQKXRtvhK8j',
|
|
|
- 'notify_url' => 'https://api.shop.hzghd.com/delivery/huolala-callback',
|
|
|
- ];
|
|
|
- }
|
|
|
- $this->baseUrl = $cfg['base_url'];
|
|
|
- $this->appSecret = $cfg['secret'];
|
|
|
- $this->appKey = $cfg['app_key'];
|
|
|
- $this->accessToken = $accessToken;
|
|
|
- $this->notifyUrl = $cfg['notify_url'];
|
|
|
- }
|
|
|
-
|
|
|
/**
|
|
|
* 获取已开通城市列表
|
|
|
*
|
|
|
* 通过此接口可获取到货拉拉货运业务所有已开通的城市列表信息
|
|
|
*
|
|
|
* @return array 包含城市列表的响应数据
|
|
|
- * [
|
|
|
- * 'ret' => 0,
|
|
|
- * 'msg' => 'Success',
|
|
|
- * 'data' => [
|
|
|
- * 'expire' => 1640728800, // 城市信息缓存刷新时间
|
|
|
- * 'city_list' => [
|
|
|
- * [
|
|
|
- * 'city_id' => 1001,
|
|
|
- * 'name' => '广州',
|
|
|
- * 'name_en' => 'guangzhou',
|
|
|
- * 'is_hot' => 1,
|
|
|
- * 'lat_lon' => ['lat' => 23.129061, 'lon' => 113.26435],
|
|
|
- * 'adcode' => '440100',
|
|
|
- * ],
|
|
|
- * // ... 更多城市
|
|
|
- * ]
|
|
|
- * ]
|
|
|
- * ]
|
|
|
*/
|
|
|
public function cityList()
|
|
|
{
|
|
|
@@ -87,56 +35,6 @@ class HuolalaAdapter implements Adapter
|
|
|
*
|
|
|
* @param int $cityId 城市ID
|
|
|
* @return array|null 包含城市车型信息的响应数据
|
|
|
- * [
|
|
|
- * 'city_id' => 城市ID,
|
|
|
- * 'name' => 城市名(中文),
|
|
|
- * 'name_en' => 城市名(英文),
|
|
|
- * 'city_info_revision' => 城市版本号,
|
|
|
- * 'vehicle_list' => [
|
|
|
- * [
|
|
|
- * 'order_vehicle_id' => 订单车型ID,
|
|
|
- * 'vehicle_name' => 订单车型名称,
|
|
|
- * 'img_url_high_light' => 订单车型图片,
|
|
|
- * 'vehicle_volume' => 订单车型体积(示例:2.8方),
|
|
|
- * 'vehicle_weight' => 订单车型载重(示例:500公斤),
|
|
|
- * 'vehicle_size' => 订单车型尺寸(示例:2.1*1.7*1.6),
|
|
|
- * 'standard_order_vehicle_id' => 国标车型id,
|
|
|
- * 'text_desc' => 车型说明,
|
|
|
- * 'vehicle_attr' => 大小车属性(1-大车,0-小车),
|
|
|
- * 'price_text_item' => [
|
|
|
- * 'base_distancekm' => 起步公里数(km),
|
|
|
- * 'base_price_fen' => 起步价格(分),
|
|
|
- * 'exceed_segment_price' => [
|
|
|
- * [
|
|
|
- * 'start_exdistancekm' => 区间起始公里数(km),
|
|
|
- * 'end_exdistancekm' => 区间结束公里数(km),
|
|
|
- * 'price_extra_fen' => 区间内每公里收费(分),
|
|
|
- * ],
|
|
|
- * // ... 更多区间
|
|
|
- * ]
|
|
|
- * ],
|
|
|
- * 'vehicle_std_item' => [
|
|
|
- * [
|
|
|
- * 'name' => 附加要求名称,
|
|
|
- * 'desc' => 附加要求描述,
|
|
|
- * 'price_fen' => 附加要求收费(分),
|
|
|
- * ],
|
|
|
- * // ... 更多附加要求
|
|
|
- * ],
|
|
|
- * ],
|
|
|
- * // ... 更多车型
|
|
|
- * ],
|
|
|
- * 'spec_req_item' => [
|
|
|
- * [
|
|
|
- * 'type' => 额外需求ID,
|
|
|
- * 'name' => 额外需求名称,
|
|
|
- * 'desc' => 额外需求描述,
|
|
|
- * 'price_type' => 计价类型(1-固定值计价, 2-百分比计价, 3-司机商议, 4-免费),
|
|
|
- * 'price_value_fen' => 价格值(分),
|
|
|
- * ],
|
|
|
- * // ... 更多额外需求
|
|
|
- * ]
|
|
|
- * ]
|
|
|
*/
|
|
|
public function getCityVehicleList($cityId)
|
|
|
{
|
|
|
@@ -292,7 +190,7 @@ class HuolalaAdapter implements Adapter
|
|
|
];
|
|
|
|
|
|
// 添加订单消息回调地址
|
|
|
- $apiData['notify_url'] = urlencode($this->notifyUrl); // 要进行 URL 转码,不然会引起签名验证出错
|
|
|
+ $apiData['notify_url'] = $this->notifyUrl;
|
|
|
|
|
|
return $apiData;
|
|
|
}
|
|
|
@@ -302,46 +200,7 @@ class HuolalaAdapter implements Adapter
|
|
|
*
|
|
|
* 根据货拉拉官方文档实现下单功能
|
|
|
*
|
|
|
- * @param array $data 订单数据,结构参考:
|
|
|
- * [
|
|
|
- * 'city_id' => 下单城市ID (必需),
|
|
|
- * 'city_info_revision' => 城市版本号 (必需),
|
|
|
- * 'order_vehicle_id' => 车型ID (必需),
|
|
|
- * 'vehicle_std' => ['双排座'] 车型附加要求 (可选),
|
|
|
- * 'order_time' => 用车时间戳(秒) (必需),
|
|
|
- * 'contact_name' => 订单联系人 (必需),
|
|
|
- * 'contact_phone_no' => 订单联系人手机 (必需),
|
|
|
- * 'addr_info' => 地址信息数组 (必需)
|
|
|
- * [
|
|
|
- * [
|
|
|
- * 'name' => 地址名称,
|
|
|
- * 'addr' => 地址详情,
|
|
|
- * 'city_id' => 城市ID,
|
|
|
- * 'city_name' => 城市名称,
|
|
|
- * 'district_name' => 区(县),
|
|
|
- * 'house_number' => 楼层及门牌号,
|
|
|
- * 'contacts_name' => 联系人,
|
|
|
- * 'contacts_phone_no' => 联系方式,
|
|
|
- * 'lat_lon' => ['lat' => 纬度, 'lon' => 经度]
|
|
|
- * ],
|
|
|
- * // ... 更多地址(最多10个)
|
|
|
- * ],
|
|
|
- * 'total_price_fen' => 订单总价(分) (必需),
|
|
|
- * 'pay_type' => 支付方式 4-账户余额, 0-三方支付 (必需),
|
|
|
- * 'price_calculate_id' => 计价ID (必需),
|
|
|
- * 'price_item_encryption' => 估价加密数据 (必需),
|
|
|
- * 'vehicle_attr' => 大小车属性 (必需),
|
|
|
- * 'remark' => 订单备注 (可选),
|
|
|
- * 'spec_req' => [2,5] 城市额外需求 (可选),
|
|
|
- * 'out_user_id' => 下单人ID (可选),
|
|
|
- * 'ip' => 下单人IP (可选),
|
|
|
- * 'notify_url' => 消息回调地址 (可选),
|
|
|
- * 'coupon_id' => 优惠券ID (可选),
|
|
|
- * 'invoice_type' => 开票类型 (可选),
|
|
|
- * 'order_service_type' => 服务类型 (可选),
|
|
|
- * 'themis_data' => 风控数据 (可选),
|
|
|
- * 'commodity_info' => 商品信息 (可选),
|
|
|
- * ]
|
|
|
+ * @param array $data 订单数据
|
|
|
* @return array API响应结果
|
|
|
*/
|
|
|
public function createOrder($order)
|
|
|
@@ -504,7 +363,7 @@ class HuolalaAdapter implements Adapter
|
|
|
|
|
|
/**
|
|
|
* 查询订单详情
|
|
|
- * @param $orderUuid 货拉拉订单号 -- 文档上写的是 order_display_id,实则要传 order_uuid
|
|
|
+ * @param string $orderUuid 货拉拉订单号 -- 文档上写的是 order_display_id,实则要传 order_uuid
|
|
|
* @return array
|
|
|
*/
|
|
|
public function selectOrder($orderUuid)
|
|
|
@@ -620,6 +479,7 @@ class HuolalaAdapter implements Adapter
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
+ * @deprecated
|
|
|
* 获取运费报价(多品类)- 需用户授权
|
|
|
*
|
|
|
* 根据货拉拉官方文档 u-price-multiCalculate 实现多品类估价功能
|
|
|
@@ -627,11 +487,11 @@ class HuolalaAdapter implements Adapter
|
|
|
* @param array $data 估价参数
|
|
|
* @param string $orderTime 配送时间
|
|
|
* @return array|null 包含多个计价方案的报价结果
|
|
|
- * @deprecated 使用 buildPriceRequests 和 processPriceResponse 替代
|
|
|
+ * @deprecated 使用 buildPriceRequest 和 processPriceResponse 替代
|
|
|
*/
|
|
|
public function getPrice($data, $orderTime)
|
|
|
{
|
|
|
- $requests = $this->buildPriceRequests($data, $orderTime);
|
|
|
+ $requests = $this->buildPriceRequest($data, $orderTime);
|
|
|
$priceList = [];
|
|
|
|
|
|
foreach ($requests as $requestInfo) {
|
|
|
@@ -646,79 +506,60 @@ class HuolalaAdapter implements Adapter
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 构建API请求参数
|
|
|
+ * 获取可选取消原因
|
|
|
*
|
|
|
- * 根据货拉拉API规范构建完整的请求参数,包括签名
|
|
|
+ * 通过此接口可获取可选择的取消原因,包括主原因和子原因
|
|
|
*
|
|
|
- * @param string $apiMethod API方法名
|
|
|
- * @param array $apiData 业务参数
|
|
|
- * @param string|null $accessToken 用户授权令牌(可选)
|
|
|
- * @return array 完整的请求参数
|
|
|
+ * @param array $data 业务参数(当前接口无必需参数,可传空数组)
|
|
|
+ * @return array|null 包含取消原因列表的响应数据
|
|
|
*/
|
|
|
- protected function buildRequestPayload($apiMethod, $apiData = [], $accessToken = null)
|
|
|
+ public function getCancelReasonList($data)
|
|
|
{
|
|
|
- // 生成随机数(nonce_str)
|
|
|
- $nonceStr = $this->generateNonceStr();
|
|
|
+ // 构建请求参数(无业务数据)
|
|
|
+ $payload = $this->buildRequestPayload('u-order-cancelReason', []);
|
|
|
|
|
|
- // 生成时间戳
|
|
|
- $timestamp = time();
|
|
|
-
|
|
|
- // 构建系统级参数
|
|
|
- $payload = [
|
|
|
- 'api_method' => $apiMethod,
|
|
|
- 'api_version' => $this->apiVersion,
|
|
|
- 'app_key' => $this->appKey,
|
|
|
- 'timestamp' => $timestamp,
|
|
|
- 'nonce_str' => $nonceStr,
|
|
|
- ];
|
|
|
-
|
|
|
- // 如果提供了访问令牌,加入参数
|
|
|
- if (!empty($accessToken)) {
|
|
|
- $payload['access_token'] = $accessToken;
|
|
|
- }
|
|
|
-
|
|
|
- // 业务数据作为JSON字符串
|
|
|
- $payload['api_data'] = json_encode($apiData, JSON_UNESCAPED_UNICODE);
|
|
|
-
|
|
|
- // 计算签名
|
|
|
- $signature = $this->generateSignature($payload);
|
|
|
- $payload['signature'] = $signature;
|
|
|
-
|
|
|
- return $payload;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 生成签名
|
|
|
- *
|
|
|
- * 复用 SignHelper 的通用签名算法
|
|
|
- * 货拉拉要求32位小写MD5签名,而 SignHelper 返回大写,所以需要转为小写
|
|
|
- *
|
|
|
- * @param array $params 待签名参数(不包括signature)
|
|
|
- * @return string 签名值(32位小写)
|
|
|
- */
|
|
|
- protected function generateSignature($params)
|
|
|
- {
|
|
|
-
|
|
|
- // 使用 SignHelper 的通用签名算法
|
|
|
- $sign = SignHelper::makeSign($params, $this->appSecret);
|
|
|
+ // 发送请求
|
|
|
+ $resp = HttpClient::post($this->baseUrl, $payload);
|
|
|
|
|
|
- // 货拉拉 API 要求32位小写,转换为小写
|
|
|
- return strtolower($sign);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 生成随机数字符串
|
|
|
- *
|
|
|
- * @param int $length 长度
|
|
|
- * @return string 随机字符串
|
|
|
- */
|
|
|
- protected function generateNonceStr($length = 16)
|
|
|
- {
|
|
|
- $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
|
- $str = '';
|
|
|
- for ($i = 0; $i < $length; $i++) {
|
|
|
- $str .= $chars[random_int(0, strlen($chars) - 1)];
|
|
|
+ // 处理响应
|
|
|
+ if (isset($resp['ret']) && $resp['ret'] == 0 && isset($resp['data'])) {
|
|
|
+ $respData = $resp['data'];
|
|
|
+
|
|
|
+ // 提取取消原因列表
|
|
|
+ $reasonList = [];
|
|
|
+ if (!empty($respData['reason']) && is_array($respData['reason'])) {
|
|
|
+ foreach ($respData['reason'] as $reason) {
|
|
|
+ // 处理子原因
|
|
|
+ $subReasons = [];
|
|
|
+ if (!empty($reason['sub_reason']) && is_array($reason['sub_reason'])) {
|
|
|
+ foreach ($reason['sub_reason'] as $subReason) {
|
|
|
+ $subReasons[] = [
|
|
|
+ 'id' => (int)($subReason['id'] ?? 0),
|
|
|
+ 'sub_name' => $subReason['sub_name'] ?? '',
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $reasonList[] = [
|
|
|
+ 'id' => $reason['id'],
|
|
|
+ //'name' => $reason['name'] ?? '',
|
|
|
+ 'name' => $subReasons,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'code' => 0,
|
|
|
+ 'data' => [
|
|
|
+ 'reason' => $reasonList,
|
|
|
+ ]
|
|
|
+ ];
|
|
|
}
|
|
|
- return $str;
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'code' => $resp['ret'] ?? -1,
|
|
|
+ 'msg' => $resp['msg'] ?? 'Unknown error',
|
|
|
+ 'data' => null
|
|
|
+ ];
|
|
|
}
|
|
|
}
|