IOS IAP succeed but does not charge the user

22 Views Asked by At

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:

  1. user clicks on the product
  2. apple storekit shows a message with pricing and purchase option
  3. apple asks for sign in inof to complete the purchase
  4. 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)
    }
}

1

There are 1 best solutions below

0
A Mir On

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.