Как интегрировать покупки из приложения iOS в проекты React Native

Недавно я опубликовал обновленное пошаговое руководство по покупке в приложении на 2021 год с использованием expo-in-app-purchases, которое я считаю лучшим решением для покупки в приложении для React Native на react-native-iap. Полная реализация вместе с инструкциями по установке Android представлена ​​в этой статье:



В этом разделе рассказывается о моей первоначальной реализации react-native-iap и подробных инструкциях по настройке на этой стороне iOS.

Монетизация приложений React Native с помощью покупок внутри приложения

В этой статье основное внимание уделяется реализации покупок из приложения (IAP) в проекте React Native. Пакет react-native-iap, который мы будем использовать, поддерживает как iOS, так и Android, но эта часть будет посвящена исключительно интеграции с iOS.

В зависимости от того, что требует ваше приложение, на вашем портале разработчиков Apple настраиваются либо одноразовые покупки, либо уровни подписки (или и то, и другое). После настройки react-native-iap может взаимодействовать с этими элементами с помощью своего ряда API, которые включают получение IAP с локализованными ценами, выполнение транзакций и получение ваших прошлых транзакций - все, что вам нужно для монетизации ваших приложений.

Локализованное ценообразование можно включить в App Store Connect, просто включив эту функцию, что позволит получать локализованные цены в ваше приложение через react-native-iap. Это отличная экономия времени - и есть другие функции локализации, которые Apple поддерживает на портале. Мы изучим портал для настройки уровней подписки в специальном разделе ниже.

Разработка IAP

Сам поток транзакций покупки в приложении в значительной степени управляется встроенными подсказками iOS, о которых нам, как разработчикам, не нужно беспокоиться. Например. когда мы нажимаем на уровень продукта или подписки, iOS управляет запросом подтверждения и даже распознает, подписался ли пользователь на определенный уровень. Вместо этого наше внимание следует сосредоточить на следующих вещах:

  • Обратные вызовы событий транзакции, когда транзакция завершена или произошла ошибка, чтобы приложение UX могло обновлять и отражать текущее состояние транзакции. Мы рассмотрим эту настройку в следующем разделе, где будет представлена ​​react-native-iap, со сценариями для встраивания ее в проект React Native.
  • Как отображаются уровни подписки с указанием сроков, цен и сбережений на переднем крае пользовательского опыта - наряду с любым активным планом, на который в настоящее время оформлена подписка. Ниже мы кратко рассмотрим структуру таблицы уровней, демонстрирующую, как аккуратно отображать планы подписки.
  • Как уровни подписки загружаются и сохраняются в приложении. Поскольку вы хотите, чтобы уровни были легко доступны, когда пользователь посещает экран подписки, мы хотим получать подписки при первой загрузке приложения и сохранять их либо в AsyncStorage, либо в состоянии с Redux или аналогичном пакете. Мы рассмотрим это тоже ниже.

Мы также расскажем, как настроить покупки в приложении на портале разработчиков, прежде чем продемонстрируем реализацию react-native-iap в функциональных компонентах, которые будут использоваться в приложении на основе React Native.

Настройка покупок в приложении для iOS

Давайте сначала узнаем, где в первую очередь настроить покупки в приложении, которые вы хотите разместить в своем приложении. через App Store Connect.

Управление IAP

Управление IAP осуществляется в App Store Connect для отдельных приложений в разделе Функции настроек вашего приложения. Как добраться:

  • В App Store Connect нажмите Мои приложения, а затем - приложение, в которое вы хотите добавить встроенные покупки.
  • Перейдите на вторую вкладку Функции, где по умолчанию будет отображаться раздел Покупки в приложении.

В этой полезной панели управления мы можем добавить элементы покупки, состоящие либо из разовых покупок, либо из подписок, а также функцию «льготного периода выставления счетов», которая откладывает завершение подписки до тех пор, пока не пройдет определенный период времени после истечения срока действия подписки.

Мы можем настроить четыре «типа» покупки, состоящие либо из модели разовой покупки, либо из модели подписки.

Первые два типа помечены как «расходные материалы» и «не расходные материалы», которые относятся к предметам, которые могут быть израсходованы, по сравнению с предметами, которые всегда присутствуют. Эти предметы приобретаются один раз и только один раз. Два других типа основаны на подписке, «автоматически возобновляемая подписка» или «невозобновляемая подписка». Нас будет интересовать категория «Автоматически продлеваемая подписка» для этого продукта.

