React-native-iap: IOS проверяет, активна ли автопродление подписки

Созданный на 27 сент. 2018  ·  61Комментарии  ·  Источник: dooboolab/react-native-iap

Версия react-native-iap

2.2.2

Платформы, на которых вы столкнулись с ошибкой (IOS или Android или оба?)

IOS

Ожидаемое поведение

Используя API RNIAP, мы можем проверить, активна ли автопродление подписки.

Фактическое поведение

На Android я делаю это, чтобы проверить, что:

export const isUserSubscriptionActive = async (subscriptionId) =>{
    // Get all the items that the user has
    const availablePurchases = await getAvailablePurchases();
    if(availablePurchases !== null && availablePurchases.length > 0){
        const subscription = availablePurchases.find((element)=>{
            return subscriptionId === element.productId;
        });
        if(subscription){
             // check for the autoRenewingAndroid flag. If it is false the sub period is over
              return subscription["autoRenewingAndroid"] == true;
            }
        }else{
            return false;
        }
    }
}

В iOS нет флага для проверки этого, и метод getAvailablePurchases возвращает все сделанные покупки, даже те подписки, которые не активны в данный момент.

Есть ли способ это проверить?

С уважением,
Маркос

📱 iOS 🚶🏻 stale

Самый полезный комментарий

Вот функция, которую я использую, которая работает как на Android, так и на iOS, правильно сортируя на iOS, чтобы гарантировать получение последних данных о квитанции:

import * as RNIap from 'react-native-iap';
import {ITUNES_CONNECT_SHARED_SECRET} from 'react-native-dotenv';

const SUBSCRIPTIONS = {
  // This is an example, we actually have this forked by iOS / Android environments
  ALL: ['monthlySubscriptionId', 'yearlySubscriptionId'],
}

async function isSubscriptionActive() {
  if (Platform.OS === 'ios') {
    const availablePurchases = await RNIap.getAvailablePurchases();
    const sortedAvailablePurchases = availablePurchases.sort(
      (a, b) => b.transactionDate - a.transactionDate
    );
    const latestAvailableReceipt = sortedAvailablePurchases[0].transactionReceipt;

    const isTestEnvironment = __DEV__;
    const decodedReceipt = await RNIap.validateReceiptIos(
      {
        'receipt-data': latestAvailableReceipt,
        password: ITUNES_CONNECT_SHARED_SECRET,
      },
      isTestEnvironment
    );
    const {latest_receipt_info: latestReceiptInfo} = decodedReceipt;
    const isSubValid = !!latestReceiptInfo.find(receipt => {
      const expirationInMilliseconds = Number(receipt.expires_date_ms);
      const nowInMilliseconds = Date.now();
      return expirationInMilliseconds > nowInMilliseconds;
    });
    return isSubValid;
  }

  if (Platform.OS === 'android') {
    // When an active subscription expires, it does not show up in
    // available purchases anymore, therefore we can use the length
    // of the availablePurchases array to determine whether or not
    // they have an active subscription.
    const availablePurchases = await RNIap.getAvailablePurchases();

    for (let i = 0; i < availablePurchases.length; i++) {
      if (SUBSCRIPTIONS.ALL.includes(availablePurchases[i].productId)) {
        return true;
      }
    }
    return false;
  }
} 

Все 61 Комментарий

Я тоже работаю над этим. Я еще не тестировал его, но из того, что я читаю, я понимаю, что это возможно, если создать «общий секрет» в iTunes Connect и передать его в validateReceiptIos с ключом «пароль». Я считаю, что это вернет объект JSON, который будет содержать код, указывающий статус проверки подписки, и ключи latest_receipt, latest_receipt_info и latest_expired_receipt info, среди прочего, которые вы можете использовать для определения статуса подписки. Я буквально только выясняю это, поэтому мне еще предстоит это проверить. Это то, что я собираю из выпусков и документации Apple. Если это сработает, это действительно должно быть очень четко указано в документации, а не закапываться в проблемах.
Я считаю, что следующие ссылки актуальны:
https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html

203 # 237

