proximity-reader
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseProximityReader iOS Development Skill
ProximityReader iOS开发技能
Build contactless payment, loyalty, and ID verification features using Apple's ProximityReader framework on iPhone — no additional hardware required.
在iPhone上使用Apple的ProximityReader框架构建非接触式支付、会员服务和身份验证功能——无需额外硬件。
When to Use This Skill
何时使用此技能
- Integrating Tap to Pay on iPhone (contactless payment acceptance)
- Reading loyalty cards / VAS passes from Apple Wallet
- Implementing Store and Forward for offline payment scenarios
- Building ID verification with the Verifier API (mobile driver's licenses, national IDs)
- Showing merchant education UI via
ProximityReaderDiscovery - Debugging or
PaymentCardReaderErrorMobileDocumentReaderError
- 集成iPhone上的Tap to Pay(接受非接触式支付)
- 读取Apple Wallet中的会员卡/VAS凭证
- 为离线支付场景实现Store and Forward(存储转发)功能
- 使用Verifier API构建身份验证(移动驾照、国民身份证)
- 通过展示商家引导UI
ProximityReaderDiscovery - 调试或
PaymentCardReaderErrorMobileDocumentReaderError
Framework Overview
框架概述
ProximityReader (iOS 15.4+, iPadOS 15.4+, Mac Catalyst 15.4+) enables an iPhone to act as a contactless reader for:
| Domain | Key Classes | Min iOS |
|---|---|---|
| Payments | | 15.4 |
| Loyalty (VAS) | | 15.4 |
| Store & Forward | | 17.0+ |
| ID Verification | | 17.0+ |
| Merchant Discovery | | 18.0+ |
ProximityReader(iOS 15.4+、iPadOS 15.4+、Mac Catalyst 15.4+)可让iPhone充当非接触式读取器,支持以下场景:
| 领域 | 核心类 | 最低iOS版本 |
|---|---|---|
| 支付 | | 15.4 |
| 会员服务(VAS) | | 15.4 |
| 存储转发 | | 17.0+ |
| 身份验证 | | 17.0+ |
| 商家发现 | | 18.0+ |
Prerequisites & Entitlements
前提条件与权限
Before writing any code, ensure:
- Entitlement: Request the Tap to Pay on iPhone entitlement from Apple via your developer account. Without it, the framework won't function.
- Payment Service Provider (PSP): You must coordinate with a Level 3 certified PSP (e.g. Stripe, Adyen, Square, Windcave). The PSP provides the reader token (JWT) required to initialize the reader.
- Device: iPhone XS or later. No additional NFC hardware needed.
- Xcode: Add the entitlement to your app's entitlements file.
com.apple.developer.proximity-reader.payment.acceptance
For the Verifier API (ID reading), a separate entitlement and server-side reader token generation is required. Read for details.
references/verifier-api.md编写代码前,请确保满足以下要求:
- 权限:通过开发者账户向Apple申请iPhone上的Tap to Pay权限。没有此权限,框架将无法正常工作。
- 支付服务提供商(PSP):您必须与Level 3认证的PSP(如Stripe、Adyen、Square、Windcave)合作。PSP提供初始化读取器所需的reader token(JWT)。
- 设备:iPhone XS或更高版本。无需额外NFC硬件。
- Xcode:在应用的权限文件中添加权限。
com.apple.developer.proximity-reader.payment.acceptance
对于Verifier API(身份证读取),需要单独的权限和服务器端reader token生成。详情请阅读。
references/verifier-api.mdArchitecture at a Glance
架构概览
┌─────────────────────────────────────────────────┐
│ Your App │
├──────────┬──────────┬───────────┬───────────────┤
│ Payment │ Loyalty │ Store & │ ID │
│ Flow │ (VAS) │ Forward │ Verification │
├──────────┴──────────┴───────────┴───────────────┤
│ ProximityReader Framework │
├─────────────────────────────────────────────────┤
│ Secure Element / NFC Hardware │
└─────────────────────────────────────────────────┘┌─────────────────────────────────────────────────┐
│ 您的应用 │
├──────────┬──────────┬───────────┬───────────────┤
│ 支付流程 │ 会员服务 │ 存储转发 │ 身份验证 │
│ │ (VAS) │ │ │
├──────────┴──────────┴───────────┴───────────────┤
│ ProximityReader 框架 │
├─────────────────────────────────────────────────┤
│ 安全元件 / NFC 硬件 │
└─────────────────────────────────────────────────┘Quick Reference: Common Patterns
快速参考:常见模式
1. Payment Card Reading (Tap to Pay)
1. 支付卡读取(Tap to Pay)
swift
import ProximityReader
// 1. Create and configure the reader
let reader = PaymentCardReader()
// 2. Obtain token from your PSP
let token = PaymentCardReader.Token(rawValue: pspProvidedJWT)
// 3. Link the merchant account (first use only)
if try await !reader.isAccountLinked(using: token) {
try await reader.linkAccount(using: token)
}
// 4. Create a session and prepare
let session = try await PaymentCardReaderSession(reader: reader, token: token)
try await session.prepare()
// 5. Read a payment card
let request = PaymentCardTransactionRequest(
amount: Decimal(29.99),
currencyCode: "USD",
type: .purchase
)
let result: PaymentCardReadResult = try await session.readPaymentCard(request)
// Send result.paymentCardData to your PSP for processingswift
import ProximityReader
// 1. 创建并配置读取器
let reader = PaymentCardReader()
// 2. 从PSP获取token
let token = PaymentCardReader.Token(rawValue: pspProvidedJWT)
// 3. 关联商家账户(仅首次使用)
if try await !reader.isAccountLinked(using: token) {
try await reader.linkAccount(using: token)
}
// 4. 创建会话并准备
let session = try await PaymentCardReaderSession(reader: reader, token: token)
try await session.prepare()
// 5. 读取支付卡
let request = PaymentCardTransactionRequest(
amount: Decimal(29.99),
currencyCode: "USD",
type: .purchase
)
let result: PaymentCardReadResult = try await session.readPaymentCard(request)
// 将result.paymentCardData发送至PSP进行处理2. Loyalty Card (VAS) Reading
2. 会员卡(VAS)读取
swift
// Can be combined with payment or standalone
let vasRequest = VASRequest(
merchantIdentifier: "pass.com.example.loyalty",
localizedDescription: "Example Loyalty Program"
)
// Read loyalty card alongside payment
let result = try await session.readPaymentCard(
request,
vasRequest: vasRequest
)
// Access loyalty data
if let vasResult = result.vasReadResult {
// Process loyalty identifiers
}swift
// 可与支付结合或独立使用
let vasRequest = VASRequest(
merchantIdentifier: "pass.com.example.loyalty",
localizedDescription: "Example Loyalty Program"
)
// 读取支付卡的同时读取会员卡
let result = try await session.readPaymentCard(
request,
vasRequest: vasRequest
)
// 访问会员卡数据
if let vasResult = result.vasReadResult {
// 处理会员标识符
}3. Store and Forward (Offline)
3. 存储转发(离线)
swift
let sfSession = try await StoreAndForwardPaymentCardReaderSession(
reader: reader,
token: token
)
try await sfSession.prepare()
// Transactions are stored locally
let result = try await sfSession.readPaymentCard(request)
// Later, when online: retrieve and send batches
let store = PaymentCardReaderStore()
let batches: [StoreAndForwardBatch] = try await store.allBatches()
for batch in batches {
// Send batch.data to PSP
// On success, delete the batch
try await store.delete(using: batch.deletionToken)
}swift
let sfSession = try await StoreAndForwardPaymentCardReaderSession(
reader: reader,
token: token
)
try await sfSession.prepare()
// 交易将存储在本地
let result = try await sfSession.readPaymentCard(request)
// 后续联网时:检索并发送批量数据
let store = PaymentCardReaderStore()
let batches: [StoreAndForwardBatch] = try await store.allBatches()
for batch in batches {
// 将batch.data发送至PSP
// 成功后,删除该批次
try await store.delete(using: batch.deletionToken)
}4. Mobile Document / ID Reading (Verifier API)
4. 移动文档/身份证读取(Verifier API)
Read for the full setup including server-side reader token generation.
references/verifier-api.mdswift
import ProximityReader
let docReader = MobileDocumentReader()
let readerToken = try await fetchReaderToken() // From your server
let session = try await MobileDocumentReaderSession(
reader: docReader,
readerToken: readerToken
)
try await session.prepare()
// Request a driver's license display
let displayRequest = MobileDriversLicenseDisplayRequest(
retainedElements: [.givenName, .familyName, .portrait, .dateOfBirth, .ageOver21]
)
try await session.readDocument(displayRequest)
// Or request validated data
let dataRequest = MobileDriversLicenseDataRequest(
retainedElements: [.givenName, .familyName, .dateOfBirth]
)
let documentResult = try await session.readDocument(dataRequest)完整设置(包括服务器端reader token生成)请阅读。
references/verifier-api.mdswift
import ProximityReader
let docReader = MobileDocumentReader()
let readerToken = try await fetchReaderToken() // 从您的服务器获取
let session = try await MobileDocumentReaderSession(
reader: docReader,
readerToken: readerToken
)
try await session.prepare()
// 请求显示驾照信息
let displayRequest = MobileDriversLicenseDisplayRequest(
retainedElements: [.givenName, .familyName, .portrait, .dateOfBirth, .ageOver21]
)
try await session.readDocument(displayRequest)
// 或请求验证后的数据
let dataRequest = MobileDriversLicenseDataRequest(
retainedElements: [.givenName, .familyName, .dateOfBirth]
)
let documentResult = try await session.readDocument(dataRequest)5. Merchant Discovery UI
5. 商家发现UI
swift
// iOS 18+: Show Apple-provided merchant education
let discovery = ProximityReaderDiscovery()
try await discovery.present()swift
// iOS 18+:展示Apple提供的商家引导界面
let discovery = ProximityReaderDiscovery()
try await discovery.present()Error Handling
错误处理
Always wrap ProximityReader calls in do-catch. The two main error types are:
swift
do {
try await session.readPaymentCard(request)
} catch let error as PaymentCardReaderError {
switch error {
case .notAllowed:
// Missing entitlement or not authorized
case .unsupported:
// Device doesn't support Tap to Pay
case .networkError:
// Connectivity issue
case .invalidReaderToken:
// Token from PSP is invalid or expired
case .readerBusy:
// Another read session is active
case .backgrounded:
// App moved to background during read — must call prepare() again
default:
break
}
} catch let error as PaymentCardReaderSession.ReadError {
switch error {
case .cancelled:
// Customer cancelled the tap
case .invalidAmount:
// Amount was negative or zero
case .notReady:
// prepare() was not called
default:
break
}
}For the Verifier API:
swift
catch let error as MobileDocumentReaderError {
// Handle session preparation and document request errors
}始终将ProximityReader调用包裹在do-catch块中。主要有两种错误类型:
swift
do {
try await session.readPaymentCard(request)
} catch let error as PaymentCardReaderError {
switch error {
case .notAllowed:
// 缺少权限或未授权
case .unsupported:
// 设备不支持Tap to Pay
case .networkError:
// 连接问题
case .invalidReaderToken:
// PSP提供的token无效或已过期
case .readerBusy:
// 另一个读取会话正在进行中
case .backgrounded:
// 读取过程中应用进入后台——必须再次调用prepare()
default:
break
}
} catch let error as PaymentCardReaderSession.ReadError {
switch error {
case .cancelled:
// 客户取消了刷卡操作
case .invalidAmount:
// 金额为负数或零
case .notReady:
// 未调用prepare()
default:
break
}
}对于Verifier API:
swift
catch let error as MobileDocumentReaderError {
// 处理会话准备和文档请求错误
}Critical Implementation Notes
关键实现注意事项
- Always call after the app returns to the foreground. The reader session is invalidated when backgrounded. Safe to call multiple times.
prepare() - Call after each transaction to reset internal state for the next transaction.
prepare() - The reader token (JWT) comes from your PSP, not from Apple directly. Each PSP has their own token generation flow.
- PIN entry is supported on iOS 16.4+ for contactless cards that require it.
- Testing: Use for simulator testing. Real NFC reads require a physical device.
ProximityReaderStub - Thread safety: ProximityReader uses Swift concurrency (async/await). All calls should be made from the main actor or appropriate actor context.
- Store and Forward: Batches persist on device. Always delete after successful processing to avoid data accumulation.
- 应用回到前台后务必调用。读取器会话在后台时会失效,多次调用是安全的。
prepare() - 每笔交易后调用,以重置内部状态为下一笔交易做准备。
prepare() - reader token(JWT)来自您的PSP,并非直接来自Apple。每个PSP都有自己的token生成流程。
- PIN输入在iOS 16.4+上支持,适用于需要PIN的非接触式卡。
- 测试:使用进行模拟器测试。真实NFC读取需要物理设备。
ProximityReaderStub - 线程安全:ProximityReader使用Swift并发(async/await)。所有调用应在主actor或合适的actor上下文中进行。
- 存储转发:批次数据会持久化在设备上。处理成功后务必删除,避免数据堆积。
Deeper Reference Docs
深入参考文档
For more detailed implementation guides, read these reference files:
| Reference | When to Read |
|---|---|
| Full class/struct/enum listing with all properties and methods |
| Complete Verifier API setup including server-side token generation |
| PSP integration patterns (Stripe, Adyen, Square), SwiftUI patterns, MVVM architecture |
| Common errors, debugging tips, device compatibility |
如需更详细的实现指南,请阅读以下参考文件:
| 参考文档 | 阅读时机 |
|---|---|
| 完整的类/结构体/枚举列表,包含所有属性和方法 |
| Verifier API的完整设置,包括服务器端token生成 |
| PSP集成模式(Stripe、Adyen、Square)、SwiftUI模式、MVVM架构 |
| 常见错误、调试技巧、设备兼容性 |
Code Generation Guidelines
代码生成指南
When generating ProximityReader code:
- Always
import ProximityReader - Use Swift concurrency (async/await) — the entire API is async
- Wrap all reader operations in do-catch with specific error handling
- Include calls after foregrounding and after each transaction
prepare() - Never hardcode PSP tokens — always fetch from server/PSP SDK
- For SwiftUI: use modifier or
.task {}view models@MainActor - Always check device capability before attempting to use the reader
- Follow Apple HIG: the tap payment sheet is system-provided — don't recreate it
生成ProximityReader代码时:
- 务必
import ProximityReader - 使用Swift并发(async/await)——整个API都是异步的
- 将所有读取器操作包裹在do-catch块中,并处理特定错误
- 包含应用回到前台后和每笔交易后的调用
prepare() - 切勿硬编码PSP token——始终从服务器/PSP SDK获取
- 对于SwiftUI:使用修饰符或
.task {}视图模型@MainActor - 在尝试使用读取器前,务必检查设备能力
- 遵循Apple HIG:刷卡支付界面由系统提供——请勿自行重建