Purchasing & Restoring Purchases

The entire payment process is managed through a central component: the OnsidePaymentQueue. This queue processes payment requests and maintains a list of transactions that your app needs to handle.

Purchasing

To interact with this queue and receive updates about purchases, you must use an observer that conforms to the OnsidePaymentTransactionObserver protocol. This observer is the cornerstone of your payment logic. It’s responsible for listening to all transaction updates, whether a purchase succeeds, fails, is being processed, or needs to be restored.

The overall workflow is:

  1. Register an Observer: At app launch, register a transaction observer to listen for events from the payment queue.

  2. Initiate a Purchase: Add a payment to the queue when a user taps a "Buy" button.

  3. Process Transactions: Handle the transaction states (e.g., purchased, failed) delivered to your observer.

  4. Unlock Content: Grant the user access to their purchased content.

  5. Finish the Transaction: Mark the transaction as complete to remove it from the queue. This is a mandatory step.

Step 1: Set Up the Transaction Observer

Your application must have an active transaction observer to process payments.

How the Queue Works: The OnsidePaymentQueue is designed to be safe. It will not process any transactions—new purchases or pending ones—and will remain in a "paused" state until at least one observer is registered via add(_:). Once an observer is present, the queue becomes active and begins processing.

Best Practice: The observer should be registered as early as possible in your app's lifecycle, ideally in your AppDelegate. This "un-pauses" the queue, allowing it to immediately process any transactions.

Step 2: Making a Purchase

Once you have fetched an OnsideProduct object, you can initiate a purchase by adding the product directly to the payment queue.

The SDK will take over from here, presenting the necessary UI (login, payment sheet, etc.). The result will be delivered asynchronously to your active transaction observer.

import OnsideKit

class StoreViewController: UIViewController {

    var products: [OnsideProduct] = [] // Assume this is populated

    func buyButtonTapped(for product: OnsideProduct) {
        // Add the product directly to the payment queue to start the purchase.
        Onside.paymentQueue().add(product)
    }
}

Step 3: Processing Transactions

This is where you handle the results of a purchase. The onsidePaymentQueue(_:updatedTransactions:) method in your observer will be called with an array of transactions. You must inspect the transactionState of each one.

func process(transaction: OnsidePaymentTransaction) {
    switch transaction.transactionState {
    
    case .purchased:
        // Purchase was successful.
        print("Transaction purchased: \(transaction.payment.productIdentifier)")
        unlockContent(for: transaction.product.productIdentifier)
        // IMPORTANT: Finish the transaction.
        Onside.paymentQueue().finishTransaction(transaction)

    case .restored:
        // A previous purchase was restored.
        print("Transaction restored: \(transaction.product.productIdentifier)")
        unlockContent(for: transaction.product.productIdentifier)
        // IMPORTANT: Finish the transaction.
        Onside.paymentQueue().finishTransaction(transaction)
        
    case .failed:
        // The transaction failed.
        print("Transaction failed with error: \(String(describing: transaction.error))")
        // IMPORTANT: Finish the transaction.
        Onside.paymentQueue().finishTransaction(transaction)
        
    case .purchasing:
        // The transaction is being processed. Your UI can show a spinner.
        print("Transaction in progress...")

    @unknown default:
        // Handle any future states.
        break
    }
}

private func unlockContent(for productIdentifier: String) {
    // 1. Determine what content to unlock based on the identifier.
    // 2. Persist the purchase (e.g., in UserDefaults or Keychain) so the user
    //    retains access across app launches.
    // 3. Update the UI.
    UserDefaults.standard.set(true, forKey: productIdentifier)
}

⚠️ CRITICAL: You MUST Finish Transactions

You must call Onside.paymentQueue().finishTransaction(_:) for every transaction that reaches a final state (.purchased, .restored, or .failed).

If you do not finish a transaction, the payment queue will assume it was not processed, and it will deliver the same transaction to your observer again the next time the app launches. This can lead to bugs, such as repeatedly asking to unlock content.

Restoring Purchases

You should provide a "Restore Purchases" button in your app (e.g., in the settings screen) for users who have reinstalled your app or are using a new device. This allows them to regain access to their non-consumable and subscription purchases without paying again.

func restoreButtonTapped() {
    Onside.paymentQueue().restoreCompletedTransactions()
}

How Restoration Works: When you call this method, OnsideKit communicates with the backend to find all previous purchases for the current user.

  • The results are not delivered directly from the method call.

  • Instead, for each restored purchase, the payment queue calls the same onsidePaymentQueue(_:updatedTransactions:) delegate method on your observer.

  • The transactions will have a state of .restored.

Your existing logic in the switch statement will handle these restored transactions, unlocking the content and finishing the transaction, just like a new purchase.

After all individual transactions have been delivered, the SDK calls one of two delegate methods to signal the completion of the entire restore operation. This is where you should update your UI (e.g., hide an activity indicator).

func onsidePaymentQueueRestoreCompletedTransactionsFinished(_ queue: OnsidePaymentQueue) {
    print("Restore process finished successfully.")
    // Hide any activity indicators and show a "Restore Complete" message.
}

// This is called if the restore process fails.
func onsidePaymentQueue(
    _ queue: OnsidePaymentQueue, 
    restoreCompletedTransactionsFailedWithError error: OnsideTransactionsRestoreError
) {
    print("Restore process failed with error: \(error)")
    // Hide any activity indicators and show an error alert to the user.
}

Last updated

Was this helpful?