РЕДАКТИРОВАТЬ : Я могу подтвердить, что упомянутый выше процесс работает. Я думаю, нам следует поработать, чтобы получить полное объяснение этого в документации. Я бы реализовал что-то вроде этого при запуске приложения, чтобы определить, истекла ли подписка:

RNIap.getPurchaseHistory()
        .then(purchases => {
                RNIap.validateReceiptIos({
                   //Get receipt for the latest purchase
                    'receipt-data': purchases[purchases.length - 1].transactionReceipt,
                    'password': 'whateveryourpasswordis'
                }, __DEV__)
                    .then(receipt => {
                       //latest_receipt_info returns an array of objects each representing a renewal of the most 
                       //recently purchased item. Kinda confusing terminology
                        const renewalHistory = receipt.latest_receipt_info
                       //This returns the expiration date of the latest renewal of the latest purchase
                        const expiration = renewalHistory[renewalHistory.length - 1].expires_date_ms
                       //Boolean for whether it has expired. Can use in your app to enable/disable subscription
                        console.log(expiration > Date.now())
                    })
                    .catch(error => console.log(`Error`))
        })

@kevinEsherick

Блестяще!

Просто некоторые вещи, которые не так тривиальны в вашем коде;

  1. Должен быть

const expiration = RenewalHistory [RenewalHistory.length - 1] .expires_date_ms

вместо (вероятно, просто опечатка)

const expiration = latestRenewalReceipt [latestRenewal.length - 1] .expires_date_ms

2) Для тех, кто не знает, что такое «пароль»: «whateveryourpasswordis»:

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

Шаги здесь: https://www.appypie.com/faqs/how-can-i-get-shared-secret-key-for-in-app-purchase

+1, чтобы обновить документы, также, возможно, @dooboolab, вы можете разделить варианты использования в IOS и Android, кажется, есть несколько разных вещей. Я могу помочь тебе, если ты хочешь.

С Уважением

@kevinEsherick

Что происходит с автопродлением подписок?

Кажется, что expires_date_ms - это первая дата истечения срока, она не обновляется

Я проверил это:

  1. Подпишитесь на элемент подписки
  2. Проверьте срок годности (например, через 5 минут)
  3. Подождите 10 минут
  4. Подписка все еще активна, кажется, подписка автопродление
  5. Если я проверю, что срок действия последней квитанции о продлении по-прежнему такой же, как в пункте 2

Любые идеи?

Мой плохой, срок годности вроде бы обновлен, это займет некоторое время.

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

С Уважением

  1. Должен быть

const expiration = RenewalHistory [RenewalHistory.length - 1] .expires_date_ms

вместо (вероятно, просто опечатка)

const expiration = latestRenewalReceipt [latestRenewal.length - 1] .expires_date_ms

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

И у меня не было такого точного сценария с датой истечения срока действия, но были некоторые вещи, в которых он вообще не обновлялся, хотя он каким-то образом разрешился сам по себе (что меня беспокоит, но я не могу воспроизвести, так что я не знаю, что еще делать). Проблема, с которой вы столкнулись, довольно распространенная. Это ожидаемое поведение со стороны Apple, поэтому вам следует оставить буферный период для продления подписки, прежде чем отменить подписку пользователя. Эта проблема в SO объясняется более подробно: https://stackoverflow.com/questions/42158460/autorenewable-subscription-iap-renewing-after-expiry-date-in-sandbox

Спасибо! Отличная информация, может быть причиной

По поводу этой проблемы, я думаю, что код @kevinEsherick должен быть в документации 👍 Это отличный способ проверить подписки!

да. Буду признателен за аккуратный документ в readme если кто-нибудь из вас запросит PR . Спасибо.

Привет, ребята. Я добавил раздел вопросов и ответов в ридми. Надеюсь, кто-нибудь из вас может дать нам PR за эту проблему.

В ближайшие дни займусь пиаром :)

Я также хочу использовать эту библиотеку специально для подписок. И iOS, и Android. Было бы очень полезно задокументировать правильный способ определения, есть ли у пользователя действующая подписка. 😄

