For the complete documentation index, see llms.txt. This page is also available as Markdown.

The Payment Queue & Transactions

How the OnsidePaymentQueue processes transactions, why a transaction observer is required, the transaction lifecycle, and why finishing transactions is mandatory.

All purchasing in OnsideKit goes through a single, process-wide payment queue. It processes purchases and restores, and keeps a list of transactions your app must handle. The model is intentionally close to StoreKit's SKPaymentQueue.

Accessing the queue

@MainActor static func defaultPaymentQueue() -> OnsidePaymentQueue
let queue = Onside.defaultPaymentQueue()

The queue is a shared singleton. You must call Onside.initialize() before accessing it.

The accessor is Onside.defaultPaymentQueue(). There is no Onside.paymentQueue().

OnsidePaymentQueue is a protocol — all of its members are @MainActor:

protocol OnsidePaymentQueue: AnyObject {
    var delegate: OnsidePaymentQueueDelegate? { get set }
    var storefront: OnsideStorefront? { get }
    var transactions: [OnsidePaymentTransaction] { get }
    var transactionObservers: [OnsidePaymentTransactionObserver] { get }

    func add(observer: OnsidePaymentTransactionObserver)
    func remove(observer: OnsidePaymentTransactionObserver)

    func add(
        _ payment: OnsidePayment,
        completion: ((Result<Void, OnsidePaymentQueueAddProductError>) -> Void)?
    )
    func restoreCompletedTransactions(
        completion: ((Result<Void, OnsidePaymentQueueRequestRestoreError>) -> Void)?
    )
    func finishTransaction(_ transaction: OnsidePaymentTransaction)
}

A transaction observer is required

The queue makes progress only while at least one transaction observer is registered. Until you add one, the queue stays idle — payments and restores are accepted but nothing is processed.

Register your observer as early as possible — ideally in your AppDelegate — so the queue can immediately process any transactions (including unfinished ones carried over from a previous launch).

Two preconditions for processing. The queue runs only when (1) at least one observer is registered and (2) a storefront exists (the user is logged in). With no observer or no session, transactions sit and wait. See Authentication & User Account.

When you add an observer and the queue already holds transactions, that observer is immediately called with the current transactions, so you can resume or finish them.

The transaction lifecycle

Each transaction is an OnsidePaymentTransaction whose transactionState moves through these public states:

State
Meaning
Your action

.purchasing

In flight — created, awaiting payment, etc.

Wait (optionally show a spinner).

.purchased

Bought successfully.

Unlock content, then finishTransaction.

.restored

Returned by restoreCompletedTransactions.

Unlock content, then finishTransaction.

.failed

Failed. transaction.error is set.

finishTransaction to remove it.

State changes are delivered to your observer's onsidePaymentQueue(_:updatedTransactions:). Inspect each transaction's transactionState and react. Because the public enum is resilient, always include an @unknown default in your switch:

.purchased vs .restored only tells you how the transaction was surfaced (a fresh purchase vs. a result of restoreCompletedTransactions). Both grant the same entitlement and both must be finished. For the full shape of a transaction, see the Models reference.

Finishing transactions is mandatory

Finishing a transaction removes it from the queue (for a purchase, this also performs the necessary server-side completion). When it is removed, observers receive onsidePaymentQueue(_:removedTransactions:).

Transactions can appear without an explicit purchase

OnsideKit reconciles the account's purchases with the server. As a result, your observer may receive transactions you didn't start in this session — for example a subscription renewal, a purchase made on another device, or one interrupted before it finished. Treat every transaction your observer delivers the same way: act on its state and finish it.

The observer protocol

Method
Required?
Called when

onsidePaymentQueue(_:updatedTransactions:)

Yes

Transactions are added or change state.

onsidePaymentQueue(_:removedTransactions:)

No

Transactions are removed (after finishing).

onsidePaymentQueueRestoreCompletedTransactionsFinished(_:)

No

A restore finished successfully.

onsidePaymentQueue(_:restoreCompletedTransactionsFailedWithError:)

No

A restore failed.

onsidePaymentQueueDidChangeStorefront(_:)

No

The storefront changed (login/logout/region).

Only onsidePaymentQueue(_:updatedTransactions:) is required; the others have default empty implementations. Observers are held weakly — keep a strong reference and remove(observer:) when done. See Threading & Object Lifetime.

When the storefront changes

If the user logs in/out or their region changes, the storefront changes: the queue resets, notifies observers via onsidePaymentQueueDidChangeStorefront(_:), and re-validates pending work. A queued purchase that would now run in a different storefront is gated by the delegate so you can confirm or cancel it. See Handling Storefront & Price Changes.

Next

Last updated

Was this helpful?