PAYATHON 2026

嵌入式设备如何不使用SDK开发当面付扫码支付服务端?

支付小周

结论

不使用支付宝服务端 SDK,也可以直接通过 HTTPS 调用开放平台接口,实现当面付扫码支付。SDK 主要封装了参数组装、RSA2 签名、HTTP 请求、响应验签和证书处理。只要嵌入式设备支持 HTTPS、JSON、URL 编码、Base64、SHA-256 和 RSA 签名,就可以自行完成调用。

通常需要使用以下接口:

  • alipay.trade.precreate:创建预下单并获取付款二维码内容
  • alipay.trade.query:查询订单支付状态
  • alipay.trade.close:关闭未支付订单
  • alipay.trade.refund:退款,按业务需要使用

在开放平台文档中,可以重点查找“当面付”“扫码支付”“自行拼接参数”“接口调用方式”“RSA2 签名”和“异步通知验签”。具体字段和网关地址可能随开放平台调整,请以应用当前签约产品页面展示的接口文档为准。

推荐架构

嵌入式设备可以直接调用接口,但生产环境更适合采用以下架构:

嵌入式设备 → 商户服务端 → 支付宝开放平台

设备直接调用接口时,需要在设备中保存应用私钥。一旦设备被拆机、固件被读取或调试接口被攻破,私钥就可能泄露,攻击者随后可能伪造商户请求。

如果业务要求必须由设备直接调用,至少要使用安全芯片、可信执行环境或硬件加密模块保存私钥,同时关闭调试接口。私钥不得以明文形式出现在固件或日志中。

调用流程

1. 准备应用和密钥

先在支付宝开放平台完成以下配置:

  1. 创建并配置应用。
  2. 签约或开通当面付相关能力。
  3. 生成 RSA 密钥对。
  4. 将应用公钥上传至开放平台。
  5. 保存支付宝公钥,用于验证支付宝响应和异步通知。
  6. 如果应用使用证书模式,还要配置应用公钥证书、支付宝公钥证书和支付宝根证书。

新接入通常使用 RSA2,也就是 SHA256withRSA。应用私钥和支付宝公钥的用途不同,不要混淆:

  • 应用私钥:为商户请求签名
  • 支付宝公钥:验证支付宝返回的内容

2. 组装预下单请求

调用 alipay.trade.precreate 时,公共参数通常包括:

app_id
method=alipay.trade.precreate
format=JSON
charset=utf-8
sign_type=RSA2
timestamp
version=1.0
notify_url
biz_content

biz_content 示例:

{
  "out_trade_no": "DEVICE001_20260909153000_0001",
  "total_amount": "0.01",
  "subject": "设备扫码支付",
  "timeout_express": "5m"
}

各字段含义如下:

  • out_trade_no 是商户订单号,必须在商户系统中保持唯一。
  • total_amount 建议使用十进制定点字符串,避免浮点数计算误差。
  • subject 是订单标题。
  • timeout_express 是否受支持以及允许使用哪些格式,应以当前接口文档为准。

3. 生成待签名字符串

一般按以下规则处理:

  1. 排除 sign 参数。
  2. 排除值为空的参数。
  3. 按参数名的字符顺序排序。
  4. 拼接成 key=value&key=value。
  5. 使用应用私钥执行 SHA256withRSA 签名。
  6. 对签名结果进行 Base64 编码。
  7. 将 Base64 编码结果写入 sign 参数。
  8. 完成签名后,再对所有参数进行 URL 编码并发送。

例如:

app_id=2021000000000000&biz_content={"out_trade_no":"DEVICE001_20260909153000_0001","subject":"设备扫码支付","total_amount":"0.01"}&charset=utf-8&format=JSON&method=alipay.trade.precreate&sign_type=RSA2&timestamp=2026-09-09 15:30:00&version=1.0

签名过程中最容易出错的是待签名字符串与实际发送的参数值不一致。JSON 多出一个空格、字段顺序改变或字符编码不同,都可能造成验签失败。

下面是与具体网络库无关的 C 风格伪代码:

params["app_id"] = APP_ID;
params["method"] = "alipay.trade.precreate";
params["format"] = "JSON";
params["charset"] = "utf-8";
params["sign_type"] = "RSA2";
params["timestamp"] = "2026-09-09 15:30:00";
params["version"] = "1.0";
params["notify_url"] = NOTIFY_URL;
params["biz_content"] =
    "{\"out_trade_no\":\"DEVICE001_20260909153000_0001\","
    "\"total_amount\":\"0.01\","
    "\"subject\":\"设备扫码支付\","
    "\"timeout_express\":\"5m\"}";

char *sign_content = build_sorted_query(params, false);
unsigned char digest[32];

sha256(
    (const unsigned char *)sign_content,
    strlen(sign_content),
    digest
);

unsigned char *signature = rsa_sha256_sign(
    APP_PRIVATE_KEY,
    (const unsigned char *)sign_content,
    strlen(sign_content)
);

params["sign"] = base64_encode(signature);

char *request_body = form_urlencode(params);
https_post(ALIPAY_GATEWAY, request_body);

rsa_sha256_sign() 应直接实现 SHA256withRSA。使用 OpenSSL 时,应优先使用 EVP 接口,不要自行拼装 RSA 填充。

4. 发送 HTTPS 请求

请求通常以 application/x-www-form-urlencoded 格式提交:

POST /gateway.do HTTP/1.1
Host: openapi.alipay.com
Content-Type: application/x-www-form-urlencoded; charset=UTF-8

