| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523 |
- <?php
- namespace common\components\delivery\services\adapter;
- use bizGhs\order\classes\OrderItemClass;
- use bizHd\order\classes\OrderGoodsClass;
- use common\components\delivery\platform\shunfeng\Shunfeng;
- use Yii;
- class ShunfengAdapter extends Shunfeng implements Adapter
- {
- public function cityList(){}
- /**
- * 格式化订单数据为顺丰同城API所需格式
- *
- * @param array $order 订单信息
- * @param string $orderType 订单类型:xhOrder 或 xhGhsOrder
- * @param array $shop 店铺信息
- * @param array $params 额外参数
- * @return array 格式化后的订单数据
- */
- public function formatOrderData($order, $orderType, $shop, $params)
- {
- // 获取订单商品信息
- if($orderType == 'xhGhsOrder'){
- $itemInfos = OrderItemClass::getAllByCondition(
- ['orderSn' => $order['orderSn']],
- null,
- 'id, name, unitPrice, num, unitWeight'
- );
- }elseif($orderType == 'xhOrder'){
- $itemInfos = OrderGoodsClass::getAllByCondition(
- ['orderSn' => $order['orderSn']],
- null,
- 'id, name, unitPrice, num'
- );
- }
- // 计算商品总重量(单位:克)
- $totalWeight = round($params['weight'], 1) * 1000;
- $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;
- }
- // 构建必填参数
- $data = [
- // ==================== 必填字段 ====================
- 'shop_order_id' => $order['orderSn'],
- 'order_ver' => time(), // 订单版本号 使用相同的商家订单号,同城侧会幂等返回,可以使用此参数+1控制生成新的同城订单
- 'order_source' => $params['order_source'] ?? '花店系统', // 订单接入来源
- 'order_time' => isset($order['addTime']) ? strtotime($order['addTime']) : time(), // 用户下单时间(秒级时间戳)
- 'push_time' => time(), // 推单时间(秒级时间戳)
- 'version' => $this->apiVersion, // API版本号
- // 收货人信息
- 'receive' => [
- 'user_name' => $order['customName'],
- 'user_phone' => Functions::getMobile($order),
- 'user_address' => $order['fullAddress'],
- 'user_lng' => (string)($order['long']),
- 'user_lat' => (string)($order['lat']),
- 'city_name' => $order['city'] ?? '', // 发单城市,用于校验是否跨城
- ],
- // 发货店铺信息
- 'shop' => [
- 'shop_name' => $shop['shopName'] == '首店' ? $shop['merchantName'] : $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, // 坐标类型(1:百度坐标,2:高德坐标(国测坐标),默认为高德坐标系)
- 'is_insured' => 0, // 是否保价(0:非保价)
- 'is_person_direct' => isset($params['type']) && $params['type'] == '专人直送' ? 1 : 0, // 是否专人直送(0:否,1:是)
- 'vehicle' => 0, // 配送交通工具(0:否,1:电动车,2:小轿车)
- 'declared_value' => 0, // 保价金额(单位:分)
- 'gratuity_fee' => 0, // 订单小费(单位:分)
- 'remark' => $params['remark'] ?? '', // 订单备注,string(512)
- 'rider_pick_method' => 1, // 物流流向(1:从门店取件送至用户)
- 'return_flag' => 511, // 返回字段控制标志位(511:全部返回)
- 'payment_method' => 1, // 支付方式(1:立即支付,2:到付)
- 'order_sequence' => $order['sendNum'],
- ];
- // 用户期望上门时间 -- expect_pickup_time
- $expectPickupTime = isset($params['pickupTime']) ? strtotime($params['pickupTime']) : time();
- if($expectPickupTime > time() + 3600){ // 用户期望上门时间大于当前时间+1小时,则认为是预约单
- $data['is_appoint'] = 1; // 是否预约单(0:非预约单)-- 修改为预约单
- $data['appoint_type'] = 2;
- $data['expect_pickup_time'] = $expectPickupTime;
- }
- return $data;
- }
- /**
- * @param $data
- * @return array
- */
- 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' => [
- 'orderId' => $respData['sf_order_id'],
- 'fee' => $respData['real_pay_money'],
- 'distance' => $respData['delivery_distance_meter'],
- ]
- ];
- }
- }
- return [
- 'code' => $resp['error_code'] ?? -1,
- 'msg' => $resp['error_msg'] ?? 'Unknown error',
- 'data' => null
- ];
- }
- /**
- * 取消订单
- *
- * 当商家处发生异常需要取消配送时,调用此接口对订单进行取消操作。
- * @param array $data 预取消查询参数,结构参考:
- * [
- * // ========== 必填参数(二选一)==========
- * 'order_id' => '订单ID',
- * 'partner_order_code' => '外部订单号',
- * // 注意:order_id 和 partner_order_code 必填一个
- *
- * // ========== 必填参数 ==========
- * 'order_cancel_code' => '取消原因code(从可用取消原因列表接口返回结果选择)',
- * ]
- * @param string $orderId 顺丰订单号(SF订单号或商家订单号)
- * @param array $reason 取消原因信息
- * [
- * 'cancel_code' => int, // 可选:取消原因代码(默认313=其他)
- * 'cancel_reason' => string, // 可选:其他取消原因说明
- * 'order_type' => int // 可选:1-顺丰订单号,2-商家订单号(默认1)
- * ]
- * @return array 取消结果
- */
- public function cancelOrder($orderId, $data)
- {
- // 构建请求参数
- $payload = [
- 'order_id' => $orderId,
- 'push_time' => time(), // 取消时间戳(秒级)
- ];
- $payload['cancel_code'] = $data['order_cancel_code'];
- if(isset($data['order_cancel_reason'])){
- $payload['cancel_reason'] = $data['order_cancel_reason'];
- }
- if (isset($data['order_type'])) {
- $payload['order_type'] = $data['order_type'];
- }
- // 调用API
- $resp = $this->httpRequest('cancelorder', $payload);
- // 处理响应
- if (isset($resp['error_code']) && $resp['error_code'] == 0 && isset($resp['result'])) {
- $result = $resp['result'];
- return [
- 'code' => 0,
- 'platform' => 'shunfeng',
- 'data' => [
- 'deductionFee' => $result['deduction_detail']['deduction_fee'], //取消收费金额(单位:分)
- 'sf_order_id' => $result['sf_order_id'],
- 'shop_order_id' => $result['shop_order_id'],
- 'shop_cancel_times' => $result['deduction_detail']['shop_cancel_times'],
- 'free_cancel_times' => $result['deduction_detail']['free_cancel_times'],
- 'push_time' => $result['push_time'],
- ]
- ];
- }
- return [
- 'code' => (int)($resp['error_code'] ?? -1),
- 'platform' => 'shunfeng',
- 'msg' => $resp['error_msg'] ?? 'Cancel order failed',
- 'data' => null
- ];
- }
- /**
- * 预取消订单
- *
- * 并非真正取消订单,用来验证是否可以取消订单并返回计价等信息。
- *
- * @param array $data 预取消订单参数
- * [
- * 'order_id' => string, // 必须:订单ID(顺丰订单号或商家订单号)
- * 'cancel_reason' => string, // 可选:取消原因
- * 'shop_id' => string, // 可选:店铺ID
- * 'shop_type' => int // 可选:店铺类型(1-顺丰,2-接入方)
- * ]
- * @return array 预取消结果(包含是否可以取消、计费信息等)
- */
- public function preCancelOrder($data)
- {
- // 验证必填参数
- if (empty($data['order_id'])) {
- return [
- 'code' => -1,
- 'msg' => 'order_id is required',
- 'data' => null
- ];
- }
- // 构建请求参数
- $payload = [
- 'order_id' => $data['order_id'],
- 'push_time' => time(), // 取消时间戳(秒级)
- ];
- // 添加可选参数
- if (isset($data['cancel_reason']) && !empty($data['cancel_reason'])) {
- $payload['cancel_reason'] = $data['cancel_reason'];
- }
- if (isset($data['shop_id']) && !empty($data['shop_id'])) {
- $payload['shop_id'] = $data['shop_id'];
- }
- if (isset($data['shop_type'])) {
- $payload['shop_type'] = $data['shop_type'];
- }
- // 调用API
- $resp = $this->httpRequest('precancelorder', $payload);
- // 处理响应
- if (isset($resp['error_code']) && $resp['error_code'] == 0 && isset($resp['result'])) {
- $result = $resp['result'];
- return [
- 'code' => 0,
- 'platform' => 'shunfeng',
- 'data' => [
- 'order_type' => $result['order_type'],
- 'could_cancel' => $result['could_cancel'],
- 'promise_delivery_time' => $result['promise_delivery_time'], // 承诺配送时长(分)
- 'delivery_type' => $result['delivery_type'], // 订单类型(0-预约送达,1-立即单,2-预约上门)
- 'is_cancel_charge_price_rule' => $result['is_cancel_charge_price_rule'],
- 'is_over_free_cancel_times' => $result['is_over_free_cancel_times'], // 是否超出免费取消次数
- 'is_deduction_fee' => $result['is_deduction_fee'], // 是否有取消收费
- 'deduction_fee' => $result['deduction_fee'], // 取消收费(单位:分)
- 'cancel_charge_price_rule' => $result['cancel_charge_price_rule'],
- 'shop_cancel_times' => $result['shop_cancel_times'],
- 'free_cancel_times' => $result['free_cancel_times'],
- 'sf_order_id' => $result['sf_order_id'],
- 'shop_order_id' => $result['shop_order_id'],
- 'push_time' => $result['push_time'],
- ]
- ];
- }
- return [
- 'code' => (int)($resp['error_code'] ?? -1),
- 'msg' => $resp['error_msg'] ?? 'Pre-cancel order failed',
- 'data' => null
- ];
- }
- /**
- * 查询订单状态
- * @param array $data
- * @return array
- */
- public function selectOrder($orderId)
- {
- if (empty($orderId)) {
- return [
- 'code' => -1,
- 'platform' => 'shunfeng',
- 'msg' => 'order_id不能为空',
- 'data' => null,
- ];
- }
- $payload = [
- 'order_id' => $orderId,
- 'push_time' => time(),
- ];
- if (isset($data['order_type'])) {
- $payload['order_type'] = (int)$data['order_type'];
- }
- if (!empty($data['shop_id'])) {
- $payload['shop_id'] = $data['shop_id'];
- }
- if (isset($data['shop_type'])) {
- $payload['shop_type'] = (int)$data['shop_type'];
- }
- $resp = $this->httpRequest('getorderstatus', $payload);
- if (isset($resp['error_code']) && $resp['error_code'] == 0 && isset($resp['result'])) {
- return [
- 'code' => 0,
- 'platform' => 'shunfeng',
- 'data' => $resp['result']
- ];
- }
- return [
- 'code' => (int)($resp['error_code'] ?? -1),
- 'platform' => 'shunfeng',
- 'msg' => $resp['error_msg'] ?? '查询订单状态失败',
- 'data' => null
- ];
- }
- /**
- * @deprecated
- * @param $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'])) {
- $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时返回,单位分(实际支付金额=总支付费用-优惠券总金额)
- // 还有其它返回值没加进来
- ];
- } else {
- Yii::error('shunfeng 报价失败:'.json_encode($resp, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
- return [
- 'platform' => 'shunfeng',
- 'error' => isset($resp['error_msg']) ? $resp['error_msg'] : '',
- ];
- }
- }
- /**
- * 构建运费报价请求信息(供并发请求使用)
- * @param array $data 报价参数
- * @param string $pickupTime 取件时间
- * @return array|null 包含 url, payload, headers, timeout 的请求信息
- */
- public function buildPriceRequest($data, $pickupTime)
- {
- unset($data['platform']);
-
- // 根据 $pickupTime ,调整 $data 中的部分参数
- $timestamp = strtotime($pickupTime);
- if($timestamp > time() + 300){ // 5分钟后算预约单
- $data['is_appoint'] = 1; // 是否预约单(0:非预约单;1:预约单)-- 修改为预约单
- $data['appoint_type'] = 2; // 预约类型 -- 1:预约送达单;2:预约上门单 (预约单的时候传入,即时单的时候不传)
- $data['expect_pickup_time'] = $timestamp; // 期望取件时间(秒级时间戳)
- }
- // 构建签名用的完整payload
- $payload = [
- 'dev_id' => $this->devId,
- 'shop_id' => $this->shopId,
- 'shop_type' => 1, //1:顺丰店铺ID 2:接入方店铺ID
- ];
- $payload = array_merge($payload, $data);
- // 计算签名(SignHelper 会自动递归过滤空值)
- $sign = $this->generateSignature($payload);
- $request = [
- 'url' => $this->baseUrl . 'precreateorder?sign=' . $sign,
- 'data' => $payload,
- 'headers' => ["Content-Type" => "application/json"],
- 'timeout' => 10.0,
- ];
- // Yii::info('顺丰构建运费报价请求信息 -- ' .json_encode($request, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
- return $request;
- }
- public function addTip($data)
- {
- $orderId = $data['orderId'] ?? null;
- $gratuityFee = $data['tips'] ?? null;
- if (empty($orderId)) {
- return [
- 'code' => -1,
- 'platform' => 'shunfeng',
- 'msg' => 'order_id不能为空',
- 'data' => null,
- ];
- }
- if (!isset($gratuityFee) || (int)$gratuityFee < 100) {
- return [
- 'code' => -1,
- 'platform' => 'shunfeng',
- 'msg' => 'gratuity_fee必须≥100(单位:分)',
- 'data' => null,
- ];
- }
- $payload = [
- 'order_id' => $orderId,
- 'gratuity_fee' => (int)$gratuityFee,
- 'push_time' => isset($data['push_time']) ? (int)$data['push_time'] : time(),
- ];
- if (isset($data['order_type'])) {
- $payload['order_type'] = (int)$data['order_type'];
- }
- if (!empty($data['shop_id'])) {
- $payload['shop_id'] = $data['shop_id'];
- }
- if (isset($data['shop_type'])) {
- $payload['shop_type'] = (int)$data['shop_type'];
- }
- if (!empty($data['serial_number'])) {
- $payload['serial_number'] = (string)$data['serial_number'];
- }
- $resp = $this->httpRequest('addordergratuityfee', $payload);
- if (isset($resp['error_code']) && (int)$resp['error_code'] === 0) {
- $result = is_string($resp['result'] ?? null) ? json_decode($resp['result'], true) : ($resp['result'] ?? []);
- return [
- 'code' => 0,
- 'platform' => 'shunfeng',
- 'data' => $result,
- ];
- }
- Yii::error('[ShunfengAdapter] addTip failed: ' . json_encode($resp, JSON_UNESCAPED_UNICODE));
- return [
- 'code' => (int)($resp['error_code'] ?? -1),
- 'platform' => 'shunfeng',
- 'msg' => $resp['error_msg'] ?? '添加小费失败',
- 'data' => null,
- ];
- }
- }
|