Auto-Renewable Subscription, наверное, самый сложный из всех типов In-App Purchase в iOS, и реализовать его правильно, от начала и до конца, совсем непросто, и даже пройдя этот нелегкий путь, вы можете столкнуться с отказом цензоров принимать ваше приложение.
В данном посте я постараюсь провести вас через все этапы внедрения подписки и, возможно, смогу отговорить вас от этой идеи.
Данный вид In-App позволяет вам предложить пользователю подписаться на предоставляемый вами контент одним действием, и потом забыть о каких либо платежах — деньги с него будут списываться ежемесячно(срок подписки вы можете выбрать произвольно), и ему больше не нужно будет задумываться о том, что период подписки подходит к концу, а вам не придется лишний раз напоминать ему об этом — деньги будут сниматься с его счета и отправляться на ваш.
Также, вам не придется встраивать возможность отказаться от подписки — это возможно только в настройках iOS — удобно, и пользователю не мозолит глаза кнопка отказа.
Казалось бы, такая идеальная схема, сделаю-ка я месяц триала, а потом пусть подписываются на доступ к приложению за доллар в месяц. Но не так-то все просто. Ключевыми словами здесь является то, что Auto-Renewable Subscription предназначены для предоставления цифрового контента (Digital Content), а это слова весьма размытые.
Например, наше приложение ежедневно предоставляет пользователю несколько новых слов, и мы думали, что это вполне себе «digital content». Парни из Apple так не думали. В итоге, пришлось переделывать все на Non-renewing subscriptions.
Поэтому, прежде чем начать внедрять этот тип покупок, подумайте, на самом ли деле вы подходите под то, что Apple вкладывала в смысл этого механизма.
Процесс внедрения Auto-Renewable Subscription состоит из трех этапов:
Сначала нам нужно получить shared secret, его мы будем использовать на серверной стороне при обращении к серверам Apple. Заходим в раздел Manage In-App Purchases и генерируем его.
Там же мы добавляем новую In-App соответсвующего типа, выбираем продолжительность, название и заполняем все поля.
Особенно нас интересует Product ID — его мы будем запрашивать из приложения.
Также нас попросят добавить ссылку на нашу Privcay Policy, без нее это невозможно. Что там писать? Да не так уж и важно, мы написали что-то такое: www.easy10.com/privacy
Что ж, shared secret получили, Product ID есть, переходим к сервереной части.
Этот этап необходим нам для проверки и дешифровки receipt, приходящих от Apple при покупке и в момент, когда нам интересно, обновлилась ли подписка в новом месяце.
Этот механизм реализован следущим образом:
При отсутсвии собственного сервера, шаги 4-6 можно сделать с помощью сервиса www.beeblex.com
Если же вы хотите иметь полный контроль над процессом, то на шаге 5 вы должны отправить на buy.itunes.apple.com/verifyReceipt JSON следущего содержания
В ответ на шаге 6 мы получим следущий JSON
Если status = 0, то все хорошо. Если он равен 21006, значит наш пользователь не продлил подписку и нам нужно прекраить предоставление контента. Все остальные коды можно посмотреть здесь:
Status codes for auto-renewable subscriptions
В самом же поле receipt нас будет больше всего интересовать значение expires_date — сравниваем его с текущим и на основе этого принимаем какие-то решения. Если пользователь не отказался от вашей подписки, оно будет автоматически обновляться.
Для этого нам необходимо создать тестового пользователя в iTunes Connect и на сервере отправлять запрос на проверку receipt на sandbox.itunes.apple.com/verifyReceipt
Хочу обратить ваше внимание на то, что в тестовом режиме подписка будет автоматически обновляться только шесть раз. И все. Об этом почти нигде не написано, и я провел бессонные ночи, не понимая, в чем же причина того, что expires_date у меня не изменялась. Именно в этом. Поэтому вам придется создавать нового пользователя, чтобы тестить именно этом момент самопродления.
Помимо этого, период действия в тестовом режиме значительно короче чем он есть на самом деле, например, месячная подписка сжимается до 5 минут.
Ну и на последок я приведу код покупок в самом приложении. Хоть он и был написан в сотнях мест, раз уж я решил собрать все вместе, без этой части не обойтись.
Для этого в iOS предназначен StoreKit.framework. Он содержит в себе все необходмое для совершения покупки, отслеживания прохождения транзакции и восстановления покупок с нового устройства.
Для начала нам нужно будет добавить обзервера транзаций, который должен пооддерживать протоколы SKPaymentTransactionObserver — это нужно для того, чтобы транзакция не потерялась, когда у пользователя внезапно выключился телефон или интернет.Поэтому это лучше всего делать где-нибудь в
Создадим класс PaymentsOberver, который будет пооддеживать все необходимые нам протоколы
Сначала мы должны сформировать запрос на продукт, указв Product ID, который нам необходим, мы можем запросить неограниченное количество продуктов сразу
По завершении запроса мы получим вызов одного из методов SKProductRequestDelegate, в случае успеха — вот такой
В нем мы добавляем полученый продукт в очередь транзакций. Дальше с ним разбирается Apple, выкидывая пользователю окно с вопросом о том, уверен ли он в покупке. По окончанию этого процесса мы имеем вызов SKPaymentTransactionObserver, в котором, в зависимости от того, прошла ли транзакция, мы продолжаем наш процесс, передавая receipt серверу на проверку.
В случае успешной проверки нашего receipt, мы должны предоставить пользователю контент и, что очень важно, удалить транзакцию из очереди.
При завершении периода подписки нужно будет просто отправлять запрос к серверу и проверять, продлена ли она или нет.
На этом все, мне это, в итоге, не пригодилось в данном проекте, но, надеюсь, что кому-то из вас повезет больше!
В данном посте я постараюсь провести вас через все этапы внедрения подписки и, возможно, смогу отговорить вас от этой идеи.
Что вообще такое Auto-Renewable Subscription
Данный вид In-App позволяет вам предложить пользователю подписаться на предоставляемый вами контент одним действием, и потом забыть о каких либо платежах — деньги с него будут списываться ежемесячно(срок подписки вы можете выбрать произвольно), и ему больше не нужно будет задумываться о том, что период подписки подходит к концу, а вам не придется лишний раз напоминать ему об этом — деньги будут сниматься с его счета и отправляться на ваш.
Также, вам не придется встраивать возможность отказаться от подписки — это возможно только в настройках iOS — удобно, и пользователю не мозолит глаза кнопка отказа.
Почему мне могут отказать?
Казалось бы, такая идеальная схема, сделаю-ка я месяц триала, а потом пусть подписываются на доступ к приложению за доллар в месяц. Но не так-то все просто. Ключевыми словами здесь является то, что Auto-Renewable Subscription предназначены для предоставления цифрового контента (Digital Content), а это слова весьма размытые.
Например, наше приложение ежедневно предоставляет пользователю несколько новых слов, и мы думали, что это вполне себе «digital content». Парни из Apple так не думали. В итоге, пришлось переделывать все на Non-renewing subscriptions.
Поэтому, прежде чем начать внедрять этот тип покупок, подумайте, на самом ли деле вы подходите под то, что Apple вкладывала в смысл этого механизма.
Что ж, тогда перейдем к реализации
Процесс внедрения Auto-Renewable Subscription состоит из трех этапов:
- Добавление и настройка в iTunes Connect
- Настройка серверной части для валидации
- Реализация покупки внутри приложения
Добавление и настройка в iTunes Connect
Сначала нам нужно получить shared secret, его мы будем использовать на серверной стороне при обращении к серверам Apple. Заходим в раздел Manage In-App Purchases и генерируем его.
Там же мы добавляем новую In-App соответсвующего типа, выбираем продолжительность, название и заполняем все поля.
Особенно нас интересует Product ID — его мы будем запрашивать из приложения.
Также нас попросят добавить ссылку на нашу Privcay Policy, без нее это невозможно. Что там писать? Да не так уж и важно, мы написали что-то такое: www.easy10.com/privacy
Что ж, shared secret получили, Product ID есть, переходим к сервереной части.
Настройка серверной части для валидации
Этот этап необходим нам для проверки и дешифровки receipt, приходящих от Apple при покупке и в момент, когда нам интересно, обновлилась ли подписка в новом месяце.
Этот механизм реализован следущим образом:
- В приложении мы делаем запрос на покупку
- Получаем от Apple receipt
- Кодируем его в base64
- Отправляем на наш сервер (эти шаги можно поменять местами)
- С нашего сервера отправляем на сервера Apple
- Те его дешифруют и присылают нам
- Мы извлекаем необходимую информацию и говорим приложению, каков статус подписки
При отсутсвии собственного сервера, шаги 4-6 можно сделать с помощью сервиса www.beeblex.com
Если же вы хотите иметь полный контроль над процессом, то на шаге 5 вы должны отправить на buy.itunes.apple.com/verifyReceipt JSON следущего содержания
{
"receipt-data" : "(receipt bytes here)",
"password" : "(shared secret bytes here)"//тот самый shared secret
}
В ответ на шаге 6 мы получим следущий JSON
{
"status" : 0,
"receipt" : { (receipt here) },
"latest_receipt" : "(base-64 encoded receipt here)",
"latest_receipt_info" : { (latest receipt info here) }
}
Если status = 0, то все хорошо. Если он равен 21006, значит наш пользователь не продлил подписку и нам нужно прекраить предоставление контента. Все остальные коды можно посмотреть здесь:
Status codes for auto-renewable subscriptions
В самом же поле receipt нас будет больше всего интересовать значение expires_date — сравниваем его с текущим и на основе этого принимаем какие-то решения. Если пользователь не отказался от вашей подписки, оно будет автоматически обновляться.
А как все это тестить?
Для этого нам необходимо создать тестового пользователя в iTunes Connect и на сервере отправлять запрос на проверку receipt на sandbox.itunes.apple.com/verifyReceipt
Хочу обратить ваше внимание на то, что в тестовом режиме подписка будет автоматически обновляться только шесть раз. И все. Об этом почти нигде не написано, и я провел бессонные ночи, не понимая, в чем же причина того, что expires_date у меня не изменялась. Именно в этом. Поэтому вам придется создавать нового пользователя, чтобы тестить именно этом момент самопродления.
Помимо этого, период действия в тестовом режиме значительно короче чем он есть на самом деле, например, месячная подписка сжимается до 5 минут.
Ну и на последок я приведу код покупок в самом приложении. Хоть он и был написан в сотнях мест, раз уж я решил собрать все вместе, без этой части не обойтись.
Реализация покупки внутри приложения
Для этого в iOS предназначен StoreKit.framework. Он содержит в себе все необходмое для совершения покупки, отслеживания прохождения транзакции и восстановления покупок с нового устройства.
Для начала нам нужно будет добавить обзервера транзаций, который должен пооддерживать протоколы SKPaymentTransactionObserver — это нужно для того, чтобы транзакция не потерялась, когда у пользователя внезапно выключился телефон или интернет.Поэтому это лучше всего делать где-нибудь в
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
////
[[SKPaymentQueue defaultQueue] addTransactionObserver:sharedPaymentsOberver];//это будет синглтон
///}
Создадим класс PaymentsOberver, который будет пооддеживать все необходимые нам протоколы
#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
@interface PaymentsObserver : NSObject <SKPaymentTransactionObserver,SKProductsRequestDelegate,SKRequestDelegate>
- (void)requestProductData:(NSString*)ofProduct;
- (BOOL)validateReceipt;
@end
Сначала мы должны сформировать запрос на продукт, указв Product ID, который нам необходим, мы можем запросить неограниченное количество продуктов сразу
- (void)requestProductData:(NSString*)ofProduct
{
if ([SKPaymentQueue canMakePayments])//Проверяем, включена ли возможность совершать покупки
{
NSLog(@"Request Began");
SKProductsRequest *request= [[SKProductsRequest alloc] initWithProductIdentifiers: [NSSet setWithObject:ofProduct]];
request.delegate = self;
[request start];
} else {
UIAlertView *noPayment = [[UIAlertView alloc]initWithTitle:NSLocalizedString(@"You can't make payments", nil ) message:NSLocalizedString(@"Enable payments in your account settings,please", nil) delegate:self cancelButtonTitle:NSLocalizedString(@"Ok", nil) otherButtonTitles: nil];
[noPayment show];
}
}
По завершении запроса мы получим вызов одного из методов SKProductRequestDelegate, в случае успеха — вот такой
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
NSArray *myProduct = response.products;
if ([response.products count]) {
SKPayment *payment = [SKPayment paymentWithProduct:[myProduct lastObject]];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
}
В нем мы добавляем полученый продукт в очередь транзакций. Дальше с ним разбирается Apple, выкидывая пользователю окно с вопросом о том, уверен ли он в покупке. По окончанию этого процесса мы имеем вызов SKPaymentTransactionObserver, в котором, в зависимости от того, прошла ли транзакция, мы продолжаем наш процесс, передавая receipt серверу на проверку.
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
[self restoreTransaction:transaction];
default:
break;
}
}
}
В случае успешной проверки нашего receipt, мы должны предоставить пользователю контент и, что очень важно, удалить транзакцию из очереди.
- (void)completeTransaction:(SKPaymentTransaction *)transaction
{
if ([self validateReceipt:transaction.transactionReceipt]) {
[self provideContent: transaction.payment.productIdentifier];
// Remove the transaction from the payment queue.
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
}
При завершении периода подписки нужно будет просто отправлять запрос к серверу и проверять, продлена ли она или нет.
На этом все, мне это, в итоге, не пригодилось в данном проекте, но, надеюсь, что кому-то из вас повезет больше!