|
@@ -2,20 +2,304 @@
|
|
|
namespace common\components\delivery\services\adapter;
|
|
namespace common\components\delivery\services\adapter;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+use bizGhs\order\classes\OrderItemClass;
|
|
|
|
|
+use common\components\delivery\helpers\HttpClient;
|
|
|
|
|
+use Yii;
|
|
|
|
|
+
|
|
|
class ShunfengAdapter
|
|
class ShunfengAdapter
|
|
|
{
|
|
{
|
|
|
|
|
+ protected $baseUrl;
|
|
|
|
|
+ protected $appId;
|
|
|
|
|
+ protected $appSecret;
|
|
|
|
|
+ protected $merchantId;
|
|
|
|
|
+ protected $shopId;
|
|
|
|
|
+ protected $accessToken;
|
|
|
|
|
+ protected $apiVersion = '1.0';
|
|
|
|
|
+
|
|
|
|
|
+ public function __construct($accessToken = '', $shopId = 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ // 根据环境设置基础URL
|
|
|
|
|
+ if (getenv('YII_ENV') == 'production') {
|
|
|
|
|
+ $this->baseUrl = 'https://openic.sf-express.com/open/api/external/';
|
|
|
|
|
+ $this->appId = 1741530942;
|
|
|
|
|
+ $this->appSecret = '07b3f83d19a4a9f89322976c936faf92';
|
|
|
|
|
+ $this->shopId = $shopId;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $this->baseUrl = 'https://openic.sf-express.com/open/api/external/';
|
|
|
|
|
+ $this->appId = 1741530942;
|
|
|
|
|
+ $this->appSecret = '07b3f83d19a4a9f89322976c936faf92';
|
|
|
|
|
+ $this->shopId = $shopId;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 配置信息(从Auth类获取)
|
|
|
|
|
+ $this->accessToken = $accessToken;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
public function cityList()
|
|
public function cityList()
|
|
|
{
|
|
{
|
|
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 格式化订单数据为顺丰同城API所需格式
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param array $order 订单信息
|
|
|
|
|
+ * @param array $shop 店铺信息
|
|
|
|
|
+ * @param array $params 额外参数
|
|
|
|
|
+ * @return array 格式化后的订单数据
|
|
|
|
|
+ */
|
|
|
|
|
+ public function formatOrderData($order, $shop, $params)
|
|
|
|
|
+ {
|
|
|
|
|
+ // 获取订单商品信息
|
|
|
|
|
+ $itemInfos = OrderItemClass::getAllByCondition(
|
|
|
|
|
+ ['orderSn' => $order['orderSn']],
|
|
|
|
|
+ null,
|
|
|
|
|
+ 'id, name, unitPrice, num, unitWeight'
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // 计算商品总重量(单位:克)
|
|
|
|
|
+ $totalWeight = 0;
|
|
|
|
|
+ $productNum = 0; // 物品个数
|
|
|
|
|
+ $productTypeNum = 0; // 物品种类个数
|
|
|
|
|
+ $productDetail = []; // 物品详情
|
|
|
|
|
+
|
|
|
|
|
+ foreach ($itemInfos as $item) {
|
|
|
|
|
+ // 累加重量(转换为克)
|
|
|
|
|
+ $itemWeight = (float)($item['unitWeight'] ?? 0) * (int)($item['num'] ?? 1) * 1000;
|
|
|
|
|
+ $totalWeight += (int)$itemWeight;
|
|
|
|
|
+
|
|
|
|
|
+ // 累加商品数量和种类
|
|
|
|
|
+ $productNum += (int)($item['num'] ?? 1);
|
|
|
|
|
+ $productTypeNum++;
|
|
|
|
|
+
|
|
|
|
|
+ // 构建商品详情
|
|
|
|
|
+ $productDetail[] = [
|
|
|
|
|
+ 'product_name' => $item['name'] ?? '鲜花',
|
|
|
|
|
+ 'product_num' => (int)($item['num'] ?? 1),
|
|
|
|
|
+ 'product_price' => (int)(($item['unitPrice'] ?? 0) * 100), // 转换为分
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 如果没有商品信息,使用订单重量
|
|
|
|
|
+ if ($totalWeight == 0 && isset($order['weight'])) {
|
|
|
|
|
+ $totalWeight = (int)($order['weight'] * 1000);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 默认重量为1000克(1公斤)
|
|
|
|
|
+ if ($totalWeight == 0) {
|
|
|
|
|
+ $totalWeight = 1000;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 如果没有商品详情,添加默认商品
|
|
|
|
|
+ if (empty($productDetail)) {
|
|
|
|
|
+ $productDetail[] = [
|
|
|
|
|
+ 'product_name' => '鲜花',
|
|
|
|
|
+ 'product_num' => 1,
|
|
|
|
|
+ ];
|
|
|
|
|
+ $productNum = 1;
|
|
|
|
|
+ $productTypeNum = 1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取订单时间
|
|
|
|
|
+ $orderTime = isset($order['addTime']) ? strtotime($order['addTime']) : time();
|
|
|
|
|
+
|
|
|
|
|
+ // 构建必填参数
|
|
|
|
|
+ $data = [
|
|
|
|
|
+ // ==================== 必填字段 ====================
|
|
|
|
|
+ 'shop_order_id' => $order['orderSn'],
|
|
|
|
|
+ 'order_ver' => time(), // 订单版本号 使用相同的商家订单号,同城侧会幂等返回,可以使用此参数+1控制生成新的同城订单
|
|
|
|
|
+ 'order_source' => $params['order_source'] ?? '花店系统', // 订单接入来源
|
|
|
|
|
+ 'order_time' => $orderTime, // 用户下单时间(秒级时间戳)
|
|
|
|
|
+ 'push_time' => time(), // 推单时间(秒级时间戳)
|
|
|
|
|
+ 'version' => 19, // API版本号
|
|
|
|
|
+
|
|
|
|
|
+ // 收货人信息
|
|
|
|
|
+ 'receive' => [
|
|
|
|
|
+ 'user_name' => $order['customName'],
|
|
|
|
|
+ 'user_phone' => $order['customMobile'],
|
|
|
|
|
+ 'user_address' => $order['fullAddress'],
|
|
|
|
|
+ 'user_lng' => (string)($order['long']),
|
|
|
|
|
+ 'user_lat' => (string)($order['lat']),
|
|
|
|
|
+ 'city_name' => $order['city'] ?? '', // 发单城市,用于校验是否跨城
|
|
|
|
|
+ ],
|
|
|
|
|
+
|
|
|
|
|
+ // 发货店铺信息
|
|
|
|
|
+ 'shop' => [
|
|
|
|
|
+ 'shop_name' => $shop['merchantName'] ?? $shop['shopName'] ?? '',
|
|
|
|
|
+ 'shop_address' => $shop['address'] ?? '',
|
|
|
|
|
+ 'shop_lng' => (string)($shop['long'] ?? ''),
|
|
|
|
|
+ 'shop_lat' => (string)($shop['lat'] ?? ''),
|
|
|
|
|
+ 'shop_phone' => $shop['mobile'] ?? '',
|
|
|
|
|
+ ],
|
|
|
|
|
+
|
|
|
|
|
+ // 订单详情
|
|
|
|
|
+ 'order_detail' => [
|
|
|
|
|
+ 'total_price' => (int)(($order['actPrice'] ?? 0) * 100), // 订单总金额(单位:分)
|
|
|
|
|
+ 'product_type' => 14, // 物品类型:14-鲜花
|
|
|
|
|
+ 'weight_gram' => $totalWeight, // 物品重量(单位:克)
|
|
|
|
|
+ 'product_num' => $productNum, // 物品个数
|
|
|
|
|
+ 'product_type_num' => $productTypeNum, // 物品种类个数
|
|
|
|
|
+ 'product_detail' => $productDetail, // 物品详情
|
|
|
|
|
+ ],
|
|
|
|
|
+
|
|
|
|
|
+ // ==================== 可选字段 ====================
|
|
|
|
|
+ 'lbs_type' => 2, // 坐标类型(2:高德坐标)
|
|
|
|
|
+ // 'is_appoint' => 0, // 是否预约单(0:非预约单) // TODO
|
|
|
|
|
+ 'is_insured' => 0, // 是否保价(0:非保价)
|
|
|
|
|
+ 'is_person_direct' => 0, // 是否专人直送(0:否)
|
|
|
|
|
+ 'vehicle' => 0, // 配送交通工具(0:否,1:电动车,2:小轿车)
|
|
|
|
|
+ 'declared_value' => 0, // 保价金额(单位:分)
|
|
|
|
|
+ 'gratuity_fee' => 0, // 订单小费(单位:分)
|
|
|
|
|
+ 'rider_pick_method' => 1, // 物流流向(1:从门店取件送至用户)
|
|
|
|
|
+ 'return_flag' => 511, // 返回字段控制标志位(511:全部返回)
|
|
|
|
|
+ 'payment_method' => 1, // 支付方式(1:立即支付,2:到付)
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ return $data;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
public function createOrder($data)
|
|
public function createOrder($data)
|
|
|
{
|
|
{
|
|
|
|
|
+ $resp = $this->httpRequest('createorder', $data);
|
|
|
|
|
|
|
|
|
|
+ // 处理响应
|
|
|
|
|
+ if (isset($resp['error_code']) && $resp['error_code'] == 0 && isset($resp['result'])) {
|
|
|
|
|
+ // business_data 是 JSON 字符串,需要解析
|
|
|
|
|
+ $respData = is_string($resp['result']) ? json_decode($resp['result'], true) : $resp['result'];
|
|
|
|
|
+
|
|
|
|
|
+ if (isset($respData['sf_order_id'])) {
|
|
|
|
|
+ return [
|
|
|
|
|
+ 'code' => 0,
|
|
|
|
|
+ 'platform' => 'shunfeng',
|
|
|
|
|
+ 'data' => [
|
|
|
|
|
+ 'order_id' => $respData['sf_order_id'],
|
|
|
|
|
+ 'fee' => $respData['real_pay_money'],
|
|
|
|
|
+ 'distance' => $respData['delivery_distance_meter'],
|
|
|
|
|
+ ]
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return [
|
|
|
|
|
+ 'code' => (int)($resp['code'] ?? -1),
|
|
|
|
|
+ 'msg' => $resp['msg'] ?? 'Unknown error',
|
|
|
|
|
+ 'data' => null
|
|
|
|
|
+ ];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public function getPrice($data)
|
|
public function getPrice($data)
|
|
|
{
|
|
{
|
|
|
|
|
+ $requestData = [
|
|
|
|
|
+ "user_lng" => "116.339392",
|
|
|
|
|
+ "user_lat" => "40.002349",
|
|
|
|
|
+ "user_address" => "北京市海淀区国泰大厦",
|
|
|
|
|
+ "weight" => 1000,
|
|
|
|
|
+ "product_type" => 4,
|
|
|
|
|
+ "push_time" => 1639558442
|
|
|
|
|
+ ];
|
|
|
|
|
+ $method = 'precreateorder';
|
|
|
|
|
+ $ret = $this->httpRequest($method, $requestData);
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 处理运费报价响应
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param array $resp API 响应
|
|
|
|
|
+ * @return array|null 解析后的报价结果
|
|
|
|
|
+ */
|
|
|
|
|
+ public function processPriceResponse($resp)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (isset($resp['error_code']) && $resp['error_code'] == 0 && isset($resp['result'])) {
|
|
|
|
|
+ //return $resp['result'];
|
|
|
|
|
+// return [
|
|
|
|
|
+// 'platform' => 'fengniao',
|
|
|
|
|
+// 'distance' => (int)($respData['distance'] ?? 0),
|
|
|
|
|
+// 'time' => (int)($respData['time'] ?? 0),
|
|
|
|
|
+// 'goods_infos' => $goodsInfos,
|
|
|
|
|
+// ];
|
|
|
|
|
+
|
|
|
|
|
+ $result = $resp['result'];
|
|
|
|
|
+ return [
|
|
|
|
|
+ 'platform' => 'shunfeng',
|
|
|
|
|
+ 'total_price' => $result['total_price'], // 配送费总额
|
|
|
|
|
+ 'distance' => $result['delivery_distance_meter'], //配送距离
|
|
|
|
|
+ 'total_pay_money' => $result['total_pay_money'], //总支付费用(优惠前),当return_flag中包含32时返回,单位分
|
|
|
|
|
+ 'real_pay_money' => $result['real_pay_money'], //实际支付金额,当return_flag中包含64时返回,单位分(实际支付金额=总支付费用-优惠券总金额)
|
|
|
|
|
+ // 还有其它返回值没加进来,TODO
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 构建运费报价请求信息(供并发请求使用)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param array $data 报价参数
|
|
|
|
|
+ * @param string $orderTime 配送时间
|
|
|
|
|
+ * @return array|null 包含 url, payload, headers, timeout 的请求信息
|
|
|
|
|
+ */
|
|
|
|
|
+ public function buildPriceRequest($data, $orderTime)
|
|
|
|
|
+ {
|
|
|
|
|
+ unset($data['platform']);
|
|
|
|
|
+ // 根据 $orderTime ,调整 $data 中的部分参数
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ // 构建签名用的完整payload
|
|
|
|
|
+ $payload = [
|
|
|
|
|
+ 'dev_id' => $this->appId,
|
|
|
|
|
+ 'shop_id' => $this->shopId,
|
|
|
|
|
+ 'shop_type' => 1, //1:顺丰店铺ID 2:接入方店铺ID
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ $payload = array_merge($payload, $data);
|
|
|
|
|
+
|
|
|
|
|
+ // 计算签名(SignHelper 会自动递归过滤空值)
|
|
|
|
|
+ $sign = $this->generateSignature($payload);
|
|
|
|
|
+
|
|
|
|
|
+ return [
|
|
|
|
|
+ 'url' => $this->baseUrl . 'precreateorder?sign=' . $sign,
|
|
|
|
|
+ 'data' => $payload,
|
|
|
|
|
+ 'headers' => ["Content-Type" => "application/json"],
|
|
|
|
|
+ 'timeout' => 5.0,
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 构建API请求参数
|
|
|
|
|
+ *
|
|
|
|
|
+ * 根据蜂鸟API规范构建完整的请求参数,包括签名
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param string $method API方法名
|
|
|
|
|
+ * @param array $businessData 业务参数
|
|
|
|
|
+ * @return array 完整的请求参数
|
|
|
|
|
+ */
|
|
|
|
|
+ protected function httpRequest($method, $businessData = [])
|
|
|
|
|
+ {
|
|
|
|
|
+ // 构建参数
|
|
|
|
|
+ $payload = [
|
|
|
|
|
+ 'dev_id' => $this->appId,
|
|
|
|
|
+ 'shop_id' => $this->shopId,
|
|
|
|
|
+ 'shop_type' => 1, //1:顺丰店铺ID 2:接入方店铺ID
|
|
|
|
|
+ //-------------------------------------------------
|
|
|
|
|
+ ];
|
|
|
|
|
+ $payload = array_merge($payload, $businessData);
|
|
|
|
|
+
|
|
|
|
|
+ // 计算签名
|
|
|
|
|
+ $signature = $this->generateSignature($payload);
|
|
|
|
|
+ $url = $this->baseUrl . $method . '?sign=' . $signature;
|
|
|
|
|
|
|
|
|
|
+ $resp = HttpClient::post($url, $payload, [
|
|
|
|
|
+ 'Content-Type' => 'application/json',
|
|
|
|
|
+ ]);
|
|
|
|
|
+ return $resp;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function generateSignature($params)
|
|
|
|
|
+ {
|
|
|
|
|
+ $post_data = json_encode($params);
|
|
|
|
|
+ $sign_char = $post_data . "&{$this->appId}&{$this->appSecret}";
|
|
|
|
|
+ $sign = base64_encode(MD5($sign_char)); // 注:md5出来的结果是32位小写16进制字符串,$sign 的最终结果末尾包含等号=
|
|
|
|
|
+ return $sign;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|