Android SDK Purchases

The SDK allows you to synchronize subscription and in-app purchase status.

Update these values after a successful purchase or subscription.

Subscription status

Update the current subscription status:

magify.subscriptionStatus = SubscriptionState.ACTIVE_PAID

In-app purchase status

The in-app purchase status is read-only — the SDK updates it automatically when you report a purchase with trackInApp, trackExternalInApp, or trackTrustedPurchase.

Read the current value and observe changes:

val status = magify.inAppStatus

magify.inAppStatusChangedCallback = {
    // React to in-app status changes.
}

Tracking in-app purchases

Call trackInApp after a successful Google Play billing flow. The method accepts a Double or String price.

// Double overload
magify.trackInApp(
    productId = "com.example.gold_pack",
    price = 4.99,
    currency = "USD",
    transactionId = purchase.orderId,
    purchaseToken = purchase.purchaseToken,
    billingCountryCode = "US"
)

// String overload
magify.trackInApp(
    productId = "com.example.gold_pack",
    price = "4.99",
    currency = "USD",
    transactionId = purchase.orderId,
    purchaseToken = purchase.purchaseToken
)

Both purchaseToken and billingCountryCode are optional (null is allowed):

  • purchaseToken — the Google Play Billing purchase token (Purchase.purchaseToken). When passed together with transactionId, the SDK automatically sends the purchase to Magify servers for server-side validation against Google Play. Pass null if you don't have it — the purchase is then tracked without Google Play validation.
  • billingCountryCode — the buyer's billing country as a 2-letter ISO 3166-1 alpha-2 code, for example US or DE. Used for revenue and tax reporting. Optional — pass null if unknown.

Tracking subscription activations

Call trackSubscriptionActivation after a new subscription purchase or trial start. Both Double and String price overloads are available.

magify.trackSubscriptionActivation(
    isTrial = false,
    productId = "com.example.premium_monthly",
    price = 9.99,
    currency = "USD",
    transactionId = purchase.orderId,
    purchaseToken = purchase.purchaseToken,
    billingCountryCode = "US"
)

Server-side validation runs automatically when transactionId and purchaseToken are non-null.

trackInApp and trackSubscriptionActivation only fire if the product was previously tapped inside a Magify campaign, tracked via trackProductClick or SimpleCampaignPresenter.

If no linked campaign click is found for the productId, the SDK logs an error and the call is a no-op.

External purchases

Use trackExternalInApp and trackExternalSubscriptionActivation for purchases that originate outside Google Play, for example web payments or promotional credits.

// External one-time purchase
magify.trackExternalInApp(
    productId = "web_purchase_gold",
    price = "4.99",
    currency = "USD",
    transactionId = "web-txn-123",
    purchaseToken = null,
    billingCountryCode = null
)

// External subscription
magify.trackExternalSubscriptionActivation(
    isTrial = false,
    productId = "web_premium_monthly",
    price = "9.99",
    currency = "USD",
    transactionId = "web-sub-456",
    purchaseToken = null
)

External purchases pass purchaseToken = null, and usually billingCountryCode = null, by design — they don't come from Google Play, so there is no Google Play token to validate against.

Provide billingCountryCode, a 2-letter ISO 3166-1 alpha-2 code, if your payment provider reports the buyer's country.

Trusted purchases

Use trusted purchases when your server has already validated the transaction and produced a signed record. The SDK forwards the record without re-validating it.

TrustedPurchaseRecord

Build a TrustedPurchaseRecord from your server's response:

import com.magify.sdk.purchases.trusted.*

val record = TrustedPurchaseRecord(
    productId = "com.example.premium_monthly",
    transactionId = "GPA.1234-5678-9012-34567",
    originalTransactionId = "GPA.1234-5678-9012-34567",
    purchasedAt = 1700000000.0,              // seconds since Unix epoch
    price = "9.99",
    currency = "USD",
    commissionAmount = "3.00",
    commissionCurrency = "USD",
    environment = TrustedPurchaseEnvironment.Production,
    storeFront = "US",                        // optional
    storeName = "google_play",
    type = PurchaseType.InitialPurchase.value,
    periodType = PeriodType.Normal.value,
    isTrialConversion = null,
    productIdType = ProductIdType.SubscriptionProductId.value
)

