Как проверить доступность вводного предложения в iOS

  • Tutorial

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


image


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


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


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



Получается, пользователь может воспользоваться вводным предложением, если:


  • ранее он не использовал вводное предложение

И


  • подписка либо еще не была оформлена, либо уже истекла

Для проверки доступности вводного предложения нужно выполнить 3 шага:


1) Провалидировать App Store-чек и вытащить массив транзакций. Если транзакций, нет, то ничего не проверяем, вводное предложение доступно. Если транзакции есть, то выполнить следующие два шага.


2) Проверить, было ли ранее использовано вводного предложение


3) Проверить текущий статус подписки


Рассмотрим подробнее эти шаги.


1. Валидация App Store чека


Для валидации чека нужно отправить запрос в Apple, передав receiptData и sharedSecret. Замените значение sharedSecret на ваше собственное. Если не знаете ваш sharedSecret, то здесь описано, где его взять.


func isEligibleForIntroductory(callback: @escaping (Bool) -> Void){

        guard let receiptUrl = Bundle.main.appStoreReceiptURL else {
            callback(true)
            return
        }

        #if DEBUG
            let urlString = "https://sandbox.itunes.apple.com/verifyReceipt"
        #else 
            let urlString = "https://buy.itunes.apple.com/verifyReceipt"
        #endif

        let receiptData = try? Data(contentsOf: receiptUrl).base64EncodedString()

        let sharedSecret = "YOUR_SHARED_SECRET"

        let requestData = ["receipt-data" : receiptData ?? "", "password" : sharedSecret, "exclude-old-transactions" : false] as [String : Any]
        var request = URLRequest(url: URL(string: urlString)!)
        request.httpMethod = "POST"
        request.setValue("Application/json", forHTTPHeaderField: "Content-Type")
        let httpBody = try? JSONSerialization.data(withJSONObject: requestData, options: [])
        request.httpBody = httpBody
        URLSession.shared.dataTask(with: request)  { (data, response, error) in
            // continue here
        }.resume()   
    }

В примере выше используется макрос #if DEBUG для определения типа подписки: sandbox или production. Если вы используете другие макросы, то вам нужно будет обновить код в этом месте.

2. Проверка, использовалось ли вводное предложение ранее


Получив ответ от Apple, переводим его в Dictionary и получаем массив транзакций:


// paste this code after "continue here" comment
guard let data = data, let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String : AnyHashable], let receipts_array = json["latest_receipt_info"] as? [[String : AnyHashable]] else {
      callback(true)
      return
}

// continue here

Проходимся по массиву транзакций и смотрим значения is_trial_period и is_in_intro_offer_period. Если одно из значений true, то пользователь уже оформлял вводное предложение. Эти значения приходят как строка, потому для надежности попытаемся конвертировать значение и в Bool, и в строку.


// paste this code after "continue here" comment
var latestExpiresDate = Date(timeIntervalSince1970: 0)
let formatter = DateFormatter()
for receipt in receipts_array {
  let used_trial : Bool = receipt["is_trial_period"] as? Bool ?? false || (receipt["is_trial_period"] as? NSString)?.boolValue ?? false
  let used_intro : Bool = receipt["is_in_intro_offer_period"] as? Bool ?? false || (receipt["is_in_intro_offer_period"] as? NSString)?.boolValue ?? false
  if used_trial || used_intro {
    callback(false)
    return
  }
// continue here

3. Проверка текущего статуса подписки


Чтобы узнать текущий статус подписки, мы должны найти самый поздний expires_date и сравнить с текущей датой. Если подписка еще не истекла, то вводное предложение недоступно:


// paste this code after "continue here" comment
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss VV"
if let expiresDateString = receipt["expires_date"] as? String, let date = formatter.date(from: expiresDateString) {
    if date > latestExpiresDate {
      latestExpiresDate = date
    }
  }
}

if latestExpiresDate > Date() {
  callback(false)
} else {
  callback(true)
}

Ссылку на полный код метода вы сможете найти в конце статьи, однако в данном методе есть много "Но".


