3 votes

Pourquoi mon application est-elle rejetée pour les achats en ligne ?

Bonjour, j'ai du mal à faire publier mon application sur l'App Store car ils insistent sur le fait que mes achats in-app ne sont pas configurés correctement.

En les testant moi-même dans le bac à sable, tout a fonctionné correctement.

Voici le message qu'ils m'ont envoyé, j'ai collé mon code ci-dessous.

Merci de prendre le temps de m'aider !

Ligne directrice 2.1 - Performance - Complétude de l'application

Nous avons constaté que vos produits d'achat in-app présentaient un ou plusieurs bogues lorsqu'ils ont été examinés sur un iPhone et un iPad fonctionnant sous iOS 12 en Wi-Fi.

Plus précisément, vos boutons d'achat dans l'application ne fonctionnent pas.

Les prochaines étapes

Lorsque vous validez des reçus sur votre serveur, celui-ci doit pouvoir de gérer une application signée en production qui reçoit ses reçus de l'environnement de environnement de test d'Apple. L'approche recommandée est que votre serveur de production de production valide toujours les reçus par rapport à l'App Store de production de production. Si la validation échoue avec le code d'erreur "Sandbox receipt used dans la production", vous devez valider contre l'environnement de test à la place.

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"
            }
        }
    }

4voto

excitedmicrobe Points 1123

L'examen des applications est très strict lorsqu'il s'agit d'Apple. Parlant d'expérience, j'ai eu ce problème de nombreuses fois. Votre code me semble correct jusqu'à ce qu'il aille dans la section givePurchasedProduct fonction.

Ok, alors les choses que j'ai remarquées :

  1. Votre application traite le paiement et nous recevons return "purchased" si tout va bien
  2. Si l'affaire était case .purchased: puis nous invoquons la fonction givePurchasedProduct

Sur votre fonction. vous séparez l'achat pour voir si c'est soit un Zap l'achat ou c'était pour retirer le annonces

Cependant, cette ligne m'embrouille Pourquoi utiliseriez-vous range quand contains ont été introduits récemment.

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)   
}

Notes secondaires. Vous avez peut-être oublié :

  1. A import Foundation
  2. Je ne sais pas ce qui se passe derrière les observateurs de notification puisque le code n'est pas inclus. Mais, il ne délivre pas

Il y a plus que ça. Receipt Validating est un mal de tête, mais quand c'est nécessaire. C'est de la détente et plus de sécurité pour votre application.

Si vous validez le reçu. ces questions et leurs réponses m'ont beaucoup aidé. veuillez voir :

Bonus. Avec SwiftyStoreKit . La validation des reçus se fait en appuyant sur un bouton :

Utilisez cette méthode pour (éventuellement) rafraîchir le reçu et effectuer la validation en une seule étape.

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)")
    }
}

D'un autre côté, pour les critiques, le contenu acheté n'est pas livré. Donc ils pensent que c'est la validation de l'achat.

Comment valider l'achat ? livrer le contenu ? veuillez mettre à jour votre question. Je suis sûr que je serai utile

Bonne chance

0voto

Satyam svv Points 4887

Je pense qu'il n'y a pas de problème avec votre code iOS. D'après la réponse d'Apple, votre serveur pointe vers l'environnement de production de l'achat Apple InApp et valide les reçus reçus de l'environnement de test de l'achat Apple InApp utilisé dans l'application.

Apple dispose de 2 environnements pour les achats InApp - Test & Production. Les deux environnements se comportent de la même manière. Lorsque vous exécutez l'application sur votre iPhone pour la faire tester par votre service d'assurance qualité ou pendant que vous déboguez, elle se connecte à l'environnement de test. Vous n'êtes pas facturé en réalité lorsque vous utilisez l'environnement Test. Mais les reçus générés sont presque les mêmes que ceux de l'environnement de production réel.

Maintenant, lorsque vous soumettez l'application au magasin, elle effectuera automatiquement les achats à partir de l'environnement de production. Les utilisateurs sont facturés et des reçus réels sont générés.

Je pense que votre application envoie ces reçus au serveur et que votre serveur utilise le mauvais environnement de serveur InApp pour vérifier les reçus. Sur votre serveur, assurez-vous que l'URL de l'environnement d'achat InApp d'Apple est correctement choisie en fonction de votre reçu d'achat InApp. Si vous ou votre équipe testez l'application, votre serveur doit utiliser l'URL de test et lorsque l'application est soumise à la production, votre serveur doit utiliser l'URL de production de l'achat InApp.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X