Модели монетизации приложений на основе подписки сейчас более популярны, чем когда-либо, и часто являются предпочтительным методом монетизации приложения как услуги. Модель единовременной покупки в основном применяется для игр и разблокируемых функций.

Обычные мобильные игры, не требующие особого ухода за сервером, подходят для монетизации, тогда как сервисы с постоянной высокой нагрузкой на сервер лучше подходят для модели подписки.

Щелкните значок + рядом с подзаголовком Покупки в приложении, чтобы добавить IAP, и выполните двухэтапный процесс выбора его типа, за которым следует его имя и ссылочный номер:

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

Там, где вы размещаете много приложений, использование com.org.app.iapId может быть более управляемым соглашением об идентификаторах продукта, гарантирующим отсутствие перекрывающихся значений между приложениями. Если вы работаете в сфере электронной коммерции и размещаете у себя интернет-магазин, идентификаторы продуктов в стиле SKU с заглавной буквы также подойдут.

Отсюда вы можете перейти к каждому предмету покупки и настроить ряд метаданных - вот некоторые из наиболее интересных функций, которые вы можете настроить:

  • Стоимость подписки с переключателем для автоматического создания цен для каждой поддерживаемой валюты. Это очень полезно для отображения цен в приложении, как мы увидим дальше, когда react-native-iap выберет эти локализованные цены на основе настроек устройства.
  • Рекламные и вводные предложения для привлечения новых клиентов. Могут быть установлены конкретные даты начала и окончания (или даже не дата окончания для текущей рекламной акции), а также конкретный тип оплаты.

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

  • Промо-изображение, отображаемое в App Store для IAP, может иметь размер до 1024x1024 пикселей. Это ключ к тому, чтобы ваши уровни или ключевые продукты были больше представлены в App Store, чтобы еще больше убедить потенциальных покупателей попробовать приложение.
  • Локализованное название и описание IAP в выбранных вами странах. Если вы ориентируетесь на определенные страны, отличные от вашего основного языка, это также ключевая функция, которую можно использовать для увеличения конверсии.

Обязательно ознакомьтесь со всеми вариантами, доступными для вашего товара для покупок в приложении, чтобы адаптировать ваше предложение.

Группирование IAP с группами подписки

Если вы обнаружите, что создаете много IAP, которые предлагают разные варианты одного предложения (разные временные рамки, доступ к функциям и т. Д.), Тогда рекомендуется воспользоваться группами подписки. Это можно сделать в разделе Покупки в приложении - ›Группы подписки.

Группа, в которой находится IAP, затем отображается вверху страницы настроек IAP под полем Название группы, обеспечивая быстрый доступ к группе. Кроме того, действия пользователя будут влиять на всю группу в различных сценариях. Воспользуйтесь предложением о бесплатной пробной версии, которое можно активировать только один раз - имея бесплатную пробную версию для одного элемента группы, вы не сможете получить еще один после этого, перейдя на другой элемент в группе. Apple подробно описывает группы подписки на странице Автоматически возобновляемые подписки.

Я обычно создаю группу подписки, даже если соответствующая группа разработчиков размещает только одно приложение. Это добавляет дополнительный уровень организации и ясности в настройку и действует как ярлык для доступа к другим IAP в группе.

Мы продолжим и извлечем настроенные IAP ниже при обсуждении таблицы планов подписки.

Отсюда предполагается, что у вас настроены элементы подписки и вы готовы загрузить их в проект React Native. В следующем разделе будет рассказано о react-native-iap и о том, как реализовать IAP в React Native.

Интеграция IAP в React Native

react-native-iap - очень полезный пакет служебных программ, который упрощает работу с IAP в проектах React Native. Несмотря на то, что документация по использованию пакета невелика, часто кажется запутанной и сложной для понимания, пакет регулярно поддерживается и действительно является надежным средством управления IAP для платформ iOS и Android. Эта часть предлагает альтернативное пошаговое руководство по реализации пакета, предоставляя более понятное руководство.

Пакет полагается на прослушиватели событий для обработки транзакций.

react-native-iap отслеживает входящие транзакции с помощью прослушивателей событий, которые запускаются при совершении новой покупки или при ошибке обработки транзакции. Мы, разработчики, должны определить логику внутри этих функций обработчика событий для обработки транзакций, что также будет включать вызов других конкретных API-интерфейсов из пакета для завершения самой транзакции.

Пакет поддерживает Typescript, а Gists и примеры кода, которые будут следовать, будут синтаксисом Typescript.

