proximity-reader

📁 ios-agent/iosagent.dev 📅 2 days ago
1
总安装量
1
周安装量
#43271
全站排名
安装命令
npx skills add https://github.com/ios-agent/iosagent.dev --skill proximity-reader

Agent 安装分布

amp 1
opencode 1
kimi-cli 1
codex 1
github-copilot 1
gemini-cli 1

Skill 文档

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:

  1. Entitlement: Request the Tap to Pay on iPhone entitlement from Apple via your developer account. Without it, the framework won’t function.
  2. 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.
  3. Device: iPhone XS or later. No additional NFC hardware needed.
  4. 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

// 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 processing

2. Loyalty Card (VAS) Reading

// 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
}

3. Store and Forward (Offline)

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)
}

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() // 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)

5. Merchant Discovery UI

// iOS 18+: Show Apple-provided merchant education
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:
        // 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:

catch let error as MobileDocumentReaderError {
    // Handle session preparation and document request errors
}

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:

  1. Always import ProximityReader
  2. Use Swift concurrency (async/await) — the entire API is async
  3. Wrap all reader operations in do-catch with specific error handling
  4. Include prepare() calls after foregrounding and after each transaction
  5. Never hardcode PSP tokens — always fetch from server/PSP SDK
  6. For SwiftUI: use .task {} modifier or @MainActor view models
  7. Always check device capability before attempting to use the reader
  8. Follow Apple HIG: the tap payment sheet is system-provided — don’t recreate it