Подводные камни


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


  • В этом примере не учтен случай возвратов подписок. Для этого необходимо проверять наличие поля cancellation_date:


    if receipt["cancellation_date"] != nil{
    // if user made a refund, no need to check for eligibility
    callback(false)
    return
    }

  • А еще здесь не учитывается льготный период (Billing Grace Period). Если пользователь на момент валидации чека находится в льготном периоде, то в pending_renewal_info будет поле grace_period_expires_date. В этом случае вы как разработчик обязаны предоставить премиум функционал пользователю без отображения экрана оплаты. И соответственно, нет смысла проверять доступность вводного предложения.


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


  • Валидация чека на самом устройстве не рекомендуется Apple. Они несколько раз говорили об этом на WWDC (с 5:50) и это указано в документации. Это небезопасно, потому что злоумышленник может перехватить данные с помощью man-in-the-middle атаки. Apple рекомендует использовать свой сервер для валидации чеков.



Проверка доступности промо-предложения


Условие доступности промо-предложения проще – главное, чтобы у пользователя была активная или истекшая подписка. Для этого нужно смотреть наличие pending_renewal_info для вашей группы подписок.


Как это реализовано в Apphud SDK


Достаточно вызвать один метод, передав в него ваш product, который вернет вам результат:


Apphud.checkEligibilityForIntroductoryOffer(product: myProduct) { result in
  if result {
    // User is eligible to purchase introductory offer
  }
}

И аналогично для промо-преложения:


Apphud.checkEligibilityForPromotionalOffer(product: myProduct) { result in
  if result {
    // User is eligible to purchase promotional offer
  }
}

Также есть методы проверки доступности сразу для нескольких продуктов за один вызов:


func checkEligibilitiesForIntroductoryOffers(products: [SKProduct], callback: ApphudEligibilityCallback)

func checkEligibilitiesForPromotionalOffers(products: [SKProduct], callback: ApphudEligibilityCallback)

Заключение


Полный код метода можете скачать здесь.


Мы в Apphud уже реализовали проверку доступности вводного и промо-предложений в удобном open-source SDK. А еще Apphud помогает отслеживать статус подписки, анализировать ключевые метрики, автоматически предлагать скидки отписавшимся пользователям и многое другое. Если при работе с подписками вы испытываете боль, попробуйте наше решение бесплатно.
Apphud
48,22
Сервис для работы с подписками в iOS-приложениях
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 4

    0
    Если приложение только установлено на чистую, как получить чек чтобы отправить его в валидацию для пункта «1. Валидация App Store чека»?

    Если дергать resore в api то будет запрос юзеру на пароль.
    Если ждать появления данных в ссылке [NSBundle mainBundle].appStoreReceiptURL то они там могут появиться очень не скоро.

    А показать о том что есть триал надо еще в intro.
      +1
      Если приложение установлено на чистую, то чек будет всегда (если скачано с апстора). Для ревьюеров и для себя нужно рефрешить чек через SKReceiptRefreshRequest, но как вы сказали, будет запрос юзеру на пароль. И некоторые ревьюеры могут отреджектить приложение из-за этого.
      Поэтому, можно не делать проверку доступности триала (intro), если нет чека.
      Вот тут можете почитать.
        0
        Тоесть я правильно понял, что чек по ссылке [NSBundle mainBundle].appStoreReceiptURL при установке из AppStore есть сразу при 1 запуске приложения. Если это верно для всех версий iOS то это супер.
        И получается, что данные в чеке от того аккаунта iTunes через который юзер скачал приложение.
        Так как аккаунт мог сменится, то без вызова SKReceiptRefreshRequest есть вероятность показать доступность триала от другого аккаунта.

        Тоесть это тоже по сути угадывание есть триал или нет. И пожалуй это один из не самых простых методов угадывания наличия триала :)

          +1
          чек по ссылке [NSBundle mainBundle].appStoreReceiptURL при установке из AppStore есть сразу при 1 запуске приложения. Если это верно для всех версий iOS то это супер.

          Да, именно так.

          App Store Аккаунт действительно можно сменить, однако это редкий случай) Максимум, чем это грозит, это то, что вы покажете новому пользователю недоступность триала, хотя он на самом деле будет доступен. Как вариант, да, можно выполнять SKReceiptRefreshRequest перед каждым отображением экрана покупки, но стоят ли эти усилия редкого кейса смены Apple ID, решать вам.

          Но тут дело не только в проверки доступности. Вы можете оформить подписку и сразу же сменить App Store аккаунт, подписка по прежнему будет активна до тех пор, пока вы не обновите чек.

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

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

    Самое читаемое