@curiousdustin Реализация Apple по этому поводу действительно ужасна, если вы говорите о среде . Поскольку вам нужно обрабатывать такие вещи в своем бэкэнде, мы не смогли полностью поддержать это в нашем модуле. Однако вы можете получить availablePurchases в Android, используя наш метод getAvailablePurchases который не будет работать с продуктом подписки ios.

Итак, вы говорите, что решение, над которым работают @ marcosmartinez7 и @kevinEsherick в этой ветке, НЕ является решением, и что для подтверждения статуса подписки iOS необходим бэкэнд-сервер, отличный от Apple?

Из чтения статьи Medium, которую вы разместили вместе с документами Apple, действительно кажется, что использование сервера является предпочтительным, но возможно определить статус подписки только с помощью устройства.

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

Тем не менее, по-прежнему можно попробовать react-native-iap поскольку мы обеспечиваем прямую загрузку подтверждения получения на собственный сервер Apple (песочница и производство), что, к счастью, сработали @ marcosmartinez7 и @kevinEsherick .

Я думаю, что в настоящее время это решение для проверки подписки на ios .

Привет, ребята, извините, прошло больше нескольких дней, я был занят работой, но я все равно отправлю этот PR, чтобы обновить README. @curiousdustin метод, о котором я писал выше, определенно работает, и я добавлю его в документацию. Я пришлю пиар сегодня или завтра :)

Спасибо за обновление.

Еще одну вещь я заметил на вашем примере.

'данные-квитанции': покупки [Purchases.length - 1] .transactionReceipt,
...
const expiration = RenewalHistory [RenewalHistory.length - 1] .expires_date_ms

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

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

Вот функция, которую я использую, которая работает как на Android, так и на iOS, правильно сортируя на iOS, чтобы гарантировать получение последних данных о квитанции:

import * as RNIap from 'react-native-iap';
import {ITUNES_CONNECT_SHARED_SECRET} from 'react-native-dotenv';

const SUBSCRIPTIONS = {
  // This is an example, we actually have this forked by iOS / Android environments
  ALL: ['monthlySubscriptionId', 'yearlySubscriptionId'],
}

async function isSubscriptionActive() {
  if (Platform.OS === 'ios') {
    const availablePurchases = await RNIap.getAvailablePurchases();
    const sortedAvailablePurchases = availablePurchases.sort(
      (a, b) => b.transactionDate - a.transactionDate
    );
    const latestAvailableReceipt = sortedAvailablePurchases[0].transactionReceipt;

    const isTestEnvironment = __DEV__;
    const decodedReceipt = await RNIap.validateReceiptIos(
      {
        'receipt-data': latestAvailableReceipt,
        password: ITUNES_CONNECT_SHARED_SECRET,
      },
      isTestEnvironment
    );
    const {latest_receipt_info: latestReceiptInfo} = decodedReceipt;
    const isSubValid = !!latestReceiptInfo.find(receipt => {
      const expirationInMilliseconds = Number(receipt.expires_date_ms);
      const nowInMilliseconds = Date.now();
      return expirationInMilliseconds > nowInMilliseconds;
    });
    return isSubValid;
  }

  if (Platform.OS === 'android') {
    // When an active subscription expires, it does not show up in
    // available purchases anymore, therefore we can use the length
    // of the availablePurchases array to determine whether or not
    // they have an active subscription.
    const availablePurchases = await RNIap.getAvailablePurchases();

    for (let i = 0; i < availablePurchases.length; i++) {
      if (SUBSCRIPTIONS.ALL.includes(availablePurchases[i].productId)) {
        return true;
      }
    }
    return false;
  }
} 

@andrewze

В сценарии Android, если подписка автоматически продлевается, она будет возвращена при наличии доступных покупок.

@curiousdustin @andrewzey Вы правы, покупок не заказывал. RenewalHistory / latest_receipt_info, однако, всегда заказывается для меня. Я помню, как понимал, что покупки не заказывались, но подумал, что нашел причину, по которой это на самом деле не вызывает беспокойства. Я займусь этим через пару часов, когда вернусь домой. Я не хочу писать PR, пока мы не разберемся с этим.
РЕДАКТИРОВАТЬ: Поэтому я не думаю, что вам нужно сортировать покупки, потому что expires_date_ms возвращает то же самое, независимо от того, какую покупку вы запрашивали. Разве это не то же самое для вас, ребята? Или вам нужна некоторая информация, которая требует сортировки? Дайте мне знать, что вы думаете. Я согласен с тем, что в документации должно быть четко указано, что покупки не упорядочены в хронологическом порядке, но, насколько я могу судить, сортировка не требуется для получения даты истечения срока действия.