Чтобы фактически вызвать транзакцию для подписки, мы используем requestSubscription функцию react-native-iap. Вот как мы импортируем его в компонент и вызываем, когда пользователь нажимает кнопку «Подписаться»:

// making a purchase with `requestSubscription`
import { requestSubscription } from 'react-native-iap';
export const NewIAP = () => (
  <button onClick={() => requestSubscription('iap-product-id')}>
    Subscribe!
  </button>
);

Отсюда iOS будет отображать подсказки пользователю для подтверждения транзакции. В случае подтверждения срабатывает событие purchaseUpdatedListener, предоставляющее нам информацию о транзакции, которую мы можем обработать и завершить. В случае отмены вместо этого срабатывает событие purchaseErrorSubscription.

Конечно, сначала необходимо инициализировать эти прослушиватели событий, желательно выше по дереву компонентов. Чтобы инициализировать и удалить эти прослушиватели событий в соответствии с компонентами, мы будем использовать ловушку useEffect(), чтобы гарантировать, что они инициализируются после рендеринга компонента и удаляются при размонтировании компонента.

Мы реализуем это в компоненте, который также включает поставщика контекста, который предоставит функциональность IAP всем вашим компонентам. Это позволит всем компонентам в рамках поставщика контекста получить доступ к состоянию диспетчера IAP, что очень важно для обновления UX при обработке IAP.

Резюме решения

Конкретно, мы будем предоставлять контекст React в верхней части вашего дерева компонентов, чтобы не только инициализировать прослушиватели событий IAP при первоначальном рендеринге вашего приложения, но также предоставить контекст состояния IAP - точнее, того, обрабатывается ли IAP в данный момент. processing и setProcessing будут предоставлены через ловушку useState, которые затем передаются поставщику контекста.

Теперь, когда дочерний компонент вызывает функцию requestSubscription, эти прослушиватели событий будут готовы обработать и завершить транзакцию. Наличие доступа к processing и setProcessing даст другим компонентам возможность обновлять свой UX, чтобы предотвратить дальнейшие запросы на подписку, пока один из них уже обрабатывается (для обработки и завершения транзакции потребуется несколько секунд, в течение которых мы не хотим вызывается другая транзакция).

Давайте посмотрим на код, чтобы все это произошло дальше.

Настройка компонента IAPManager

Во-первых, установите пакет и свяжите его с папкой проекта iOS:

// install and link react-native-iap
yarn add react-native-iap
react-native link react-native-iap && cd ios && pod install

В дополнение к этому, убедитесь, что в ваш проект Xcode добавлена ​​возможность покупки в приложении. Выбрав приложение в навигаторе проекта, выберите вкладку Подписание и возможности и нажмите + Возможности. В списке выберите Покупка в приложении.

Теперь, чтобы сохранить все функции IAP в одном компоненте, мы будем обертывать react-native-iap функции в контексте React и предоставлять другим компонентам доступ для обработки транзакций.

Суть, которой следует придерживаться, описывает <IAPManager /> компонент, предназначенный для обтекания вашего приложения. Описание высокоуровневых функций сценария:

  • IAPContext, который предоставляет поля processing, setProcessing и activePlan для заинтересованных компонентов, а также useIap() ловушка для функциональных компонентов для доступа к контексту.
  • Функция для сохранения активного плана подписки через API React Natives AsyncStorage, которая сохранит план на устройстве при последующих посещениях приложения. Эта функция была просто названа storePlanAsync.
  • Компонент для реализации прослушивателей событий и поставщика контекста. Это было названо IAPManagerWrapped, так как оно также будет обернуто состоянием Redux. В этом компоненте также определена функция processNewPurchase для обработки новой транзакции.

processNewPurchase вызывается в прослушивателе событий purchaseUpdatedListener после успешного завершения транзакции.

  • Основной IAPManagerWrapped заключен в оболочку connect() method Redux, предоставляя компоненту любые данные аутентификации, чтобы связать IAP с конкретным пользователем. Экспорт по умолчанию будет это IAPManager HOC.

Вот полная суть:

Этот сценарий довольно длинный, но, надеюсь, с первого взгляда не требует пояснений. Мы разберем это ниже.

Давайте коснемся некоторых аспектов этого скрипта и того, как IAP связаны с самим React.

Как useState используется для обработки транзакций

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

