160 行
6.6 KiB
PHP
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));
|
|
}
|
|
}
|