@kevinEsherick Спасибо! В моем случае ни покупки, ни продления истории не заказывались.

@kevinEsherick @andrewzey Ребята, можете ли вы дать PR за это решение? Мы хотели бы поделиться с другими, кто страдает.

Конечно, я подготовлю его сразу после того, как мы закончим запуск нашего общедоступного приложения =).

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

@kevinEsherick Извините, я это пропустил.

expires_date_ms определенно уникален для меня в каждой квитанции о продлении. Может, я неправильно понял? Я заметил, что массив квитанций о продлении, прикрепленный к каждой декодированной квитанции, одинаков, независимо от того, какая квитанция выбрана, поэтому я мог видеть, что вы можете быть правы в следующем (из моего примера), который не требуется:

const sortedAvailablePurchases = availablePurchases.sort(
      (a, b) => b.transactionDate - a.transactionDate
    );

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

Есть какое-нибудь ETA на PR? Я подумываю о том, чтобы отказаться от

@schumannd Учитывая, что PR будет только для обновления документации, вы можете перейти к react-native-iap сейчас.

Я подтвердил, что мой фрагмент кода действительно работает без проблем в производственной среде с нашим недавно запущенным приложением: https://itunes.apple.com/us/app/rp-diet/id1330041267?ls=1&mt=8

РЕДАКТИРОВАТЬ Я обязательно сделаю пиар (у меня он есть в моем списке «Вернуть OSS» в нашем Trello), но после сумасшествия черной пятницы на выходных 😄

@schumannd Я @andrewzey . Все, что может сказать пиарщик, содержится в этом посте. Я собирался дойти до этого, но нам потребовалось некоторое время, чтобы разобраться, что именно нужно сделать, и это, смешанное с поездками и беспокойными часами запуска, означает, что у меня еще не было времени на пиар. Я все еще планирую сделать это в ближайшее время, но тем временем любой может подойти, если захочет. И @andrewzey поздравляю, чувак! Отлично смотрится, дам скачать!

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

Я исследовал правильный способ управления автоматически возобновляемыми подписками в React Native в течение нескольких недель, но безрезультатно, поэтому я переключился и начал исследовать, как управлять ими изначально в Objective-C / Swift. Это дало мне ориентир для сравнения встроенных API StoreKit и документированного использования с тем, что было реализовано в этой библиотеке.

Эта статья является лучшим объяснением, которое я нашел до сих пор о том, как покупка, проверка и восстановление подписки должны обрабатываться с помощью собственного приложения для iOS: https://www.raywenderlich.com/659-in-app-purchases-auto -renewable-subscriptions-tutorial, и хотя все API-интерфейсы используются в react-native-iap , они реализуют другой рабочий процесс.

Ключевое отличие, которое я вижу, - это использование appStoreReceiptUrl .

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

Я не уверен, есть ли / как эта разница в реализации проблематична, но API react-native-iap похоже, создает неадекватную зависимость от restoreCompletedTransactions StoreKit, что в конечном итоге вызывается getPurchaseHistory .

Мне кажется, что react-native-iap API должен поддерживать следующий рабочий процесс для iOS:

  • Попытка загрузить текущую квитанцию ​​приложения (через appStoreReceiptUrl ) при запуске приложения
  • Предоставьте возможность проверить квитанцию
  • Если appStoreReceiptUrl имеет значение null, инициируйте «восстановление» на текущем устройстве, которое затем установит значение appStoreReceiptUrl и логику проверки можно будет повторить.

Мысли?

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

@sellmeadog

Я думаю, что ключевой момент, который также упоминается в статье, на которую вы ссылаетесь, заключается в следующем:

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

