| name | proximity-reader |
| description | Comprehensive iOS development skill for Apple's ProximityReader framework. Covers Tap to Pay on iPhone (payment card reading), loyalty card (VAS) integration, Store and Forward mode, the Verifier API (mobile document / ID reading), and merchant discovery UI. Use this skill whenever the user mentions ProximityReader, Tap to Pay on iPhone, contactless payments on iPhone, NFC payment reading, loyalty card reading from Wallet, VAS requests, mobile driver's license verification, ID verification on iPhone, MobileDocumentReader, PaymentCardReader, PaymentCardReaderSession, or any related topic. Also trigger when the user wants to build a point-of-sale (POS) app, accept contactless payments without hardware, or read digital wallet passes on iPhone.
|
ProximityReader iOS Development Skill
Build contactless payment, loyalty, and ID verification features using Apple's ProximityReader framework on iPhone — no additional hardware required.
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
PaymentCardReaderError or MobileDocumentReaderError
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 | PaymentCardReader, PaymentCardReaderSession | 15.4 |
| Loyalty (VAS) | VASRequest, VASReadResult | 15.4 |
| Store & Forward | StoreAndForwardPaymentCardReaderSession, PaymentCardReaderStore | 17.0+ |
| ID Verification | MobileDocumentReader, MobileDocumentReaderSession | 17.0+ |
| Merchant Discovery | ProximityReaderDiscovery | 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
com.apple.developer.proximity-reader.payment.acceptance entitlement to your app's entitlements file.
For the Verifier API (ID reading), a separate entitlement and server-side reader token generation is required. Read references/verifier-api.md for details.
Architecture at a Glance
┌─────────────────────────────────────────────────┐
│ Your App │
├──────────┬──────────┬───────────┬───────────────┤
│ Payment │ Loyalty │ Store & │ ID │
│ Flow │ (VAS) │ Forward │ Verification │
├──────────┴──────────┴───────────┴───────────────┤
│ ProximityReader Framework │
├─────────────────────────────────────────────────┤
│ Secure Element / NFC Hardware │
└─────────────────────────────────────────────────┘
Quick Reference: Common Patterns
1. Payment Card Reading (Tap to Pay)
import ProximityReader
let reader = PaymentCardReader()
let token = PaymentCardReader.Token(rawValue: pspProvidedJWT)
if try await !reader.isAccountLinked(using: token) {
try await reader.linkAccount(using: token)
}
let session = try await PaymentCardReaderSession(reader: reader, token: token)
try await session.prepare()
let request = PaymentCardTransactionRequest(
amount: Decimal(29.99),
currencyCode: "USD",
type: .purchase
)
let result: PaymentCardReadResult = try await session.readPaymentCard(request)
2. Loyalty Card (VAS) Reading
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)
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 {
try await store.delete(using: batch.deletionToken)
}
4. Mobile Document / ID Reading (Verifier API)
Read references/verifier-api.md for the full setup including server-side reader token generation.
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
let discovery = ProximityReaderDiscovery()
try await discovery.present()
Error Handling
Always wrap ProximityReader calls in do-catch. The two main error types are:
do {
try await session.readPaymentCard(request)
} catch let error as PaymentCardReaderError {
switch error {
case .notAllowed:
case .unsupported:
case .networkError:
case .invalidReaderToken:
case .readerBusy:
case .backgrounded:
default:
break
}
} catch let error as PaymentCardReaderSession.ReadError {
switch error {
case .cancelled:
case .invalidAmount:
case .notReady:
default:
break
}
}
For the Verifier API:
catch let error as MobileDocumentReaderError {
}
Critical Implementation Notes
- Always call
prepare() after the app returns to the foreground. The reader session is invalidated when backgrounded. Safe to call multiple times.
- Call
prepare() after each transaction to reset internal state for the next transaction.
- 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
ProximityReaderStub for simulator testing. Real NFC reads require a physical device.
- 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.
Deeper Reference Docs
For more detailed implementation guides, read these reference files:
| Reference | When to Read |
|---|
references/api-reference.md | Full class/struct/enum listing with all properties and methods |
references/verifier-api.md | Complete Verifier API setup including server-side token generation |
references/integration-patterns.md | PSP integration patterns (Stripe, Adyen, Square), SwiftUI patterns, MVVM architecture |
references/troubleshooting.md | Common errors, debugging tips, device compatibility |
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
prepare() calls after foregrounding and after each transaction
- Never hardcode PSP tokens — always fetch from server/PSP SDK
- For SwiftUI: use
.task {} modifier or @MainActor view models
- Always check device capability before attempting to use the reader
- Follow Apple HIG: the tap payment sheet is system-provided — don't recreate it