0.13.0
didn't find same issues
I've implemented Auto-renewable subscription using your library, then then Apple rejected it according to the problem:
When validating receipts on your server, your server needs to be able to handle a production-signed app getting its receipts from Apple’s test environment. The recommended approach is for your production server to always validate receipts against the production App Store first. If validation fails with the error code “Sandbox receipt used in production,” you should validate against the test environment instead.
I'm verifying receipt using Apple server, so my code looks like that after, considering this case
Code on controller:
IAPHelper.purchase() { result in
switch result {
case .success(let product):
print("\(product.productId) was bought")
if product.needsFinishTransaction {
SwiftyStoreKit.finishTransaction(product.transaction)
}
IAPHelper.verifySubscription { verifyReceiptResult, verifySubscriptionResult in
if let verifyReceiptResult = verifyReceiptResult {
switch verifyReceiptResult {
case .error(_):
self.stopAnimating()
self.showAlert(alert: self.alertForVerifyReceipt(result: verifyReceiptResult))
case .success(_):
if let verifySubscriptionResult = verifySubscriptionResult {
switch verifySubscriptionResult {
case .purchased(let expiryDate, _):
Storage.user.subscriptionExpirationDate = expiryDate
default:
break
}
self.stopAnimating()
self.showAlert(alert: self.alertForVerifySubscription(result: verifySubscriptionResult))
}
}
}
self.destroyViewController()
}
case .error(let error):
self.stopAnimating()
self.showAlert(alert: self.alertForPurchaseResult(result: result))
self.destroyViewController()
}
}
Code on model:
static func purchase(completion: @escaping (PurchaseResult) -> Void) {
SwiftyStoreKit.purchaseProduct(Constants.subscriptionID, atomically: true) { result in
completion(result)
}
}
static func verifyReciept(completion: @escaping (NetworkService.JSONResult) -> Void) {
let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: Constants.sharedSecret)
let sandboxValidator = AppleReceiptValidator(service: .sandbox, sharedSecret: Constants.sharedSecret)
SwiftyStoreKit.verifyReceipt(using: appleValidator, forceRefresh: true) { result in
switch result {
case .success(let json):
completion(.success(json as NetworkService.JSON))
case .error(let error):
var isSandbox = false
SwiftyStoreKit.verifyReceipt(using: sandboxValidator, forceRefresh: true) {
verifySandboxReceiptResult in
switch verifySandboxReceiptResult {
case .success(let json):
isSandbox = true
completion(.success(json as NetworkService.JSON))
case .error(_):
break
}
}
if !isSandbox {
completion(.failed(Message.custom(error.localizedDescription)))
}
}
}
}
static func verifySubscription(completion: @escaping (VerifyReceiptResult?, VerifySubscriptionResult?) -> Void) {
let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: Constants.sharedSecret)
let sandboxValidator = AppleReceiptValidator(service: .sandbox, sharedSecret: Constants.sharedSecret)
SwiftyStoreKit.verifyReceipt(using: appleValidator, forceRefresh: true) { verifyReceiptResult in
switch verifyReceiptResult {
case .error(let error):
var isSandbox = false
SwiftyStoreKit.verifyReceipt(using: sandboxValidator, forceRefresh: true) {
verifySandboxReceiptResult in
switch verifySandboxReceiptResult {
case .success(let receipt):
isSandbox = true
let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(ofType: .autoRenewable, productId: Constants.subscriptionID, inReceipt: receipt)
completion(verifySandboxReceiptResult, verifySubscriptionResult)
case .error(_):
break
}
}
if !isSandbox {
Crashlytics.sharedInstance().recordError(error)
completion(verifyReceiptResult, nil)
}
case .success(let receipt):
let verifySubscriptionResult = SwiftyStoreKit.verifySubscription(ofType: .autoRenewable, productId: Constants.subscriptionID, inReceipt: receipt)
completion(verifyReceiptResult, verifySubscriptionResult)
}
}
}
In AppDelegate didFinishLaunchWithOptions() I run this code:
SwiftyStoreKit.completeTransactions(atomically: true) { purchases in
for purchase in purchases {
switch purchase.transaction.transactionState {
case .purchased, .restored:
if purchase.needsFinishTransaction {
// Deliver content from server, then:
SwiftyStoreKit.finishTransaction(purchase.transaction)
}
// Unlock content
case .failed, .purchasing, .deferred:
break // do nothing
}
}
}
// update subscription status
if Storage.user != nil && Storage.user.subscriptionExpirationDate != nil {
IAPHelper.verifySubscription { verifyReceiptResult, verifySubscriptionResult in
if let verifyReceiptResult = verifyReceiptResult {
switch verifyReceiptResult {
case .error(_):
break
case .success(_):
if let verifySubscriptionResult = verifySubscriptionResult {
switch verifySubscriptionResult {
case .purchased(let expiryDate, _):
Storage.user.subscriptionExpirationDate = expiryDate
default:
break
}
}
}
}
}
}
Now, this sometimes works properly, but in some cases it popup "All in set" alert and properly buys product, but return error "Cannot connect iTunes store" and app shows error in this case. Therefore, app doesn't verify receipt and doesn't unlock special functionality.
I can provide more info if needed. Sorry that issue written out of your template, but I believe it's still understandable.
Thank you in advance!
UPD: If I manage to buy subscription and delete app, then try again while subscription is still active (1 month - 5 min), "You are currently subscribed", but still "Cannot connect to iTunes store" returned as error
UPD: This issue helped me to solve half of the problem, but still. What about reviewers comment on my app?
When validating receipts on your server, your server needs to be able to handle a production-signed app getting its receipts from Apple’s test environment. The recommended approach is for your production server to always validate receipts against the production App Store first. If validation fails with the error code “Sandbox receipt used in production,” you should validate against the test environment instead.
Does my code cover this case now and do you see any wrong implementation here in my code?
Okay, as I understood my problem is that I was trying to complete transaction of the atomic purchase (also pinned issue). Today I've got my app accepted, may be it would help some one, pls edit readme and add info about this where you presented code in AppDelegate which tries to complete all disrupted purchases, at app start, or please correct me, if I said something wrong. Thank you in advance.
UPD: hmmm, I think I've done some thing wrong here, pls reply, I need some clarification and help or at least give me related links to research @bizz84
While debugging if found, this scenario arrises in apples default in-app purchase method too, Probably it is related to sandbox server. Please Update on this issue.
Okay, as I understood my problem is that I was trying to complete transaction of the atomic purchase (also pinned issue). Today I've got my app accepted, may be it would help some one, pls edit readme and add info about this where you presented code in AppDelegate which tries to complete all disrupted purchases, at app start, or please correct me, if I said something wrong. Thank you in advance.
UPD: hmmm, I think I've done some thing wrong here, pls reply, I need some clarification and help or at least give me related links to research @bizz84
So how exactly did you have this fixed?
Hi all! Does anyone have any updates on this issue?
Most helpful comment
So how exactly did you have this fixed?