Reputation: 352
Hello I am having getting my App published on the App Store as they keep insisting my in-app purchases are not set up correctly.
Upon testing them myself in the sandbox, everything worked correctly.
This is the message they sent me, I pasted my code below.
Thank you for taking the time to help me out!
Guideline 2.1 - Performance - App Completeness
We found that your in-app purchase products exhibited one or more bugs when reviewed on iPhone and iPad running iOS 12 on Wi-Fi.
Specifically, your in app purchase buttons do not work.
Next Steps
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.
class IAPService: NSObject {
private override init() {}
static let shared = IAPService()
var products = [SKProduct]()
let paymentQueue = SKPaymentQueue.default()
func getProducts() {
let products: Set = [IAPProduct.consumable.rawValue,
IAPProduct.nonConsumable.rawValue]
let request = SKProductsRequest(productIdentifiers: products)
request.delegate = self
request.start()
paymentQueue.add(self)
}
func purchase(product: IAPProduct) {
for p in products {
if p.productIdentifier == product.rawValue {
let payment = SKPayment(product: p)
paymentQueue.add(payment)
print("Adding product to payment queue")
}
}
}
func restorePurchase() {
print("Restoring purchases")
paymentQueue.restoreCompletedTransactions()
}
func givePurchasedProduct(productID: String) {
if productID.range(of: "Zap") != nil {
NotificationCenter.default.post(name: Notification.Name.init("zapPurchased"), object: nil)
} else if productID.range(of: "Ads") != nil {
NotificationCenter.default.post(name: Notification.Name.init("noAdsPurchased"), object: nil)
}
}
}
extension IAPService: SKProductsRequestDelegate {
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
self.products = response.products
for product in response.products {
print(product.localizedTitle)
}
}
}
extension IAPService: SKPaymentTransactionObserver {
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
print(transaction.transactionState.status(), transaction.payment.productIdentifier)
switch transaction.transactionState {
case .purchasing, .deferred: break // do nothing
case .purchased:
queue.finishTransaction(transaction)
givePurchasedProduct(productID: transaction.payment.productIdentifier)
case .restored:
self.restorePurchase()
queue.finishTransaction(transaction)
case .failed:
queue.finishTransaction(transaction)
}
}
}
}
extension SKPaymentTransactionState {
func status() -> String {
switch self {
case .deferred:
return "deferred"
case .failed:
return "failed"
case .purchased:
return "purchased"
case .purchasing:
return "purchasing"
case .restored:
return "restored"
}
}
}
Upvotes: 3
Views: 2045
Reputation: 15894
I think there is no problem with your iOS code. From the Apple's response, they say that, your server is pointing to production environment of Apple InApp purchase and validating the receipts received from test environment of Apple InApp purchase used within App.
Apple has 2 environments for InApp purchases - Test & Production. Both the environments behave same. When you run the app on your iPhone to test by your QA or while you are debugging, it connects to Test environment. You are not charged in real when using Test environment. But the receipts generated are almost same as that of real production environment
Now when you submit the app to store, it will automatically make purchases from Production environment. Users are charged and real receipts are generated.
Your app is sending those receipts to server I think and your server is using the wrong InApp server environment to verify the receipts.
On your server make sure that the Apple InApp purchase environment URL is appropriately picked based on your InApp purchase receipt. If you or your team is testing the app, your server has to use Test URL and when the app is submitted to production, your server has to use Production URL of InApp Purchase.
Upvotes: 0
Reputation: 2561
App review is very strict when it comes to Apple. Speaking from experience, I have had this problem many times. Your code seems fine to me till it goes to the givePurchasedProduct
function.
Okay so things i noticed:
return "purchased"
if nothing goes wrongcase .purchased:
then we invoke the givePurchasedProductOn your function. you separate the purchase to see if it's either a Zap purchase or it was to remove the ads
However. this line is confusing me-- Why would you use range
when contains
where introduced recently.
if productID.contains("Zap") {
// No Zapp? it has to be Ads then
NotificationCenter.default.post(name: Notification.Name.init("zapPurchased"), object: nil)
} else {
NotificationCenter.default.post(name: Notification.Name.init("noAdsPurchased"), object: nil)
}
Side notes. You might have forgot:
import Foundation
There's more to it. Receipt Validating
is a headache, but when it's needed. It's relaxation and more security to your app.
If you're validating the receipt. these question and it's answers helped me a lot. please see:
Bonus. With SwiftyStoreKit
. Receipt validating is just like tapping a button:
Use this method to (optionally) refresh the receipt and perform validation in one step.
let appleValidator = AppleReceiptValidator(service: .production, sharedSecret: "your-shared-secret")
SwiftyStoreKit.verifyReceipt(using: appleValidator, forceRefresh: false) { result in
switch result {
case .success(let receipt):
print("Verify receipt success: \(receipt)")
case .error(let error):
print("Verify receipt failed: \(error)")
}
}
Now on the other hand. to the reviewers the purchased content is not delivering. So they think it's purchase validating.
How do you validate the purchase? deliver the content? please update your question. I'm sure i'll be helpful
Good Luck
Upvotes: 4