# Using ContextSDK with Purchasely

ContextSDK integrates seamlessly with Purchasely to help you show paywalls at the perfect moment. This integration leverages ContextSDK's ML-powered recommendations to optimize when Purchasely displays paywalls, improving conversion rates.

## How it works

ContextSDK provides real-world context about whether it's a good moment to show a paywall through the **`shouldUpsell`** property - a boolean indicating whether it's a good moment to show a paywall.

This value is passed to Purchasely as a user attribute (`context_should_upsell`), allowing you to:

* Use it in paywall display rules and audience targeting
* Track context in your analytics
* A/B test different timing strategies
* Reduce interruptions during bad moments and maximize conversions

## Integration Guide

### Step 1: Capture Context and Show Paywall

Before showing a paywall, capture the user's context using `instantContext` and pass it to Purchasely as a user attribute:

```swift
import ContextSDK
import Purchasely

class OnboardingViewController: UIViewController {

    func showPaywall() {
        // 1. Capture the user's real-world context
        let context = ContextManager.instantContext(flowName: "purchasely_onboarding", duration: 3)

        // 2. Set the context as a Purchasely user attribute
        Purchasely.setUserAttribute(withBoolValue: context.shouldUpsell, forKey: "context_should_upsell")

        // 3. Load and show the Purchasely paywall
        let paywallController = Purchasely.presentationController(
            for: "onboarding",
            loaded: { [weak self] controller, success, error in
                if let controller = controller, success {
                    self?.present(controller, animated: true)
                } else if let error = error {
                    print("Failed to load paywall: \(error)")
                    context.log(.skipped)
                }
            },
            completion: { result, _ in
                // 4. Log the outcome to help train the ML model
                switch result {
                case .purchased:
                    context.log(.positive)
                case .cancelled:
                    context.log(.negative)
                case .restored:
                    context.log(.skipped)
                @unknown default:
                    context.log(.skipped)
                }
            }
        )
    }
}
```

### Step 2: Configure Purchasely Rules

You can use the context attribute in Purchasely's audience rules to control when paywalls appear:

**In Purchasely Console:**

1. Go to your placement configuration
2. Add audience rules using `context_should_upsell` (Boolean)
3. For example: Only show the paywall when `context_should_upsell` is `true`

This gives you the flexibility to A/B test different strategies and adjust timing logic without code changes.

### Understanding the Flow

Let's break down what's happening:

1. **`ContextManager.instantContext(flowName:duration:)`** - This captures the user's real-world context synchronously. The flow name (e.g., `"purchasely_onboarding"`) uniquely identifies this opportunity in your app. Use descriptive names like `"purchasely_settings"`, `"purchasely_post_action"`, etc.
2. **`Purchasely.setUserAttribute()`** - This passes the `shouldUpsell` value to Purchasely as a user attribute. During the initial calibration phase, this value is always `true`. Once your custom ML model is trained and deployed, it makes real-time decisions based on the user's context.
3. **`Purchasely.presentationController(for:)`** - This is your standard Purchasely integration. The placement ID (e.g., `"onboarding"`) should match what you've configured in your Purchasely dashboard.
4. **`context.log()`** - This logs the outcome, which is crucial for training the ML model:
   * `.positive` - User completed a purchase
   * `.negative` - User dismissed the paywall
   * `.skipped` - Paywall wasn't shown, or user restored purchases

## Logging Revenue Outcomes

For in-app purchases, you can optionally log revenue information to get more detailed analytics. Update your code to use `logRevenueOutcome`:

```swift
let context = ContextManager.instantContext(flowName: "purchasely_onboarding", duration: 3)

// Set the context as a Purchasely user attribute
Purchasely.setUserAttribute(withBoolValue: context.shouldUpsell, forKey: "context_should_upsell")

let paywallController = Purchasely.presentationController(
    for: "onboarding",
    loaded: { [weak self] controller, success, error in
        if let controller = controller, success {
            self?.present(controller, animated: true)
        } else if let error = error {
            print("Failed to load paywall: \(error)")
            context.log(.skipped)
        }
    },
    completion: { result, plan in
        switch result {
        case .purchased:
            // Log revenue with product details
            if let plan = plan {
                context.logRevenueOutcome(
                    revenue: plan.amount,
                    currency: "USD",
                    productId: plan.vendorId
                )
            } else {
                // Fallback if plan details aren't available
                context.log(.positive)
            }
        case .cancelled:
            context.log(.negative)
        case .restored:
            context.log(.skipped)
        @unknown default:
            context.log(.skipped)
        }
    }
)
```

