Цель:
Я хотел бы выкупить предложение о подписке, если подписка с автоматическим продлением в настоящее время активна и срок ее действия не истек.
Что я пробовал:
Я настроил демонстрационный проект Store Kit на Xcode 10.2, чтобы протестировать новые предложения подписки. После выполнения основных положений WWDC 2019 и устранения неполадок я смог достичь следующего:
- Успешно настроить подписку с возможностью автоматического продления на 1 месяц с помощью StoreKit.
- Нажмите конечную точку / verifyReceipt и успешно расшифровали данные квитанции.
- Настройка локального сервера и успешно сгенерированная подпись.
- Настройка предложения подписки (1 месяц бесплатно) в Appstore Connect.
- Успешно погашенное предложение подписки после истечения срока действия возобновляемой подписки.
Моя текущая проблема:
Всякий раз, когда я пытаюсь получить предложение о подписке, и моя автоматически возобновляемая подписка все еще активна, я получаю следующую ошибку от StoreKit:
Error Domain=SKErrorDomain Code=0 "Cannot connect to iTunes Store" UserInfo={NSLocalizedDescription=Cannot connect to iTunes Store}
Удивительно, однако, что при попытке запросить предложение появляется окно с сообщением «Все готово. Ваша покупка прошла успешно. [Среда: Песочница]», несмотря на то, что в журналах появилось сообщение об ошибке выше.
Согласно документации Apple на SKErrorDomain (ссылка: https://developer.apple.com/documentation/storekit/skerror/code), Код 0 - «Неизвестная ошибка». Это кирпичная стена, которую я натолкнул.
В процессе разработки этого демонстрационного проекта я получил другие коды ошибок, например ErrorCode = 12, что означает «недопустимая подпись», которую я решил, поэтому я уверен, что с подписью все в порядке. Проверьте мои настройки ниже для большего контекста.
Я призываю вас игнорировать мои неоптимальные методы кодирования здесь. Я взломал это вместе как доказательство концепции, а не для производства.
Это действие для нажатия кнопки «Вознаграждение», которая пытается выкупить предложение подписки:
// PrimaryVC.swift
@IBAction func aClaimReward(_ sender: UIButton) {
// Hard code offer information
let username = "mail@mysandbox.com" // sandbox username
guard let usernameData = username.data(using: .utf8) else { return }
let usernameHash = usernameData.md5().toHexString()
// Enums for simplifying the product identifiers.
let productIdentifier = IAPProduct.autoRenewable.rawValue
let offerIdentifier = IAPProduct.reward.rawValue
// Call prepare offer method and get discount in completion block
IAPService.shared.prepareOffer(usernameHash: usernameHash, productIdentifier: productIdentifier, offerIdentifier: offerIdentifier) { (discount) in
// Find the autorenewable subscription in products set
guard let product = IAPService.shared.products.filter({ $0.productIdentifier == IAPProduct.autoRenewable.rawValue }).first else {
return
}
// Complete transaction
self.buyProduct(product: product, forApplicationUsername: usernameHash, withOffer: discount)
}
}
Это код для метода buyProduct (), использованного в конце действия выше:
// PrimaryVC.swift
func buyProduct(product: SKProduct, forApplicationUsername usernameHash: String, withOffer offer: SKPaymentDiscount) {
// Create payment object
let payment = SKMutablePayment(product: product)
// Apply username and offer to the payment
payment.applicationUsername = usernameHash
payment.paymentDiscount = offer
// Add payment to paymentQueue
IAPService.shared.paymentQueue.add(payment)
}
Я создал класс обслуживания покупок в приложении, в котором живет большая часть логики покупок в приложении. Этот синглтон используется в действии кнопки, описанном выше, и используется следующий метод:
// IAPService.swift
import SwiftyJSON
import Alamofire
// ...
func prepareOffer(usernameHash: String, productIdentifier: String, offerIdentifier: String, completion: @escaping (SKPaymentDiscount) -> Void) {
// Create parameters dictionary
let parameters: Parameters = [
"appBundleID": "my.bundle.id",
"productIdentifier": productIdentifier, // "my.product.id",
"offerID": offerIdentifier, // "REFERRALBONUSMONTH"
"applicationUsername": usernameHash
]
// Generate new signature by making get request to local server.
// I used the starter code from the wwdc2019 lecture on subscription offers
AF.request("https://mylocalserver/offer", parameters: parameters).responseJSON { response in
var signature: String?
var keyID: String?
var timestamp: NSNumber?
var nonce: UUID?
switch response.result {
case let .success(value):
let json = JSON(value)
// Get required parameters for creating offer
signature = json["signature"].stringValue
keyID = json["keyID"].stringValue
timestamp = json["timestamp"].numberValue
nonce = UUID(uuidString: json["nonce"].stringValue)
case let .failure(error):
print(error)
return
}
// Create offer
let discountOffer = SKPaymentDiscount(identifier: offerIdentifier, keyIdentifier: keyID!, nonce: nonce!, signature: signature!, timestamp: timestamp!)
// Pass offer in completion block
completion(discountOffer)
}
}
Вывод:
Согласно лекции о предложениях подписки WWDC2019, пользователи должны иметь возможность использовать предложение подписки даже во время активной подписки, но я продолжаю получать SKErrorCode = 0, когда я пытаюсь использовать предложение подписки во время активной подписки. Я могу выкупить подписку после истечения срока действия подписки с возможностью автоматического продления, и я проверил квитанцию и увидел данные о предложении подписки на квитанции.
Есть идеи, где я могу пойти не так? Или это проблема со стороны Apple?