PAYATHON 2026

Android Tablet 上的 Web Based POS 如何集成信用卡处理

支付小周

结论

两种硬件方案都可以实现,但正确的集成方式不是在刷卡后自动填写网页中的卡号、有效期和 CVV2,而是使用支付服务商提供的认证读卡器、Android SDK 或半集成终端:

  1. Rails 应用向支付网关创建交易或 Payment Intent。
  2. Android 端的原生应用、支付 SDK 或独立终端与读卡器通信。
  3. 读卡器处理插卡、挥卡、刷卡、PIN 输入和加密。
  4. 支付服务商返回 token、交易状态和必要的脱敏信息。
  5. 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 根据实际数据流进行确认。

实施步骤

建议按以下顺序实施:

  1. 确定营业国家、收单机构、币种,以及退款和小费等业务要求。
  2. 选择支持 Android、EMV、NFC 和目标支付服务商的认证读卡器。
  3. 要求供应商提供 Android SDK、终端认证范围和 PCI/P2PE 相关文件。
  4. 决定使用 Android 原生外壳,还是半集成智能终端。
  5. 绘制完整的 cardholder data flow,标出所有可能接触 PAN 的组件。
  6. 在 Rails 后端实现交易创建、查询、撤销、退款、幂等键和 webhook 验证。
  7. Android 端只传递订单、金额、终端编号和非敏感交易结果。
  8. 测试超时、断网、重复点击、终端断开、部分批准,以及“终端成功但 POS 未收到结果”等异常情况。
  9. 正式上线前,请支付服务商和收单机构确认 card-present 交易分类及 PCI DSS 验证要求。

通常最稳妥的分工是:Rails 管理订单,认证终端处理卡片,支付服务商负责授权。不要让读卡器把银行卡信息作为普通键盘输入交给 Web 表单,也不要自行解析磁道或芯片数据。

备注:内容仅供参考。