Recently pubished an app to apple store (couple of hours ago). The in app purchase goes through showing a success message. However, it does not ask for payment information nor charge the user. This is the purchase flow:
- user clicks on the product
- apple storekit shows a message with pricing and purchase option
- apple asks for sign in inof to complete the purchase
- apple shows a successfull purchase message
In Sandbox mode, the same flow happens, however, it says you will not be charged as you are in sandbox mode.
In production, it just doesnt charge the user.
the product is approved in app store connect
The IAP code is:
import Foundation
import StoreKit
public struct InAppPurchasesCallbacks {
var onFetchCompleted: (([SKProduct]) -> Void)?
var onProductsNotFound: (([String]?) -> Void)?
var onPurchaseSucceeded: ((SKProduct, SKPaymentTransaction) -> Void)?
var onPurchaseFailed: ((SKProduct, Error?) -> Void)?
var onRestoreCompleted: (([SKPaymentTransaction]) -> Void)?
var onRestoreFailed: ((Error?) -> Void)?
public init(onFetchCompleted: (([SKProduct]) -> Void)? = nil, onProductsNotFound: (([String]?) -> Void)? = nil, onPurchaseSucceeded: ((SKProduct, SKPaymentTransaction) -> Void)? = nil, onPurchaseFailed: ((SKProduct, Error?) -> Void)? = nil, onRestoreCompleted: (([SKPaymentTransaction]) -> Void)? = nil, onRestoreFailed: ( (Error?) -> Void)? = nil) {
self.onFetchCompleted = onFetchCompleted
self.onProductsNotFound = onProductsNotFound
self.onPurchaseSucceeded = onPurchaseSucceeded
self.onPurchaseFailed = onPurchaseFailed
self.onRestoreCompleted = onRestoreCompleted
self.onRestoreFailed = onRestoreFailed
}
}
public class InAppPurchaseWrapper: NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver {
private var products = [SKProduct]()
private var callbacks: InAppPurchasesCallbacks
private var productIdentifiers: Set<String>
public init(productIdentifiers: Set<String>, callbacks: InAppPurchasesCallbacks) {
self.productIdentifiers = productIdentifiers
self.callbacks = callbacks
super.init()
SKPaymentQueue.default().add(self)
fetchProductInfo()
}
private func fetchProductInfo() {
let request = SKProductsRequest(productIdentifiers: productIdentifiers)
request.delegate = self
request.start()
}
public func purchaseProduct(_ product: SKProduct) {
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)
}
public func restorePurchases() {
SKPaymentQueue.default().restoreCompletedTransactions()
}
private func validateTransaction(transaction: SKPaymentTransaction, product: SKProduct) {
// some backend server verification calls, removed for security here.
}
}
// MARK: - Delegate methods
extension InAppPurchaseWrapper {
public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
products = response.products
let unrecognized = response.invalidProductIdentifiers
if products.count == 0 {
if unrecognized.count > 0 {
callbacks.onProductsNotFound?(unrecognized)
} else {
callbacks.onProductsNotFound?(nil)
}
} else {
callbacks.onFetchCompleted?(products)
if unrecognized.count > 0{
callbacks.onProductsNotFound?(unrecognized)
}
}
}
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case .purchasing:
break
case .purchased:
let product = products.first {
$0.productIdentifier == transaction.payment.productIdentifier
}
if let p = product {
validateTransaction(transaction: transaction, product: p)
callbacks.onPurchaseSucceeded?(p, transaction)
} else {
callbacks.onProductsNotFound?(nil)
queue.finishTransaction(transaction)
}
case .failed:
let product = products.first {
$0.productIdentifier == transaction.payment.productIdentifier
}
if let p = product, let e = transaction.error {
callbacks.onPurchaseFailed?(p, e)
} else {
callbacks.onProductsNotFound?(nil)
}
queue.finishTransaction(transaction)
case .restored:
let product = products.first {
$0.productIdentifier == transaction.original?.payment.productIdentifier
}
if let p = product {
callbacks.onPurchaseSucceeded?(p, transaction)
} else {
callbacks.onProductsNotFound?(nil)
}
queue.finishTransaction(transaction)
case .deferred:
break
@unknown default:
break
}
}
}
public func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
callbacks.onRestoreFailed?(error)
}
public func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
callbacks.onRestoreCompleted?(queue.transactions)
}
}
Apparently, there was a 5-hour delay in charging the customer from Apple's side. not sure if this was due to being a new app, or that is always the case. However, all purchases went through after about 5 hours.