如何使用 JavaScript 连接 iZettle reader 2 并完成支付?
结论
仅靠网页中的 JavaScript,无法通过 Bluetooth 直接连接 iZettle Reader 2 并完成刷卡支付。
Reader 2 并不是普通的蓝牙外设。设备配对、加密通信、读卡、PIN 输入、交易授权和支付结果确认,都需要通过受支持的官方支付应用或原生 SDK 完成。即使 Android 浏览器支持 Web Bluetooth,网页可以发现设备,也不等于它能调用 Reader 2 的支付协议。
通常有两种实现方式:
- 使用 Zettle/PayPal 仍在支持的 Android 集成方案,由原生 Android 层完成支付,再将结果返回 WebView。
- 如果目前没有可用的官方 Reader SDK,则跳转到官方支付应用,或改用适合网页的在线支付方案。
为什么 PHP API 不能解决这个问题
产品、订单 API 和实体读卡器支付属于不同层面的能力。
PHP package 通常只是服务端 REST API 的封装,可以用来:
- 管理商品和库存;
- 创建或查询订单;
- 获取交易记录;
- 处理账户侧数据。
实体读卡器支付还需要处理:
- 蓝牙设备认证;
- 安全密钥和加密协议;
- EMV 芯片卡及非接触式支付流程;
- PIN 安全;
- 支付终端认证;
- 交易授权及合规要求。
创建商品或订单的 API 无法间接完成这些操作,普通 HTTP 请求也不能让 Reader 2 开始收款。
为什么 Web Bluetooth 也不够
Web Bluetooth 只能提供访问 Bluetooth Low Energy 服务和特征值的底层能力,例如:
const device = await navigator.bluetooth.requestDevice({
acceptAllDevices: true,
optionalServices: ["some-service-uuid"]
});
这段代码最多只能请求发现兼容设备。要与 Reader 2 真正通信,还需要知道并有权使用以下信息:
- GATT service UUID;
- characteristic UUID;
- 配对和认证流程;
- 私有通信协议;
- 加密及密钥交换机制;
- 支付状态机。
这些内容不属于通用蓝牙标准。自行逆向或模拟支付协议既不可靠,也可能违反支付安全要求、设备认证规则及服务条款。因此,不能像操作蓝牙打印机或传感器一样直接操作 Reader 2。
推荐的实现结构
如果 WebView 应用运行在 Android 设备上,可以采用下面的结构:
Web 页面
│ JavaScript 调用
▼
Android JavaScript Bridge
│
▼
官方 Android SDK 或官方支付应用
│
▼
iZettle Reader 2
支付流程可以这样设计:
- Web 页面创建本地订单并计算应付金额。
- JavaScript 调用 Android 暴露的支付接口。
- Android 原生代码调用官方 SDK,或者打开官方支付应用。
- 用户在 Reader 2 上刷卡、插卡或使用非接触式支付。
- 原生层收到成功、取消或失败的结果。
- 原生层将结果返回 Web 页面。
- 后端根据可信的交易标识核验支付状态,然后更新订单。
WebView 与 Android 原生层的桥接示例
下面的代码只演示 WebView 与 Android 之间如何通信。startOfficialPayment() 是占位方法,必须替换为当前官方文档提供的 SDK 或应用跳转接口,不能直接用它控制 Reader 2。
Android 端:
class PaymentBridge(
private val activity: Activity,
private val webView: WebView
) {
@JavascriptInterface
fun startPayment(amountMinor: Long, currency: String, reference: String) {
activity.runOnUiThread {
startOfficialPayment(
amountMinor = amountMinor,
currency = currency,
reference = reference
) { result ->
val payload = JSONObject().apply {
put("status", result.status)
put("transactionId", result.transactionId)
put("reference", reference)
put("message", result.message)
}
webView.evaluateJavascript(
"window.onCardPaymentResult(${JSONObject.quote(payload.toString())})",
null
)
}
}
}
}
注册桥接接口:
webView.settings.javaScriptEnabled = true
webView.addJavascriptInterface(
PaymentBridge(this, webView),
"NativePayment"
)
网页端调用:
function pay() {
const payment = {
amountMinor: 1299,
currency: "EUR",
reference: "ORDER-2026-0001"
};
if (!window.NativePayment) {
throw new Error("当前环境不支持实体读卡器支付");
}
window.NativePayment.startPayment(
payment.amountMinor,
payment.currency,
payment.reference
);
}
window.onCardPaymentResult = function (json) {
const result = JSON.parse(json);
if (result.status === "success") {
console.log("Payment completed:", result.transactionId);
// 将 transactionId 发送到后端核验,不要仅凭此回调确认订单。
} else if (result.status === "cancelled") {
console.log("Payment cancelled");
} else {
console.error("Payment failed:", result.message);
}
};
金额最好使用最小货币单位。例如,1299 表示 12.99 EUR,这样可以避免 JavaScript 浮点数计算产生误差。
如果应用运行在普通浏览器中
如果所谓的 webbased app 只是通过 Chrome 打开的网站,而不是由开发者控制的 Android WebView,就无法添加原生桥接层。此时可以考虑:
- 使用官方支付应用提供的跳转或应用间集成能力;
- 将网页封装成原生或混合 Android 应用;
- 改用适合浏览器的在线支付页面;
- 由收银员在官方应用中单独输入金额并完成支付。
官方应用跳转能否传递金额和订单引用,以及能否回传支付结果,取决于账户所在地区和官方目前仍在维护的集成能力,不能假定这些功能一定可用。
实施前需要确认的事项
iZettle 已纳入 PayPal 旗下,其产品名称、SDK、支持地区和接入政策可能会发生变化。开发前应向 Zettle/PayPal 确认:
- Reader 2 是否仍支持第三方 Android 应用集成;
- 当前提供的是 Android SDK、应用间跳转,还是其他 POS 集成方式;
- 商户账户及所在国家或地区是否符合要求;
- SDK 是否允许通过 WebView 桥接调用;
- 如何在服务端核验支付结果;
- 如何申请测试账户、测试交易和生产审核。
网页回调和 Android 回调都可能被伪造,因此不能仅凭客户端返回的 "success" 将订单标记为已支付。后端应通过受信任的服务端接口、Webhook 或官方交易查询能力,核对交易编号、金额、币种和最终状态。
问题不在于 JavaScript 能否访问蓝牙,而在于支付服务商是否提供受支持的 Reader 2 集成通道。如果没有官方 Android SDK 或应用间接口,浏览器 JavaScript 就无法实现安全、合规的实体读卡器支付。
备注:内容仅供参考。
