---
title: "Совершение покупок в мобильном приложении с помощью Kotlin Multiplatform SDK"
description: "Гайд по обработке встроенных покупок и подписок с использованием Adapty."
---

Отображение пейволов в мобильном приложении — важный шаг для предоставления пользователям доступа к премиум-контенту или сервисам. Однако просто показать пейвол достаточно для проведения покупок лишь в том случае, если вы используете [Paywall Builder](adapty-paywall-builder) для его настройки.

Если вы не используете Paywall Builder, для завершения покупки и открытия нужного контента необходимо вызвать отдельный метод `.makePurchase()`. Именно через него пользователи взаимодействуют с пейволами и совершают нужные транзакции.

Если для продукта, который пользователь пытается купить, настроен активный promotional offer, Adapty автоматически применит его в момент покупки.

:::warning
Обратите внимание: introductory offer применяется автоматически только при использовании пейволов, настроенных через Paywall Builder.

В остальных случаях нужно [проверить право пользователя на introductory offer на iOS](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios). Пропуск этого шага может привести к отклонению приложения при релизе, а также к списанию полной цены с пользователей, которым доступен introductory offer.
:::

Убедитесь, что вы выполнили [начальную настройку](quickstart), не пропустив ни одного шага. Без неё валидация покупок невозможна.

## Совершение покупки \{#make-purchase\}

:::note
**Используете [Paywall Builder](adapty-paywall-builder)?** Покупки обрабатываются автоматически — этот шаг можно пропустить.

**Нужны пошаговые инструкции?** Смотрите [гайд по быстрому старту](kmp-implement-paywalls-manually) с полным описанием реализации.
:::

```kotlin showLineNumbers

Adapty.makePurchase(product = product).onSuccess { purchaseResult ->
    when (purchaseResult) {
        is AdaptyPurchaseResult.Success -> {
            val profile = purchaseResult.profile
            if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) {
                // Grant access to the paid features
            }
        }
        is AdaptyPurchaseResult.UserCanceled -> {
            // Handle the case where the user canceled the purchase
        }
        is AdaptyPurchaseResult.Pending -> {
            // Handle deferred purchases (e.g., the user will pay offline with cash)
        }
    }
}.onError { error ->
    // Handle the error
}
```

Параметры запроса:

| Параметр    | Обязательность | Описание                                                                                                                                      |
| :---------- | :------------- |:----------------------------------------------------------------------------------------------------------------------------------------------|
| **Product** | обязательный   | Объект [`AdaptyPaywallProduct`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall-product/), полученный из пейвола. |

Параметры ответа:

| Параметр | Описание                                                                                                                                                                                                                                                                                                                                                                                                                        |
|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Profile** | <p>При успешном запросе ответ содержит этот объект. Объект [AdaptyProfile](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-profile/) предоставляет исчерпывающую информацию об уровнях доступа пользователя, подписках и разовых покупках внутри приложения.</p><p>Проверьте статус уровня доступа, чтобы определить, есть ли у пользователя необходимый доступ к приложению.</p> |

