文件
2026-09-23 17:04:10 +08:00

160 行
6.6 KiB
PHP

<?php
namespace app\common\library;
class PrinterService
{
protected $user;
protected $ukey;
protected $sn;
protected $url = 'https://open.xpyun.net/api/openapi/xprinter/';
public function __construct()
{
$config = function_exists('config') ? config('printer') : [];
if (empty($config)) {
$configFile = __DIR__ . '/../../extra/printer.php';
$config = file_exists($configFile) ? include $configFile : [];
}
$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']) : '';
}
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 = 1)
{
$content = $this->buildReceiptContent($orderData);
if ($this->gbkLength($content) > 12 * 1024) {
return $this->failure('打印内容超过芯烨云 12KB 限制');
}
$result = $this->sendRequest('print', [
'sn' => $this->sn,
'content' => $content,
'copies' => max(1, min(65535, (int)$copies)),
// 离线时由芯烨云暂存,避免本地重发造成重复小票。
'mode' => 1,
'expiresIn' => 3600,
'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'] : '小众厨房设备商城');
$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 = '<CB>' . $title . '<BR></CB>';
$info .= '<CB>-- 订单小票 --<BR></CB>';
$info .= '--------------------------------<BR>';
$info .= '订单编号:' . $orderSn . '<BR>';
$info .= '下单时间:' . $createTime . '<BR>';
$info .= '--------------------------------<BR>';
$info .= '商品名称 数量 单价<BR>';
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') . '<BR>';
}
$info .= '--------------------------------<BR>';
$info .= '合计金额:¥' . $totalAmount . ' 元<BR>';
$info .= '客户姓名:' . $consignee . '<BR>';
$info .= '联系电话:' . $phone . '<BR>';
$info .= '送货地址:' . $address . '<BR>';
$info .= '--------------------------------<BR>';
return $info . '<CB>谢谢惠顾,欢迎再次光临!<BR><BR></CB>';
}
protected function sendRequest($endpoint, array $data)
{
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));
}
}