config = config('ccblife'); $this->httpClient = new CcbHttpClient($this->config); } /** * 推送订单到建行生活平台 * 当用户下单后调用此方法同步订单信息 * * @param int $orderId Shopro订单ID * @return array ['status' => bool, 'message' => string, 'data' => array] * @throws \Exception */ public function pushOrder($orderId) { $startTime = microtime(true); try { // 获取订单信息 $order = Db::name('shopro_order') ->alias('o') ->join('user u', 'o.user_id = u.id', 'LEFT') ->where('o.id', $orderId) ->field('o.*, u.ccb_user_id') ->find(); if (!$order) { throw new \Exception('订单不存在'); } // 获取建行用户ID $ccbUserId = $order['ccb_user_id']; if (!$ccbUserId) { throw new \Exception('用户未绑定建行生活账号'); } // 获取订单商品列表 $orderItems = Db::name('shopro_order_item') ->where('order_id', $orderId) ->select(); // 构建订单数据(符合A3341TP01接口规范) $orderData = $this->buildOrderData($order, $orderItems, $ccbUserId); // 记录请求数据(同步日志) $txSeq = CcbMD5::generateTransactionSeq(); $this->recordSyncLog($orderId, 'A3341TP01', $txSeq, $orderData, 'request'); // 调用建行API推送订单 $response = $this->httpClient->pushOrder($orderData); // 记录响应数据和耗时 $costTime = round((microtime(true) - $startTime) * 1000, 2); $this->recordSyncLog($orderId, 'A3341TP01', $txSeq, $response, 'response', true, $costTime); // 更新订单同步状态 $this->updateOrderSyncStatus($orderId, 1); return [ 'status' => true, 'message' => '订单推送成功', 'data' => $response ]; } catch (\Exception $e) { // 记录错误 $costTime = round((microtime(true) - $startTime) * 1000, 2); $this->recordSyncLog($orderId, 'A3341TP01', '', null, 'error', false, $costTime, $e->getMessage()); // 更新同步状态为失败 $this->updateOrderSyncStatus($orderId, 2); Log::error('建行订单推送失败: ' . $e->getMessage()); return [ 'status' => false, 'message' => $e->getMessage(), 'data' => null ]; } } /** * 更新订单状态到建行生活 * * @param int $orderId 订单ID * @param string $status 订单状态 * @param string $refundStatus 退款状态 * @return array */ public function updateOrderStatus($orderId, $status = null, $refundStatus = null) { $startTime = microtime(true); $txSeq = CcbMD5::generateTransactionSeq(); try { // 获取订单信息 $order = Db::name('shopro_order') ->alias('o') ->join('user u', 'o.user_id = u.id', 'LEFT') ->where('o.id', $orderId) ->field('o.*, u.ccb_user_id') ->find(); if (!$order) { throw new \Exception('订单不存在'); } // 获取建行用户ID $ccbUserId = $order['ccb_user_id']; if (!$ccbUserId) { throw new \Exception('用户未绑定建行生活账号'); } // 映射订单状态 $orderStatus = $status ?: $this->mapOrderStatus($order['status']); $refundStatus = $refundStatus ?: $this->mapRefundStatus($order['refund_status'] ?? 0); // 记录请求 $requestData = [ 'ccb_user_id' => $ccbUserId, 'order_sn' => $order['order_sn'], 'order_status' => $orderStatus, 'refund_status' => $refundStatus ]; $this->recordSyncLog($orderId, 'A3341TP02', $txSeq, $requestData, 'request'); // 调用建行API更新状态 $response = $this->httpClient->updateOrderStatus( $ccbUserId, $order['order_sn'], $orderStatus, $refundStatus ); // 记录响应 $costTime = round((microtime(true) - $startTime) * 1000, 2); $this->recordSyncLog($orderId, 'A3341TP02', $txSeq, $response, 'response', true, $costTime); return [ 'status' => true, 'message' => '订单状态更新成功', 'data' => $response ]; } catch (\Exception $e) { $costTime = round((microtime(true) - $startTime) * 1000, 2); $this->recordSyncLog($orderId, 'A3341TP02', $txSeq, null, 'error', false, $costTime, $e->getMessage()); Log::error('建行订单状态更新失败: ' . $e->getMessage()); return [ 'status' => false, 'message' => $e->getMessage(), 'data' => null ]; } } /** * 查询建行订单信息 * * @param string $orderSn 订单号 * @return array */ public function queryOrder($orderSn) { try { // 调用建行API查询订单 $response = $this->httpClient->queryOrder($orderSn); return [ 'status' => true, 'message' => '订单查询成功', 'data' => $response ]; } catch (\Exception $e) { Log::error('建行订单查询失败: ' . $e->getMessage()); return [ 'status' => false, 'message' => $e->getMessage(), 'data' => null ]; } } /** * 处理订单退款 * * @param int $orderId 订单ID * @param float $refundAmount 退款金额 * @param string $refundReason 退款原因 * @return array */ public function refundOrder($orderId, $refundAmount, $refundReason = '') { try { // 获取订单信息 $order = Order::find($orderId); if (!$order) { throw new \Exception('订单不存在'); } // 验证退款金额 if ($refundAmount > $order['total_amount']) { throw new \Exception('退款金额不能超过订单总额'); } // 调用建行API发起退款 $response = $this->httpClient->refund( $order['order_sn'], number_format($refundAmount, 2, '.', ''), $refundReason ); // 更新订单退款状态 $this->updateOrderStatus($orderId, null, '2'); return [ 'status' => true, 'message' => '退款申请成功', 'data' => $response ]; } catch (\Exception $e) { Log::error('建行订单退款失败: ' . $e->getMessage()); return [ 'status' => false, 'message' => $e->getMessage(), 'data' => null ]; } } /** * 构建符合建行要求的订单数据 * * ⚠️ 注意:Shopro字段说明 * - total_fee: 实际支付金额 * - total_amount: 订单原价 * - discount_fee: 优惠金额 * - paid_time: 支付时间(毫秒时间戳!) * - createtime: 创建时间(秒级时间戳) * * @param array $order 订单数组 * @param array $orderItems 订单商品列表 * @param string $ccbUserId 建行用户ID * @return array */ private function buildOrderData($order, $orderItems, $ccbUserId) { // 构建商品列表 $goodsList = $this->buildGoodsList($orderItems); // 计算各项金额(Shopro使用total_fee作为实付金额) $totalAmount = number_format($order['total_amount'] ?? 0, 2, '.', ''); $payAmount = number_format($order['total_fee'] ?? 0, 2, '.', ''); $discountAmount = number_format($order['discount_fee'] ?? 0, 2, '.', ''); // 处理支付时间(Shopro的paid_time是毫秒时间戳,需要除以1000) $payTime = ''; if (!empty($order['paid_time'])) { $payTime = date('YmdHis', intval($order['paid_time'] / 1000)); } // 处理创建时间(Shopro的createtime是秒级时间戳) $createTime = date('YmdHis', $order['createtime'] ?? time()); // 构建订单数据(34个必填字段) return [ 'USER_ID' => $ccbUserId, // 建行用户ID 'ORDER_ID' => $order['order_sn'], // 订单号 'ORDER_DT' => $createTime, // 订单时间 'TOTAL_AMT' => $totalAmount, // 订单原金额 'PAY_AMT' => $payAmount, // 实付金额(total_fee) 'DISCOUNT_AMT' => $discountAmount, // 优惠金额(discount_fee) 'ORDER_STATUS' => $this->mapOrderStatus($order['status']), // 订单状态 'REFUND_STATUS' => $this->mapRefundStatus($order['aftersale_status'] ?? 0), // 退款状态(aftersale_status) 'MCT_NM' => $this->config['merchant']['name'] ?? '商户名称', // 商户名称 'MCT_ORDER_ID' => $order['id'], // 商户订单ID 'GOODS_LIST' => json_encode($goodsList, JSON_UNESCAPED_UNICODE), // 商品列表 'PAY_TYPE' => $this->mapPayType($order['pay_type'] ?? ''), // 支付方式 'PAY_TIME' => $payTime, // 支付时间(毫秒转秒) 'DELIVERY_TYPE' => '01', // 配送方式(01快递) 'DELIVERY_STATUS' => $this->mapDeliveryStatus($order['status']), // 配送状态 'DELIVERY_TIME' => $order['delivery_time'] ? date('YmdHis', $order['delivery_time']) : '', // 发货时间 'RECEIVE_NAME' => $order['consignee'] ?? '', // 收货人姓名 'RECEIVE_PHONE' => $order['mobile'] ?? '', // 收货人电话 'RECEIVE_ADDRESS' => $this->buildAddress($order), // 收货地址 'EXPRESS_COMPANY' => $order['express_company'] ?? '', // 快递公司 'EXPRESS_NO' => $order['express_no'] ?? '', // 快递单号 'REMARK' => $order['remark'] ?? '', // 备注 'ORDER_TYPE' => '01', // 订单类型(01普通订单) 'IS_VIRTUAL' => '0', // 是否虚拟商品 'ORDER_URL' => $this->config['merchant']['order_detail_url'] . $order['id'], // 订单详情链接 'CREATE_TIME' => $createTime, // 创建时间 'UPDATE_TIME' => date('YmdHis'), // 更新时间 'SHOP_ID' => '1', // 店铺ID 'SHOP_NAME' => $this->config['merchant']['name'] ?? '', // 店铺名称 'ACTIVITY_ID' => '', // 活动ID 'ACTIVITY_NAME' => '', // 活动名称 'COUPON_AMT' => '0.00', // 优惠券金额 'FREIGHT_AMT' => number_format($order['freight_amount'] ?? 0, 2, '.', ''), // 运费 ]; } /** * 构建商品列表 * * @param array $items 订单商品项 * @return array */ private function buildGoodsList($items) { $goodsList = []; foreach ($items as $item) { $goodsList[] = [ 'goods_id' => $item['goods_id'], 'goods_name' => $item['goods_title'], 'goods_price' => number_format($item['goods_price'], 2, '.', ''), 'goods_num' => $item['goods_num'], 'goods_amount' => number_format($item['goods_amount'], 2, '.', ''), 'goods_image' => $item['goods_image'] ?? '', 'goods_sku' => $item['goods_sku_text'] ?? '' ]; } return $goodsList; } /** * 构建收货地址 * * @param object $order 订单对象 * @return string */ private function buildAddress($order) { $address = ''; if ($order['province']) $address .= $order['province']; if ($order['city']) $address .= $order['city']; if ($order['area']) $address .= $order['area']; if ($order['address']) $address .= $order['address']; return $address; } /** * 记录同步日志 * * @param int $orderId 订单ID * @param string $txCode 交易代码 * @param string $txSeq 交易流水号 * @param mixed $data 数据 * @param string $type 类型:request/response/error * @param bool $success 是否成功 * @param float $costTime 耗时(毫秒) * @param string $errorMsg 错误信息 */ private function recordSyncLog($orderId, $txCode, $txSeq, $data, $type = 'request', $success = true, $costTime = 0, $errorMsg = '') { try { // 获取订单号 $orderSn = Db::name('shopro_order')->where('id', $orderId)->value('order_sn'); $logData = [ 'order_id' => $orderId, 'order_sn' => $orderSn ?: '', 'tx_code' => $txCode, 'tx_seq' => $txSeq, 'sync_status' => $success ? 1 : 0, 'sync_time' => time(), 'cost_time' => intval($costTime), 'retry_times' => 0 ]; if ($type == 'request') { $logData['request_data'] = is_array($data) ? json_encode($data, JSON_UNESCAPED_UNICODE) : $data; } elseif ($type == 'response') { $logData['response_data'] = is_array($data) ? json_encode($data, JSON_UNESCAPED_UNICODE) : $data; } elseif ($type == 'error') { $logData['error_msg'] = $errorMsg; } Db::name('ccb_sync_log')->insert($logData); } catch (\Exception $e) { Log::error('记录同步日志失败: ' . $e->getMessage()); } } /** * 更新订单同步状态 * * @param int $orderId 订单ID * @param int $status 同步状态:0-未同步 1-已同步 2-同步失败 */ private function updateOrderSyncStatus($orderId, $status) { Db::name('shopro_order')->where('id', $orderId)->update([ 'ccb_sync_status' => $status, 'ccb_sync_time' => time(), 'updatetime' => time() ]); } /** * 映射订单状态 * * @param string $status Shopro订单状态 * @return string 建行订单状态 */ private function mapOrderStatus($status) { $statusMap = [ 'unpaid' => '0', // 待支付 'paid' => '1', // 已支付 'shipped' => '2', // 已发货 'received' => '3', // 已收货 'completed' => '4', // 已完成 'cancelled' => '5', // 已取消 'refunded' => '6' // 已退款 ]; return $statusMap[$status] ?? '0'; } /** * 映射退款状态 * * @param int $refundStatus 退款状态 * @return string */ private function mapRefundStatus($refundStatus) { if ($refundStatus == 0) return '0'; // 无退款 if ($refundStatus == 1) return '1'; // 退款中 if ($refundStatus == 2) return '2'; // 已退款 return '0'; } /** * 映射支付方式 * * @param string $payType 支付类型 * @return string */ private function mapPayType($payType) { $payMap = [ 'wechat' => '01', // 微信支付 'alipay' => '02', // 支付宝 'ccb' => '03', // 建行支付 'balance' => '04' // 余额支付 ]; return $payMap[$payType] ?? '00'; } /** * 映射配送状态 * * @param string $status 订单状态 * @return string */ private function mapDeliveryStatus($status) { if (in_array($status, ['unpaid', 'paid'])) return '0'; // 未发货 if ($status == 'shipped') return '1'; // 已发货 if (in_array($status, ['received', 'completed'])) return '2'; // 已收货 return '0'; } /** * 批量同步订单 * 用于初始化或定时同步 * * @param array $conditions 查询条件 * @param int $limit 批量数量 * @return array */ public function batchSync($conditions = [], $limit = 100) { $successCount = 0; $failCount = 0; $errors = []; try { // 构建查询 $query = Db::name('shopro_order'); if (!empty($conditions)) { $query->where($conditions); } // 查询需要同步的订单 $orders = $query->where('status', '<>', 'cancelled') ->where('ccb_sync_status', 'in', [0, 2]) // 未同步或同步失败的 ->limit($limit) ->select(); foreach ($orders as $order) { $result = $this->pushOrder($order['id']); if ($result['status']) { $successCount++; } else { $failCount++; $errors[] = "订单{$order['order_sn']}: {$result['message']}"; } } return [ 'status' => true, 'message' => "批量同步完成", 'data' => [ 'total' => count($orders), 'success' => $successCount, 'fail' => $failCount, 'errors' => $errors ] ]; } catch (\Exception $e) { return [ 'status' => false, 'message' => '批量同步失败: ' . $e->getMessage(), 'data' => null ]; } } }