Migration guide to iOS SDK version 0.6.0

Migration guide to upgrade the Ranforest iOS version from 0.2.0 to 0.6.0

This guide covers everything needed to upgrade RainforestSDK from 0.2.0 to 0.6.0. Read the Breaking Changes section fully before touching call sites — some old code will compile with only warnings but throw at runtime (see 2.1 below).

TL;DR

  • Minimum deployment target is now iOS 17.4 for the standard integration. A Compat build exists for apps that still need to embed the SDK on older iOS.
  • Every TapToPhoneProtocol method dropped its deviceRegistrationId parameter — it now lives on the SDK instance, set once at creation.
  • Every RainforestPay.TapToPhone(...) factory is now async throws. The old 2-arg factory (no deviceRegistrationId) still compiles but is deprecated and will throw at runtime if you call setup()/prepare()/etc. on the instance it returns.
  • There's a new "Auto mode" (RainforestPay.TapToPhoneAuto) that manages prepare/resume for you — optional.
  • RainforestErrorCode, RainforestError, and RainforestEvent all gained new cases. If you have exhaustive switch statements over any of them, add cases or a default/@unknown default.
  • presentPayin() (0.6.0) no longer throws the underlying provider's raw PaymentCardReaderSession.ReadError for reader errors it doesn't explicitly recognize — every such error is now wrapped as RainforestError.error(.cardReader, ...). If you catch or pattern-match on the concrete provider error type at a presentPayin() call site, update it (see 2.4 below).

1. Requirements changes

  • iOS 17.4+ — RainforestPay, TapEvent, TapToPhoneProtocol, and TapToPhoneCoreProtocol are now marked @available(iOS 17.4, *). If your app's deployment target is lower, guard your call sites with if #available(iOS 17.4, *) or move to the Compat build.
  • New Compat build — if you need to embed the SDK in an app that runs on iOS <17.4, use the RainforestSDK-Compat xcframework instead of the standard one. This changes how you embed/link the SDK's two dependency frameworks (RainforestTapToPhone, CloudCommerce) — full steps in docs/COMPAT_BUILD.md. Skip this if your app already targets 17.4+.

2. Breaking changes

2.1 TapToPhoneProtocol — deviceRegistrationId moved from per-call param to instance creation

At 0.2.0, every method took deviceRegistrationId explicitly:

// 0.2.0
let rfTTP = RainforestPay.TapToPhone(appCfg.environment, sessionKey: sessionKey)

let setupResult = try await rfTTP.setup(deviceRegistrationId: deviceRegistrationId)
try await rfTTP.prepare(deviceRegistrationId: deviceRegistrationId)
let resp = try await rfTTP.presentPayin(deviceRegistrationId: deviceRegistrationId, payinConfigId: payinConfigId)

At 0.5.5, deviceRegistrationId is supplied once, when you create the instance, and every method call drops it:

// 0.5.5
// 🚨 device registration ID must be passed to the factory function
// 2 arg factory is no longer valid
let rfTTP = try await RainforestPay.TapToPhone(
    appCfg.environment,
    sessionKey: sessionKey,
    deviceRegistrationId: deviceRegistrationId
)

let setupResult = try await rfTTP.setup()
try await rfTTP.prepare()
let resp = try await rfTTP.presentPayin(payinConfigId: payinConfigId)

The events property was also renamed to a method, eventStream():

// 0.2.0
for await sdkEvent in rfTTP.events { ... }

// 0.5.5
for await sdkEvent in rfTTP.eventStream() { ... }

2.2 The factory itself is now async throws

// 0.2.0 — synchronous, non-throwing
let rfTTP = RainforestPay.TapToPhone(appCfg.environment, sessionKey: sessionKey)
let rfTTP = try await RainforestPay.TapToPhone(
    appCfg.environment,
    sessionKey: sessionKey,
    deviceRegistrationId: deviceRegistrationId
)

Update any code that treated the result as a raw token string to read .accessToken instead.

2.3 New cases on public enums — check exhaustive switch statements

None of the existing cases were renamed or removed, but all three of these gained new cases. If you switch over them without a default/@unknown default, your build will break:

EnumNew cases in 0.5.5
RainforestErrorCode.initialize, .invalidSessionKey, .usage
RainforestError.prepareInFlight, .resumeInFlight, .presentPayinInFlight
RainforestEvent.configured, .invalidSessionKey, .prepareSucceeded, .prepareFailed(String)

Note: RainforestError.errorDescription (via LocalizedError) replaces the old hand-written localizedDescription override, but error.localizedDescription still returns the same string content as before — no change needed there, it's a compatible refactor.