Мне потребовалось некоторое время, чтобы разобраться во всей этой автоматически возобновляемой подписке, но, насколько я понимаю, лучший способ справиться с этим - это использовать собственный бэкэнд в качестве единственного источника истины:

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

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

Итак, мой ответ на вопрос OP: периодически проверяйте подписки в бэкэнде, а не во внешнем интерфейсе.

@schumannd

Я не уверен, что мы понимаем или обсуждаем ту же проблему.

Проверка получения должна производиться на сервере. Согласно собственной документации Apple :

Не вызывайте конечную точку сервера App Store /verifyReceipt из своего приложения.

Я интерпретировал «чит» в упомянутой статье как конкретно прямые вызовы приложения-примера к /verifyReceipt непосредственно из приложения для краткости, а не делегирование на внутренний сервер.

Мне кажется, что Apple / App Store есть и должен быть единственным источником истины. В конечном итоге Apple обрабатывает покупки, продления, отмены и т. Д. И соответственно формирует квитанции и делает эти данные доступными через API StoreKit .

react-native-iap обрабатывает покупки и восстанавливает покупки без проблем, но я все еще думаю, что есть пробел в доступе к данным квитанции в App Store, которые доступны локально по appStoreReceiptUrl что снова является документированным способом доступа Apple к квитанции. данные :

Чтобы получить данные квитанции, используйте метод appStoreReceiptURL из NSBundle чтобы найти квитанцию ​​приложения ...

Рабочий процесс, который я понял из документации Apple и ссылки на собственные реализации Object-C / Swift, выглядит следующим образом:

  • Загрузить товары для покупки
  • Приобрести продукт (расходный, не расходный, подписка, не имеет значения)
  • Квитанция об этой покупке хранится локально по адресу appStoreReceiptUrl по StoreKit за кулисами.
  • Приложение должно отправлять данные квитанции на внутренний сервер (а не напрямую в App Store /verifyReceipt ) для проверки по мере необходимости.
  • appStoreReceiptUrl обновляется на StoreKit всякий раз, когда происходят отмены и / или продления (при условии, что правильные делегаты StoreKit зарегистрированы правильно)
  • Когда appStoreReceiptUrl имеет значение null, потому что пользователь находится на новом устройстве и т. Д., Используйте рабочий процесс восстановления покупки, который будет извлекать текущие данные квитанции из Apple / AppStore, а StoreKit сохранит это локально в appStoreReceiptUrl за кулисами

Опять же, react-native-iap обрабатывает все это, кроме загрузки данных чека из appStoreReceiptUrl . Я думаю, что метод getReceipt который запрашивал локальную квитанцию, облегчил бы бремя управления подписками и / или любыми другими покупками.

Apple сделала это намного сложнее, чем следовало бы.

appStoreReceiptUrl обновляется на StoreKit всякий раз, когда происходят отмены и / или продления (при условии, что соответствующие делегаты StoreKit зарегистрированы правильно)

Это описывает проблему, с которой я столкнулся с этим подходом: вы не получаете уведомление о каком-либо обновлении / отмене / истечении срока действия и т. Д., Если пользователь не откроет ваше приложение впоследствии с работающим подключением к Интернету.

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

@sellmeadog Я думаю, что это та же идея, которую я опубликовал здесь: # 356
Apple рекомендует подтверждать квитанции на месте. Это полностью не зависит от каких-либо запросов к серверу / сети.

Ага. Есть и обратные стороны, касающиеся «нет подключения к сети - нет проверки».
Но для этого есть варианты использования.
Мне нужна быстрая проверка квитанции при каждом запуске приложения, и я не хочу самостоятельно отслеживать покупки в приложении через UserDefaults или Keychain . StoreKit уже предоставляет механизм хранения.

Как вы должны проверять, продлил ли пользователь IOS свою подписку или отменил ее, не предлагая ему войти в учетную запись itunes через что-то вроде getAvailablePurchases?

Например:
При первой покупке подписки мы проверяем квитанцию ​​и сохраняем дату истечения срока действия в их профиле firebase. Мы используем этот узел firebase для проверки при запуске, есть ли у них активная подписка. Проблема возникает, когда истекает срок действия и начинается новый расчетный период. Как мы можем проверить, обновляются ли они автоматически или отменяются? Как уже упоминалось, мы использовали getAvailablePurchases, но при этом запрашивается вход в itunes, как при нажатии кнопки восстановления - что ужасно.

