Любой (ранний) опыт работы с автоматически обновляемыми подписками для iOS - PullRequest
27 голосов
/ 16 февраля 2011

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

Я ожидал, что смогу работать только с клиентом iOS, просто спросив iTunes через магазинный комплект API, он / она уже купил это (подписка-) и получение ответа «да / нет» вместе с датой истечения срока действия.

Есть ли у кого-нибудь опыт работы с автоматически возобновляемыми подписками или (потому что они кажутся чем-то похожими) непотребляемыми продуктами?Есть ли хорошие уроки по этому поводу?

Спасибо.

Ответы [ 4 ]

51 голосов
/ 09 июня 2011

У меня это работает в песочнице, почти вживую ...

Нужно использовать сервер для проверки квитанций.

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

На устройстве не нужно хранить какие-либо конфиденциальные данные и не следует :)

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

Я разработал приложение Rails3.x для обработки на стороне сервера, фактический код для проверки выглядит следующим образом:

APPLE_SHARED_PASS = "enter_yours"
APPLE_RECEIPT_VERIFY_URL = "https://sandbox.itunes.apple.com/verifyReceipt" #test
# APPLE_RECEIPT_VERIFY_URL = "https://buy.itunes.apple.com/verifyReceipt"     #real
def self.verify_receipt(b64_receipt)
  json_resp = nil
  url = URI.parse(APPLE_RECEIPT_VERIFY_URL)
  http = Net::HTTP.new(url.host, url.port)
  http.use_ssl = true
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
  json_request = {'receipt-data' => b64_receipt, 'password' => APPLE_SHARED_PASS}.to_json
  resp, resp_body = http.post(url.path, json_request.to_s, {'Content-Type' => 'application/x-www-form-urlencoded'})
  if resp.code == '200'
    json_resp = JSON.parse(resp_body)
    logger.info "verify_receipt response: #{json_resp}"
  end
  json_resp
end
#App Store error responses
#21000 The App Store could not read the JSON object you provided.
#21002 The data in the receipt-data property was malformed.
#21003 The receipt could not be authenticated.
#21004 The shared secret you provided does not match the shared secret on file for your account.
#21005 The receipt server is not currently available.
#21006 This receipt is valid but the subscription has expired.

ОБНОВЛЕНИЕ

Мое приложение было отклонено, поскольку в метаданных 1017 * явно не указана информация об автообновляемых подписках.

В ваших метаданныхв iTunes Connect (в описании вашего приложения): вам необходимо четко и наглядно раскрыть пользователям следующую информацию о вашей автоматически возобновляемой подписке:

  • Название публикации или услуги
  • Длинаподписки (период времени и / или количество доставок в течение каждого периода подписки)
  • Цена подписки и цена за выпуск при необходимости
  • Оплата будет списана с учетной записи iTunes при подтверждении покупки
  • Подписка автоматически продлевается, если автоматическое продление не отключено по крайней мере за 24 часа до окончания срока действияnt period
  • С аккаунта будет взиматься плата за продление в течение 24 часов до конца текущего периода, и указывается стоимость продления
  • Подписка может управляться пользователем и автоматическипродление можно отключить, зайдя в настройки учетной записи пользователя после покупки
  • Отмена текущей подписки не допускается в течение активного периода подписки
  • Ссылки на вашу политику конфиденциальности и условия использования
  • Любая неиспользованная часть бесплатного пробного периода, если таковая предлагается, будет аннулирована, когда пользователь приобретет подписку на эту публикацию. "

ОБНОВЛЕНИЕ II

Приложениеполучил снова.Квитанция о подписке не проверяется производственным URL AppStore.Я не могу воспроизвести эту проблему в песочнице, мое приложение работает безупречно.Единственный способ отладки - это снова отправить приложение на проверку и посмотреть журнал сервера.

ОБНОВЛЕНИЕ III

Еще одно отклонение.Между тем Apple задокументировала еще два статуса:

#21007 This receipt is a sandbox receipt, but it was sent to the production service for verification.
#21008 This receipt is a production receipt, but it was sent to the sandbox service for verification.

Прежде чем отправлять приложение на проверку, не следует переключать сервер на URL подтверждения получения чека.если это так, то при проверке возвращается статус 21007.

На этот раз отклонение выглядит следующим образом:

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

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

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

ОБНОВЛЕНИЕ IV

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

Если транзакции не завершены, они возвращаются обратно в приложение, и все происходит странным образом.

Сначала нужно инициировать платеж, например:

//make the payment
SKPayment *payment = [SKPayment paymentWithProductIdentifier:productIdentifier];
[[SKPaymentQueue defaultQueue] addPayment:payment];

Затем приложение в скором времени выйдет из активного состояния, и этот метод для делегата приложения будет вызван:

- (void)applicationWillResignActive:(UIApplication *)application

Пока приложение неактивно, в App Store появляются его диалоговые окна.как приложение снова становится активным:

- (void)applicationDidBecomeActive:(UIApplication *)application

ОС доставляет транзакцию через:

- (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];
            break;
        }
        default:
            break;
      }
  }
}

А затем завершает транзакцию:

//a fresh purchase
- (void) completeTransaction: (SKPaymentTransaction *)transaction
{
    [self recordTransaction: transaction];
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; 
}

См.,как вызывать метод finishTransaction сразу после передачи полученной транзакции на recordTransaction, который затем вызывает сервер приложений и выполняет проверку получения подписки в App Store.Например:

- (void)recordTransaction: (SKPaymentTransaction *)transaction 
{
    [self subscribeWithTransaction:transaction];
}


- (void)subscribeWithTransaction:(SKPaymentTransaction*)transaction {

    NSData *receiptData = [transaction transactionReceipt];
    NSString *receiptEncoded = [Kriya base64encode:(uint8_t*)receiptData.bytes length:receiptData.length];//encode to base64 before sending

    NSString *urlString = [NSString stringWithFormat:@"%@/api/%@/%@/subscribe", [Kriya server_url], APP_ID, [Kriya deviceId]];

    NSURL *url = [NSURL URLWithString:urlString];
    ASIFormDataRequest *request = [[[ASIFormDataRequest alloc] initWithURL:url] autorelease];
    [request setPostValue:[[transaction payment] productIdentifier] forKey:@"product"];
    [request setPostValue:receiptEncoded forKey:@"receipt"];
    [request setPostValue:[Kriya deviceModelString] forKey:@"model"];
    [request setPostValue:[Kriya deviceiOSString] forKey:@"ios"];
    [request setPostValue:[appDelegate version] forKey:@"v"];

    [request setDidFinishSelector:@selector(subscribeWithTransactionFinished:)];
    [request setDidFailSelector:@selector(subscribeWithTransactionFailed:)];
    [request setDelegate:self];

    [request startAsynchronous];

}

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

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

APPLE_SHARED_PASS = "83f1ec5e7d864e89beef4d2402091cd0" #you can get this in iTunes Connect
APPLE_RECEIPT_VERIFY_URL_SANDBOX    = "https://sandbox.itunes.apple.com/verifyReceipt"
APPLE_RECEIPT_VERIFY_URL_PRODUCTION = "https://buy.itunes.apple.com/verifyReceipt"

  def self.verify_receipt_for(b64_receipt, receipt_verify_url)
    json_resp = nil
    url = URI.parse(receipt_verify_url)
    http = Net::HTTP.new(url.host, url.port)
    http.use_ssl = true
    http.verify_mode = OpenSSL::SSL::VERIFY_NONE
    json_request = {'receipt-data' => b64_receipt, 'password' => APPLE_SHARED_PASS}.to_json
    resp, resp_body = http.post(url.path, json_request.to_s, {'Content-Type' => 'application/x-www-form-urlencoded'})
    if resp.code == '200'
      json_resp = JSON.parse(resp_body)
    end
    json_resp
end

def self.verify_receipt(b64_receipt)
    json_resp = Subscription.verify_receipt_for(b64_receipt, APPLE_RECEIPT_VERIFY_URL_PRODUCTION)
    if json_resp!=nil
      if json_resp.kind_of? Hash
        if json_resp['status']==21007 
          #try the sandbox then
          json_resp = Subscription.verify_receipt_for(b64_receipt, APPLE_RECEIPT_VERIFY_URL_SANDBOX)
        end
      end
    end
    json_resp
end

Таким образом, в основном каждый всегда сверяется с рабочим URL, но если он возвращает код 21007, то это означает, что квитанция песочницы была отправлена ​​на рабочий URL изатем просто попробуйте снова с URL песочницы.Таким образом, ваше приложение работает одинаково в песочнице и в производственном режиме.

И, наконец, Apple захотела, чтобы я добавил кнопку RESTORE рядом с кнопками подписки для обработки нескольких устройств, принадлежащих одному пользователю.Затем эта кнопка вызывает [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];, и приложение будет доставлено с восстановленными транзакциями (если есть).

Кроме того, иногда тестовые учетные записи пользователей как-то загрязняются, и все перестает работать, и вы можете получить «Не удается подключиться»в iTunes store "сообщение при подписке.Это помогает создать нового тестового пользователя.

Вот остаток соответствующего кода:

- (void) restoreTransaction: (SKPaymentTransaction *)transaction
{
    [self recordTransaction: transaction];
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; 
}

- (void) failedTransaction: (SKPaymentTransaction *)transaction
{
    if (transaction.error.code == SKErrorPaymentCancelled)
    {
        //present error to user here 
    }
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];    

}

Я желаю вам приятного опыта программирования InAppPurchase.: -)

10 голосов
/ 20 февраля 2011

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

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

Таким образом, единственный вариант - как Apple рекомендует - сохранить, а затем проверить квитанцию ​​магазина.

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

Это означает, что ответ на ваш вопрос «могу ли я работать только с клиентом iOS» - «технически да», но делать это было бы крайне нежелательно из-за ряда проблем безопасности. К счастью, архитектура серверной части, которую вам нужно создать, очень проста - просто связать квитанции iTunes с UDID устройств и простой API для взаимодействия с ними. Если вы не можете решить эту проблему, я уверен, что очень скоро существующие сторонние помощники по покупке приложений, такие как Urban Airship, добавят в свои продукты автоматические обновления подписчиков.

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

2 голосов
/ 21 февраля 2011

Может быть, автообновляемые серверы покупки песочницы не работают? Покупки расходных / нерасходимых / подписных продуктов в песочнице работают, но автоматически возобновляемая покупка возвращает эту ошибку:

Ошибка домена = SKErrorDomain Code = 0 "Не удается подключиться к iTunes Store" UserInfo = 0x15b600 {NSLocalizedDescription = Не удается подключиться к iTunes Магазин}

0 голосов
/ 20 февраля 2011

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

но в настоящее время кажется, что серверы не работают или что-то. проверка с помощью яблочных серверов не работает

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...