buildRequestPayload('cityList', $businessData); // 发送请求 $url = $this->baseUrl . '/cityList'; $resp = HttpClient::post($url, $payload, [ 'Content-Type' => 'application/json', ]); Yii::info("[FengniaoAdapter] cityList Response: " . json_encode($resp)); return $resp; } /** * 转化订单数据 * @param $order * @param string $type 订单类型:xhOrder 或 xhGhsOrder * @param $shop * @param $params * @return array * @throws \Exception */ public function formatOrderData($order, $type, $shop, $params) { // 订单类型为预约单时必传,如需要送达时间 – 推单时间 < 60min,则蜂鸟配送开放平台自动将订单类型置为即时单 $timeDiff = strtotime($params['pickupTime']) - time(); $orderType = 1; if ($timeDiff > 3600) { $orderType = 3; } $sn = $order['orderSn']; // 批发 if($type == 'xhGhsOrder'){ $itemInfos = OrderItemClass::getAllByCondition(['orderSn' => $sn], null, 'id, name, unitPrice, num, unitWeight'); //$goodsWeight = 0; $goodsItemList = []; foreach ($itemInfos as $item){ $goodsItemList[] = [ 'item_actual_amount_cent' => $item['unitPrice'] * 100 * $item['num'], 'item_amount_cent' => $item['unitPrice'] * 100, 'item_id' => $item['id'], 'item_name' => $item['name'], 'item_quantity' => $item['num'], ]; //$goodsWeight += $item['unitWeight']; } $goodsCount = count($goodsItemList); // 零售 }else if($type == 'xhOrder'){ $itemInfos = OrderGoodsClass::getAllByCondition(['orderSn' => $sn], null, 'id, name, unitPrice, num'); //$goodsWeight = 0; $goodsItemList = []; foreach ($itemInfos as $item){ $goodsItemList[] = [ 'item_actual_amount_cent' => $item['unitPrice'] * 100 * $item['num'], 'item_amount_cent' => $item['unitPrice'] * 100, 'item_id' => $item['id'], 'item_name' => $item['name'], 'item_quantity' => $item['num'], ]; } $goodsCount = count($goodsItemList); }else{ Yii::error('订单编号出错: '.$sn); util::fail('订单编号出错'); } $goodsWeight = floatval($params['weight']);//取客户端传来的重量 $orderData = [ //'out_shop_code' => $params['mainId'], // 当使用门店发单 out_shop_code 和 chain_store_id 必填1个 'chain_store_id' => $this->getShopId(), // 动态修改为门店号 -- (点对点不传) 'partner_order_code' => $order['orderSn'], 'receiver_primary_phone' => Functions::getMobile($order), 'receiver_name' => $order['customName'], 'receiver_latitude' => (float)$order['lat'], 'receiver_longitude' => (float)$order['long'], 'receiver_address' => $order['fullAddress'], 'position_source' => 3, // 坐标经纬度来源 -- 3 高德地图 'goods_count' => $goodsCount, 'goods_weight' => (float)$goodsWeight, 'goods_total_amount_cent' => (int)($order['prePrice'] * 100), //订单商品总金额 'goods_actual_amount_cent' => (int)($order['actPrice'] * 100), //订单货物实付金额 'goods_item_list' => $goodsItemList, 'order_type' => $orderType, 'order_remark' => $params['remark'], 'serial_number' => $order['sendNum'] // 配送编号,方便寻找订单对应的花材 ]; if($orderType == 3) { // 预计出餐时间 单位:毫秒 -- 预约单该字段必填 $orderData['expect_fetch_time'] = strtotime($params['pickupTime']) * 1000; // 需要送达时间 = 取件时间 + 30分钟 $orderData['require_receive_time'] = (strtotime($params['pickupTime']) + 1800) * 1000; } return $orderData; } /** * 创建订单(需用户授权) * * 根据蜂鸟官方文档实现下单功能。 * 若需校验配送费价格,需先走预下单接口,并在下单时填上预下单信息。 * * @param array $data 订单数据,结构参考: * [ * // ========== 必填参数 ========== * 'partner_order_code' => '外部订单号(用于幂等)', * 'receiver_primary_phone' => '收货人主要联系方式(手机号、400电话、座机等)', * 'receiver_name' => '收货人姓名', * 'receiver_latitude' => '收货人纬度', * 'receiver_longitude' => '收货人经度', * 'receiver_address' => '收货人地址', * 'position_source' => '经纬度来源(1:腾讯, 2:百度, 3:高德,推荐3)', * 'goods_count' => '货物件数', * 'goods_weight' => '货物总重量(kg)', * 'goods_total_amount_cent' => '订单商品总金额(分)', * 'goods_actual_amount_cent' => '订单货物实付金额(分)', * 'goods_item_list' => '货物明细列表', * 'order_type' => '订单类型(1:即时单, 3:预约单)', * * // ========== 可选参数 ========== * // 门店信息(二选一) * 'chain_store_id' => '门店ID', * 'out_shop_code' => '外部门店ID', * * // 预下单关联 * 'pre_create_order_t_index_id' => '预下单标识(用于校验配送费价格)', * 'actual_delivery_amount_cent' => '优惠后配送费总价(分,不为null则校验价格)', * * // 取货点信息(点对点特殊场景) * 'transport_address' => '取货点地址', * 'transport_name' => '取货点名称', * 'transport_latitude' => '取货点纬度', * 'transport_longitude' => '取货点经度', * 'transport_tel' => '取货点联系人电话', * * // 时间相关 * 'order_add_time' => '下单时间(毫秒)', * 'require_receive_time' => '需要送达时间(毫秒,预约单时重要)', * 'expect_fetch_time' => '预计出餐时间(毫秒,预约单必填)', * * // 订单备注和来源 * 'order_remark' => '用户备注', * 'order_source' => '商户订单来源(0:手发单, 2:美团, 4:口碑, 6:饿了么等)', * 'order_source_order_id' => '订单来源单号', * 'serial_number' => '商家订单流水号(长度<=8)', * * // 骑手交互 * 'fetch_code' => '取餐码', * 'write_off_code' => '核销码', * 'cancel_code' => '取消码', * 'customer_ext_tel' => '分机号', * * // 收货人备用信息 * 'receiver_second_phone' => '收货人备用联系方式', * * // 小费和优惠 * 'order_tip_amount_cent' => '订单小费金额(分)', * 'use_coupon' => '是否使用优惠券(0:否, 1:是,默认使用)', * 'warehouse_id' => '优惠券记录ID', * * // 商品选择 * 'base_goods_id' => '基础商品ID', * 'service_goods_id' => '服务商品ID', * 'appoint_extra_goods_ids' => '指定增值商品ID列表', * * 'goods_item_list' => [ // 货物明细(必填) * [ * 'item_id' => '商品编号', * 'item_name' => '商品名称', * 'item_amount_cent' => '商品原价(分,必填)', * 'item_actual_amount_cent' => '商品实际支付金额(分,必填)', * 'item_size' => '商品尺寸(1:小, 2:中, 3:大)', * 'item_quantity' => '商品数量', * 'item_remark' => '商品备注', * ], * ], * ] * @return array API响应结果 * 成功:['code' => 0, 'data' => ['order_id' => 订单ID]] * 失败:['code' => 错误码, 'msg' => 错误信息, 'data' => null] */ public function createOrder($data) { // 构建货物明细列表 $goodsItemList = []; if (!empty($data['goods_item_list']) && is_array($data['goods_item_list'])) { foreach ($data['goods_item_list'] as $item) { $goodsItem = [ 'item_actual_amount_cent' => isset($item['item_actual_amount_cent']) ? (int)$item['item_actual_amount_cent'] : 0, 'item_amount_cent' => isset($item['item_amount_cent']) ? (int)$item['item_amount_cent'] : 0, ]; if (!empty($item['item_id'])) { $goodsItem['item_id'] = $item['item_id']; } if (!empty($item['item_name'])) { $goodsItem['item_name'] = $item['item_name']; } if (isset($item['item_size'])) { $goodsItem['item_size'] = (int)$item['item_size']; } if (isset($item['item_quantity'])) { $goodsItem['item_quantity'] = (int)$item['item_quantity']; } if (!empty($item['item_remark'])) { $goodsItem['item_remark'] = $item['item_remark']; } $goodsItemList[] = $goodsItem; } } // 构建业务数据 - 必填字段 $businessData = [ 'partner_order_code' => $data['partner_order_code'], 'receiver_primary_phone' => $data['receiver_primary_phone'], 'receiver_name' => $data['receiver_name'], 'receiver_latitude' => (double)($data['receiver_latitude']), 'receiver_longitude' => (double)($data['receiver_longitude']), 'receiver_address' => $data['receiver_address'], 'position_source' => $data['position_source'], 'goods_count' => (int)$data['goods_count'], 'goods_weight' => (float)$data['goods_weight'], 'goods_total_amount_cent' => $data['goods_total_amount_cent'], 'goods_actual_amount_cent' => $data['goods_actual_amount_cent'], 'goods_item_list' => $goodsItemList, 'order_type' => $data['order_type'], ]; // 可选参数 - 门店信息(二选一:chain_store_id 或 out_shop_code) if (!empty($data['chain_store_id'])) { $businessData['chain_store_id'] = (int)$data['chain_store_id']; } if (!empty($data['out_shop_code'])) { $businessData['out_shop_code'] = $data['out_shop_code']; } // 可选参数 - 预下单关联(用于价格校验) if (!empty($data['pre_create_order_t_index_id'])) { $businessData['pre_create_order_t_index_id'] = $data['pre_create_order_t_index_id']; } if (isset($data['actual_delivery_amount_cent']) && $data['actual_delivery_amount_cent'] !== null) { $businessData['actual_delivery_amount_cent'] = (int)$data['actual_delivery_amount_cent']; } // 可选参数 - 取货点信息(点对点特殊场景) if (!empty($data['transport_address'])) { $businessData['transport_address'] = $data['transport_address']; } if (!empty($data['transport_name'])) { $businessData['transport_name'] = $data['transport_name']; } if (isset($data['transport_latitude']) && $data['transport_latitude'] !== '') { $businessData['transport_latitude'] = (double)$data['transport_latitude']; } if (isset($data['transport_longitude']) && $data['transport_longitude'] !== '') { $businessData['transport_longitude'] = (double)$data['transport_longitude']; } if (!empty($data['transport_tel'])) { $businessData['transport_tel'] = $data['transport_tel']; } // 可选参数 - 时间相关 if (!empty($data['order_add_time'])) { // 单位:毫秒 $businessData['order_add_time'] = (int)$data['order_add_time']; } if (!empty($data['require_receive_time'])) { $businessData['require_receive_time'] = $data['require_receive_time']; } // 预计出餐时间 单位:毫秒 预约单该字段必填 if (!empty($data['expect_fetch_time'])) { $businessData['expect_fetch_time'] = (int)$data['expect_fetch_time']; } // 可选参数 - 订单备注和来源 if (!empty($data['order_remark'])) { $businessData['order_remark'] = $data['order_remark']; } if (!empty($data['order_source'])) { $businessData['order_source'] = $data['order_source']; } if (!empty($data['order_source_order_id'])) { $businessData['order_source_order_id'] = $data['order_source_order_id']; } if (!empty($data['serial_number'])) { $businessData['serial_number'] = $data['serial_number']; } // 可选参数 - 骑手交互 if (isset($data['fetch_code'])) { $businessData['fetch_code'] = (int)$data['fetch_code']; } if (isset($data['write_off_code'])) { $businessData['write_off_code'] = (int)$data['write_off_code']; } if (isset($data['cancel_code'])) { $businessData['cancel_code'] = (int)$data['cancel_code']; } if (!empty($data['customer_ext_tel'])) { $businessData['customer_ext_tel'] = $data['customer_ext_tel']; } // 可选参数 - 收货人备用信息 if (!empty($data['receiver_second_phone'])) { $businessData['receiver_second_phone'] = $data['receiver_second_phone']; } // 可选参数 - 小费和优惠 if (isset($data['order_tip_amount_cent'])) { $businessData['order_tip_amount_cent'] = (int)$data['order_tip_amount_cent']; } if (isset($data['use_coupon'])) { $businessData['use_coupon'] = (int)$data['use_coupon']; } if (!empty($data['warehouse_id'])) { $businessData['warehouse_id'] = (int)$data['warehouse_id']; } // 可选参数 - 商品选择 // 注意:基础商品ID和服务商品ID不能传0(0有意义),应传null或不传 if (isset($data['base_goods_id']) && $data['base_goods_id'] !== null && $data['base_goods_id'] != 0) { $businessData['base_goods_id'] = (int)$data['base_goods_id']; } if (isset($data['service_goods_id']) && $data['service_goods_id'] !== null && $data['service_goods_id'] != 0) { $businessData['service_goods_id'] = (int)$data['service_goods_id']; } if (!empty($data['appoint_extra_goods_ids']) && is_array($data['appoint_extra_goods_ids'])) { $businessData['appoint_extra_goods_ids'] = array_map('intval', $data['appoint_extra_goods_ids']); } // 构建请求参数 $payload = $this->buildRequestPayload('createOrder', $businessData); // 发送请求 $url = $this->baseUrl . '/createOrder'; $resp = HttpClient::post($url, $payload, [ 'Content-Type' => 'application/json', ]); Yii::info("[FengniaoAdapter] createOrder Response: " . json_encode($resp)); // 处理响应 if (isset($resp['code']) && $resp['code'] == '200' && isset($resp['business_data'])) { // business_data 是 JSON 字符串,需要解析 $respData = is_string($resp['business_data']) ? json_decode($resp['business_data'], true) : $resp['business_data']; if (is_null($respData)) { Yii::error("[FengniaoAdapter] Failed to parse business_data: " . $resp['business_data']); return [ 'code' => -1, 'msg' => 'Failed to parse response data', 'data' => null ]; } if (isset($respData['order_id'])) { return [ 'code' => 0, 'platform' => 'fengniao', 'data' => [ 'orderId' => $respData['order_id'], 'fee' => 0, // 请从"查询订单详情"接口获取 -- order_actual_amount_cent 'distance' => 0, // 请从"查询订单详情"接口获取 -- order_distance ] ]; } } return [ 'code' => (int)($resp['code'] ?? -1), 'msg' => $resp['msg'] ?? 'Unknown error', 'data' => null ]; } /** * 构建运费报价请求信息(供并发请求使用) * * @param array $data 报价参数 * @param string $orderTime 配送时间 * @return array|null 包含 url, payload, headers, timeout 的请求信息 */ public function buildPriceRequest($data, $orderTime) { // 订单类型为预约单时必传,如需要送达时间 – 推单时间 < 60min,则蜂鸟配送开放平台自动将订单类型置为即时单 $timeDiff = strtotime($orderTime) - time(); $orderType = 1; if ($timeDiff > 3600 ) { $orderType = 3; } // 构建货物明细列表 $goodsItemList = []; if (!empty($data['goods_item_list']) && is_array($data['goods_item_list'])) { foreach ($data['goods_item_list'] as $item) { $goodsItem = [ 'item_actual_amount_cent' => isset($item['item_actual_amount_cent']) ? (int)$item['item_actual_amount_cent'] : 0, 'item_amount_cent' => isset($item['item_amount_cent']) ? (int)$item['item_amount_cent'] : 0, ]; if (!empty($item['item_id'])) { $goodsItem['item_id'] = $item['item_id']; } if (!empty($item['item_name'])) { $goodsItem['item_name'] = $item['item_name']; } if (isset($item['item_size'])) { $goodsItem['item_size'] = (int)$item['item_size']; } if (isset($item['item_quantity'])) { $goodsItem['item_quantity'] = (int)$item['item_quantity']; } if (!empty($item['item_remark'])) { $goodsItem['item_remark'] = $item['item_remark']; } $goodsItemList[] = $goodsItem; } } // 构建业务数据 - 必填字段 $businessData = [ 'partner_order_code' => $data['partner_order_code'], 'position_source' => isset($data['position_source']) ? (int)$data['position_source'] : 3, // 默认高德地图 'receiver_address' => $data['receiver_address'], 'receiver_longitude' => (string)($data['receiver_longitude']), 'receiver_latitude' => (string)($data['receiver_latitude']), 'goods_total_amount_cent' => $data['goods_total_amount_cent'], 'goods_actual_amount_cent' => $data['goods_actual_amount_cent'], 'goods_weight' => (float)$data['goods_weight'], 'goods_count' => (int)$data['goods_count'], 'goods_item_list' => $goodsItemList, 'order_type' => $orderType, ]; // 可选参数 - 门店信息(二选一:chain_store_id 或 out_shop_code) if (!empty($data['chain_store_id'])) { $businessData['chain_store_id'] = (int)$data['chain_store_id']; } if (!empty($data['out_shop_code'])) { $businessData['out_shop_code'] = $data['out_shop_code']; } // 可选参数 - 取货点信息 if (!empty($data['transport_address'])) { $businessData['transport_address'] = $data['transport_address']; } if (isset($data['transport_longitude']) && $data['transport_longitude'] !== '') { $businessData['transport_longitude'] = (double)$data['transport_longitude']; } if (isset($data['transport_latitude']) && $data['transport_latitude'] !== '') { $businessData['transport_latitude'] = (double)$data['transport_latitude']; } if (!empty($data['transport_tel'])) { $businessData['transport_tel'] = $data['transport_tel']; } // 可选参数 - 时间相关 if (!empty($data['order_add_time'])) { $businessData['order_add_time'] = (int)$data['order_add_time']; } // 订单类型为预约单时必传,如需要送达时间 –--- 推单时间 < 60min,则蜂鸟配送开放平台自动将订单类型置为即时单 if ($orderType == 3) { $businessData['require_receive_time'] = strtotime($orderTime)*1000; } // 可选参数 - 订单相关 if (isset($data['order_tip_amount_cent'])) { $businessData['order_tip_amount_cent'] = (int)$data['order_tip_amount_cent']; } if (!empty($data['order_source'])) { $businessData['order_source'] = $data['order_source']; } if (!empty($data['order_source_order_id'])) { $businessData['order_source_order_id'] = $data['order_source_order_id']; } // 可选参数 - 优惠券及商品选择 if (isset($data['use_coupon'])) { $businessData['use_coupon'] = (int)$data['use_coupon']; } // 注意:基础商品ID和服务商品ID不能传0(0有意义),应传null或不传 if (isset($data['base_goods_id']) && $data['base_goods_id'] !== null && $data['base_goods_id'] != 0) { $businessData['base_goods_id'] = (int)$data['base_goods_id']; } if (isset($data['service_goods_id']) && $data['service_goods_id'] !== null && $data['service_goods_id'] != 0) { $businessData['service_goods_id'] = (int)$data['service_goods_id']; } if (!empty($data['appoint_extra_goods_ids']) && is_array($data['appoint_extra_goods_ids'])) { $businessData['appoint_extra_goods_ids'] = array_map('intval', $data['appoint_extra_goods_ids']); } // 构建请求参数 $payload = $this->buildRequestPayload('preCreateOrder', $businessData); $url = $this->baseUrl . '/preCreateOrder'; return [ 'url' => $url, 'data' => $payload, 'headers' => ['Content-Type' => 'application/json'], 'timeout' => 5.0, ]; } /** * 处理运费报价响应 * * @param array $resp API 响应 * @return array|null 解析后的报价结果 */ public function processPriceResponse($resp) { //Yii::info("[FengniaoAdapter] preCreateOrder Response: " . json_encode($resp)); // 处理响应 if (isset($resp['code']) && $resp['code'] == '200' && isset($resp['business_data'])) { // business_data 是 JSON 字符串,需要解析 $respData = is_string($resp['business_data']) ? json_decode($resp['business_data'], true) : $resp['business_data']; if (is_null($respData)) { Yii::error("[FengniaoAdapter] Failed to parse business_data: " . $resp['business_data']); return null; } // 解析商品信息列表 $goodsInfos = []; if (!empty($respData['goods_infos']) && is_array($respData['goods_infos'])) { foreach ($respData['goods_infos'] as $goodsInfo) { // 解析价格明细 $priceDetail = []; if (!empty($goodsInfo['price_detail']) && is_array($goodsInfo['price_detail'])) { $pd = $goodsInfo['price_detail']; $priceDetail = [ 'start_price_cent' => (int)($pd['start_price_cent'] ?? 0), 'distance_price_cent' => (int)($pd['distance_price_cent'] ?? 0), 'weight_price_cent' => (int)($pd['weight_price_cent'] ?? 0), 'pressure_surcharge_cent' => (int)($pd['pressure_surcharge_cent'] ?? 0), 'time_period_surcharge_cent' => (int)($pd['time_period_surcharge_cent'] ?? 0), 'river_crossing_surcharge_cent' => (int)($pd['river_crossing_surcharge_cent'] ?? 0), 'category_surcharge_cent' => (int)($pd['category_surcharge_cent'] ?? 0), 'temporary_surcharge_cent' => (int)($pd['temporary_surcharge_cent'] ?? 0), 'order_price_surcharge_cent' => (int)($pd['order_price_surcharge_cent'] ?? 0), ]; } $goodsInfos[] = [ 'base_goods_id' => (int)($goodsInfo['base_goods_id'] ?? 0), 'service_goods_id' => (int)($goodsInfo['service_goods_id'] ?? 0), 't_index_id' => $goodsInfo['t_index_id'] ?? '', 'is_valid' => (int)($goodsInfo['is_valid'] ?? 0), 'disable_reason' => $goodsInfo['disable_reason'] ?? '', 'slogan' => $goodsInfo['slogan'] ?? '', 'actual_delivery_amount_cent' => (int)($goodsInfo['actual_delivery_amount_cent'] ?? 0), 'total_delivery_amount_cent' => (int)($goodsInfo['total_delivery_amount_cent'] ?? 0), 'predict_delivery_minutes' => (int)($goodsInfo['predict_delivery_minutes'] ?? 0), 'predict_delivery_time' => (int)($goodsInfo['predict_delivery_time'] ?? 0), 'can_add_tip' => (int)($goodsInfo['can_add_tip'] ?? 0), 'warehouse_id' => $goodsInfo['warehouse_id'] ?? null, 'price_detail' => $priceDetail, ]; } } return [ 'platform' => 'fengniao', 'distance' => (int)($respData['distance'] ?? 0), 'time' => (int)($respData['time'] ?? 0), 'goods_infos' => $goodsInfos, ]; } Yii::error('fengniao 报价失败:'.json_encode($resp)); return [ 'platform' => 'fengniao', 'code' => $resp['code'], 'error' => $resp['msg'] ]; } /** * @deprecated * 获取运费报价(预下单接口) * * 根据蜂鸟官方文档实现报价功能,返回所有可选的商品列表 * 每项包含当前使用该商品下单时对应的价格等信息 * * @param array $data 报价参数 * @param string $orderTime 配送时间 * @return array|null 包含可用商品列表及报价信息的结果 * @deprecated 使用 buildPriceRequest 和 processPriceResponse 替代 */ public function getPrice($data, $orderTime) { // $request = $this->buildPriceRequest($data, $orderTime); // $resp = HttpClient::post($request['url'], $request['data'], $request['headers']); // return $this->processPriceResponse($resp); } /** * 获取可用订单取消原因列表 * * 根据蜂鸟官方文档获取该订单可用的取消原因列表。 * 用户取消订单时需先调用此接口获取可选的取消原因。 * * @param array $data 查询参数,结构参考: * [ * // ========== 必填参数(二选一)========== * 'order_id' => '订单ID', * 'partner_order_code' => '外部订单号', * // 注意:order_id 和 partner_order_code 必填一个 * ] * @return array API响应结果 * 成功:[ * 'code' => 0, * 'data' => [ * 'cancel_reason_list' => [ * [ * 'order_cancel_code' => 取消原因code, * 'order_cancel_desc' => '取消原因描述', * ], * // ... 更多原因 * ] * ] * ] * 失败:['code' => 错误码, 'msg' => 错误信息, 'data' => null] */ public function getCancelReasonList($data) { // 验证 order_id 和 partner_order_code 必填一个 $orderId = $data['order_id'] ?? $data; $partnerOrderCode = null; // 如果 $orderId 是 XSD 开头,则给 partnerOrderCode 赋值为 $orderId if (strpos($orderId, 'XSD') === 0) { $partnerOrderCode = $orderId; $orderId = null; } if (empty($orderId) && empty($partnerOrderCode)) { Yii::warning("[FengniaoAdapter] getCancelReasonList failed: order_id and partner_order_code cannot both be empty"); return [ 'code' => -1, 'msg' => 'order_id and partner_order_code must have one', 'data' => null ]; } // 构建业务数据 $businessData = []; // 订单标识(二选一) if (!empty($orderId)) { $businessData['order_id'] = (int)$orderId; } if (!empty($partnerOrderCode)) { $businessData['partner_order_code'] = $partnerOrderCode; } // 构建请求参数 $payload = $this->buildRequestPayload('getCancelReasonList', $businessData); // 发送请求 $url = $this->baseUrl . '/getCancelReasonList'; $resp = HttpClient::post($url, $payload, [ 'Content-Type' => 'application/json', ]); //Yii::info("[FengniaoAdapter] getCancelReasonList Response: " . json_encode($resp)); // 处理响应 if (isset($resp['code']) && $resp['code'] == '200' && isset($resp['business_data'])) { // business_data 是 JSON 字符串,需要解析 $respData = is_string($resp['business_data']) ? json_decode($resp['business_data'], true) : $resp['business_data']; if (is_null($respData)) { Yii::error("[FengniaoAdapter] Failed to parse business_data: " . $resp['business_data']); return [ 'code' => -1, 'msg' => 'Failed to parse response data', 'data' => null ]; } // 解析取消原因列表 $cancelReasonList = []; if (!empty($respData['cancel_reason_list']) && is_array($respData['cancel_reason_list'])) { foreach ($respData['cancel_reason_list'] as $reason) { $cancelReasonList[] = [ 'order_cancel_code' => (int)($reason['order_cancel_code'] ?? 0), 'order_cancel_desc' => $reason['order_cancel_desc'] ?? '', ]; } } return [ 'code' => 0, 'data' => [ 'reasonList' => $cancelReasonList, ] ]; } return [ 'code' => (int)($resp['code'] ?? -1), 'msg' => $resp['msg'] ?? 'Unknown error', 'data' => null ]; } /** * 订单取消 * * 根据蜂鸟官方文档实现订单取消功能。 * 支持商户取消和用户取消两种场景。 * * @param array $data 取消参数,结构参考: * [ * // ========== 必填参数(二选一)========== * 'order_id' => '订单ID', * 'partner_order_code' => '外部订单号', * // 注意:order_id 和 partner_order_code 必填一个 * * // ========== 必填参数 ========== * 'order_cancel_role' => '取消的角色(1:商户取消, 2:用户取消)', * 'order_cancel_code' => '取消原因code', * 'order_cancel_other_reason' => '取消原因补充(20字以内)', * * // ========== 可选参数 ========== * 'need_reverse_order' => '是否期望发起售中逆向(Boolean)', * 'actual_cancel_cost_cent' => '取消价格(分)', * ] * @return array API响应结果 * 成功:['code' => 0, 'data' => ['result' => true]] * 失败:['code' => 错误码, 'msg' => 错误信息, 'data' => null] */ public function cancelOrder($orderId, $params) { // 验证 order_id 和 partner_order_code 必填一个 $partnerOrderCode = null; // 如果 $orderId 是 XSD_ 开头,则给 partnerOrderCode 赋值为 $orderId if (strpos($orderId, 'XSD') === 0) { $partnerOrderCode = $orderId; $orderId = null; } if (empty($orderId) && empty($partnerOrderCode)) { Yii::warning("[FengniaoAdapter] cancelOrder failed: order_id and partner_order_code cannot both be empty"); return [ 'code' => -1, 'msg' => 'order_id and partner_order_code must have one', 'data' => null ]; } // 构建业务数据 $businessData = []; // 必填参数 - 订单标识(二选一) if (!empty($orderId)) { $businessData['order_id'] = $orderId; } if (!empty($partnerOrderCode)) { $businessData['partner_order_code'] = $partnerOrderCode; } // 必填参数 - 取消角色 if (isset($params['order_cancel_role'])) { $businessData['order_cancel_role'] = (int)$params['order_cancel_role']; } else { Yii::warning("[FengniaoAdapter] cancelOrder failed: order_cancel_role is required"); return [ 'code' => -1, 'msg' => 'order_cancel_role is required (1:商户取消, 2:用户取消)', 'data' => null ]; } // 必填参数 - 取消原因code if (isset($params['order_cancel_code'])) { $businessData['order_cancel_code'] = (int)$params['order_cancel_code']; } else { Yii::warning("[FengniaoAdapter] cancelOrder failed: order_cancel_code is required"); return [ 'code' => -1, 'msg' => 'order_cancel_code is required', 'data' => null ]; } // 必填参数 - 取消原因补充 if (!empty($params['order_cancel_reason'])) { $businessData['order_cancel_other_reason'] = $params['order_cancel_reason']; } else { Yii::warning("[FengniaoAdapter] cancelOrder failed: order_cancel_other_reason is required"); return [ 'code' => -1, 'msg' => 'order_cancel_other_reason is required (20字以内)', 'data' => null ]; } // 可选参数 - 是否期望发起售中逆向 if (isset($params['need_reverse_order']) && $params['need_reverse_order'] !== null) { $businessData['need_reverse_order'] = (bool)$params['need_reverse_order']; } // 可选参数 - 取消价格 if (isset($params['actual_cancel_cost_cent']) && $params['actual_cancel_cost_cent'] !== null) { $businessData['actual_cancel_cost_cent'] = (int)$params['actual_cancel_cost_cent']; } // 构建请求参数 $payload = $this->buildRequestPayload('cancelOrder', $businessData); // 发送请求 $url = $this->baseUrl . '/cancelOrder'; $resp = HttpClient::post($url, $payload, [ 'Content-Type' => 'application/json', ]); Yii::info("[FengniaoAdapter] cancelOrder Response: " . json_encode($resp)); // 处理响应 if (isset($resp['code']) && $resp['code'] == '200' && isset($resp['business_data'])) { // business_data 是 JSON 字符串,需要解析 $respData = is_string($resp['business_data']) ? json_decode($resp['business_data'], true) : $resp['business_data']; if (is_null($respData)) { Yii::error("[FengniaoAdapter] Failed to parse business_data: " . $resp['business_data']); return [ 'code' => -1, 'platform' => 'fengniao', 'msg' => 'Failed to parse response data', 'data' => null ]; } return [ 'code' => 0, 'platform' => 'fengniao', 'data' => [ 'result' => (bool)($respData['result'] ?? false), 'reverse_order_ok' => (bool)($respData['reverse_order_ok'] ?? false), ] ]; } return [ 'code' => (int)($resp['code'] ?? -1), 'platform' => 'fengniao', 'msg' => $resp['msg'] ?? 'Unknown error', 'data' => null ]; } /** * 订单预取消接口 * * 根据蜂鸟官方文档获取取消价格。 * 需要查询并校验取消价格时使用,若强校验价格需在正式取消接口中把出参价格传过去。 * * @param array $data 预取消查询参数,结构参考: * [ * // ========== 必填参数(二选一)========== * 'order_id' => '订单ID', * 'partner_order_code' => '外部订单号', * // 注意:order_id 和 partner_order_code 必填一个 * * // ========== 必填参数 ========== * 'order_cancel_code' => '取消原因code(从可用取消原因列表接口返回结果选择)', * ] * @return array API响应结果 * 成功:[ * 'code' => 0, * 'data' => [ * 'actual_cancel_cost_cent' => '取消实际需金额(分,默认使用已有免责取消权益使用后金额为0)', * ] * ] * 失败:['code' => 错误码, 'msg' => 错误信息, 'data' => null] * * @note * - 获取取消价格,用于查询并校验取消价格 * - actual_cancel_cost_cent 为取消实际需要支付的金额(分),通常为 0(使用免责取消权益) * - 若需强校验价格,应在正式取消接口(cancelOrder)中把此出参价格传过去 * - 流程:先调用此方法获取取消价格 → 校验价格 → 调用 cancelOrder 正式取消 */ public function preCancelOrder($data) { // 验证 order_id 和 partner_order_code 必填一个 $orderId = $data['order_id']; $partnerOrderCode = null; // 如果 $orderId 是 XSD_ 开头,则给 partnerOrderCode 赋值为 $orderId if (strpos($orderId, 'XSD') === 0) { $partnerOrderCode = $orderId; $orderId = null; } if (empty($orderId) && empty($partnerOrderCode)) { Yii::warning("[FengniaoAdapter] preCancelOrder failed: order_id and partner_order_code cannot both be empty"); return [ 'code' => -1, 'msg' => 'order_id and partner_order_code must have one', 'data' => null ]; } // 验证 order_cancel_code(取消原因code)必填 if (!isset($data['order_cancel_code'])) { Yii::warning("[FengniaoAdapter] preCancelOrder failed: order_cancel_code is required"); return [ 'code' => -1, 'msg' => 'order_cancel_code is required (from getCancelReasonList)', 'data' => null ]; } // 构建业务数据 $businessData = []; // 订单标识(二选一) if (!empty($orderId)) { $businessData['order_id'] = (int)$orderId; } if (!empty($partnerOrderCode)) { $businessData['partner_order_code'] = $partnerOrderCode; } // 必填参数 - 取消原因code $businessData['order_cancel_code'] = (int)$data['order_cancel_code']; // 构建请求参数 $payload = $this->buildRequestPayload('preCancelOrder', $businessData); // 发送请求 $url = $this->baseUrl . '/preCancelOrder'; $resp = HttpClient::post($url, $payload, [ 'Content-Type' => 'application/json', ]); Yii::info("[FengniaoAdapter] preCancelOrder Response: " . json_encode($resp)); // 处理响应 if (isset($resp['code']) && $resp['code'] == '200' && isset($resp['business_data'])) { // business_data 是 JSON 字符串,需要解析 $respData = is_string($resp['business_data']) ? json_decode($resp['business_data'], true) : $resp['business_data']; if (is_null($respData)) { Yii::error("[FengniaoAdapter] Failed to parse business_data: " . $resp['business_data']); return [ 'code' => -1, 'msg' => 'Failed to parse response data', 'data' => null ]; } return [ 'code' => 0, 'data' => [ 'actual_cancel_cost_cent' => (int)($respData['actual_cancel_cost_cent'] ?? 0), ] ]; } return [ 'code' => (int)($resp['code'] ?? -1), 'msg' => $resp['msg'] ?? 'Unknown error', 'data' => null ]; } /** * 追加小费 * * 根据蜂鸟官方文档为订单追加小费。 * 订单小费总金额为每次累加。 * * @param array $data 加小费参数,结构参考: * [ * // ========== 必填参数(二选一)========== * 'order_id' => '订单ID', * 'partner_order_code' => '外部订单号', * // 注意:order_id 和 partner_order_code 必填一个 * * // ========== 必填参数 ========== * 'third_index_id' => '本次加小费唯一标识(Long类型)', * 'add_tip_amount_cent' => '加小费金额(分)', * ] * @return array API响应结果 * 成功:['code' => 0, 'data' => ['result' => true]] * 失败:['code' => 错误码, 'msg' => 错误信息, 'data' => null] * * @note * - 追加小费,订单小费总金额为每次累加 * - 不是所有订单都支持加小费,受合同服务 & 接单运力 & 当时订单状态等多个因素影响 * - 订单详情接口有返回是否可加小费字段(if_can_add_tip) * - 联调环境使用加小费接口需先使用联调工具把订单状态改为1,否则订单状态会不支持加小费 * - third_index_id用于幂等,相同third_index_id的后续请求会被忽略 */ public function addTip($data = []) { // 验证 order_id 和 partner_order_code 必填一个 $orderId = $data['orderId'] ?? null; $partnerOrderCode = $data['partner_order_code'] ?? null; $tips = $data['tips'] ?? null; if($tips != null){ $data['add_tip_amount_cent'] = $tips; } if (empty($orderId) && empty($partnerOrderCode)) { Yii::warning("[FengniaoAdapter] addTip failed: order_id and partner_order_code cannot both be empty"); return [ 'code' => -1, 'msg' => 'order_id and partner_order_code must have one', 'data' => null ]; } // 验证 third_index_id(幂等标识) if (!isset($data['third_index_id'])) { Yii::warning("[FengniaoAdapter] addTip failed: third_index_id is required"); $data['third_index_id'] = time(); // return [ // 'code' => -1, // 'msg' => 'third_index_id is required for idempotency', // 'data' => null // ]; } // 验证 add_tip_amount_cent(加小费金额) if (!isset($data['add_tip_amount_cent']) || $data['add_tip_amount_cent'] <= 0) { Yii::warning("[FengniaoAdapter] addTip failed: add_tip_amount_cent must be greater than 0"); return [ 'code' => -1, 'msg' => 'add_tip_amount_cent is required and must be greater than 0', 'data' => null ]; } // 构建业务数据 $businessData = []; // 订单标识(二选一) if (!empty($orderId)) { $businessData['order_id'] = (int)$orderId; } if (!empty($partnerOrderCode)) { $businessData['partner_order_code'] = $partnerOrderCode; } // 必填参数 $businessData['third_index_id'] = (int)$data['third_index_id']; $businessData['add_tip_amount_cent'] = (int)$data['add_tip_amount_cent']; // 构建请求参数 $payload = $this->buildRequestPayload('addTip', $businessData); // 发送请求 $url = $this->baseUrl . '/addTip'; $resp = HttpClient::post($url, $payload, [ 'Content-Type' => 'application/json', ]); Yii::info("[FengniaoAdapter] addTip Response: " . json_encode($resp)); // 处理响应 if (isset($resp['code']) && $resp['code'] == '200' && isset($resp['business_data'])) { // business_data 是 JSON 字符串,需要解析 $respData = is_string($resp['business_data']) ? json_decode($resp['business_data'], true) : $resp['business_data']; if (is_null($respData)) { Yii::error("[FengniaoAdapter] Failed to parse business_data: " . $resp['business_data']); return [ 'code' => -1, 'msg' => 'Failed to parse response data', 'data' => null ]; } return [ 'code' => 0, 'data' => [ 'result' => (bool)($respData['result'] ?? false), ] ]; } return [ 'code' => (int)($resp['code'] ?? -1), 'msg' => $resp['msg'] ?? 'Unknown error', 'data' => null ]; } /** * 查询订单详情 * * 根据蜂鸟官方文档获取订单的详细信息。 * 包括订单状态、配送员信息、事件节点、价格明细等完整信息。 * * @param array $data 查询参数,结构参考: * [ * // ========== 必填参数(二选一)========== * 'order_id' => '订单ID', * 'partner_order_code' => '外部订单号', * // 注意:order_id 和 partner_order_code 必填一个 * ] * @return array API响应结果 */ //public function getOrderDetail($data = []) public function selectOrder($data) { // 验证 order_id 和 partner_order_code 必填一个 $orderId = $data['order_id'] ?? $data; $partnerOrderCode = null; // 如果 $orderId 是 XSD 开头,则给 partnerOrderCode 赋值为 $orderId if (strpos($orderId, 'XSD') === 0) { $partnerOrderCode = $orderId; $orderId = null; } if (empty($orderId) && empty($partnerOrderCode)) { Yii::warning("[FengniaoAdapter] getOrderDetail failed: order_id and partner_order_code cannot both be empty"); return [ 'code' => -1, 'msg' => 'order_id and partner_order_code must have one', 'data' => null ]; } // 构建业务数据 $businessData = []; // 订单标识(二选一) if (!empty($orderId)) { $businessData['order_id'] = $orderId; } if (!empty($partnerOrderCode)) { $businessData['partner_order_code'] = $partnerOrderCode; } // 构建请求参数 $payload = $this->buildRequestPayload('getOrderDetail', $businessData); // 发送请求 $url = $this->baseUrl . '/getOrderDetail'; $resp = HttpClient::post($url, $payload, [ 'Content-Type' => 'application/json', ]); Yii::info("[FengniaoAdapter] getOrderDetail Response: " . json_encode($resp)); // 处理响应 if (isset($resp['code']) && $resp['code'] == '200' && isset($resp['business_data'])) { // business_data 是 JSON 字符串,需要解析 $respData = is_string($resp['business_data']) ? json_decode($resp['business_data'], true) : $resp['business_data']; if (is_null($respData)) { Yii::error("[FengniaoAdapter] Failed to parse business_data: " . $resp['business_data']); return [ 'code' => -1, 'msg' => 'Failed to parse response data', 'data' => null ]; } // 解析价格明细 $priceDetail = []; if (!empty($respData['price_detail']) && is_array($respData['price_detail'])) { $pd = $respData['price_detail']; $priceDetail = [ 'start_price_cent' => (int)($pd['start_price_cent'] ?? 0), 'distance_price_cent' => (int)($pd['distance_price_cent'] ?? 0), 'weight_price_cent' => (int)($pd['weight_price_cent'] ?? 0), 'pressure_surcharge_cent' => (int)($pd['pressure_surcharge_cent'] ?? 0), 'time_period_surcharge_cent' => (int)($pd['time_period_surcharge_cent'] ?? 0), 'river_crossing_surcharge_cent' => (int)($pd['river_crossing_surcharge_cent'] ?? 0), 'category_surcharge_cent' => (int)($pd['category_surcharge_cent'] ?? 0), 'temporary_surcharge_cent' => (int)($pd['temporary_surcharge_cent'] ?? 0), 'order_price_surcharge_cent' => (int)($pd['order_price_surcharge_cent'] ?? 0), ]; } // 解析事件日志列表 $eventLogDetails = []; if (!empty($respData['event_log_details']) && is_array($respData['event_log_details'])) { foreach ($respData['event_log_details'] as $log) { $eventLogDetails[] = [ 'order_status' => (int)($log['order_status'] ?? 0), 'occur_time' => (int)($log['occur_time'] ?? 0), 'carrier_driver_name' => $log['carrier_driver_name'] ?? '', 'carrier_driver_phone' => $log['carrier_driver_phone'] ?? '', ]; } } // 解析逆向单运单事件节点 $reverseEventLogDetails = []; if (!empty($respData['reverse_event_log_details']) && is_array($respData['reverse_event_log_details'])) { $reverse = $respData['reverse_event_log_details']; $reverseEventLogDetails = [ 'order_status' => (int)($reverse['order_status'] ?? 0), 'occur_time' => (int)($reverse['occur_time'] ?? 0), 'carrier_driver_name' => $reverse['carrier_driver_name'] ?? '', 'carrier_driver_phone' => $reverse['carrier_driver_phone'] ?? '', ]; } // 解析天气信息 $weatherInfo = []; if (!empty($respData['weather_info']) && is_array($respData['weather_info'])) { $weather = $respData['weather_info']; $weatherInfo = [ 'weatherRate' => (int)($weather['weatherRate'] ?? 0), 'weatherDesc' => $weather['weatherDesc'] ?? '', ]; } // 解析取货照片列表 $deliveryFetchPhotos = []; if (!empty($respData['delivery_fetch_photos']) && is_array($respData['delivery_fetch_photos'])) { $deliveryFetchPhotos = $respData['delivery_fetch_photos']; } return [ 'code' => 0, 'data' => [ // 基本信息 'order_id' => (int)($respData['order_id'] ?? 0), 'partner_order_code' => $respData['partner_order_code'] ?? '', 'tracking_id' => (int)($respData['tracking_id'] ?? 0), 'order_status' => (int)($respData['order_status'] ?? 0), 'serial_number' => $respData['serial_number'] ?? '', 'remark' => $respData['remark'] ?? '', // 配送员信息 'carrier_driver_id' => $respData['carrier_driver_id'] ?? '', 'carrier_driver_name' => $respData['carrier_driver_name'] ?? '', 'carrier_driver_phone' => $respData['carrier_driver_phone'] ?? '', 'temperature' => $respData['temperature'] ?? '', // 金额信息(单位:分) 'order_total_amount_cent' => (int)($respData['order_total_amount_cent'] ?? 0), 'order_actual_amount_cent' => (int)($respData['order_actual_amount_cent'] ?? 0), 'order_tip_amount_cent' => (int)($respData['order_tip_amount_cent'] ?? 0), 'overtime_compensation_cost_cent' => (int)($respData['overtime_compensation_cost_cent'] ?? 0), // 配送信息 'order_distance' => (double)($respData['order_distance'] ?? 0), 'estimate_arrive_time' => (int)($respData['estimate_arrive_time'] ?? 0), 'if_can_add_tip' => (int)($respData['if_can_add_tip'] ?? 0), 'fetch_code' => $respData['fetch_code'] ?? '', 'write_off_code' => $respData['write_off_code'] ?? '', 'cancel_code' => $respData['cancel_code'] ?? '', // 价格明细 'price_detail' => $priceDetail, // 事件日志 'event_log_details' => $eventLogDetails, 'reverse_event_log_details' => $reverseEventLogDetails, // 异常信息 'abnormal_code' => $respData['abnormal_code'] ?? '', 'abnormal_desc' => $respData['abnormal_desc'] ?? '', // 索赔信息 'claim_id' => (int)($respData['claim_id'] ?? 0), 'claim_status' => (int)($respData['claim_status'] ?? 0), 'claim_status_desc' => $respData['claim_status_desc'] ?? '', 'claim_reason_desc' => $respData['claim_reason_desc'] ?? '', // 投诉信息 'complaint_id' => (int)($respData['complaint_id'] ?? 0), 'complaint_status' => (int)($respData['complaint_status'] ?? 0), 'complaint_response_desc' => $respData['complaint_response_desc'] ?? '', 'complaint_reason_desc' => $respData['complaint_reason_desc'] ?? '', // 其他 'delivery_fetch_photos' => $deliveryFetchPhotos, 'weather_info' => $weatherInfo, ] ]; } return [ 'code' => (int)($resp['code'] ?? -1), 'msg' => $resp['msg'] ?? 'Unknown error', 'data' => null ]; } /** * 查询骑手信息 * * 根据蜂鸟官方文档获取订单对应的骑手(配送员)的实时位置和联系信息。 * 返回骑手的ID、姓名、电话以及实时位置坐标(高德地图坐标系)。 * * @param array $data 查询参数,结构参考: * [ * // ========== 必填参数(二选一)========== * 'order_id' => '订单ID', * 'partner_order_code' => '外部订单号', * // 注意:order_id 和 partner_order_code 必填一个 * ] * @return array API响应结果 * 成功:[ * 'code' => 0, * 'data' => [ * 'carrier_driver_id' => '骑手ID', * 'carrier_driver_name' => '骑手姓名', * 'carrier_driver_phone' => '骑手电话', * 'carrier_driver_latitude' => '骑手位置纬度', * 'carrier_driver_longitude' => '骑手位置经度', * // 返回的是高德地图的经纬度 * ] * ] * 失败:['code' => 错误码, 'msg' => 错误信息, 'data' => null] */ public function getKnightInfo($data = []) { // 验证 order_id 和 partner_order_code 必填一个 $orderId = $data['order_id'] ?? null; $partnerOrderCode = $data['partner_order_code'] ?? null; if (empty($orderId) && empty($partnerOrderCode)) { Yii::warning("[FengniaoAdapter] getKnightInfo failed: order_id and partner_order_code cannot both be empty"); return [ 'code' => -1, 'msg' => 'order_id and partner_order_code must have one', 'data' => null ]; } // 构建业务数据 $businessData = []; // 订单标识(二选一) if (!empty($orderId)) { $businessData['order_id'] = (int)$orderId; } if (!empty($partnerOrderCode)) { $businessData['partner_order_code'] = $partnerOrderCode; } // 构建请求参数 $payload = $this->buildRequestPayload('getKnightInfo', $businessData); // 发送请求 $url = $this->baseUrl . '/getKnightInfo'; $resp = HttpClient::post($url, $payload, [ 'Content-Type' => 'application/json', ]); Yii::info("[FengniaoAdapter] getKnightInfo Response: " . json_encode($resp)); // 处理响应 if (isset($resp['code']) && $resp['code'] == '200' && isset($resp['business_data'])) { // business_data 是 JSON 字符串,需要解析 $respData = is_string($resp['business_data']) ? json_decode($resp['business_data'], true) : $resp['business_data']; if (is_null($respData)) { Yii::error("[FengniaoAdapter] Failed to parse business_data: " . $resp['business_data']); return [ 'code' => -1, 'msg' => 'Failed to parse response data', 'data' => null ]; } return [ 'code' => 0, 'data' => [ 'carrier_driver_id' => $respData['carrier_driver_id'] ?? '', 'carrier_driver_name' => $respData['carrier_driver_name'] ?? '', 'carrier_driver_phone' => $respData['carrier_driver_phone'] ?? '', 'carrier_driver_latitude' => $respData['carrier_driver_latitude'] ?? '', 'carrier_driver_longitude' => $respData['carrier_driver_longitude'] ?? '', ] ]; } return [ 'code' => (int)($resp['code'] ?? -1), 'msg' => $resp['msg'] ?? 'Unknown error', 'data' => null ]; } }