Мне тоже интересно то же самое.

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

Однако я считаю, что это все еще возможно на стороне устройства. Я не являюсь экспертом в этом вопросе, но, насколько я понимаю, приложение будет получать обновленные транзакции / квитанции от ОС всякий раз, когда происходит обновление. Я подозреваю, что этот плагин обрабатывает эти квитанции при запуске приложения. (Я вижу кучу журналов при запуске из Xcode, которые, похоже, обрабатывают несколько транзакций) НО, насколько я могу судить, нет возможности подключиться к этим событиям в коде JS.

Можем ли мы получить дополнительную информацию по этому поводу?

@csumrell @curiousdustin , ребята, вы подтвердили это поведение в производственной среде? Мне интересно, может ли это происходить просто из-за песочницы, когда учетная запись песочницы требует входа, поскольку на вашем устройстве предположительно есть другая учетная запись, не входящая в песочницу, тогда как в производственной среде пользователь предположительно войдет в свою обычную учетную запись, и это модуль может получать покупки без запроса входа в систему.

Извините, я НЕ подтвердил, что getAvailablePurchases() вызывает запрос пароля в рабочей среде.

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

@hyochan @JJMoon Можем ли мы получить больше информации по этому

@curiousdustin да, поэтому, когда я выхожу из своей обычной учетной записи iTunes и перехожу в учетную запись песочницы, она больше не предлагает мне войти в систему при вызове этого метода. Итак, я предполагаю / надеюсь, что в производственной среде, где у пользователей нет используемой учетной записи песочницы, проблема не должна возникать. Бета-тестирование должно работать так же, как и в производственной среде, поскольку у пользователей нет учетных записей песочницы, настроенных на устройстве, это просто проблема устройств разработки. Итак, я собираюсь протестировать это завтра с бета-пользователями и сообщить вам, что я нашел.
РЕДАКТИРОВАТЬ: Я могу подтвердить, что это происходит на устройствах без песочницы. Это ужасный UX и довольно проблематичный. Есть идеи?

Поэтому, когда вы проверяете на своем собственном сервере, вызывая конечную точку App Store server / verifyReceipt из своего приложения, оно всегда будет возвращать информацию о последнем обновлении на дату для этого пользователя только с оригинальной квитанцией о первой покупке.

Кто-нибудь знает, всегда ли при использовании локального метода проверки будет возвращаться самая последняя информация? Или это только для той квитанции, которой вы проверяете ее на месте?

Кроме того, есть ли у кого-нибудь совет по настройке собственного сервера для проверки в Apple? У меня нет опыта в этом

@csumrell, вы можете получить квитанцию ​​о последней покупке, позвонив в getPurchaseHistory и выполнив сортировку по ней. В комментариях выше подробно описано, как это сделать. Здесь должна быть самая последняя информация.

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

@kevinEsherick да, но getPurchaseHistory вызовет всплывающее окно входа в Itunes. Я говорю о сохранении оригинальной квитанции в локальном хранилище при первой покупке пользователем. Затем вместо вызова getPurchaseHistory для проверки следующего биллинга вызовите validateReceiptIos (originalReceipt), который проверяется локально. Однако я не уверен, возвращает ли validateReceiptIos актуальные транзакции, такие как сервер Apple / verifyReceipt.

@csumrell Попался. Что ж, это легко и быстро проверить. Вы еще не пробовали? Если нет, то проверю позже сегодня.
РЕДАКТИРОВАТЬ: @csumrell validateReceiptIos на самом деле отслеживает последние автоматические продления для этой покупки, так что это допустимый метод. Я бы сказал, что это лучше, чем предложенные выше методы проверки подписок, потому что он позволяет избежать приглашения на вход в iTunes (если кто-то не может придумать исправление для этого). Недостаток в том, что квитанции нужно хранить локально. Это не должно вызывать особых хлопот. Кроме того, вы можете не захотеть, чтобы эти квитанции отправлялись на ваш сервер по соображениям безопасности или из-за того, что они огромны (~ 60 тыс. Символов), поэтому это будет означать, что если подписанный пользователь выйдет из системы или начнет использовать новое устройство, вам потребуется предложите им войти в систему, чтобы получить последнюю квитанцию ​​о покупке. Вы можете хранить их статус подписки внутри, так что вы будете запрашивать только тех пользователей, которые подписывались последней, которую вы проверили.