и processing, и setProcessing включены в значение поставщика контекста, предоставляя компонентам доступ для чтения и записи значения. Вызов setProcessing приведет к повторной визуализации вашего дерева компонентов из контекста в те компоненты, которые слушают значение processing, давая всему вашему приложению возможность отразить это состояние.

Это состояние можно получить с помощью контекстного перехвата useIap(), например, в компоненте кнопки подписки:

Таким образом, это состояние дает нам возможность обновлять UX, где это необходимо.

Как useEffect используется для управления слушателями событий

Наши прослушиватели событий IAP инициализируются в useEffect, и на них ссылаются через локальные компонентные переменные, которые изначально были инициализированы как null, то есть purchaseUpdateSubscription и purchaseErrorSubscription соответственно.

Функция возврата useEffect обычно используется для очистки, такой как отключение от веб-сокетов, других внешних API-интерфейсов и отмена подписки на события. Оба вышеупомянутых прослушивателя событий здесь удаляются вызовом remove(), а также сбросом обеих ссылочных переменных на null.

Изучая код внутри прослушивателя событий, мы видим, что именно здесь транзакция завершается и обрабатывается:

// breaking down `purchaseUpdatedListener`
...
purchaseUpdateSubscription = purchaseUpdatedListener(
  async (purchase: InAppPurchase | SubscriptionPurchase) => {
    
    // extracting receipt from provided `purchase` object
    const receipt = purchase.transactionReceipt;
    
    if (receipt) {
      try {
        // iOS only finalisation method
        if (Platform.OS === 'ios') {
          finishTransactionIOS(purchase.transactionId);
        }
        
        // requierd finish transaction 
        await finishTransaction(purchase);
        
        // further processing with component function
        await processNewPurchase(purchase);
      
       } catch (ackErr) {
         // console.log('ackErr', ackErr);
       }
     }
  },
);
...

Там, где вызывается только finishTransactionIOS iOS, API React Native Platform использовался как удобное средство разделения вызовов функций, зависящих от платформы.

Согласно документации, для завершения транзакции требуются и finishTransactionIOS, и finishTransaction. После того, как эти функции будут выполнены и ошибок не будет обнаружено, мы можем продолжить обработку с уверенностью, что транзакция была успешно выполнена. В приведенном выше примере вызывается функция processNewPurchase, которая отправляет запрос на выборку внутреннему серверу для обработки транзакции.

Чтобы завершить поток транзакции, setProcessing устанавливается в false внутри нашей функции processNewPurchase, которая, следовательно, обновляет состояние компонента и запускает повторную визуализацию компонентов прослушивания, отражающую новую покупку.

Асинхронное сохранение активного плана с помощью AsyncStorage

Также обратите внимание, что мы вызываем функцию storePlanAsync в processNewPurchase, даже если сервер возвращает успешный ответ. Это время, когда мы должны зафиксировать активный план AsyncStorage, сохраняя план на устройстве во время посещения приложения. Нарушение storePlanAsync:

const storePlanAsync = async (planData: any) => {
  
  // fetch existing `user_settings` value from AsyncStorage
  const userSettings: any = 
    await AsyncStorage.getItem('user_settings');
  
  // convert string to JSON object
   let json = JSON.parse(userSettings);
  // update with planData
  json.plan = planData;
  // commit updated json to AsyncStorage, as stringified value
  await AsyncStorage.setItem('user_settings', JSON.stringify(json));
}

Если состояние Redux теряется после закрытия приложения, AsyncStorage будет сохранять свои значения до тех пор, пока приложение не будет удалено, что делает его наиболее подходящим местом для хранения глобальной конфигурации на устройстве, такой как активный план пользователя.

Теперь, когда мы изучили реализацию IAP, сейчас самое время посмотреть, как получать и отображать сами уровни подписки или IAP, которые мы настроили ранее в App Store Connect. Это довольно просто по сравнению с тем, что мы делали выше, и будет кратко рассмотрено ниже.

Получение и отображение уровней подписки

Вы можете вспомнить ранее, что для метода requestSubscription() из react-native-iap требуется идентификатор продукта - или SKU, как мы их здесь будем называть, - который совпадает с IAP в App Store Connect. Если у вас есть только определенное количество уровней подписки, вполне нормально включить их в качестве глобальных констант в ваш проект React Native:

// defining IAP SKUs by platform in `constants.ts`
import { Platform } from 'react-native'
export const IAP_SKUS = Platform.select({
  ios: [
    '1MONTH',
    '6MONTHS',
    '12MONTHS'
  ],
});