storeFront is an optional 2-letter ISO 3166-1 alpha-2 country code identifying the App Store storefront where the purchase was made, for example US or GB. Pass null if not available.

Enums

The table below shows each enum class, its members, and the raw .value string sent to the server.

Use .value when building the record — the fields type, periodType, and productIdType are plain String properties.

Sending a trusted record

trackTrustedPurchase routes the record to the correct event type based on the isSubscription and isExternal flags:

// App store in-app purchase (already validated server-side)
magify.trackTrustedPurchase(
    record,
    isSubscription = false,
    isExternal = false
)

// App store subscription
magify.trackTrustedPurchase(
    record,
    isSubscription = true,
    isExternal = false
)

// External in-app purchase
magify.trackTrustedPurchase(
    record,
    isSubscription = false,
    isExternal = true
)

// External subscription
magify.trackTrustedPurchase(
    record,
    isSubscription = true,
    isExternal = true
)

// Server-only processing — no analytics event emitted
magify.trackTrustedPurchase(
    record,
    isSubscription = null,
    isExternal = null
)

Pass billingCountryCode, a 2-letter ISO 3166-1 alpha-2 code, for example US, as the optional fourth parameter when available:

magify.trackTrustedPurchase(
    record,
    isSubscription = true,
    isExternal = false,
    billingCountryCode = "US"
)

For trusted subscription purchases, update subscriptionStatus after sending the record:

magify.trackTrustedPurchase(
    record,
    isSubscription = true,
    isExternal = false
)

magify.subscriptionStatus = SubscriptionState.ACTIVE_PAID

Use the actual subscription state, for example ACTIVE_TRIAL for trial activation or ACTIVE_PAID for paid subscription activation.

Checking if a purchase was processed

Use hasProcessedPurchase to check whether the SDK has already recorded a product:

if (magify.hasProcessedPurchase("com.example.premium_monthly")) {
    // product already tracked
}

Custom purchase verification

Implement IPurchaseVerificationHandler to intercept validation results from the Magify server and control retry behavior.

import com.magify.sdk.handler.IPurchaseVerificationHandler
import com.magify.sdk.handler.PurchaseVerificationResult
import com.magify.sdk.handler.PurchaseVerificationResultCode
import com.magify.sdk.handler.RepeatState

class MyVerificationHandler : IPurchaseVerificationHandler {
    override fun handlePurchaseVerification(
        result: PurchaseVerificationResult
    ): RepeatState? {
        println("Verified ${result.productId} — code: ${result.code}")

        return when (result.code) {
            PurchaseVerificationResultCode.Success ->
                RepeatState.Finish

            PurchaseVerificationResultCode.InvalidPurchase,
            PurchaseVerificationResultCode.InvalidGoogleCredentials ->
                RepeatState.Finish

            PurchaseVerificationResultCode.Cancelled ->
                RepeatState.Retry

            PurchaseVerificationResultCode.Fail,
            PurchaseVerificationResultCode.DoesntSupport ->
                RepeatState.Wait
        }
    }
}

Register the handler after creating the Magify instance:

magify.setPurchaseVerificationHandler(MyVerificationHandler())

Clear the handler when it is no longer needed:

magify.setPurchaseVerificationHandler(null)

PurchaseVerificationResult

PurchaseVerificationResultCode

RepeatState

Return null to let the SDK apply its default handling.

Retry interval for pending validations

verificationRetryInterval controls how long the SDK waits, in milliseconds, between retries when a purchase validation is pending.

The default is 60000 ms, and the minimum enforced value is 1000 ms — values below the minimum are silently raised to 1000.

magify.verificationRetryInterval = 3000L  // retry every 3 s

This setting affects only the retry polling for purchase records that are pending server validation, for example when the network is temporarily unavailable. It does not affect the initial validation request.

Observing status changes (RxJava2)

Subscribe to status change streams to react to purchase-driven state transitions:

magify.observeSubscriptionStatusChanged()
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe { /* subscription status changed */ }

magify.observeInAppStatusChanged()
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe { /* in-app status changed */ }

magify.observePurchasedProductsChanged()
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe { /* purchased product set changed */ }

Next step

To integrate ad tracking and synchronize ad-related events with the Magify Android SDK, see the Advertisement section.

Related articles

CreativeBackground

RevenueCat

NetworkStatus

UnityPurchasing

Obtainers

AghanimProductCounting