PAYATHON 2026

如何在 Flutter 中使用 NFC 实现信用卡支付?

支付阿杰

结论

普通 Flutter 应用无法把实体信用卡“扫描并保存”到手机中,再通过 NFC 模拟这张卡向 POS 机付款。Apple Pay 和 Google Pay 也不是简单的 NFC 读卡、存卡和卡片模拟工具。它们依赖发卡行、卡组织、设备安全硬件、支付令牌化和 EMV 非接触支付认证等一整套体系。

通常有两种可行方案:

  1. 用户在 App 内付款时,接入支付服务商提供的银行卡支付、Apple Pay 或 Google Pay SDK。
  2. 用户需要用手机在实体 POS 机上碰一碰付款时,直接调用系统钱包,或者与发卡行、卡组织和令牌服务商合作,将银行卡添加到钱包。普通应用无法自行实现同等功能。

不要读取并保存实体信用卡的 NFC 数据,也不要在本地保存卡号、有效期或 CVV。

为什么普通 NFC 功能无法实现

银行卡不是普通的 NFC 标签

普通 NFC 标签通常存储固定的 NDEF 数据,应用读取后就能解析。非接触银行卡使用 EMV Contactless 协议,支付时还会执行应用选择、风险控制和动态密码计算等操作。

POS 机需要的不是一份可复制的静态卡片数据,而是针对每笔交易生成的动态认证结果。即使应用读取到部分公开数据,也无法据此生成下一笔合法交易所需的动态密码。

Apple Pay 和 Google Pay 使用支付令牌

系统钱包通常不会把真实卡号直接发送给终端,而是使用由卡组织或令牌服务商签发的设备支付令牌,并通过安全硬件或受保护的执行环境生成交易凭证。

因此,手动输入以下信息并不能创建一张可被 NFC 终端识别的支付卡:

  • 卡号
  • 持卡人姓名
  • 有效期
  • CVV
  • 从实体卡读取到的部分 EMV 数据

这些信息最多只能用于普通的线上银行卡支付,而且仍应交给合规的支付服务商处理。

iOS 和 Android 都有限制

iOS 不允许普通应用随意模拟 EMV 支付卡。支付凭证的配置和使用受到 Wallet、发卡机构授权及相关系统能力的限制。

Android 提供 Host Card Emulation(HCE),但 HCE 只是一种卡片模拟机制,不代表应用因此获得了银行卡支付能力。自行实现 ISO-DEP 服务,最多可以模拟自定义门禁卡、会员卡或闭环支付凭证,并不会自动成为 Visa、Mastercard、银联等银行卡网络认可的支付卡。

推荐的实现架构

如果用户需要在 Flutter App 中购买商品,可以采用以下流程:

Flutter App
    │
    │ 请求创建订单
    ▼
业务后端
    │
    │ 调用支付服务商 API
    ▼
支付服务商
    │
    │ 返回一次性支付会话或 client secret
    ▼
Flutter App
    │
    │ 展示银行卡 / Apple Pay / Google Pay 支付界面
    ▼
支付服务商完成令牌化和扣款
    │
    │ Webhook 通知最终结果
    ▼
业务后端更新订单状态

在这种架构中:

  • App 不直接接触或保存完整的银行卡数据。
  • 银行卡信息由支付服务商的 SDK、安全输入组件或托管页面收集。
  • 后端负责创建支付订单并验证异步通知。
  • 后续支付使用服务商返回的 customer ID、payment method ID 或其他令牌,不使用原始卡号。

Flutter 端的基本示例

下面是一个不绑定具体支付厂商的流程示例。PaymentSdk 代表所选支付服务商的官方 Flutter SDK,实际类名和参数应以该服务商的当前文档为准。

import 'dart:convert';
import 'package:http/http.dart' as http;

class CheckoutService {
  CheckoutService({
    required this.backendBaseUrl,
    required this.paymentSdk,
  });

  final String backendBaseUrl;
  final PaymentSdk paymentSdk;

  Future<void> pay({
    required String orderId,
  }) async {
    final response = await http.post(
      Uri.parse('$backendBaseUrl/payments/session'),
      headers: {
        'Content-Type': 'application/json',
      },
      body: jsonEncode({
        'orderId': orderId,
      }),
    );

    if (response.statusCode != 200) {
      throw Exception('创建支付会话失败');
    }

    final data = jsonDecode(response.body) as Map<String, dynamic>;
    final clientToken = data['clientToken'] as String;

    final result = await paymentSdk.presentPaymentSheet(
      clientToken: clientToken,
      enableApplePay: true,
      enableGooglePay: true,
    );

    if (!result.completed) {
      throw Exception('支付未完成');
    }
  }
}