:::warning
**Примечание:** если вы всё ещё используете StoreKit версии ниже 2.0 и Adapty SDK версии ниже 2.9.0, вам нужно указать [общий секрет Apple App Store](app-store-connection-configuration#step-5-enter-app-store-shared-secret). Этот метод в настоящее время признан устаревшим компанией Apple.
:::

## Смена подписки при совершении покупки \{#change-subscription-when-making-a-purchase\}

Когда пользователь выбирает новую подписку вместо продления текущей, поведение зависит от магазина приложений. В Google Play подписка не обновляется автоматически — переключение нужно реализовать в коде мобильного приложения, как описано ниже.

Чтобы заменить подписку на другую в Android, вызовите метод `.makePurchase()` с дополнительным параметром:

```kotlin showLineNumbers

val subscriptionUpdateParams = AdaptyAndroidSubscriptionUpdateParameters(
    oldSubVendorProductId = "old_subscription_product_id",
    replacementMode = AdaptyAndroidSubscriptionUpdateReplacementMode.CHARGE_FULL_PRICE
)

val purchaseParams = AdaptyPurchaseParameters.Builder()
    .setSubscriptionUpdateParams(subscriptionUpdateParams)
    .build()

Adapty.makePurchase(
    product = product,
    parameters = purchaseParams
).onSuccess { purchaseResult ->
    when (purchaseResult) {
        is AdaptyPurchaseResult.Success -> {
            val profile = purchaseResult.profile
            // successful cross-grade
        }
        is AdaptyPurchaseResult.UserCanceled -> {
            // user canceled the purchase flow
        }
        is AdaptyPurchaseResult.Pending -> {
            // the purchase has not been finished yet, e.g. user will pay offline by cash
        }
    }
}.onError { error ->
    // Handle the error
}

```
Дополнительный параметр запроса:

| Параметр       | Обязательность | Описание                                                                                                                                                                                                                                                                                      |
|:---------------|:---------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **parameters** | необязательный | Объект [`AdaptyAndroidSubscriptionUpdateParameters`](https://kmp.adapty.io/////adapty/com.adapty.kmp.models/-adapty-android-subscription-update-parameters/), передаваемый через [`AdaptyPurchaseParameters`](https://kmp.adapty.io/adapty/com.adapty.kmp.models/-adapty-purchase-parameters/). |

Подробнее о подписках и режимах замены можно прочитать в документации Google для разработчиков:

- [О режимах замены](https://developer.android.com/google/play/billing/subscriptions#replacement-modes)
- [Рекомендации Google по режимам замены](https://developer.android.com/google/play/billing/subscriptions#replacement-recommendations)
- Режим замены [`CHARGE_PRORATED_PRICE`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#CHARGE_PRORATED_PRICE()). Примечание: этот метод доступен только при повышении уровня подписки. Понижение не поддерживается.
- Режим замены [`DEFERRED`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#DEFERRED()). Примечание: реальная смена подписки произойдёт только по окончании текущего расчётного периода.

## Погашение промокодов в iOS \{#redeem-offer-codes-in-ios\}

---
no_index: true
---
import Callout from '../../../components/Callout.astro';

<Details>
<summary>Об офферных кодах</summary>

Офферные коды позволяют предоставлять скидки или бесплатные пробные периоды конкретным пользователям. В отличие от обычных офферов, которые применяются автоматически, офферные коды распространяются за пределами приложения — через email-рассылки, социальные сети или печатные материалы. Пользователи активируют их, вводя код в App Store, переходя по ссылке для активации или через диалог внутри приложения.

Чтобы настроить офферные коды, откройте подписку в App Store Connect и перейдите в раздел **Offer Codes**. Вы можете создать [три вида](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-subscription-offer-codes) офферных кодов:

- **Free** — подписка бесплатна на заданный период, следующее продление — по полной цене.
- **Pay as you go** — пользователь платит сниженную цену в каждом расчётном периоде на протяжении заданного срока, после чего подписка продлевается по полной цене.
- **Pay up front** — пользователь единовременно платит сниженную цену за весь срок оффера, после чего подписка продлевается по полной цене.

Добавлять офферные коды в Adapty не нужно. Apple помечает каждую транзакцию в период действия оффера категорией офферного кода. Это касается как первоначальной активации, так и всех последующих продлений со скидкой. Adapty обнаруживает метку и записывает каждую транзакцию с категорией оффера `offer_code`. Как только период оффера заканчивается и подписка продлевается по полной цене, метка исчезает. Вы можете фильтровать аналитику по типу оффера **Offer Code** в [дашборде Adapty](controls-filters-grouping-compare-proceeds).

#### Устранение расхождений в выручке \{#revenue-discrepancy-troubleshooting\}

Если транзакция по офферному коду отображается в Adapty по полной цене продукта вместо сниженной цены оффера, проверьте следующее в App Store Connect:

- Для офферного кода настроены корректные цены для всех регионов, где пользователи могут его активировать.
- Цена оффера задана для конкретной страны или региона пользователя. Apple передаёт региональную цену в транзакции. Если для оффера не настроена региональная цена, Apple может передать полную цену продукта.

Вы можете фильтровать и проверять транзакции по офферным кодам в [дашборде Adapty](controls-filters-grouping-compare-proceeds) по фильтрам типа оффера **Offer Code** и **Offer Discount Type**.

#### Устаревшие промокоды (deprecated) \{#legacy-promo-codes-deprecated\}

<Callout type="warning">
Apple прекратила поддержку промокодов для встроенных покупок в марте 2026 года. Офферные коды заменяют их с расширенными возможностями: настраиваемые условия применения, сроки действия и до 1 миллиона кодов в квартал. Если вы ранее использовали промокоды для встроенных покупок, перейдите на офферные коды в App Store Connect.
</Callout>

Устаревшие промокоды (не более 100 на приложение на версию) предоставляли бесплатный доступ к подписке. В отличие от офферных кодов, Apple не включала информацию о скидке в транзакции по промокодам — в чеке указывалась полная цена продукта. В результате Adapty записывал эти транзакции по полной цене, что приводило к расхождениям в выручке между аналитикой Adapty и App Store Connect.

Если вы видите исторические транзакции по полной цене, которые должны были быть бесплатными, скорее всего, они связаны с устаревшими промокодами. Поскольку эти коды больше не поддерживаются, перейдите на офферные коды для точного учёта выручки.

</Details>

Чтобы отобразить в приложении экран погашения кода:

```kotlin showLineNumbers
Adapty.presentCodeRedemptionSheet()
    .onSuccess {
        // code redemption sheet presented successfully
    }
    .onError { error ->
        // handle the error
    }
```

:::danger
По нашим наблюдениям, экран погашения промокодов в некоторых приложениях может работать нестабильно. Рекомендуем направлять пользователя напрямую в App Store.

Для этого откройте URL следующего формата:
`https://apps.apple.com/redeem?ctx=offercodes&id={apple_app_id}&code={code}`
:::

## Управление предоплаченными планами (Android) \{#manage-prepaid-plans-android\}

Если пользователи вашего приложения могут приобретать [предоплаченные планы](https://developer.android.com/google/play/billing/subscriptions#prepaid-plans) (например, покупать неавтопродлевающуюся подписку на несколько месяцев), вы можете включить [отложенные транзакции](https://developer.android.com/google/play/billing/subscriptions#pending) для таких планов.

```kotlin showLineNumbers

Adapty.activate(
    AdaptyConfig.Builder("PUBLIC_SDK_KEY")
        .withGoogleEnablePendingPrepaidPlans(true)
        .build()
).onSuccess {
    // successful activation
}.onError { error ->
    // handle the error
}
```