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
| Value | Description |
|---|
.storeKit2 | Native StoreKit 2 integration (default) |
.revenueCat | RevenueCat integration |
.adapty(apiKey:) | Adapty integration with your API key |
.none | Disable 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
| Property | Type | Description |
|---|
id | String | Product identifier |
displayName | String | Localized product name |
description | String | Localized product description |
price | Decimal | Numeric price value |
displayPrice | String | Formatted price string (e.g., “$9.99”) |
subscription | SubscriptionInfo? | 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
| Property | Type | Description |
|---|
transactionId | String | App Store transaction identifier |
productId | String | Purchased product identifier |
purchaseDate | Date | Date of the purchase |
environment | String | Transaction 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
| Property | Type | Description |
|---|
identifier | String | Entitlement identifier |
isActive | Bool | Whether the entitlement is currently active |
expiresAt | Date? | Expiration date, if applicable |
productId | String | Product 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:
| Event | Triggered When |
|---|
purchase_started | A purchase flow is initiated |
purchase_completed | A purchase completes successfully |
purchase_failed | A purchase fails with an error |
purchase_canceled | The user cancels the purchase dialog |
restore_started | A restore operation begins |
restore_completed | A 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)
}
}