/// 仅用于展示调用边界,并非某个真实 SDK 的完整接口。
abstract class PaymentSdk {
  Future<PaymentResult> presentPaymentSheet({
    required String clientToken,
    required bool enableApplePay,
    required bool enableGooglePay,
  });
}

class PaymentResult {
  const PaymentResult({required this.completed});

  final bool completed;
}

clientToken 必须由后端创建。支付服务商的私钥不能放入 Flutter 应用,因为移动端安装包中的内容可以被提取。

后端应负责什么

后端至少需要完成以下工作:

POST /payments/session
1. 验证当前用户和订单
2. 从数据库重新计算应付金额
3. 调用支付服务商创建支付会话
4. 将一次性客户端令牌返回给 App

POST /payments/webhook
1. 验证支付服务商的签名
2. 检查订单号、币种和金额
3. 根据最终支付状态更新订单
4. 保证重复通知不会重复发货

客户端上传的金额不能直接采信,也不能仅凭 App 显示“支付成功”就安排发货。最终支付结果应以服务端查询结果或通过签名验证的 Webhook 为准。

如何实现“保存银行卡”

这里的“保存银行卡”应理解为保存支付服务商生成的引用,而不是保存卡片本身。

可以保存以下信息:

{
  "customerId": "cus_xxx",
  "paymentMethodId": "pm_xxx",
  "brand": "visa",
  "last4": "4242",
  "expiryMonth": 12,
  "expiryYear": 2028
}

customerId 和 paymentMethodId 只是示例名称,不同支付服务商使用的字段可能不同。完整卡号和 CVV 应由支付服务商保管。

CVV 通常不得作为后续支付凭证长期保存。即使经过加密,也不应由业务方自行存储。

如果必须在实体 POS 机上碰一碰支付

这属于支付产品接入问题,并非普通的 Flutter 开发问题。通常需要:

  • 与银行、发卡机构或持牌支付机构合作;
  • 接入卡组织的令牌服务;
  • 完成设备绑定和持卡人身份验证;
  • 使用平台允许的安全凭证配置方式;
  • 满足 EMV、PCI DSS 以及当地支付监管要求;
  • 通过相关安全测试和认证。

Flutter 可以用于账户页面、绑卡入口和交易记录等界面,但真正的支付凭证配置通常依赖原生系统能力、支付机构后端和平台授权。安装一个 NFC 插件并不能完成这些工作。

如果实际需求是“让商户手机接收银行卡付款”,应调查支付服务商提供的 Tap to Pay on iPhone 或 Android SoftPOS 方案。这与“让消费者手机模拟银行卡”是相反的业务方向,同样必须使用经过认证的收单 SDK,不能自行读取银行卡并扣款。

NFC 插件适合哪些场景

Flutter NFC 插件适合:

  • 读取 NDEF 标签;
  • 写入普通 NFC 标签;
  • 读取使用自有协议的设备;
  • 实现会员卡、门禁或企业内部闭环凭证;
  • 在 Android 上开发获得授权的自定义 HCE 协议。

它们不适合:

  • 克隆信用卡;
  • 保存卡片数据后模拟 EMV 银行卡;
  • 绕过 Apple Pay 或 Google Pay;
  • 直接生成能被普通银行卡 POS 机接受的支付交易。

安全与合规注意事项

  • 不要把卡号、CVV、支付密钥或服务端密钥写入 App。
  • 不要记录银行卡输入内容、支付令牌或敏感接口响应。
  • 优先使用支付服务商提供的原生输入组件或托管支付页面。
  • 使用 HTTPS,并在后端验证订单金额、币种和用户身份。
  • 使用 Webhook 确认最终支付状态,并验证其签名。
  • 为重复请求设置幂等键,防止重复扣款。
  • 上线 Apple Pay 或 Google Pay 前,需要按照平台和支付服务商的要求配置商户身份、域名、签名及支付能力。
  • 如果业务会直接处理银行卡数据,应先评估 PCI DSS 范围。不要认为“数据已加密”就等于合规。

正确的实现方式不是使用 Flutter NFC 保存并模拟信用卡,而是选择支持目标国家、币种和业务类型的支付服务商,通过其 Flutter SDK 接入银行卡支付和系统钱包。只有具备发卡资质、卡组织合作关系及平台授权,才应考虑实现面向实体 POS 机的 NFC 支付凭证。

备注:内容仅供参考。