Making purchases in mobile app

For: Developers

Displaying paywalls within your mobile app is an essential step in offering users access to premium content or services. However, simply presenting these paywalls initiates purchases only if you use Paywall Builder to customize your paywalls. If you don't use the Paywall Builder, you must use a separate method called .makePurchase() to complete a purchase and unlock the desired content. This method serves as the gateway for users to engage with the paywalls and proceed with their desired transactions

If your paywall has an active promotional offer for the product a user is trying to buy, Adapty will automatically apply it at the time of purchase.

🚧

Keep in mind that the introductory offer will be applied automatically only if you use the paywalls set up using the Paywall Builder. In other cases, you'll need to verify the user's eligibility for an introductory offer on iOS. Skipping this step may result in your app being rejected during release. Moreover, it could lead to charging the full price to users who are eligible for an introductory offer.

Make sure you've done the initial configuration without skipping a single step. Without it, we can't validate purchases.

Make purchase if Paywall Builder is not used

📘

If you use the Paywall Builder, please skip this step since the Paywall Builderprocesses making purchase automatically without additional code from you.

If you do not use the Paywall Builder, process making purchases by calling the makePurchase method. If you use the Paywall Builder, skip this step since the Paywall Builder processes making and restoring purchases automatically.

Adapty.makePurchase(product: product) { result in
    switch result {
    case let .success(info):
      if info.profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false {
        // successful purchase
      }
    case let .failure(error):
        // handle the error
    }
}
Adapty.makePurchase(activity, product) { result ->
    when (result) {
        is AdaptyResult.Success -> {
            val info = result.value
            //NOTE: info is null in case of cross-grade with DEFERRED proration mode
            val profile = info?.profile
        
            if (profile?.accessLevels?.get("YOUR_ACCESS_LEVEL")?.isActive == true) {
                // grant access to premium features
            }
        }
        is AdaptyResult.Error -> {
            val error = result.error
            // handle the error
        }
    }
}
Adapty.makePurchase(activity, product, result -> {
    if (result instanceof AdaptyResult.Success) {
        AdaptyPurchasedInfo info = ((AdaptyResult.Success<AdaptyPurchasedInfo>) result).getValue();
        //NOTE: info is null in case of cross-grade with DEFERRED proration mode
        AdaptyProfile profile = info != null ? info.getProfile() : null;
        
      	if (profile != null) {
            AdaptyProfile.AccessLevel premium = profile.getAccessLevels().get("YOUR_ACCESS_LEVEL");
            
          	if (premium != null && premium.isActive()) {
                // successful purchase
            }
        }
    } else if (result instanceof AdaptyResult.Error) {
        AdaptyError error = ((AdaptyResult.Error) result).getError();
        // handle the error
    }
});
try {
  final profile = await Adapty().makePurchase(product: product);
  if (profile?.accessLevels['YOUR_ACCESS_LEVEL']?.isActive ?? false) {
		// successful purchase      
  }
} on AdaptyError catch (adaptyError) {
	// handle the error
} catch (e) {
}
Adapty.MakePurchase(product, (profile, error) => {
  if(error != null) {
      // handle error
      return;
  }
  
  var accessLevel = profile.AccessLevels["YOUR_ACCESS_LEVEL"];
  if (accessLevel != null && accessLevel.IsActive) {
      // grant access to features
  }
});
try {
	const profile = await adapty.makePurchase(product);
  const isSubscribed = profile?.accessLevels['YOUR_ACCESS_LEVEL']?.isActive;
  
	if (isSubscribed) {
		// grant access to features in accordance with access level
	}
} catch (error) {
	// handle the error
}

Request parameters:

ParameterPresenceDescription
ProductrequiredAn AdaptyPaywallProduct object retrieved from the paywall.

Response parameters:

ParameterDescription
ProfileAn AdaptyProfile object provides comprehensive information about a user's access levels, subscriptions, and non-subscription purchases within the app.
Check the access level status to ascertain whether the user has the required access to the app.

Below is a complete example of making the purchase of the access level premium. Premium is the default access level, so in most cases, your code will look this way:

Adapty.makePurchase(product: product) { result in
    switch result {
    case let .success(info):
        if info.profile.accessLevels["premium"]?.isActive ?? false {
            // grant access to premium features
        }
    case let .failure(error):
        // handle the error
    }
}
Adapty.makePurchase(activity, product) { result ->
    when (result) {
        is AdaptyResult.Success -> {
            val info = result.value
            //NOTE: info is null in case of cross-grade with DEFERRED proration mode
            val profile = info?.profile
            
            if (profile?.accessLevels?.get("premium")?.isActive == true) {
                // grant access to premium features
            }
        }
        is AdaptyResult.Error -> {
            val error = result.error
            // handle the error
        }
    }
}
Adapty.makePurchase(activity, product, result -> {
    if (result instanceof AdaptyResult.Success) {
        AdaptyPurchasedInfo info = ((AdaptyResult.Success<AdaptyPurchasedInfo>) result).getValue();
        //NOTE: info is null in case of cross-grade with DEFERRED proration mode
        AdaptyProfile profile = info != null ? info.getProfile() : null;
        
      	if (profile != null) {
            AdaptyProfile.AccessLevel premium = profile.getAccessLevels().get("premium");
            
          	if (premium != null && premium.isActive()) {
                // grant access to premium features
            }
        }
    } else if (result instanceof AdaptyResult.Error) {
        AdaptyError error = ((AdaptyResult.Error) result).getError();
        // handle the error
    }
});
try {
  final profile = await Adapty().makePurchase(product: product);
  if (profile?.accessLevels['premium']?.isActive ?? false) {
		// grant access to premium features      
  }
} on AdaptyError catch (adaptyError) {
	// handle the error
} catch (e) {
}
Adapty.MakePurchase(product, (profile, error) => {
  if(error != null) {
      // handle error
      return;
  }
  
  // "premium" is an identifier of default access level
  var accessLevel = profile.AccessLevels["premium"];
  if (accessLevel != null && accessLevel.IsActive) {
      // grant access to premium features
  }
});
try {
	const profile = await adapty.makePurchase(product);
	const isSubscribed = profile?.accessLevels['premium']?.isActive;
  
	if (isSubscribed) {
		// grant access to premium features
	}
} catch (error) {
	// handle the error
}

🚧

Note: if you're still on Apple's StoreKit version lower than v2.0 and Adapty SDK version lowers than v.2.9.0, you need to provide Apple App Store shared secret instead. This method is currently deprecated by Apple.

Change subscription when making a purchase

When a user opts for a new subscription instead of renewing the current one, the way it works depends on the app store:

  • For the App Store, the subscription is automatically updated within the subscription group. If a user purchases a subscription from one group while already having a subscription from another, both subscriptions will be active at the same time.
  • For Google Play, the subscription isn't automatically updated. You'll need to manage the switch in your mobile app code as described below.

To replace the subscription with another one in Android, call .makePurchase() method with the additional parameter:

Adapty.makePurchase(activity, product, subscriptionUpdateParams) { result ->
    when (result) {
        is AdaptyResult.Success -> {
            val info = result.value
            //NOTE: info is null in case of cross-grade with DEFERRED proration mode
            val profile = info?.profile
            
            // successful cross-grade
        }
        is AdaptyResult.Error -> {
            val error = result.error
            // handle the error
        }
    }
}
Adapty.makePurchase(activity, product, subscriptionUpdateParams, result -> {
    if (result instanceof AdaptyResult.Success) {
        AdaptyPurchasedInfo info = ((AdaptyResult.Success<AdaptyPurchasedInfo>) result).getValue();
        //NOTE: info is null in case of cross-grade with DEFERRED proration mode
        AdaptyProfile profile = info != null ? info.getProfile() : null;
        
      	// successful cross-grade
    } else if (result instanceof AdaptyResult.Error) {
        AdaptyError error = ((AdaptyResult.Error) result).getError();
        // handle the error
    }
});
// TODO: add example
// TODO: add example
// TODO: add example

Additional request parameter:

ParameterPresenceDescription
subscriptionUpdateParamsrequiredan AdaptySubscriptionUpdateParameters object.

You can read more about subscriptions and proration modes in the Google Developer documentation:

Make a deferred purchase in iOS

For deferred purchases on iOS, Adapty SDK has an optional delegate method, which is called when the user starts the purchase in the App Store, and the transaction continues in your app. Just store makeDeferredPurchase and call it later if you want to hold your purchase for now. Then show the paywall to your user. To continue purchase, call makeDeferredPurchase.

extension AppDelegate: AdaptyDelegate {
    func paymentQueue(shouldAddStorePaymentFor product: AdaptyDeferredProduct, defermentCompletion makeDeferredPurchase: @escaping (ResultCompletion<AdaptyPurchasedInfo>?) -> Void) {
        // you can store makeDeferredPurchase callback and call it later
        
        // or you can call it right away
        makeDeferredPurchase { result in
            // check the purchase
        }
    }
}

Redeem Offer Code in iOS

Since iOS 14.0, your users can redeem Offer Codes. Code redemption means using a special code, like a promotional or gift card code, to get free access to content or features in an app or on the App Store. To enable users to redeem offer codes, you can display the offer code redemption sheet by using the appropriate SDK method:

Adapty.presentCodeRedemptionSheet()
adapty.presentCodeRedemptionSheet();

❗️

Based on our observations, the Offer Code Redemption sheet in some apps may not work reliably. We recommend redirecting the user directly to the App Store.

In order to do this, you need to open the url of the following format:
https://apps.apple.com/redeem?ctx=offercodes&id={apple_app_id}&code={code}