{% hint style="info" %}
Revenue logging helps ContextSDK optimize not just for conversion rates, but for revenue maximization. Higher-value purchases can be weighted differently in the ML model training.
{% endhint %}

## Best Practices

### Choose a Flow Name

Select a descriptive flow name that represents your use case:

```swift
// Examples of good flow names:
ContextManager.instantContext(flowName: "purchasely_onboarding", duration: 3)
ContextManager.instantContext(flowName: "purchasely_premium_upgrade", duration: 3)
ContextManager.instantContext(flowName: "purchasely_feature_unlock", duration: 3)
ContextManager.instantContext(flowName: "purchasely_post_level", duration: 3)
```

Use a consistent naming pattern with `snake_case` and group related flows with the same prefix (e.g., all Purchasely flows start with `purchasely_`).

### Always Log Outcomes

**Critical:** Always log an outcome for every context you create. This data trains the ML model:

```swift
// Always log an outcome for every context
switch result {
case .purchased:
    context.log(.positive)
    // Or with revenue:
    // context.logRevenueOutcome(revenue: amount, currency: "USD", productId: id)
case .cancelled:
    context.log(.negative)
case .restored:
    // User restored purchases - this is not a conversion
    context.log(.skipped)
@unknown default:
    context.log(.skipped)
}
```

### Handle Multiple Placements

You can use this pattern across multiple placements in your app:

```swift
// During onboarding
func showOnboardingPaywall() {
    let context = ContextManager.instantContext(flowName: "purchasely_onboarding", duration: 3)
    presentPurchaselyPaywall(placement: "onboarding", context: context)
}

// After completing a key action
func showPostActionPaywall() {
    let context = ContextManager.instantContext(flowName: "purchasely_post_action", duration: 3)
    presentPurchaselyPaywall(placement: "post_action", context: context)
}

// In settings
func showSettingsPaywall() {
    let context = ContextManager.instantContext(flowName: "purchasely_settings", duration: 3)
    presentPurchaselyPaywall(placement: "settings", context: context)
}

private func presentPurchaselyPaywall(placement: String, context: Context) {
    // Set the context as a Purchasely user attribute
    Purchasely.setUserAttribute(withBoolValue: context.shouldUpsell, forKey: "context_should_upsell")

    let paywallController = Purchasely.presentationController(
        for: placement,
        loaded: { [weak self] controller, success, error in
            if let controller = controller, success {
                self?.present(controller, animated: true)
            } else {
                context.log(.skipped)
            }
        },
        completion: { result, _ in
            switch result {
            case .purchased:
                context.log(.positive)
            case .cancelled:
                context.log(.negative)
            case .restored:
                context.log(.skipped)
            @unknown default:
                context.log(.skipped)
            }
        }
    )
}
```

### Placement Naming Convention

Your ContextSDK flow names don't need to match your Purchasely placement IDs, but having a clear relationship helps maintain your code. For example:

* Flow name: `"purchasely_onboarding"` → Placement: `"onboarding"`
* Flow name: `"purchasely_settings"` → Placement: `"settings"`
* Flow name: `"purchasely_post_level"` → Placement: `"post_level"`

## Related Documentation

* [Revenue Outcomes](/context-decision/revenue-outcomes.md) - Learn more about logging revenue outcomes
* [Logging Conversions](/context-decision/logging-conversions.md) - General guide to logging outcomes
* [Custom Outcome Metadata](/context-decision/advanced/custom-outcome-metadata.md) - Track additional purchase context

{% hint style="success" %}
ContextSDK's ML models learn from your outcome data. The more purchases you log with `log(.positive)` or `logRevenueOutcome()`, the better the model becomes at predicting optimal moments to show paywalls.
{% endhint %}


---

# 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.contextsdk.com/context-decision/revenue-outcomes/purchasely.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.
