需求:POS 系統生成含二維碼的小票,顧客掃碼即可完成付款。涉及二維碼生成、小票排版列印、掃碼支付回調三大核心環節。以下按語言/平台給出成熟方案與關鍵代碼片段。
1. 後端生成訂單 → 2. 生成支付二維碼(含訂單ID、金額、簽名) → 3. 小票打印機列印二維碼
4. 顧客掃碼 → 5. 支付平台(微信/支付寶)回調通知 → 6. 後端驗證簽名、更新訂單狀態 → 7. 輪詢/WebSocket 推送付款成功給 POS
// NuGet: Install-Package QRCoder
using QRCoder;
public byte[] GenerateQrCode(string payload, int pixelsPerModule = 20) {
using var qrGenerator = new QRCodeGenerator();
using var qrCodeData = qrGenerator.CreateQrCode(payload, QRCodeGenerator.ECCLevel.Q);
using var qrCode = new PngByteQRCode(qrCodeData);
return qrCode.GetGraphic(pixelsPerModule); // 返回 PNG 位元組陣列
}
// 列印小票(ESC/POS 指令,適用熱敏打印機)
public void PrintReceipt(byte[] qrPngBytes, OrderInfo order) {
var printer = new RawPrinterHelper("POS-58"); // 打印機名稱
printer.PrintBytes(new byte[] { 0x1B, 0x40 }); // 初始化
printer.PrintText($"訂單號: {order.Id}\n金額: {order.Amount:C}\n時間: {order.Time:yyyy-MM-dd HH:mm}\n");
printer.PrintBytes(new byte[] { 0x1B, 0x33, 0x18 }); // 設定行距
printer.PrintImage(qrPngBytes); // 列印二維碼圖片
printer.PrintBytes(new byte[] { 0x1D, 0x56, 0x00 }); // 切紙
}
<!-- 瀏覽器端 -->
<div id="qrcode"></div>
<script src="https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js"></script>
<script>
new QRCode(document.getElementById("qrcode"), {
text: "https://pay.example.com/order/12345?amt=100&sign=abc",
width: 200, height: 200,
correctLevel: QRCode.CorrectLevel.H
});
</script>
<div id="reader"></div>
<script src="https://cdn.jsdelivr.net/npm/html5-qrcode@2.3.8/minified/html5-qrcode.min.js"></script>
<script>
const scanner = new Html5Qrcode("reader");
scanner.start({ facingMode: "environment" }, { fps: 10, qrbox: 250 },
(decodedText) => { console.log("掃碼結果:", decodedText); callPayApi(decodedText); },
(err) => { /* 忽略掃描失敗 */ }
);
</script>
// composer require yansongda/pay
use Yansongda\Pay\Pay;
$config = [
'app_id' => 'wx_app_id',
'mch_id' => 'mch_id',
'key' => 'api_key',
'cert_client' => './cert/apiclient_cert.pem',
'cert_key' => './cert/apiclient_key.pem',
'notify_url' => 'https://your.com/pay/notify',
];
$pay = Pay::wechat($config);
// 統一下單,獲取 code_url(二維碼內容)
$result = $pay->driver('native')->pay([
'out_trade_no' => 'ORDER_12345',
'total_fee' => 100, // 分
'body' => '商品名稱',
'openid' => 'user_openid', // JSAPI 需要,NATIVE 可選
]);
$qrContent = $result['code_url']; // 直接用此字串生成二維碼列印
// notify.php
$pay = Pay::wechat($config);
try {
$data = $pay->verify(); // 自動驗證簽名
if ($data['return_code'] === 'SUCCESS' && $data['result_code'] === 'SUCCESS') {
$orderId = $data['out_trade_no'];
// 更新訂單狀態:已付款
Order::where('id', $orderId)->update(['status' => 'paid', 'paid_at' => now()]);
// 推送通知給 POS(WebSocket / 長輪詢 / Server-Sent Events)
Pusher::trigger('pos-channel', 'order.paid', ['order_id' => $orderId]);
}
return $pay->success(); // 返回 SUCCESS 給微信
} catch (\Exception $e) {
Log::error($e);
return $pay->fail();
}
| 環節 | 風險點 | 對策 |
|---|---|---|
| 二維碼內容 | 包含敏感資訊、長度過長導致識別率下降 | 僅放支付連結/訂單ID,簽名驗證在後端;使用高容錯等級 (Q/H) |
| 小票列印 | 熱敏紙褪色、二維碼模糊無法掃描 | 設定較高 DPI、適當放大模組像素、保留足夠靜區 |
| 支付回調 | 重複通知、偽造通知、網路中斷未收到 | 冪等處理(訂單狀態機)、驗證簽名、提供主動查單 API 兜底 |
| 並發安全 | 同一訂單重複支付 | 資料庫唯一索引 out_trade_no、樂觀鎖、分散式鎖 |
POST /api/pay/qrcode 返回 { qr_code: "data:image/png;base64,..." },POS 端(C#/Java/Node)只需調用並列印。order.paid 事件,避免輪詢延遲。掌握上述三棧技術要點,即可構建穩定、安全的「小票二維碼掃碼付款」系統。