@hyochan профинансировал эту проблему в размере 20 долларов США. Смотрите на IssueHunt

@hyochan, какое конкретное решение вы финансируете? Мы предоставили несколько здесь, в этой ветке, и я думаю, что мой последний комментарий дает самое надежное решение с наименьшим количеством проблем. Вы финансируете кого-то, чтобы собрать эти решения в один краткий PR для README? Или это реальная проблема с кодом, которую вы все еще хотите решить?

@kevinEsherick

Вы финансируете кого-то, чтобы собрать эти решения в один краткий PR для README?

да. Совершенно верно. Кроме того, я просто хотел проверить, как issue hunt может работать из коробки. Мой следующий план - выделить средства на создание тестовых примеров.

@kevinEsherick Вы правы, локальная проверка с помощью validateReceiptIos фактически возвращает последнюю информацию о подписке, используя только исходный идентификатор первой транзакции (хранящийся в состоянии redux)

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

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

Привет, похоже, в последнее время не было никакой активности по этой проблеме. Проблема устранена или все еще требует внимания сообщества? Эта проблема может быть закрыта, если больше не будет никаких действий. Вы также можете пометить этот вопрос как «Для обсуждения» или «Хороший первый выпуск», и я оставлю его открытым. Спасибо за ваш вклад.

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

Может быть, весь этот поток нуждается в разъяснении в документации?

Особенно если речь идет об автоматическом продлении подписки? Новичку очень непонятно, можете ли вы проверить expires_date_ms , активна ли подписка или нет?

Я предлагаю, чтобы лучшее место для этого было в более полном рабочем примере?

Будет ли это интересно людям? Если у меня будет время, я смогу над этим поработать?

@alexpchin да, это определенно сэкономит массу времени на разработку, и ваша карма станет кристально чистой: D

Соглашаться! Кого-нибудь заинтересует # 856?

Привет,

Метод @andrewzey отлично работал у нас до вчерашнего дня, когда мы внезапно перестали проверять какие-либо квитанции для наших подписок - мы получаем ошибку синтаксического анализа JSON от iOS при вызове validateReceiptIos (). Я так понимаю, никто больше не сталкивался с этим ...? Единственное изменение, которое было внесено со вчерашнего дня, - это добавление промокода в ITC, который мы с тех пор удалили, чтобы помочь устранить переменные. Есть ли причина, по которой квитанция не может анализировать JSON? Он не выполняется для каждой возвращенной квитанции, а не только для квитанции с индексом 0 sortedAvailablePurchases. Код в основном идентичен примеру Эндрюзи - я пропустил все после validateReceiptIos, поскольку там возникает ошибка.

Мы используем RN 0.61.4, RNIap: 4.4.2 и iOS 13.3.1.

Мы пробовали:

  • Переустановка приложения
  • Новая учетная запись пользователя
  • Использование нового секрета приложения
  • Тестирование всех квитанций на каждого пользователя - ни одна квитанция не может быть проанализирована

Нам интересно, правильно ли мы использовали finishTransactionIOS, когда совершали покупки в песочнице?

