Make purchases in mobile app in Capacitor SDK

Displaying paywalls within your mobile app is an essential step in offering users access to premium content or services. However, presenting a paywall supports purchases on its own only when Adapty renders the screen — that is, a flow, or a paywall from the old Paywall Builder.

If you render the screen in your own code, 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.

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

Make purchase

Note

Does Adapty render your screen? For a flow or a Paywall Builder paywall, purchases are processed automatically—you can skip this step.

Looking for step-by-step guidance? Check out the quickstart guide for end-to-end implementation instructions with full context.

import { adapty } from '@adapty/capacitor';

try {
  const result = await adapty.makePurchase({ product });
  
  if (result.type === 'success') {
    const isSubscribed = result.profile?.accessLevels?.['YOUR_ACCESS_LEVEL']?.isActive;
    
    if (isSubscribed) {
      // Grant access to the paid features
      console.log('User is now subscribed!');
    }
  } else if (result.type === 'user_cancelled') {
    console.log('Purchase cancelled by user');
  } else if (result.type === 'pending') {
    console.log('Purchase is pending');
  }
} catch (error) {
  console.error('Purchase failed:', error);
}

Request parameters:

ParameterPresenceDescription
productrequiredAn AdaptyPaywallProduct object retrieved from the flow via getPaywallProducts.

Response parameters:

ParameterDescription
resultAn AdaptyPurchaseResult object with a type field indicating the purchase outcome ('success', 'user_cancelled', or 'pending') and a profile field containing the updated AdaptyProfile on successful purchases.

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:

import { adapty } from '@adapty/capacitor';

try {
  const result = await adapty.makePurchase({ 
    product,
    params: {
      android: {
        subscriptionUpdateParams: {
          oldSubVendorProductId: 'old_product_id',
          prorationMode: 'charge_prorated_price'
        },
        isOfferPersonalized: true
      }
    }
  });
  
  if (result.type === 'success') {
    const isSubscribed = result.profile?.accessLevels?.['YOUR_ACCESS_LEVEL']?.isActive;
    
    if (isSubscribed) {
      // Grant access to the paid features
      console.log('Subscription updated successfully!');
    }
  } else if (result.type === 'user_cancelled') {
    console.log('Purchase cancelled by user');
  } else if (result.type === 'pending') {
    console.log('Purchase is pending');
  }
} catch (error) {
  console.error('Purchase failed:', error);
}

Additional request parameter:

ParameterPresenceDescription
paramsoptionalAn object of the MakePurchaseParamsInput type containing platform-specific purchase parameters.

The MakePurchaseParamsInput structure includes:

{
  android: {
    subscriptionUpdateParams: {
      oldSubVendorProductId: 'old_product_id',
      prorationMode: 'charge_prorated_price'
    },
    isOfferPersonalized: true
  }
}

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

Manage prepaid plans (Android)

If your app users can purchase prepaid plans (e.g., buy a non-renewable subscription for several months), you can enable pending transactions for prepaid plans.

await adapty.activate({
  apiKey: 'YOUR_PUBLIC_SDK_KEY',
  params: {
    android: {
        pendingPrepaidPlansEnabled: true,
    },
  }
});

Redeem offer codes in iOS

About offer codes

Offer codes allow you to give discounts or free trials to specific users. Unlike regular offers that are applied automatically, offer codes are distributed outside the app — through email campaigns, social media, or printed materials. Users redeem them by entering the code in the App Store, following a redemption URL, or through an in-app dialog.

To set up offer codes, open a subscription in App Store Connect and go to its Offer Codes section. You can create three kinds of offer codes:

  • Free — the subscription is free for a set duration, and the next renewal is at full price.
  • Pay as you go — the user pays a discounted price each billing cycle for a set duration, then the subscription renews at full price.
  • Pay up front — the user pays a single discounted price for the entire offer duration, then the subscription renews at full price.

You don’t need to add offer codes to Adapty. Apple tags every transaction during the offer period with the offer code category. This includes the initial redemption and all subsequent discounted renewals. Adapty detects the tag and records each transaction with the offer category offer_code. Once the offer period ends and the subscription renews at full price, the tag is no longer present. You can filter analytics by the Offer Code offer type in the Adapty Dashboard.

Revenue discrepancy troubleshooting

If you notice that an offer code transaction appears in Adapty at the full product price instead of the discounted offer price, verify the following in App Store Connect:

  • The offer code has the correct pricing configured for all regions where users can redeem it.
  • The offer price is set for the specific country or region of the user. Apple sends the regional price in the transaction. If no regional price is configured for the offer, Apple may send the full product price instead.

You can filter and verify offer code transactions in the Adapty Dashboard by the Offer Code offer type and Offer Discount Type filters.

Legacy promo codes (deprecated)

Warning

Apple deprecated promo codes for in-app purchases in March 2026. Offer codes replace them with more capabilities: configurable eligibility, expiration dates, and up to 1 million codes per quarter. If you previously used promo codes for in-app purchases, transition to offer codes in App Store Connect.

Legacy promo codes (limited to 100 per app per version) granted free access to a subscription. Unlike offer codes, Apple did not include discount information in promo code transactions — it sent the full product price in the receipt. As a result, Adapty recorded these transactions at the full price, which caused revenue discrepancies between Adapty analytics and App Store Connect.

If you see historical transactions at full price that should have been free, they are likely from legacy promo codes. Since these codes are now deprecated, transition to offer codes for accurate revenue tracking.

To display the code redemption sheet in your app:

import { adapty } from '@adapty/capacitor';

try {
  await adapty.presentCodeRedemptionSheet();
} catch (error) {
  console.error('Failed to present code redemption sheet:', error);
}
Danger

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}

Promoted in-app purchases from the App Store

Info

Your app can take over promoted in-app purchases from SDK version 4.1.1, on iOS 16.4 or later. Below iOS 16.4, the event never fires and promoted purchases complete on their own. The event never fires on Android either.

When a user starts a purchase from your App Store product page and the transaction carries over to your app, the SDK completes it automatically — the Apple purchase system screen shows right away, and Adapty processes the transaction like any other purchase. This needs no code on your side.

If the promoted product carries a subscription offer, the SDK applies it at purchase automatically. The offer is read from the App Store purchase intent, which exposes it on iOS 18.0 and later. On iOS 16.4–17.x, the purchase goes through at the base price.

To take over completion yourself — for example to show your own screen first — listen for the 'onPromotedPurchaseReceived' event and pass the product to makePromotedPurchase:

import { adapty } from '@adapty/capacitor';

const listener = await adapty.addListener('onPromotedPurchaseReceived', async ({ product }) => {
  const result = await adapty.makePromotedPurchase({ product });
  // process the purchase result
});
Warning

While your listener is registered, the SDK stops completing promoted purchases for you. If your handler never calls makePromotedPurchase, the purchase never happens: the App Store hands the product to your app and waits.

makePromotedPurchase takes no purchase parameters — a promoted product comes from the App Store rather than a paywall, so it carries no paywall context. It returns the same AdaptyPurchaseResult as makePurchase.

Calling adapty.removeAllListeners() drops your promoted-purchase listener along with the rest, and the SDK’s automatic completion resumes in its place. Register the listener again if your app still owns completion.