如何在 Arduino 上接入并显示个人支付二维码?
结论
可以。Arduino 或 ESP32 能在液晶屏、OLED 或电子墨水屏上显示个人收款二维码,用户使用支付应用扫码后即可付款。
不过,这通常只是把个人收款码显示在屏幕上,并不代表设备已经接入支付系统。使用普通个人收款码时,Arduino 一般无法:
- 自动设置订单金额和订单号;
- 可靠判断用户是否已经付款;
- 获取支付结果通知;
- 自动处理退款、对账和异常订单。
如果设备只需展示固定收款码,可以完全离线运行。如果需要实现“一单一码”和自动确认付款,则应使用支付平台提供的商户接口,并通过联网服务器创建订单、验证支付结果。
实现方式
方式一:显示固定个人收款码
这是最简单的实现方法:
- 在支付应用中保存个人收款二维码。
- 将二维码图片转换为 Arduino 可使用的单色位图或 RGB565 图片。
- 把图片写入程序存储器、SD 卡或 SPI Flash。
- 付款时在显示屏上绘制图片。
这种方法不需要联网,Arduino Uno、Nano、Mega 和 ESP32 都可以使用。能否正常显示,主要取决于屏幕分辨率和可用的图片存储空间。
固定二维码不包含当前订单的信息。即使顾客已经付款,设备通常也不能只根据二维码判断支付结果。
方式二:根据二维码内容实时生成图像
如果已经取得二维码的编码内容,可以使用二维码生成库动态计算矩阵,再将黑白模块绘制到屏幕上。
ESP32、ESP8266 等内存较充足的开发板更适合这种方法。Arduino Uno 只有 2 KB SRAM,很容易受到内存限制,一般更适合直接显示预先生成的位图。
下面以 ESP32、TFT_eSPI 和常见的 QRCode 库接口为例。不同二维码库可能使用同名类,安装前应确认其 API 包含 qrcode_getBufferSize()、qrcode_initText() 和 qrcode_getModule()。
#include <TFT_eSPI.h>
#include <qrcode.h>
TFT_eSPI tft = TFT_eSPI();
const char *qrText = "YOUR_QR_CODE_CONTENT";
void drawQRCode(const char *text) {
const uint8_t version = 5;
const uint8_t ecc = ECC_LOW;
uint8_t qrcodeData[qrcode_getBufferSize(version)];
QRCode qrcode;
qrcode_initText(&qrcode, qrcodeData, version, ecc, text);
int16_t margin = 4;
int16_t availableWidth = tft.width();
int16_t availableHeight = tft.height();
int16_t scaleX = availableWidth / (qrcode.size + margin * 2);
int16_t scaleY = availableHeight / (qrcode.size + margin * 2);
int16_t scale = min(scaleX, scaleY);
if (scale < 1) {
tft.fillScreen(TFT_RED);
tft.setTextColor(TFT_WHITE, TFT_RED);
tft.drawString("QR code is too large", 4, 4);
return;
}
int16_t qrPixels = (qrcode.size + margin * 2) * scale;
int16_t offsetX = (availableWidth - qrPixels) / 2;
int16_t offsetY = (availableHeight - qrPixels) / 2;
tft.fillScreen(TFT_WHITE);
for (uint8_t y = 0; y < qrcode.size; y++) {
for (uint8_t x = 0; x < qrcode.size; x++) {
if (qrcode_getModule(&qrcode, x, y)) {
int16_t px = offsetX + (x + margin) * scale;
int16_t py = offsetY + (y + margin) * scale;
tft.fillRect(px, py, scale, scale, TFT_BLACK);
}
}
}
}
void setup() {
tft.init();
tft.setRotation(1);
drawQRCode(qrText);
}
void loop() {
}
应将 YOUR_QR_CODE_CONTENT 替换为二维码实际编码的数据,而不是二维码图片的文件名。二维码版本决定最大数据容量。如果内容过长,qrcode_initText() 可能无法正确编码,此时需要提高版本或缩短内容。
使用 TFT_eSPI 时,还要根据屏幕控制器、引脚和 SPI 配置设置驱动,不能假定示例中的默认配置适用于所有硬件。
方式三:接入正式支付流程
如果设备用于自助售货、充电或其他无人值守场景,仅显示个人收款码通常无法满足需求。较完整的支付流程如下:
- Arduino 或 ESP32 向业务服务器提交商品、金额和设备编号。
- 服务器调用支付平台的商户下单接口。
- 支付平台返回二维码内容或支付链接。
- 服务器将二维码内容发送给设备。
- 设备生成并显示本次订单的二维码。
- 支付平台向服务器发送支付结果通知。
- 服务器验证签名、金额、订单号和订单状态。
- 设备查询服务器,或通过 MQTT、WebSocket 等方式接收确认结果。
- 确认付款成功后,设备再执行出货、开锁或启动服务。
逻辑关系可以简化为:
Arduino/ESP32
│ 创建订单
▼
业务服务器 ──调用接口──> 支付平台
│ │
│ 二维码内容 │ 支付结果通知
▼ ▼
Arduino/ESP32 <──查询或推送── 业务服务器
不要把支付密钥、证书和签名私钥保存在 Arduino 固件中。设备可能被拆解,Flash 也可能被读取。一旦密钥泄露,攻击者可能伪造订单或支付结果。签名、回调验证和订单状态管理应放在服务器端处理。
显示屏选择
二维码需要清晰的黑白边界和足够的留白。选择显示屏时,可以参考以下原则:
- OLED 适合显示短内容和小尺寸二维码,但分辨率过低时可能难以扫码。
- TFT 屏分辨率较高、刷新速度快,适合显示动态订单二维码。
- 电子墨水屏断电后仍能保留画面,适合低功耗设备。
- 不要对二维码进行非整数倍缩放,否则模块边缘可能变得模糊。
- 二维码四周应保留至少 4 个模块宽度的空白区域。
- 使用标准的高对比度配色,优先选择白底黑码。
- 屏幕反光、亮度过低和可视角度也会影响扫码成功率。
正式使用前,应使用不同品牌和型号的手机,在多种距离、亮度和角度下测试扫码效果。
如何取得二维码内容
如果手中只有一张收款码图片,最稳妥的方法是直接显示这张图片。也可以用二维码解析工具读取其中的编码内容,再让设备重新生成二维码,但需要先确认:
- 解析结果完整,没有被截断;
- 重新生成的二维码能被支付应用正确识别;
- 二维码不是依赖应用内部状态的临时码;
- 支付平台规则允许这样展示或使用该收款码。
不要凭经验修改二维码中的 URL、参数或金额字段。支付链接可能包含签名、校验信息或平台内部标识。手动拼接的二维码即使能被扫描,也不一定能够正常支付。
支付成功不能靠截图或界面判断
Arduino 不能通过“二维码被扫描”判断付款是否成功。扫码、打开付款页面和完成支付是三个不同的状态。
设备也不应根据蜂鸣器提示、手机截图或非官方通知自动出货。可靠的判断依据是服务器收到并验证过的支付平台通知,同时还要检查:
订单号匹配
&& 支付状态成功
&& 实付金额正确
&& 商户身份正确
&& 通知签名有效
&& 订单尚未处理
检查订单是否尚未处理,可以防止同一条支付通知被重复执行。设备端和服务器端还要处理断网、超时、重复通知,以及设备重启后的订单恢复。
注意事项
个人收款码能否用于经营、自助设备或公开展示,取决于支付平台当前的规则和当地监管要求。个人码与商户支付产品在额度、风控、对账、退款和接口能力上通常有所不同。用于商业设备前,应查看对应平台的最新协议。需要自动确认付款时,应优先申请正规的商户支付能力。
不要在日志、串口输出或公开代码仓库中保存支付密钥、证书、完整用户标识等敏感信息。固定个人收款码适合简单演示或由人工确认付款的场景。无人值守设备则应采用“设备展示二维码、服务器处理支付”的架构。
备注:内容仅供参考。