在 iPhone 应用中集成外接读卡器信用卡支付
结论
可以集成,但 iMag、iDynamo、Square 等读卡器并没有通用的 iOS SDK。你通常需要选择支付服务商或硬件厂商提供的官方 SDK,并配合经过认证的读卡器、商户账户和后端支付接口使用。
不要直接读取磁条数据,再自行提交卡号。这种做法安全风险很高,也会大幅扩大 PCI DSS 合规范围。涉及 EMV 芯片卡或非接触式支付时,还必须满足终端认证和支付网络规则。
推荐的集成方式
建议选择提供完整“读卡器 + iOS SDK + 支付处理”方案的平台,例如:
- Square Reader SDK
- Stripe Terminal
- Adyen Terminal API / Mobile SDK
- 读卡器厂商提供的专用 SDK,例如 MagTek 为部分 iDynamo 或相关设备提供的开发组件
支持的 iPhone、iPad、连接方式和国家或地区,应以所选平台当前发布的官方兼容列表为准。部分使用音频接口的老式读卡器或早期 iMag 型号可能已经停产,也可能无法继续支持新版 iOS。
这类 SDK 通常会完成以下工作:
- 发现并连接读卡器。
- 管理蓝牙、USB、Lightning 或其他配件连接。
- 读取磁条、EMV 芯片或 NFC 卡片。
- 加密敏感支付数据。
- 显示插卡、刷卡、挥卡及重试提示。
- 把加密结果提交给支付平台进行授权。
- 返回成功、拒付、取消等状态。
应用一般不会获得完整卡号、磁道数据或芯片原始数据。
典型架构
支付流程通常需要后端参与,不能只在 iOS 客户端完成:
iOS App
│
├── 请求后端创建支付
│
Backend
├── 验证订单金额和币种
├── 调用支付平台创建 Payment Intent / Checkout
│
iOS App
├── 通过官方 SDK 连接读卡器
├── 收集并确认卡片支付
│
Payment Provider
├── 完成授权和扣款
└── 通过 Webhook 通知后端最终结果
金额必须由后端根据订单计算,不能直接信任客户端传来的金额。客户端显示“支付成功”后,也不能立即完成发货。后端还应查询支付状态或验证 Webhook,以确认最终结果。
iOS 端的组织方式
各厂商的 API 名称差异很大。可以先在业务层定义一套统一接口,再针对具体 SDK 编写实现:
import Foundation
enum CardPaymentError: Error {
case readerNotConnected
case cancelled
case declined
case providerError(String)
}
struct PaymentRequest {
let paymentId: String
let amount: Int // 最小货币单位,例如“分”
let currency: String
}
struct PaymentResult {
let transactionId: String
let status: String
}
protocol CardReaderService {
func discoverReaders() async throws -> [String]
func connect(readerId: String) async throws
func collectPayment(_ request: PaymentRequest) async throws -> PaymentResult
func disconnect() async
}
厂商适配层负责调用官方 SDK:
final class ProviderCardReaderService: CardReaderService {
func discoverReaders() async throws -> [String] {
// 调用所选支付平台 SDK 的 reader discovery API。
// 返回应用内部使用的 reader identifier。
fatalError("Implement with the provider SDK")
}
func connect(readerId: String) async throws {
// 调用 SDK 的 connect API。
// 某些平台还要求提供 locationId 或 connection token。
fatalError("Implement with the provider SDK")
}
func collectPayment(_ request: PaymentRequest) async throws -> PaymentResult {
// 典型流程:
// 1. 后端创建支付对象;
// 2. SDK 收集读卡器上的付款方式;
// 3. SDK 确认支付;
// 4. 将结果交给后端复核。
fatalError("Implement with the provider SDK")
}
func disconnect() async {
// 调用 SDK 的 disconnect API。
}
}
这段代码只用于说明应用架构,并不是某个厂商 SDK 的可运行实现。实际的方法名、回调模型和初始化参数必须按照所选 SDK 的当前文档填写。不同平台的读卡器和 SDK 不能混用。
后端创建支付的示例
下面是一组不绑定具体支付平台的示意接口。生产环境还需加入用户身份验证、订单锁定、幂等控制和错误处理:
POST /api/payments
Content-Type: application/json
Authorization: Bearer <access-token>
{
"orderId": "ORDER-10001",
"currency": "USD"
}
后端根据订单记录计算金额,再返回客户端 SDK 所需的支付标识:
{
"paymentId": "provider_payment_identifier",
"amount": 2599,
"currency": "USD"
}
创建和确认支付时应使用幂等键,避免网络重试导致重复扣款:
Idempotency-Key: ORDER-10001-card-present
密钥、商户私钥和 Webhook 验证密钥只能保存在后端,不能写入 iOS 应用。
能否直接通过 ExternalAccessory 读取读卡器
如果硬件加入了 Apple 的配件体系,并提供专用协议,应用可能需要在 Info.plist 中声明该协议,然后通过 ExternalAccessory 与设备通信:
<key>UISupportedExternalAccessoryProtocols</key>
<array>
<string>com.vendor.reader.protocol</string>
</array>
协议字符串、通信格式、加密方式和设备授权信息通常只能由硬件厂商提供。ExternalAccessory 只负责配件通信,既不会自动解析银行卡数据,也不会完成支付认证。
对于蓝牙读卡器,也不应在发现设备后自行使用 Core Bluetooth 猜测协议。许多支付终端采用私有协议、安全芯片和设备证书,必须通过厂商 SDK 完成配对和通信。
集成步骤
- 确定业务所在的国家或地区、币种及所需卡种。
- 选择支持线下
card-present支付的平台。 - 从平台支持列表中购买或申请匹配的认证读卡器。
- 开通商户账户,完成实名、结算和风险审核。
- 在后端实现支付创建、状态查询、退款及 Webhook 验证。
- 在 iOS 应用中接入官方 SDK,处理读卡器发现、连接和支付。
- 使用厂商提供的测试环境、测试卡或模拟读卡器验证流程。
- 测试断网、拒付、取消、超时、重复请求、App 进入后台及读卡器断连等情况。
- 上线前确认 PCI DSS、隐私政策、日志脱敏和当地支付法规要求。
需要特别注意的问题
不要记录卡片敏感信息
以下信息不得出现在日志、崩溃报告或分析系统中:
- 完整银行卡号
- CVV/CVC
- 磁道数据
- PIN
- EMV 芯片原始数据
- SDK 返回的敏感支付令牌
显示卡号时,通常只能使用平台返回的脱敏信息,例如卡品牌和末四位。
Apple 内购不适用于一般线下刷卡
如果用户购买的是实体商品或现实世界中的服务,通常可以使用外部支付服务商和读卡器。如果销售的是在应用内消费的数字内容、功能或订阅,则需要另行核对 Apple 当前的审核规则及适用地区政策。接入外接读卡器并不代表可以自动绕过应用内购买要求。
老读卡器未必值得继续集成
早期使用耳机口或旧式 Lightning 接口的磁条读卡器,在现代设备上可能需要转接器,也可能已经失去厂商支持。新项目通常更适合使用支持 EMV 和 NFC 的认证蓝牙读卡器。
App Store 与硬件资质是两回事
应用通过 App Store 审核,不代表支付终端已经获得银行卡组织或支付平台的认证。还需要确认:
- 读卡器是否为平台认证型号;
- 设备固件是否受支持;
- 商户所在地区是否开放;
- App 是否需要声明外接配件协议;
- 厂商 SDK 的许可证及发布限制。
可行的做法是先确定支付平台,再按照该平台的官方 Reader SDK 和认证硬件完成集成,而不是寻找一段可以读取所有外接读卡器的通用示例代码。
备注:内容仅供参考。