$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, ]; } }