Ответ 1
У меня он работает в песочнице, почти вживую...
Для проверки чеков следует использовать сервер.
На сервере вы можете записать устройство 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.
ОБНОВЛЕНИЕ
Мое приложение было отклонено, потому что метаданные явно не указали некоторую информацию об абонементах с автоматическим возобновлением.
В ваших метаданных в iTunes Connect (в описании вашего приложения): You необходимо четко и четко раскрыть пользователям следующие информацию о подписке на автообновление:
- Название публикации или услуги
- Длина подписки (период времени и/или количество поставок в течение каждого периода подписки)
- Цена подписки и цена за выпуск в случае необходимости
- Платеж будет снят с учетной записи iTunes при подтверждении покупки.
- Подписка автоматически возобновляется, если автоматическое обновление не отключается по крайней мере за 24 часа до конца текущего периода.
- Аккаунт будет взиматься за продление в течение 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-адрес проверки содержимого приложения Sandbox, но когда он находится в процессе обзора, это как-то между мирами. Поэтому мне пришлось изменить код сервера следующим образом:
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.: -)