下面是一条示意命令。执行时,必须将 SIGNATURE_URL_ENCODED 替换为真实签名经过 URL 编码后的结果:

curl -X POST 'https://openapi.alipay.com/gateway.do' \
  -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
  --data-urlencode 'app_id=2021000000000000' \
  --data-urlencode 'method=alipay.trade.precreate' \
  --data-urlencode 'format=JSON' \
  --data-urlencode 'charset=utf-8' \
  --data-urlencode 'sign_type=RSA2' \
  --data-urlencode 'timestamp=2026-09-09 15:30:00' \
  --data-urlencode 'version=1.0' \
  --data-urlencode 'biz_content={"out_trade_no":"DEVICE001_20260909153000_0001","total_amount":"0.01","subject":"设备扫码支付","timeout_express":"5m"}' \
  --data-urlencode 'sign=SIGNATURE_URL_ENCODED'

不要直接使用示例中的 app_id、时间、订单号或签名。网关地址、接口权限和沙箱配置也要以开放平台的当前文档为准。

5. 处理预下单结果

成功响应通常包含以下内容:

{
  "alipay_trade_precreate_response": {
    "code": "10000",
    "msg": "Success",
    "out_trade_no": "DEVICE001_20260909153000_0001",
    "qr_code": "https://qr.alipay.com/..."
  },
  "sign": "..."
}

收到响应后,要完成两项检查:

  1. 使用支付宝公钥验证响应签名。
  2. 检查业务响应码是否表示成功,不能只判断 HTTP 状态码。

qr_code 是生成二维码所需的内容,不一定是图片。嵌入式设备可以用本地二维码库将该字符串编码成二维码并显示。

响应验签必须使用支付宝返回的原始待验签内容。如果先解析 JSON、调整字段顺序,再重新序列化后验签,字节内容可能发生变化,进而导致验签失败。待验签内容的具体截取规则必须严格按照开放平台当前的“响应验签”文档实现。

判断支付是否完成

二维码生成成功不等于付款已经完成。设备还要通过以下方式确认最终支付状态:

  • 接收支付宝异步通知。
  • 主动调用 alipay.trade.query 查询订单。

如果嵌入式设备没有公网可访问地址,就无法直接、可靠地接收 notify_url 通知。此时可以定时查询订单。更稳妥的做法仍然是由商户服务端接收通知,再将结果下发给设备。

常见状态包括:

WAIT_BUYER_PAY
TRADE_SUCCESS
TRADE_FINISHED
TRADE_CLOSED

设备只有在确认订单支付成功,并核对订单号、金额、商户身份等信息后,才能出货、开闸或提供服务。

处理异步通知时,应遵守以下原则:

  1. 验证通知签名。
  2. 核对 app_id、out_trade_no、金额和收款方信息。
  3. 查询本地订单,避免处理不存在的订单。
  4. 使用幂等逻辑更新订单状态。
  5. 完成业务处理后,按照接口要求返回纯文本 success。
  6. 验签失败或业务处理失败时,不得返回成功。

嵌入式环境中的实现要点

时间同步

请求中的 timestamp 必须准确。设备应通过可信的 NTP 服务或商户服务端校时。时钟偏差过大时,请求可能被拒绝。

金额处理

不要使用 float 或 double 直接计算支付金额。可以用整数按“分”保存金额,提交请求时再将其转换为保留两位小数的字符串:

long amount_cent = 1234;
snprintf(total_amount, sizeof(total_amount),
         "%ld.%02ld",
         amount_cent / 100,
         amount_cent % 100);

转换结果为:

12.34

订单号唯一性

out_trade_no 要持久化保存,不能只依赖内存计数器。如果设备断电重启后生成了重复的订单号,旧订单可能被误认为新订单。

同一订单因网络超时而重新提交时,应继续使用原订单号,并通过查询接口确认结果,不要马上创建新订单。

网络异常

HTTPS 请求超时不表示支付宝一定没有收到请求。遇到超时时,应按以下方式处理:

  1. 保留原订单号。
  2. 调用 alipay.trade.query 查询。
  3. 如果查询不到,再根据业务策略重试预下单。
  4. 不得因简单重试生成多个商户订单。

TLS 校验

必须验证服务器证书和主机名,不能因为嵌入式环境适配困难而关闭 TLS 证书校验。设备还要维护可用的根证书,并具备相应的根证书更新机制。

私钥安全

私钥不得:

  • 写死在可以直接提取的固件字符串中
  • 输出到串口或调试日志
  • 通过配置文件明文下发
  • 出现在错误日志中
  • 随设备升级包公开分发

大量设备共用同一应用私钥会扩大泄露后的影响。如果设备数量较多,建议由商户服务端统一完成签名和支付请求。

可行性判断

从技术上看,不使用 SDK 完全可行。开发工作的重点不在于调用某个特定语言库,而在于正确实现以下通用协议能力:

JSON
HTTP/HTTPS
application/x-www-form-urlencoded
UTF-8
参数排序与拼接
SHA-256
RSA2 签名和验签
Base64
二维码编码
订单状态机与幂等处理

资源受限的 Linux、RTOS 或裸机设备只要具备 TLS 和 RSA 能力,就可以自行实现这些步骤。如果设备无法安全保存私钥、缺少可靠时钟、不能升级根证书,或者没有稳定的订单持久化能力,则应增加商户中转服务。设备只负责提交订单信息并接收支付结果。

备注:内容仅供参考。