Skip to main content
The AppDNA billing module provides a unified interface for in-app purchases, subscription management, and entitlement checking. It supports StoreKit 2 natively, with optional integrations for RevenueCat and Adapty.

Configuration

Set the billing provider when configuring the SDK:
AppDNA.configure(
    apiKey: "adn_live_xxx",
    environment: .production,
    options: AppDNAOptions(billingProvider: .storeKit2)
)

BillingProvider Options

ValueDescription
.storeKit2Native StoreKit 2 integration (default)
.revenueCatRevenueCat integration
.adapty(apiKey:)Adapty integration with your API key
.noneDisable the billing module entirely
You must set the billingProvider in AppDNAOptions before accessing AppDNA.billing. If set to .none, billing module methods will throw errors.

Module Access

Access the billing module through the AppDNA.billing property:
let billing = AppDNA.billing

Get Products

Fetch product information from the App Store:
let products = try await AppDNA.billing.getProducts([
    "premium_monthly",
    "premium_annual"
])

for product in products {
    print("\(product.displayName): \(product.displayPrice)")
}

ProductInfo

PropertyTypeDescription
idStringProduct identifier
displayNameStringLocalized product name
descriptionStringLocalized product description
priceDecimalNumeric price value
displayPriceStringFormatted price string (e.g., “$9.99”)
subscriptionSubscriptionInfo?Subscription details, if applicable

Purchase a Product

Initiate a purchase:
let transaction = try await AppDNA.billing.purchase("premium_monthly")
print("Purchased: \(transaction.productId), txn: \(transaction.transactionId)")

TransactionInfo

PropertyTypeDescription
transactionIdStringApp Store transaction identifier
productIdStringPurchased product identifier
purchaseDateDateDate of the purchase
environmentStringTransaction environment (e.g., “Production”, “Sandbox”)
Server-side receipt verification is performed automatically by the SDK. You do not need to send receipts to your own server for validation.

Restore Purchases

Restore previously purchased products (e.g., after a device change or reinstall):
let restoredProductIds = try await AppDNA.billing.restorePurchases()
print("Restored \(restoredProductIds.count) products")
Returns an array of String product identifiers that were restored.

Entitlements

Retrieve the current user’s entitlements:
let entitlements = await AppDNA.billing.getEntitlements()

for entitlement in entitlements {
    print("\(entitlement.identifier): active=\(entitlement.isActive)")
}

Entitlement

PropertyTypeDescription
identifierStringEntitlement identifier
isActiveBoolWhether the entitlement is currently active
expiresAtDate?Expiration date, if applicable
productIdStringProduct that granted this entitlement

Check Active Subscription

Quickly check if the user has any active subscription:
let hasSubscription = await AppDNA.billing.hasActiveSubscription()

if hasSubscription {
    // Unlock premium features
}

Listen for Entitlement Changes

Register a callback to be notified when entitlements change (e.g., subscription renewal, expiration, or new purchase):
AppDNA.billing.onEntitlementsChanged { entitlements in
    let activeIds = entitlements
        .filter { $0.isActive }
        .map { $0.identifier }
    print("Active entitlements: \(activeIds)")
}

AppDNABillingDelegate

For more granular control, implement the AppDNABillingDelegate protocol:
protocol AppDNABillingDelegate {
    func onPurchaseCompleted(productId: String, transaction: TransactionInfo)
    func onPurchaseFailed(productId: String, error: Error)
    func onEntitlementsChanged(entitlements: [Entitlement])
    func onRestoreCompleted(restoredProducts: [String])
}

Example Implementation

class BillingHandler: AppDNABillingDelegate {
    func onPurchaseCompleted(productId: String, transaction: TransactionInfo) {
        print("Purchase completed: \(productId)")
        // Update UI, unlock features
    }

    func onPurchaseFailed(productId: String, error: Error) {
        print("Purchase failed: \(productId)\(error.localizedDescription)")
        // Show error to user
    }

    func onEntitlementsChanged(entitlements: [Entitlement]) {
        let active = entitlements.filter { $0.isActive }
        print("Entitlements updated: \(active.count) active")
    }

    func onRestoreCompleted(restoredProducts: [String]) {
        print("Restored \(restoredProducts.count) products")
    }
}

Auto-Tracked Events

The SDK automatically tracks the following billing-related events:
EventTriggered When
purchase_startedA purchase flow is initiated
purchase_completedA purchase completes successfully
purchase_failedA purchase fails with an error
purchase_canceledThe user cancels the purchase dialog
restore_startedA restore operation begins
restore_completedA restore operation completes
These events include the product identifier and relevant metadata. They are tracked regardless of whether you implement AppDNABillingDelegate.

Full Example

import AppDNASDK

class SubscriptionManager: AppDNABillingDelegate {
    func loadProducts() async {
        do {
            let products = try await AppDNA.billing.getProducts([
                "premium_monthly",
                "premium_annual"
            ])
            // Display products in UI
            updateUI(with: products)
        } catch {
            print("Failed to load products: \(error)")
        }
    }

    func purchaseMonthly() async {
        do {
            let transaction = try await AppDNA.billing.purchase("premium_monthly")
            print("Success: \(transaction.transactionId)")
        } catch {
            print("Purchase error: \(error)")
        }
    }

    func checkAccess() async {
        let hasAccess = await AppDNA.billing.hasActiveSubscription()
        if hasAccess {
            unlockPremiumFeatures()
        }
    }

    // MARK: - AppDNABillingDelegate

    func onPurchaseCompleted(productId: String, transaction: TransactionInfo) {
        unlockPremiumFeatures()
    }

    func onPurchaseFailed(productId: String, error: Error) {
        showError(error)
    }

    func onEntitlementsChanged(entitlements: [Entitlement]) {
        refreshAccessState(entitlements)
    }

    func onRestoreCompleted(restoredProducts: [String]) {
        showRestoredAlert(count: restoredProducts.count)
    }
}