-
Notifications
You must be signed in to change notification settings - Fork 3
ProximityReader
Package
SwiftBindings.Apple.ProximityReader· Version26.2.6Auto-published fromapple-frameworks/ProximityReader/PROXIMITYREADER-GUIDE.md.
SwiftBindings.Apple.ProximityReader exposes Apple's ProximityReader framework — Tap to Pay on iPhone, contactless payment-card reads, VAS (Wallet pass / loyalty) reads, and mobile-document (e.g. mobile driver's license) reads — to C# through .NET 10's native Swift interop. These are direct Swift calls, not Objective-C proxy wrappers.
This is an orientation guide: it covers the few entry points you build a flow around and is honest about what requires Apple entitlements and a live device session. Every type, method, and enum case below is verbatim from the generated C#.
- Requirements & install
- Naming conventions
- Quick start
- The reader lifecycle
- Reading a payment card
- Read requests
- Read results
- Mobile documents
- Errors
- Memory & threading
- Reference links
- .NET 10.0+
- iOS 26.2+, Mac Catalyst 26.2+
- macOS host for development
- iPhone XS or later (Tap to Pay is device-only — not available in the simulator)
- Tap to Pay on iPhone entitlement (Apple-issued), merchant onboarding, and account linking before any live read will succeed
dotnet add package SwiftBindings.Apple.ProximityReader
using ProximityReader;ProximityReader is permission- and session-heavy. You can construct request objects and check
PaymentCardReader.IsSupportedfreely, butPrepare/Readcalls require the entitlement, a merchant account, and a physical device. The co-located test app only validates type metadata and plain enum round-trips for exactly this reason.
| Swift | C# | Rule |
|---|---|---|
func prepare(...) async throws |
PrepareAsync(...) |
async methods gain an Async suffix and return Task/Task<T>
|
trailing completion: / event closures |
Action<…>? handler overloads |
a closure parameter becomes an optional Action<>; a parameterless overload is also emitted |
static var isSupported |
PaymentCardReader.IsSupported |
static Swift members become static C# properties |
nested enum Event / struct Options
|
PaymentCardReader.Event, PaymentCardReader.OptionsType
|
nested types that collide with a member get a Type suffix |
enum CardEffectiveState |
PaymentCardReadResult.CardEffectiveStateType (returned as nullable) |
optional enum results project to …Type?
|
Every async method also accepts a trailing CancellationToken (defaulted).
Constructing requests and checking support works anywhere. The actual read requires a live, entitled session.
using ProximityReader;
using Foundation;
// 0. Capability check (safe on any device)
if (!PaymentCardReader.IsSupported)
return;
// 1. Build the reader and a transaction request for $1.99 USD
using var reader = new PaymentCardReader();
using var request = new PaymentCardTransactionRequest(
NSDecimalNumber.FromString("1.99"),
currencyCode: "USD",
type: PaymentCardTransactionRequest.TransactionType.Purchase);
// 2. Prepare a session with your reader token, then read.
// (Requires the Tap to Pay entitlement + a linked merchant account.)
var token = new PaymentCardReader.Token("<reader-token-from-your-backend>");
using PaymentCardReaderSession session = await reader.PrepareAsync(token);
using PaymentCardReadResult result = await session.ReadPaymentCardAsync(request);
if (result.Outcome == PaymentCardReadResult.ReadOutcome.Success)
{
string? encrypted = result.PaymentCardData; // signed payment data for your PSP
}PaymentCardReader is the device-level object; PaymentCardReaderSession is the prepared, read-capable session.
public partial class PaymentCardReader
{
public static bool IsSupported { get; }
public string Id { get; }
public IAsyncEnumerable<PaymentCardReader.Event> Events { get; }
public PaymentCardReader.OptionsType Options { get; }
public PaymentCardReader();
public PaymentCardReader(PaymentCardReader.OptionsType options);
public virtual Task<string> GetReaderIdentifierAsync(CancellationToken ct = default);
public virtual Task<bool> IsAccountLinkedAsync(PaymentCardReader.Token token, CancellationToken ct = default);
public virtual Task LinkAccountAsync(PaymentCardReader.Token token, CancellationToken ct = default);
public virtual Task RelinkAccountAsync(PaymentCardReader.Token token, CancellationToken ct = default);
public virtual Task<PaymentCardReaderSession> PrepareAsync(PaymentCardReader.Token token, CancellationToken ct = default);
public virtual Task<PaymentCardReaderSession> PrepareAsync(PaymentCardReader.Token token, Action<PaymentCardReader.UpdateEvent>? updateHandler, CancellationToken ct = default);
public virtual Task<StoreAndForwardPaymentCardReaderSession> PrepareStoreAndForwardAsync(CancellationToken ct = default);
public virtual PaymentCardReaderStore FetchPaymentCardReaderStore();
}Reader tokens come from your backend (issued via Apple's onboarding). Wrap one with new PaymentCardReader.Token(string rawValue). Watch live reader state through the Events async sequence.
PaymentCardReaderSession carries the actual read operations:
public partial class PaymentCardReaderSession
{
public string Id { get; }
public DateTimeOffset? CurrentOSVersionDeprecationDate { get; }
public virtual Task<bool> CancelReadAsync(CancellationToken ct = default);
// Payment / verification reads (each has a plain and an event-handler overload)
public virtual Task<PaymentCardReadResult> ReadPaymentCardAsync(PaymentCardTransactionRequest request, CancellationToken ct = default);
public virtual Task<PaymentCardReadResult> ReadPaymentCardAsync(PaymentCardTransactionRequest request, Action<PaymentCardReaderSession.Event>? eventHandler = null, CancellationToken ct = default);
public virtual Task<PaymentCardReadResult> ReadPaymentCardAsync(PaymentCardVerificationRequest request, CancellationToken ct = default);
public virtual Task<PaymentCardReadResult> ReadPaymentCardAsync(PaymentCardVerificationRequest request, Action<PaymentCardReaderSession.Event>? eventHandler = null, CancellationToken ct = default);
// VAS (Wallet pass / loyalty)
public virtual Task<VASReadResult> ReadVASAsync(VASRequest request, CancellationToken ct = default);
public virtual Task<VASReadResult> ReadVASAsync(VASRequest request, Action<PaymentCardReaderSession.Event>? eventHandler = null, CancellationToken ct = default);
// Combined payment + VAS in one tap
public virtual Task<(PaymentCardReadResult?, VASReadResult?)> ReadPaymentCardAsync(
PaymentCardTransactionRequest request, VASRequest vasRequest, bool stopOnVASResult, CancellationToken ct = default);
// PIN capture
public virtual Task<PaymentCardReadResult> CapturePINAsync(PaymentCardReaderSession.PINToken token, string cardReaderTransactionID, CancellationToken ct = default);
}The event-handler overloads stream PaymentCardReaderSession.Event values (a plain enum) so you can drive UI ("hold card near reader", "remove card", etc.).
All three request types are constructible up front, with no session:
// Purchase / refund
using var purchase = new PaymentCardTransactionRequest(
NSDecimalNumber.FromString("9.99"), currencyCode: "USD",
type: PaymentCardTransactionRequest.TransactionType.Purchase); // or .Refund
// Card verification (no charge) — e.g. look-up, save-card, open-tab
using var verify = new PaymentCardVerificationRequest(
currencyCode: "USD",
reason: PaymentCardVerificationRequest.Reason.SaveCard); // LookUp / SaveCard / OpenTab / Other
// VAS (Apple Wallet pass / loyalty)
using var merchant = new VASRequest.Merchant("merchant.com.yourapp", localizedName: "My Store");
using var vas = new VASRequest(new[] { merchant }, localizedVASType: "loyalty");PaymentCardTransactionRequest.PaymentCycle (Weekly / Monthly / Yearly) is available for installment-style descriptions via TransactionAmountDescription.
public partial class PaymentCardReadResult
{
public string Id { get; }
public PaymentCardReadResult.ReadOutcome Outcome { get; } // Success / CardDeclined / Failure
public string? PaymentCardData { get; } // signed payment data for your PSP
public string? GeneralCardData { get; }
public string? ApplicationTypeIdentifier { get; }
public bool PinBypassed { get; }
public bool IsPINFallback { get; }
public PaymentCardReadResult.CardEffectiveStateType? CardEffectiveState { get; } // Active/Inactive/Invalid/Unknown
public PaymentCardReadResult.CardExpirationStateType? CardExpirationState { get; } // NotExpired/Expired/Invalid/Unknown
}VASReadResult carries ReadEntry items (each with a StatusType). The store-and-forward flow (StoreAndForwardPaymentCardReaderSession, StoreAndForwardBatch, StoreAndForwardStatus, StoreAndForwardBatchDeletionToken, and the PaymentCardReaderStore batch APIs — FetchStoredPaymentCardReadResultCountAsync, FetchStoredPaymentCardReadResultBatchAsync, ResolveBatchAsync, ResetBatchStateAsync) lets you queue reads offline and resolve them later.
ProximityReader also reads mobile identity documents (mobile driver's license, national ID, photo ID). The entry point is MobileDocumentReader:
using var docReader = new MobileDocumentReader();
MobileDocumentReader.ConfigurationType config = await docReader.GetConfigurationAsync();
using MobileDocumentReaderSession docSession = await docReader.PrepareAsync(/* token */ null);The data-request types (MobileDriversLicenseDataRequest, MobileNationalIDCardDataRequest, MobilePhotoIDDataRequest, and their …RawDataRequest / …DisplayRequest variants) and their nested Element / Response types model exactly which document fields you request and receive. MobileDocumentAnyOfDataRequest lets you accept any of several document types. This is a large, structured surface — consult Apple's docs for the field-by-field semantics.
Known limitation —
MobileDocumentReaderSession.requestDocument: This method takes an app-defined conformer of theMobileDocumentRequestprotocol (a protocol with associated types). The binding generator cannot specialize the call site for open-ended conformer sets, sorequestDocumentis not bound and has no C# equivalent. This is a won't-fix under the current source-generation model; a future source-generator-based approach would be required to support arbitrary app-defined conformers. All otherMobileDocumentReaderandMobileDocumentReaderSessionAPIs are fully bound.
Throwing reader calls surface Swift errors as Swift.Runtime.SwiftException<PaymentCardReaderError>. Discriminate via the error's Tag (PaymentCardReaderError.CaseTag):
using Swift.Runtime;
try
{
using var session = await reader.PrepareAsync(token);
}
catch (SwiftException<PaymentCardReaderError> ex)
{
// ex.Error.Tag — e.g. InvalidReaderToken, PrepareFailed, DeviceBanned,
// NotAllowed, Unsupported, OsVersionNotSupported, ModelNotSupported,
// PasscodeDisabled, NetworkError, AccountNotLinked, AccountLinkingFailed,
// MerchantBlocked, TokenExpired, PrepareExpired, ReaderBusy, …
Console.WriteLine($"reader error: {ex.Message}");
}PaymentCardReaderError also exposes static singletons for common cases (e.g. PaymentCardReaderError.PrepareExpired).
Mobile-document errors use a plain enum, MobileDocumentReaderError (Unknown=0, NotAllowed, NotSupported, Cancelled, SessionExpired, NetworkUnavailable, ServiceUnavailable, SystemBusy, InvalidToken, InvalidRequest, InvalidResponse). Its GetErrorDescription() extension returns the localized message (e.g. MobileDocumentReaderError.Unknown.GetErrorDescription()).
Generated types implement ISwiftObject / IDisposable. Most request/result types wrap a Swift struct; reader and session types are class wrappers. using var is the recommended pattern for deterministic cleanup — Dispose is safe on every generated type and double-Dispose is a no-op.
using var reader = new PaymentCardReader();
using var request = new PaymentCardTransactionRequest(amount, "USD",
PaymentCardTransactionRequest.TransactionType.Purchase);-
Async sequences are cold. Enumerating
PaymentCardReader.Eventsstarts the underlying SwiftAsyncSequence; run it on a background task keyed to the session lifetime. - Continuations resume on a thread-pool thread — marshal back to your UI thread before touching UI.
- Apple — ProximityReader — upstream documentation
- Apple — Tap to Pay on iPhone — entitlement request, merchant attestation, onboarding
-
ActivityKit —
SwiftBindings.Apple.ActivityKitv26.2.6 -
CryptoKit —
SwiftBindings.Apple.CryptoKitv26.2.6 -
FamilyControls —
SwiftBindings.Apple.FamilyControlsv26.2.6 -
LiveCommunicationKit —
SwiftBindings.Apple.LiveCommunicationKitv26.2.6 -
Matter —
SwiftBindings.Apple.Matterv26.2.6 -
MatterSupport —
SwiftBindings.Apple.MatterSupportv26.2.6 -
MusicKit —
SwiftBindings.Apple.MusicKitv26.2.6 -
ProximityReader —
SwiftBindings.Apple.ProximityReaderv26.2.6 -
RealityFoundation —
SwiftBindings.Apple.RealityFoundationv26.2.6 -
RealityKit —
SwiftBindings.Apple.RealityKitv26.2.6 -
RoomPlan —
SwiftBindings.Apple.RoomPlanv26.2.6 -
StoreKit2 —
SwiftBindings.Apple.StoreKit2v26.2.6 -
TipKit —
SwiftBindings.Apple.TipKitv26.2.6 -
Translation —
SwiftBindings.Apple.Translationv26.2.6 -
WeatherKit —
SwiftBindings.Apple.WeatherKitv26.2.6 -
WorkoutKit —
SwiftBindings.Apple.WorkoutKitv26.2.6