Android Tablet 上的 Web Based POS 如何集成信用卡处理
结论
两种硬件方案都可以实现,但正确的集成方式不是在刷卡后自动填写网页中的卡号、有效期和 CVV2,而是使用支付服务商提供的认证读卡器、Android SDK 或半集成终端:
- Rails 应用向支付网关创建交易或 Payment Intent。
- Android 端的原生应用、支付 SDK 或独立终端与读卡器通信。
- 读卡器处理插卡、挥卡、刷卡、PIN 输入和加密。
- 支付服务商返回 token、交易状态和必要的脱敏信息。
- Rails 后端通过 API 和 webhook 确认支付结果。
这样可以避免 Web 应用接触 PAN、磁道数据、PIN 和 CVV,大幅缩小 PCI DSS 的合规范围。
带嵌入式读卡器的触屏 POS 也应使用相同模式。部分读卡器可以模拟键盘,将磁道数据输入网页,但这种方式通常不适合生产环境。它会扩大 PCI DSS 范围,难以支持 EMV,还可能让浏览器、日志和应用代码接触敏感数据。
推荐架构
典型的数据流如下:
Rails POS
│ 创建交易
▼
Rails Backend ───────────► Payment Provider
│ │
│ 返回 transaction_id │
▼ │
Android POS App │
│ 调用 Terminal SDK │
▼ │
Certified Card Reader ───────┘
│
│ token / approved / declined
▼
Android POS App ─────────► Rails Backend
Payment Provider ─webhook─► Rails Backend
基本原则是让卡片数据从读卡器直接进入支付服务商的受控环境。Rails 页面只处理交易编号、token、金额和支付状态。
Android Tablet 与读卡器如何集成
市面上有支持 Android 的读卡器,常见连接方式包括:
- Bluetooth
- USB 或 USB-C
- 音频接口,主要用于较旧的设备
- 局域网连接
- 读卡器通过 Wi-Fi 或蜂窝网络直接连接支付平台
选型时不能只看设备是否“支持 Android”,还要确认:
- 目标支付服务商或收单机构是否支持该读卡器;
- 厂商是否提供 Android SDK;
- 设备是否支持 EMV 芯片卡和 NFC/contactless;
- 设备是否支持端到端加密或 P2PE;
- 设备是否适用于商户所在国家、币种和商户类别;
- 支付处理商是否已经完成对应的终端认证。
EMV 集成并不只是读取芯片内容,还涉及终端硬件认证、EMV kernel、支付应用,以及终端与处理机构之间的认证流程。通常不应自行读取芯片数据,更不应在 Rails 中实现 EMV 交易逻辑。
浏览器为什么不适合直接控制读卡器
运行在普通 Android 浏览器中的 Rails 页面,通常无法稳定地直接访问 Bluetooth 或 USB 读卡器。Web Bluetooth、WebUSB 等接口受到浏览器兼容性、权限、后台运行和设备驱动等因素的限制。支付终端厂商也很少使用这些通用接口实现正式的 EMV 支付流程。
较可靠的实现方式有三种。
方式一:Android 原生外壳加支付 SDK
可以将 Rails POS 运行在 WebView 中,或者采用前后端分离页面,再编写一个很薄的 Android 原生应用。原生应用负责集成支付服务商的 Terminal SDK,并向页面开放有限的支付接口。
网页只能发出“收取某个金额”的请求,不能请求“读取卡号”:
async function startCardPayment(orderId, amountCents) {
if (!window.AndroidPayments) {
throw new Error("Payment bridge is unavailable");
}
const result = await window.AndroidPayments.collectPayment(
JSON.stringify({
order_id: orderId,
amount: amountCents,
currency: "USD"
})
);
return JSON.parse(result);
}
原生桥接层只应返回非敏感结果,例如:
{
"transaction_id": "txn_123",
"status": "processing",
"card": {
"brand": "visa",
"last4": "4242"
}
}
不要通过 JavaScript bridge 返回 PAN、磁道数据、PIN block、cryptogram 或 CVV。
还要防止不受信任的页面调用 WebView bridge。Android 应用应限制可加载的域名,强制使用 HTTPS,并尽量减少对外暴露的接口。
方式二:本地支付服务或桌面代理
如果硬件厂商提供本地代理程序,网页可以通过受限的 localhost API、custom URL scheme 或厂商协议发起交易:
const response = await fetch("http://127.0.0.1:9137/payments", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
order_id: "order_7842",
amount: 2599,
currency: "USD"
})
});
这段代码只用于说明结构。实际端口、认证方式和请求格式必须遵循厂商的正式协议。生产环境还需要处理:
- 防止任意网页调用 localhost 服务;
- 请求签名和来源校验;
- 重放攻击;
- 本地服务被恶意程序冒充;
- 浏览器的 mixed content 和 CORS 限制;
- 设备断线后的交易结果恢复。
方式三:半集成或独立智能终端
Rails 后端将金额和订单号发送给支付终端。终端自行完成读卡和支付,再把结果返回系统。
这种模式通常最容易落地。POS 应用不处理卡片数据,只负责关联订单和终端交易:
# app/controllers/terminal_payments_controller.rb
class TerminalPaymentsController < ApplicationController
def create
order = current_store.orders.find(params[:order_id])
transaction = PaymentGateway.create_terminal_payment(
amount: order.total_cents,
currency: order.currency,
reference: order.id.to_s,
terminal_id: params.require(:terminal_id)
)
render json: {
payment_id: transaction.id,
status: transaction.status
}, status: :accepted
end
end
PaymentGateway 应通过支付服务商的服务器端 SDK 或 HTTPS API 实现。API 密钥只能保存在服务器端,不能写入 Rails 页面或 Android JavaScript。
支付结果必须由服务器确认
Android 端返回“approved”后,不应马上将订单永久标记为已支付。客户端结果可能丢失或被篡改,也可能与最终清算状态不一致。
支付结果应通过服务器查询,或者由经过签名验证的 webhook 确认:
# app/controllers/payment_webhooks_controller.rb
class PaymentWebhooksController < ActionController::API
def create
event = PaymentGateway.verify_webhook!(
payload: request.raw_post,
signature: request.headers["Payment-Signature"]
)
case event.type
when "payment.succeeded"
payment = Payment.find_by!(provider_payment_id: event.payment_id)
payment.with_lock do
payment.update!(status: "paid") unless payment.paid?
end
when "payment.failed"
Payment.find_by(provider_payment_id: event.payment_id)
&.update!(status: "failed")
end
head :ok
end
end
具体的事件名、签名请求头和验签算法由支付服务商决定,不能直接使用示例中的占位名称。Webhook 处理还必须具备幂等性,以免重复通知造成重复入账。
为什么不应自动填充银行卡表单
磁条刷卡器常见的一种工作模式是 keyboard wedge。设备会模拟键盘,将 Track 1 或 Track 2 数据输入当前获得焦点的字段。JavaScript 在技术上可以监听这些字符,并从中解析卡号和有效期,但不应按这种方式设计生产系统。
主要问题包括:
- 浏览器和 Rails 应用会直接接触完整 PAN;
- 磁道数据属于敏感认证数据,授权完成后不得保存;
- 输入内容可能进入前端监控、错误报告、访问日志或浏览器扩展;
- 如果键盘焦点出错,磁道数据可能被输入搜索框、聊天窗口或其他程序;
- 普通磁条读取不能代替 EMV 芯片交易;
- 系统可能需要承担更多欺诈、拒付和 PCI DSS 验证责任;
- 将读取到的卡号提交给在线网关时,交易可能被当作 card-not-present 或手工录入交易,而不是合规的 card-present EMV 交易。
因此,不要编写类似下面的流程:
// 不推荐:不要把读卡器输出写入网页支付字段
cardNumberInput.value = parsedTrackData.pan;
expiryInput.value = parsedTrackData.expiry;
只有在厂商提供受支持的支付 SDK、驱动或半集成接口时,触屏 POS 的嵌入式读卡器才真正适合支付集成。硬件“带读卡器”并不表示它可以连接任意在线支付网关。
CVV2 不应成为门店刷卡流程的一部分
正常的 card-present 交易通常通过插卡、挥卡或刷卡完成验证,部分交易还会要求输入 PIN 或签名。刷卡后一般不应再让客户向 POS 网页输入 CVV2。
CVV/CVC 属于敏感认证数据。即使经过加密,授权完成后也不得存储。应用还应防止它进入:
- 数据库;
- Rails 参数日志;
- 异常监控;
- 分析系统;
- session;
- 浏览器 localStorage;
- 截图或客服记录。
如果业务确实需要手工录卡作为备用方式,应优先使用支付服务商托管的输入组件、虚拟终端或安全的 hosted fields。卡号和 CVV 由服务商的 iframe 直接接收,Rails 只获得 token。
例如,页面可以向服务端提交 token,而不是卡片数据:
const paymentMethod = await gatewayFields.createPaymentMethod();
await fetch("/manual_payments", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify({
order_id: "order_7842",
payment_method_token: paymentMethod.token
})
});
托管字段可以减少应用接触卡片数据的范围,但不会自动免除全部 PCI DSS 责任。
PCI DSS 需要处理的事项
使用 Android Tablet 本身不会违反 PCI DSS。合规范围取决于卡片数据的流向、哪些系统能够访问这些数据,以及所选支付方案是否经过验证。
至少需要处理以下事项:
- 使用支付服务商正式支持的读卡器和 SDK;
- 优先采用 PCI-listed P2PE,或支付商提供的同类加密终端方案;
- 不在 Rails、JavaScript、Android 应用或日志中保存磁道数据、CVV、PIN 或 PIN block;
- 如果必须显示 PAN,只展示业务所需的最少信息,通常是卡片品牌和后四位;
- 客户端与服务器之间的所有通信均使用 TLS;
- 将支付 API 密钥保存在后端密钥管理系统或受保护的环境配置中;
- 对 Android 设备进行补丁管理、设备锁定、应用白名单和 MDM 管理;
- 禁止或限制 root、USB debugging、未知来源应用和不受控的浏览器扩展;
- 限制支付后台账号权限,并启用多因素认证;
- 保留支付操作的审计记录,但日志中不得包含敏感卡片数据;
- 定期检查依赖、Web 应用漏洞、终端篡改迹象和支付设备清单;
- 制定响应流程,处理终端遗失、被替换或疑似被植入窃卡设备等情况。
商户最终需要填写哪类 SAQ、能否使用 SAQ P2PE,以及是否需要外部扫描,取决于支付架构、收单机构要求和所用方案的认证状态。仅凭“采用 tokenization”无法确定问卷类型。上线前应由收单机构、支付服务商或 PCI Qualified Security Assessor 根据实际数据流进行确认。
实施步骤
建议按以下顺序实施:
- 确定营业国家、收单机构、币种,以及退款和小费等业务要求。
- 选择支持 Android、EMV、NFC 和目标支付服务商的认证读卡器。
- 要求供应商提供 Android SDK、终端认证范围和 PCI/P2PE 相关文件。
- 决定使用 Android 原生外壳,还是半集成智能终端。
- 绘制完整的 cardholder data flow,标出所有可能接触 PAN 的组件。
- 在 Rails 后端实现交易创建、查询、撤销、退款、幂等键和 webhook 验证。
- Android 端只传递订单、金额、终端编号和非敏感交易结果。
- 测试超时、断网、重复点击、终端断开、部分批准,以及“终端成功但 POS 未收到结果”等异常情况。
- 正式上线前,请支付服务商和收单机构确认 card-present 交易分类及 PCI DSS 验证要求。
通常最稳妥的分工是:Rails 管理订单,认证终端处理卡片,支付服务商负责授权。不要让读卡器把银行卡信息作为普通键盘输入交给 Web 表单,也不要自行解析磁道或芯片数据。
备注:内容仅供参考。
