$value) { if ($value !== '' && $value !== null) { $config[$key] = $value; } } } } catch (\Exception $e) { \think\Log::warning('读取后台小票配置失败: ' . $e->getMessage()); } $this->user = isset($config['client_id']) ? trim($config['client_id']) : ''; $this->ukey = isset($config['client_secret']) ? trim($config['client_secret']) : ''; $this->sn = isset($config['sn']) ? trim($config['sn']) : ''; $this->enabled = !isset($config['enable']) || (string)$config['enable'] !== '0'; $this->copies = isset($config['copies']) ? max(1, min(65535, (int)$config['copies'])) : 1; $this->expiresIn = isset($config['expires_in']) ? max(60, min(604800, (int)$config['expires_in'])) : 3600; $this->shopName = !empty($config['shop_name']) ? trim($config['shop_name']) : $this->shopName; } public function queryStatus() { return $this->sendRequest('queryPrinterStatus', ['sn' => $this->sn]); } public function queryOrderState($cloudOrderId) { $result = $this->sendRequest('queryOrderState', ['orderId' => (string)$cloudOrderId]); $result['printed'] = !empty($result['accepted']) && !empty($result['data']); return $result; } public function printOrder($orderData = [], $copies = null) { $content = $this->buildReceiptContent($orderData); if ($this->gbkLength($content) > 12 * 1024) { return $this->failure('打印内容超过芯烨云 12KB 限制'); } $result = $this->sendRequest('print', [ 'sn' => $this->sn, 'content' => $content, 'copies' => $copies === null ? $this->copies : max(1, min(65535, (int)$copies)), // 离线时由芯烨云暂存,避免本地重发造成重复小票。 'mode' => 1, 'expiresIn' => $this->expiresIn, 'idempotent' => isset($orderData['print_idempotent']) ? $orderData['print_idempotent'] : '', ]); if (!empty($result['accepted']) && !is_scalar($result['data'])) { return $this->failure('芯烨云未返回打印订单号', $result['response']); } if (!empty($result['accepted'])) { $result['cloud_order_id'] = (string)$result['data']; } return $result; } public function buildReceiptContent($order = []) { $title = $this->cleanText(isset($order['shop_name']) ? $order['shop_name'] : $this->shopName); $orderSn = $this->cleanText(isset($order['order_sn']) ? $order['order_sn'] : 'TEST' . date('YmdHis')); $createTime = isset($order['createtime']) ? date('Y-m-d H:i:s', $order['createtime']) : date('Y-m-d H:i:s'); $consignee = $this->cleanText(isset($order['consignee']) ? $order['consignee'] : '测试用户'); $phone = $this->cleanText(isset($order['phone']) ? $order['phone'] : '138****8888'); $address = $this->cleanText(isset($order['address']) ? $order['address'] : '贵州省修文县城内'); $totalAmount = $this->cleanText(isset($order['total_fee']) ? $order['total_fee'] : '0.01'); $info = '' . $title . '
'; $info .= '-- 订单小票 --
'; $info .= '--------------------------------
'; $info .= '订单编号:' . $orderSn . '
'; $info .= '下单时间:' . $createTime . '
'; $info .= '--------------------------------
'; $info .= '商品名称 数量 单价
'; foreach (!empty($order['items']) && is_array($order['items']) ? $order['items'] : [] as $item) { $info .= $this->cleanText(isset($item['name']) ? $item['name'] : '商品') . ' ' . $this->cleanText(isset($item['num']) ? $item['num'] : 1) . ' ¥' . $this->cleanText(isset($item['price']) ? $item['price'] : '0.00') . '
'; } $info .= '--------------------------------
'; $info .= '合计金额:¥' . $totalAmount . ' 元
'; $info .= '客户姓名:' . $consignee . '
'; $info .= '联系电话:' . $phone . '
'; $info .= '送货地址:' . $address . '
'; $info .= '--------------------------------
'; return $info . '谢谢惠顾,欢迎再次光临!

'; } protected function sendRequest($endpoint, array $data) { if (!$this->enabled) { return $this->failure('后台未启用小票打印'); } if (!$this->user || !$this->ukey || !$this->sn) { return $this->failure('打印机配置不完整'); } $timestamp = (string)time(); $payload = array_merge($data, [ 'user' => $this->user, 'timestamp' => $timestamp, 'sign' => sha1($this->user . $this->ukey . $timestamp), ]); $json = json_encode($payload, JSON_UNESCAPED_UNICODE); if ($json === false) { return $this->failure('打印请求编码失败'); } $ch = curl_init($this->url . $endpoint); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $json); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json;charset=UTF-8', 'Accept: application/json']); curl_setopt($ch, CURLOPT_TIMEOUT, 10); $body = curl_exec($ch); $error = curl_error($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($error) { return $this->failure('云打印请求失败: ' . $error, '', true); } $response = trim((string)$body); $decoded = json_decode($response, true); $accepted = $httpCode >= 200 && $httpCode < 300 && is_array($decoded) && isset($decoded['code']) && (int)$decoded['code'] === 0; return [ 'accepted' => $accepted, 'data' => is_array($decoded) && array_key_exists('data', $decoded) ? $decoded['data'] : null, 'response' => mb_substr($response, 0, 1000, 'UTF-8'), 'error' => $accepted ? '' : (is_array($decoded) && !empty($decoded['msg']) ? $decoded['msg'] : '云打印未受理,HTTP ' . $httpCode), 'uncertain' => false, ]; } protected function failure($error, $response = '', $uncertain = false) { return ['accepted' => false, 'data' => null, 'response' => $response, 'error' => $error, 'uncertain' => $uncertain]; } protected function gbkLength($content) { $encoded = function_exists('mb_convert_encoding') ? @mb_convert_encoding($content, 'GBK', 'UTF-8') : @iconv('UTF-8', 'GBK//IGNORE', $content); return $encoded === false ? strlen($content) : strlen($encoded); } protected function cleanText($value) { return preg_replace('/[\r\n\t]+/', ' ', strip_tags((string)$value)); } }