Обратите внимание, что Platform был использован снова и, вероятно, будет гарантирован, если версия Android вашего приложения будет иметь другую схему именования ваших SKU / идентификаторов продуктов.

Для больших наборов SKU, требующих дополнительного обслуживания, может быть предпочтительнее, чтобы ваше приложение отправляло запрос на выборку с вашего сервера, чтобы получить актуальный список активных IAP.

Как только ваше приложение знает, какие SKU нужно получить, мы можем вызвать другой метод из react-native-iap - метод getProducts. В следующем примере эта функция заключена в отдельную getProducts() функцию компонента, которая также обновляет некоторое состояние после получения продуктов:

// fetching SKU data from App Store Connect
import * as RNIap from 'react-native-iap';
...
const getProducts = async () => {    
  try {
    
    const products: RNIap.Product[] = 
    await RNIap.getProducts(IAP_SKUS); 
    props.setProducts(products);
  
  } catch (err) {
    console.log(err);
  }
}

Если вы продолжите console.log() возвращенные продукты, вы обнаружите, что в нашем распоряжении есть очень полезная информация, включая название и описание IAP, а также локализованные цены, валюту, а также любые рекламные акции, которые вы настроили для этого. ИАП.

Следующий ответ представляет собой массив, содержащий объекты для каждого возвращенного IAP. Посмотрите следующий пример 12-месячного плана подписки, локализованного в гонконгских долларах:

// fetched subscription tier example 
Array [
  Object {
    "currency": "HKD",
    "description": "12 months subscription plan.",
    "discounts": Array [],
    "introductoryPrice": "",
    "introductoryPriceNumberOfPeriodsIOS": "",
    "introductoryPricePaymentModeIOS": "",
    "introductoryPriceSubscriptionPeriodIOS": "",
    "localizedPrice": "HK$198.00",
    "price": "198",
    "productId": "12MONTHS",
    "subscriptionPeriodNumberIOS": "1",
    "subscriptionPeriodUnitIOS": "YEAR",
    "title": "12 Months Subscription Plan",
    "type": "Do not use this. It returned sub only before",
  },
  ...
]

Используйте эти поля, чтобы заполнить таблицу подписок по своему усмотрению, указав локализованные цены, название и описания прямо из коробки.

Как должна выглядеть таблица подписки

Как мы можем взять вышеуказанные данные и отобразить их в удобной для пользователя форме? Рассмотрим следующую иллюстрацию, на которой показана трехуровневая служба подписки и каждый уровень отображается в таблице под каруселью, демонстрирующей функции плана:

Макет уровня подписки представляет собой дизайн Flexbox с площадью 33% для каждого уровня. Прокрутка не требуется, а цены (и экономия) легко читаются. Кроме того, очевиден и действующий на данный момент план.

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

Если вы заинтересованы в разработке карусели, ознакомьтесь с моей специальной статьей на эту тему:



В итоге

В этой статье мы затронули основные аспекты управления покупками в приложении для подписок на стороне React Native вашего приложения.

Что касается React Native, то эта статья должна дать читателю выгодное положение, чтобы продолжить реализацию IAP непосредственно в React. Мы обсудили, как правильно интегрировать прослушиватели событий react-native-iap, а также состояние транзакции в компонент <IAPManager />, а также поток транзакции. Мы также узнали, как получать и встраивать уровни подписки в виде таблицы подписки, которую пользователи могут просматривать и с которой взаимодействовать.

Куда идти дальше: Дополнительная литература

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

Проверка транзакций на стороне сервера. Ранее мы упоминали, что вызов на стороне сервера выполняется внутри функции processNewPurchase при обработке новой транзакции. Здесь транзакция должна быть подтверждена на сервере. Чтобы включить в это режим Mode, ознакомьтесь с пакетом node-apple-receipt-verify, упрощающим этот процесс в Node JS. Подробнее об этом процессе читайте в моей статье, посвященной проверке квитанций:



Автоматическая синхронизация продлений и отмен подписок с базой данных вашего приложения. Я опубликовал еще одну статью, в которой документируется решение, позволяющее сделать именно это с помощью проверки получения транзакции внутри среды выполнения Node.js - эта статья предоставляет продолжение того, что обсуждается в упомянутой выше статье о проверке квитанции, и предлагает читателю продолжение интеграции покупок в приложении:



Я надеюсь, что эта статья облегчит монетизацию вашего приложения и откроет им путь к успешной бизнес-модели!