# 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**](#step-1-set-up-the-transaction-observer): At app launch, register a transaction observer to listen for events from the payment queue.
2. [**Initiate a Purchase**](#step-2-making-a-purchase): Add a payment to the queue when a user taps a "Buy" button.
3. [**Process Transactions**](#step-3-processing-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](https://docs.onside.io/sdk/products), you can initiate a purchase by creating payment object and adding it 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.

```swift
import OnsideKit

class StoreViewController: UIViewController {

    var products: [OnsideProduct] = [] // Assume this is populated
    
    func buyButtonTapped(for product: OnsideProduct) {
        let payment = OnsidePayment(product: product)
        Onside.defaultPaymentQueue().add(payment)
    }

}
```

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

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

    case .restored:
        // A previous purchase was restored.
        print("Transaction restored: \(transaction.payment.product.productIdentifier)")
        unlockContent(for: transaction.payment.product.productIdentifier)
        // IMPORTANT: Finish the transaction.
        Onside.defaultPaymentQueue().finishTransaction(transaction)
        
    case .failed:
        // The transaction failed.
        print("Transaction failed with error: \(String(describing: transaction.error))")
        // IMPORTANT: Finish the transaction.
        Onside.defaultPaymentQueue().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.defaultPaymentQueue().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.

```swift
func restoreButtonTapped() {
    Onside.defaultPaymentQueue().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).

```swift
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.
}
```

## **Purchase Validation**

This document outlines the process of validating purchases for publishers using the Onside SDK.

**Overview**

Publishers integrate the Onside Mobile SDK to facilitate in-app purchases. The SDK manages the purchase process and generates a signed transaction history. The publisher's backend is responsible for validating these transactions to ensure purchase integrity and unlock content.

Validation involves two crucial steps: obtaining a signed transaction history from the Onside SDK via the mobile application, and verifying specific orders on the publisher's backend against the Onside Merchant API with a secure, server-to-server authentication mechanism.

**Integration Flow**

The following diagram illustrates the complete validation process.

{% @mermaid/diagram content="sequenceDiagram
participant App as Mobile App
participant SDK as Onside SDK
participant Backend as Publisher Backend
participant API as Onside Merchant API

```
App->>SDK: Request Transaction History
SDK->>App: Return Signed JWS
App->>App: Verify JWS Signature (Optional)
App->>Backend: Send Order ID (and JWS)
Backend->>Backend: Generate Merchant JWT
Backend->>API: GET /history/{order_id}
API->>Backend: Return Signed Transaction Details (JWS)
Backend->>Backend: Validate JWS Signature (Onside Public Key)
Backend->>Backend: Grant Entitlements
Backend->>App: Confirm Validation" %}
```

**Prerequisites**

To interact with the Merchant API, obtain a Merchant ID, a Merchant Secret, and a Secret Key ID (kid) from the Onside Manager. These credentials enable secure server-to-server authentication.

**Client-Side Transaction History**

The mobile application requests transaction history via the Onside SDK, which returns a JSON Web Signature (JWS) containing the transaction history. Each purchase includes a unique `order_id` for verification.

#### SDK API: Getting a signed JWS on the client

Use `Onside.makeSignedInAppsHistoryRequest()` to create a request that downloads a signed in-app purchase history as a compact JWS.

```swift
static func makeSignedInAppsHistoryRequest()
    -> Result<OnsideSignedInAppsHistoryRequest, OnsideSignedInAppsHistoryRequestError>
```

The call returns:

* `OnsideSignedInAppsHistoryRequestError` if the SDK cannot create a request instance.
* `OnsideSignedInAppsHistoryRequest` on success.

`OnsideSignedInAppsHistoryRequest` is a request object you control and retain.

```swift
public protocol OnsideSignedInAppsHistoryRequest: AnyObject {
    var delegate: OnsideSignedInAppsHistoryRequestDelegate? { get set }

    @MainActor func start()
    @MainActor func stop()
}
```

**Typical flow**

1. Create the request with `Onside.makeSignedInAppsHistoryRequest()`.
2. Provide an `OnsideSignedInAppsHistoryRequestDelegate` implementation.
3. Keep a strong reference to the request for the whole operation.
4. Call `start()`.
5. In the delegate callbacks, handle either:
   * `OnsideSignedInAppsHistory` on success.
   * An error describing why the history could not be loaded.

```swift
public struct OnsideSignedInAppsHistory: Sendable {
    public var data: Data
    public var string: String?
}
```

`OnsideSignedInAppsHistory.data` is the raw JWS blob.

`OnsideSignedInAppsHistory.string` is the same blob as a `String`, when available.

Send this JWS to your backend (or validate it locally) to verify purchase authenticity and build entitlements from the original signed history.

**Backend Validation Request**

To validate a purchase, the backend receives the `order_id` from the mobile app and queries the Onside Merchant API.

**Authentication**

Authenticate requests to the Onside Merchant API with a JSON Web Token (JWT) in the `Authorization` header, signing it using HS256 and your Merchant Secret. The JWT header must specify `alg` as `HS256` and `kid` as your Secret Key ID, while the payload claims should include `aud` set to `onside`, `iss` set to `onside`, and `sub` set to your Merchant ID. Ensure `exp` and `nbf` define a validity period of no more than 30 seconds, and include a unique `jti`.

#### API Request

To obtain the in-app purchase history, send a GET request to the endpoint `/merchant-api/v1/purchases/in-app/history/{order_id}`. Make sure to include the JWT token in the Authorization header for authentication.

#### Response Format

Upon a successful request, the Onside Merchant API provides a signed JSON Web Signature (JWS). After verifying the JWS, the payload includes a `TransactionHistory` object, detailing transaction events.

**Example Response Payload (Decoded)**

The decoded response contains a JSON object with a list of transactions:

```json
{
  "transactions": [
    {
      "app_account_token": "user-uuid-1234",
      "bundle_id": "com.example.app",
      "currency": "EUR",
      "expires_at": "2026-01-26T10:00:00Z",
      "order_id": "3e46c75a-7e04-4530-979a-e275f3a7c50c",
      "original_order_id": "3e46c75a-7e04-4530-979a-e275f3a7c50c",
      "original_purchase_date": "2025-12-26T10:00:00Z",
      "price": 999,
      "product_id": "premium_subscription_monthly",
      "product_slug": "premium-monthly",
      "product_type": "SUBSCRIPTION",
      "purchase_date": "2025-12-26T10:00:00Z",
      "quantity": 1,
      "revocation_date": null,
      "revocation_reason": null,
      "fetched_at": "2026-01-26T10:05:00Z",
      "subscription_id": "sub-group-xyz",
      "transaction_reason": "PURCHASE",
      "user_country": "DE"
    },
    {
      "app_account_token": "user-uuid-1234",
      "bundle_id": "com.example.app",
      "currency": "EUR",
      "expires_at": "2026-02-26T10:00:00Z",
      "order_id": "4f57d86b-8f15-5641-080b-f386g4b8d61d",
      "original_order_id": "3e46c75a-7e04-4530-979a-e275f3a7c50c",
      "original_purchase_date": "2025-12-26T10:00:00Z",
      "price": 999,
      "product_id": "premium_subscription_monthly",
      "product_slug": "premium-monthly",
      "product_type": "SUBSCRIPTION",
      "purchase_date": "2026-01-26T10:00:00Z",
      "quantity": 1,
      "revocation_date": null,
      "revocation_reason": null,
      "fetched_at": "2026-01-26T10:05:00Z",
      "subscription_id": "sub-group-xyz",
      "transaction_reason": "RENEWAL",
      "user_country": "DE"
    },
    {
      "app_account_token": "user-uuid-1234",
      "bundle_id": "com.example.app",
      "currency": "EUR",
      "expires_at": null,
      "order_id": "5a68e97c-9g26-6752-191c-g497h5c9e72e",
      "original_order_id": "5a68e97c-9g26-6752-191c-g497h5c9e72e",
      "original_purchase_date": "2025-09-01T12:00:00Z",
      "price": 499,
      "product_id": "remove_ads",
      "product_slug": "remove-ads",
      "product_type": "NON_CONSUMABLE",
      "purchase_date": "2025-09-01T12:00:00Z",
      "quantity": 1,
      "revocation_date": "2025-09-05T10:00:00Z",
      "revocation_reason": "Refund",
      "fetched_at": "2026-01-26T10:05:00Z",
      "subscription_id": null,
      "transaction_reason": "PURCHASE",
      "user_country": "DE"
    }
  ]
}
```

#### Response Validation

To ensure authenticity, verify the JWS signature using Onside's public keys available at `https://onside.io/.well-known/jwks.json`. Confirm the payload against the `TransactionHistory` schema as specified in the API documentation.


---

# 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-and-restoring-purchases.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.