Что действительно странно, так это то, что когда мы проверяем квитанцию ​​с помощью этого онлайн-инструмента , все выглядит нормально, и мы можем видеть все метаданные квитанции.

  isSubscriptionActive = async () => {
      const availablePurchases = await RNIap.getAvailablePurchases();
      const sortedAvailablePurchases = availablePurchases.sort(
        (a, b) => b.transactionDate - a.transactionDate
      );
      const latestAvailableReceipt = sortedAvailablePurchases[0].transactionReceipt;
      const isTestEnvironment = __DEV__;

      try {
        const decodedReceipt = await RNIap.validateReceiptIos(
          {
            'receipt-data': latestAvailableReceipt,
            password: Config.IN_APP_PURCHASE_SECRET,
          },
          isTestEnvironment
        );
        console.log('response!', decodedReceipt)
      } catch (error) {
        console.warn('Error validating receipt', error) // JSON PARSE ERROR HERE
      }
...

Вопрос по поводу размещенного кода Date.now() будет текущим временем устройства, и пользователь может изменить это и получить бесконечную подписку? 🤔

Кроме того, не обескураживает ли проверка квитанции из самого приложения?

@doteric Не могли бы вы указать мне, где они упоминают, что это не рекомендуется? Я знаю, что вы можете настроить уведомления на стороне сервера, но, с моей точки зрения, гораздо проще обрабатывать эту проверку на клиенте, а не на сервере.
Я изо всех сил пытаюсь найти подходящую документацию от Apple или других надежных исходных кодов.

@doteric yes Date.now () - время устройства, поэтому его можно обойти. Тем не менее, шансы, что пользователь сделает это, мизерны, и даже другие методы можно обойти для бесконечной подписки. Например, при использовании простой проверки на стороне сервера они могут перейти в режим полета до открытия приложения, чтобы приложение не осознало, что срок действия подписки истек. Конечно, есть и другие средства защиты, которые вы могли бы установить, но я считаю, что клиентская сторона, использующая Date.now (), безусловно, работает. @captaincole У меня нет под рукой документации, но я могу подтвердить, что я тоже читал, что проверка на стороне клиента не рекомендуется. Я прочитал это в документах Apple, я верю. При этом я думаю, что клиентская сторона выполняет свою работу.

Привет,

Я пробовал один из подходов, обсуждаемых в этом потоке, используя validateReceiptIos и latest_receipt_data (сравнивая expires_date_ms с фактической датой). Он отлично работал при разработке в Xcode. Однако, когда я тестировал его в Testflight, он больше не работал. Его сложно отладить, поэтому я не могу определить точную проблему, похоже, он не получает данные о квитанции. У кого-нибудь была аналогичная проблема с testflight?

Заранее спасибо!

Привет,

Я пробовал один из подходов, обсуждаемых в этом потоке, используя validateReceiptIos и latest_receipt_data (сравнивая expires_date_ms с фактической датой). Он отлично работал при разработке в Xcode. Однако, когда я тестировал его в Testflight, он больше не работал. Его сложно отладить, поэтому я не могу определить точную проблему, похоже, он не получает данные о квитанции. У кого-нибудь была аналогичная проблема с testflight?

Заранее спасибо!

+1

@asobralr @ Somnus007 Я могу ошибаться, но не думаю, что вы можете тестировать IAP с помощью testflight, поскольку пользователи не могут делать покупки с помощью testflight. Apple не предоставляет больших возможностей для тестирования покупок в производственных средах.

@asobralr @ Somnus007 Я могу ошибаться, но не думаю, что вы можете тестировать IAP с помощью testflight, поскольку пользователи не могут делать покупки с помощью testflight. Apple не предоставляет больших возможностей для тестирования покупок в производственных средах.

Правильный. Вы даже не можете смоделировать сценарии отказа в песочнице, что очень досадно. 😞

Привет, @kevinEsherick , спасибо за ответ. Вы можете ошибаться. Пользователь может совершать покупки через teslflight. В testflight кажется, что пользователь должен войти в учетную запись песочницы, которая имеет тот же адрес электронной почты, что и его настоящая учетная запись Apple. Тогда все работает хорошо. Кстати, getPurchaseHistory вызовет всплывающее окно входа в Itunes в производственной среде?

Похоже, что в ios 14 это решение не работает из-за того, что при вызове getAvailablePurchases () перед вызовом requestSubscription () возникают проблемы.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги

Смежные вопросы

jvandenaardweg picture jvandenaardweg  ·  4Комментарии

safciplak picture safciplak  ·  3Комментарии

sanilcgs picture sanilcgs  ·  3Комментарии

bakedbean picture bakedbean  ·  5Комментарии

makarsky picture makarsky  ·  3Комментарии