Purchases
The SDK allows applications to synchronize subscription status after successful purchases and subscription state changes.
Always update subscriptionStatus and call the appropriate tracking logic after a successful purchase.
Subscription status synchronization
Use the subscriptionStatus property to provide the current subscription state.
import UIKit
import Magify
class ViewController: UIViewController {
func handleSubscription() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
appDelegate.magify.subscriptionStatus = .active(isTrial: false)
}
}
Update the subscription status whenever the user's subscription becomes active, expires, or changes state. Available states:
.active(isTrial:)— an active subscription (isTrial: truewhile in a trial)..expired(isTrial:)— a previously active subscription that has lapsed (maps topaid_cancelled/trial_cancelledin analytics). Use this for churned subscribers, not.inactive..inactive— the user has never had a subscription.
Purchase flow
After a successful purchase, synchronize the subscription state with the SDK.
import UIKit
import Magify
class ViewController: UIViewController {
func handlePurchase() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
}
}
Tracking in-app purchases
Call trackInApp after a successful StoreKit transaction. Pass the price as a Decimal or as a pre-formatted string.
// Decimal overload — SDK converts to string internally
magify.trackInApp(
productId: product.productIdentifier,
price: Decimal(string: product.price.stringValue) ?? 0,
currency: product.priceLocale.currencyCode ?? "USD",
transactionId: transaction.transactionIdentifier,
originalTransactionId: transaction.original?.transactionIdentifier
)
// String overload — supply needValidation: false to skip server-side validation
magify.trackInApp(
productId: product.productIdentifier,
priceString: product.localizedPrice,
currency: "USD",
transactionId: transaction.transactionIdentifier,
originalTransactionId: nil,
needValidation: false
)
Tracking subscription activations
Call trackSubscriptionActivation after a new subscription purchase or trial start.
magify.trackSubscriptionActivation(
isTrial: false,
productId: "com.example.premium_monthly",
priceString: "9.99",
currency: "USD",
period: "P1M",
transactionId: transaction.transactionIdentifier,
originalTransactionId: transaction.original?.transactionIdentifier
// needValidation defaults to true
)
Set needValidation: false to skip the receipt upload step — for example when validation is handled on your backend.
Server-side validation
The SDK automatically uploads the App Store receipt for server-side validation after every trackInApp or trackSubscriptionActivation call where needValidation is true (the default).
Enable receipt observation at init time so the SDK also processes background transactions:
let magify = MagifyClient(
for: "MyApp",
defaultConfigURL: configURL,
isSandbox: false,
receiptObservationEnabled: true // default — observes StoreKit queue
)
Set receiptObservationEnabled: false if you drive all validation through trackInApp/trackSubscriptionActivation manually.
External purchases
Use trackExternalInApp and trackExternalSubscriptionActivation for purchases that originate outside the App Store (e.g. web, promotional credits).
// External one-time purchase
magify.trackExternalInApp(
productId: "web_purchase_gold",
priceString: "4.99",
currency: "USD",
transactionId: "web-txn-123",
originalTransactionId: nil
)
// External subscription
magify.trackExternalSubscriptionActivation(
isTrial: false,
productId: "web_premium_monthly",
priceString: "9.99",
currency: "USD",
period: nil,
transactionId: "web-sub-456",
originalTransactionId: nil
)
Both methods accept needValidation: Bool = true to control whether a receipt upload is triggered.
Trusted purchases
Use trusted purchases when your server has already validated the transaction and produced a signed record. The SDK skips re-validation and forwards the record directly.
TrustedPurchaseRecord
Build a TrustedPurchaseRecord from your server's response:
let record = TrustedPurchaseRecord(
productId: "com.example.premium_monthly",
transactionId: "70001234567890",
originalTransactionId: "70001234567890",
purchasedAt: 1700000000.0, // Unix timestamp (seconds)
price: "9.99",
currency: "USD",
commissionAmount: "3.00",
commissionCurrency: "USD",
storeFront: "US", // ISO 3166-1 alpha-2, optional
storeName: .appStore,
type: .initialPurchase,
periodType: .normal,
isTrialConversion: nil,
productIdType: .subscription,
environment: .production // defaults to .production
)
Enums
Sending a trusted record
trackTrustedPurchase(_:isSubscription:isExternal:) routes the record to the correct event type:
// App Store in-app purchase (already validated server-side)
magify.trackTrustedPurchase(record)
// App Store subscription
magify.trackTrustedPurchase(record, isSubscription: true)
// External in-app purchase
magify.trackTrustedPurchase(record, isExternal: true)
// External subscription
magify.trackTrustedPurchase(record, isSubscription: true, isExternal: true)
When both isSubscription and isExternal are nil (the default), the SDK sends the record to the server for its own internal processing without emitting an analytics event. It does not update the local purchased-product registry, so inAppStatus, campaign limits, and purchasedProductIds are unaffected by this call.
Restoring purchases
Call the restore helpers after a StoreKit restore flow completes to update the SDK's internal purchase registry:
// Restore a previously purchased consumable / non-consumable
magify.trackRestoredInApp(productId: "com.example.gold_pack")
// Restore a subscription
magify.trackRestoredSubscription(productId: "com.example.premium_monthly")
These methods do not trigger server-side validation. Call them for every product that appears in the restored transactions list. Both methods update the SDK's local purchased-product registry, which affects inAppStatus, purchasedProductIds, and campaign targeting.
Custom purchase verification
Implement IPurchaseVerificationHandler to intercept validation results from the Magify server and control retry behavior.
import Magify
final class MyVerificationHandler: IPurchaseVerificationHandler {
func handlePurchaseVerification(_ result: PurchaseVerificationResult) -> RepeatState? {
print("Verified \(result.productId) — code: \(result.code)")
switch result.code {
case .success:
return .finish // validation succeeded — remove from queue
case .invalid, .invalidCredentials:
return .finish // invalid receipt — stop retrying
case .cancelled:
return .retry // server cancelled — retry immediately
case .fail, .doesntSupport:
return .wait // transient error — defer to next flush tick
}
}
}
Register the handler at init time or after initialization:
// At init
let magify = MagifyClient(
for: "MyApp",
defaultConfigURL: configURL,
isSandbox: false,
purchaseHandler: MyVerificationHandler()
)
// Or later
magify.purchaseHandler = MyVerificationHandler()
PurchaseVerificationResult
PurchaseVerificationResultCode
RepeatState
Return nil to let the SDK apply its default handling.
Next step
For application-level event tracking, see the Analytics section.