> For the complete documentation index, see [llms.txt](https://docs.onside.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.onside.io/sdk/purchasing/making-a-purchase.md).

# Making a Purchase

Start a purchase by adding a payment to the queue, process the resulting transaction, and finish it.

A purchase is initiated by adding a payment to the [payment queue](/sdk/core-concepts/payment-queue.md). The SDK presents whatever UI is needed (login, payment sheet), and the result is delivered to your transaction observer.

{% hint style="info" %}
Make sure you have registered a transaction observer first — the queue does nothing until one is registered. See [The Payment Queue & Transactions](/sdk/core-concepts/payment-queue.md).
{% endhint %}

## Start the purchase

Create an `OnsidePayment` from an [`OnsideProduct`](/sdk/products-and-subscriptions/fetching-products.md) and add it to the queue:

```swift
@MainActor func add(
    _ payment: OnsidePayment,
    completion: ((Result<Void, OnsidePaymentQueueAddProductError>) -> Void)?
)
```

```swift
func buy(_ product: OnsideProduct) {
    let payment = OnsidePayment(product: product)
    Onside.defaultPaymentQueue().add(payment) { result in
        if case .failure(let error) = result {
            // Pre-flight failure, e.g. the user dismissed the login screen.
            print("Couldn't start the purchase: \(error)")
        }
    }
}
```

{% hint style="warning" %}
The `completion` argument is **required** — pass a closure, or `completion: nil` if you don't need the pre-flight result. The completion reports whether the payment was accepted into the queue. It can run after asynchronous login and delegate decisions; success does not mean the purchase completed. Actual transaction updates arrive through your observer.
{% endhint %}

If the user is not logged in, OnsideKit presents the login flow automatically and resumes the purchase afterwards. See [Authentication & User Account](/sdk/core-concepts/authentication.md).

### Pre-flight errors

| `OnsidePaymentQueueAddProductError` | Meaning                                                                        |
| ----------------------------------- | ------------------------------------------------------------------------------ |
| `.loginDiscarded`                   | Required login did not complete.                                               |
| `.rejectedAfterLogin`               | Your queue delegate rejected the payment in `shouldContinueAfterLogin`.        |
| `.notSupportedForExternalLogin`     | The signed-in external profile cannot buy this subscription or non-consumable. |
| `.applePayRequired`                 | An external profile is buying a consumable, but Apple Pay is unavailable.      |

These failures happen before enqueueing, so there is no transaction to finish and no transaction update for the rejected attempt. The external-profile checks run before `shouldContinueAfterLogin`; that delegate cannot override the restrictions.

### Purchases with external accounts

[External profiles](/sdk/core-concepts/external-login.md) can buy only consumables, using [Apple Pay](/sdk/purchasing/apple-pay.md). Saved cards and payment-method switching are unavailable. Configure the full OnsideKit build and Apple Pay before the first purchase.

After a successful external-account purchase, the SDK shows a receipt reminder with options to open/share the receipt or skip. This uses `OnsideScreen.externalAccountNotification` for presentation customization. Continue processing and finishing transactions through your observer as usual; dismissing that reminder is not the signal to grant content.

### Associating a purchase with your account system

`OnsidePayment.appAccountToken` lets you tie a purchase to your own user/account. Its only initializer is `init(product:)`, so set the token by mutating the value:

```swift
var payment = OnsidePayment(product: product)
payment.appAccountToken = currentUser.id   // your opaque account token
Onside.defaultPaymentQueue().add(payment, completion: nil)
```

The token is carried with the transaction and echoed back on `transaction.payment.appAccountToken`. It is separate from the identifier returned by `onsideExternalAccountToken()`: setting `appAccountToken` does not authenticate the user or enable external login.

## Process the transaction

Updates are delivered to your observer's `onsidePaymentQueue(_:updatedTransactions:)`. Inspect each transaction's state, unlock content, and **finish** it:

```swift
func onsidePaymentQueue(
    _ queue: OnsidePaymentQueue,
    updatedTransactions: [OnsidePaymentTransaction]
) {
    for transaction in updatedTransactions {
        switch transaction.transactionState {
        case .purchased:
            unlockContent(for: transaction.payment.product.productIdentifier)
            queue.finishTransaction(transaction)

        case .restored:
            unlockContent(for: transaction.payment.product.productIdentifier)
            queue.finishTransaction(transaction)

        case .failed:
            // transaction.error is an OnsidePaymentTransactionError (e.g. .cancelled).
            print("Transaction failed: \(String(describing: transaction.error))")
            queue.finishTransaction(transaction)

        case .purchasing:
            break   // in progress — show a spinner if you like

        @unknown default:
            break
        }
    }
}

private func unlockContent(for productIdentifier: String) {
    UserDefaults.standard.set(true, forKey: productIdentifier)
    // Update your UI and grant access.
}
```

{% hint style="danger" %}
**You must finish every transaction** that reaches `.purchased`, `.restored`, or `.failed`. Otherwise OnsideKit re-delivers it on the next launch. See [The Payment Queue & Transactions](/sdk/core-concepts/payment-queue.md#finishing-transactions-is-mandatory).
{% endhint %}

{% hint style="info" %}
`transaction.error` is an [`OnsidePaymentTransactionError`](/sdk/reference/errors.md). On a `.failed` transaction, `.cancelled` means the user backed out rather than a hard error; `.presentationFailed` means OnsideKit couldn't present the purchase UI (no active window scene was available) so the purchase never started — retry once the app is in the foreground.
{% endhint %}

## Next

* [Restoring Purchases](/sdk/purchasing/restoring-purchases.md) — let users get their purchases back
* [Handling Storefront & Price Changes](/sdk/purchasing/storefront-price-changes.md) — confirm or cancel a purchase when the region/price changes
* [Purchase Validation](/sdk/purchase-validation/purchase-validation.md) — verify purchases on your backend


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.onside.io/sdk/purchasing/making-a-purchase.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
