Auto-Renewable Subscription, наверное, самый сложный из всех типов In-App Purchase в iOS, и реализовать его правильно, от начала и до конца, совсем непросто, и даже пройдя этот нелегкий путь, вы можете столкнуться с отказом цензоров принимать ваше приложение.
В данном посте я постараюсь провести вас через все этапы внедрения подписки и, возможно, смогу отговорить вас от этой идеи.
Что вообще такое 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];
}
}
При завершении периода подписки нужно будет просто отправлять запрос к серверу и проверять, продлена ли она или нет.
На этом все, мне это, в итоге, не пригодилось в данном проекте, но, надеюсь, что кому-то из вас повезет больше!
Автор: pestrov