# Making a Purchase

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)")   // .loginDiscarded
        }
    }
}
```

{% 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 only the synchronous pre-flight outcome (`OnsidePaymentQueueAddProductError.loginDiscarded`). The 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).

### 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`.

## 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), currently only `.cancelled` — a `.failed` transaction with `.cancelled` means the user backed out rather than a hard error.
{% 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: Querying This Documentation

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

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

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

The question should be specific, self-contained, and written in natural language.
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.