2.4 presentPayin() — unrecognized reader errors now wrap as RainforestError (0.6.0)

At 0.5.5, if the card reader returned an error presentPayin() didn't explicitly handle (including a call-in-progress condition), the SDK logged it and then threw the underlying provider's error type directly:

// 0.5.5
do {
    try await rfTTP.presentPayin(payinConfigId: payinConfigId)
} catch let err as PaymentCardReaderSession.ReadError {
    // had to catch the underlying provider type to see these
} catch let err as RainforestError {
    // ...
}

At 0.6.0, every such error is wrapped in RainforestError.error(.cardReader, ...) before being thrown, so presentPayin() never surfaces the raw provider error type anymore:

// 0.6.0
do {
    try await rfTTP.presentPayin(payinConfigId: payinConfigId)
} catch let err as RainforestError {
    // now the only case you need — covers what used to require catching
    // PaymentCardReaderSession.ReadError directly, including the new
    // "Cannot process payments while on a call" message
}

If you had a catch clause on presentPayin() typed to the provider's PaymentCardReaderSession.ReadError (or any other underlying reader-error type), it will stop matching — remove it and handle the case via RainforestError instead.

3. Deprecated (still compiles, migrate anyway)

If you've completed section 2 migrations, then is section is redundant. The 2-arg factory function is no longer supported.

  • RainforestPay.TapToPhone(_:sessionKey:) (2-arg) → use RainforestPay.TapToPhone(_:sessionKey:deviceRegistrationId:). As covered in 2.2, this isn't a "safe to leave" deprecation — it's functionally broken at runtime.

4. New capabilities (additive, opt-in)

  • Auto mode — RainforestPay.TapToPhoneAuto(environment:sessionKey:deviceRegistrationId:) async throws -> TapToPhoneCoreProtocol. The SDK manages the prepare/resume lifecycle for you (background prepare loop, auto-resume on foreground); you just call presentPayin(payinConfigId:) when needed. See docs/AUTO_MODE.md for details and the events to watch (.invalidSessionKey, .prepareFailed, .canceled).
  • RainforestPay.setupTapToPhone(environment:sessionKey:deviceRegistrationId:) async throws -> SetupResult — one-shot convenience that creates an instance and calls setup() in a single call.
  • clientMetadata() async throws -> ClientMetadata — new method on both TapToPhoneProtocol and TapToPhoneCoreProtocol, returns { merchantTermsAccepted: Bool }.
  • updateSessionKey(_ newKey: String) async — rotate a session key on a live instance without tearing it down and recreating it.
  • Significantly expanded error-code mapping — many more underlying provider error codes (attestation, crypto, merchant, network, GPS, etc.) now map to specific RainforestErrorCodes with actionable messages instead of falling through to a generic error.
  • cancelPresented() now also aborts an in-flight card-read transaction if presentPayin hasn't started polling yet, so cancellation is more reliable.
  • More reliable recovery from noReaderSession errors (0.5.5) — the SDK now fully re-arms the card reader during recovery instead of only reconfiguring it.
  • presentPayin() now surfaces an active phone call as a specific, actionable error (0.6.0) — RainforestError.error(.cardReader, "Cannot process payments while on a call") — instead of falling through to a generic "unknown card reader error".
  • presentPayin()'s internal retry loop now routes terminal reader errors through the same handling as a first attempt (0.6.0), so cancellation and the call-in-progress case behave consistently whether they happen on the initial read or a retry.

5. Migration checklist

  1. Confirm your app's deployment target is iOS 17.4+, or plan to switch to the Compat build (docs/COMPAT_BUILD.md).
  2. Replace every RainforestPay.TapToPhone(env, sessionKey:) call with the 3-arg RainforestPay.TapToPhone(env, sessionKey:deviceRegistrationId:) (or TapToPhoneAuto if you want managed lifecycle), and add try await.
  3. Remove deviceRegistrationId: from every setup(), prepare(), resume(), presentPayin(), cancelPresented() call — it's no longer a parameter.
  4. Rename rfTTP.events to rfTTP.eventStream().
  5. Search for exhaustive switch statements over RainforestErrorCode, RainforestError, and RainforestEvent and add the new cases (or a default case).
  6. Consider whether Auto mode fits your integration — it removes the need to call prepare()/resume() yourself.
  7. Search presentPayin() call sites for catch clauses typed to the underlying provider's reader-error type (e.g. PaymentCardReaderSession.ReadError) and replace them with catch let err as RainforestError (see 2.4).

Did this page help you?