web-dev-qa-db-fra.com

Comment ajouter un achat intégré à une application iOS?

Comment ajouter un achat intégré à une application iOS? Quels sont tous les détails et y at-il un exemple de code? 

Ceci est censé être un fourre-tout de sortes sur la façon d'ajouter des achats in-app aux applications iOS

246
Jojodmo

La meilleure façon d'obtenir un achat intégré qui fonctionne pour iOS 10 (et iOS 9, 8 and 7) dans Xcode 5+ consiste à effectuer les opérations suivantes:

  1. Allez à appstoreconnect.Apple.com et connectez-vous
  2. Cliquez sur My Apps puis sur l'application à laquelle vous souhaitez ajouter l'achat.
  3. Cliquez sur l'en-tête Featuresname__, puis sélectionnez In-App Purchases à gauche.
  4. Cliquez sur l'icône + au milieu
  5. Pour ce didacticiel, nous allons ajouter un achat intégré à l'application pour supprimer les annonces. Choisissez donc non-consumable. Si vous envisagiez d'envoyer un objet physique à l'utilisateur ou de lui donner quelque chose qu'il puisse acheter plusieurs fois, choisissez consumablename__.
  6. Pour le nom de référence, mettez ce que vous voulez (mais assurez-vous de savoir ce que c'est)
  7. Pour le produit id mis tld.websitename.appname.referencename, cela fonctionnera le mieux. Par exemple, vous pouvez utiliser com.jojodmo.blix.removeads
  8. Choisissez cleared for sale puis choisissez un niveau de prix égal à 1 (99 ¢). Le niveau 2 serait de 1,99 $ et le niveau 3, de 2,99 $. La liste complète est disponible si vous cliquez sur view pricing matrix. Je vous recommande d’utiliser le niveau 1, car c’est généralement le montant le plus cher à payer pour la suppression des annonces.
  9. Cliquez sur le bouton bleu add language et entrez les informations. Ceci sera TOUT montré au client, alors ne mettez rien que vous ne vouliez pas qu'ils voient
  10. Pour hosting content with Apple choisir non
  11. Vous pouvez laisser les notes de révision vides POUR LE MOMENT .
  12. Ignorez le screenshot for review POUR MAINTENANT , nous reviendrons sur tout ce que nous ignorons.
  13. Cliquez sur "Enregistrer"

L'enregistrement de votre ID de produit dans App Store Connect peut prendre quelques heures. Soyez patient.

Maintenant que vous avez configuré les informations relatives à vos achats intégrés sur App Store Connect, accédez à votre projet Xcode et accédez au gestionnaire d'applications (icône bleue ressemblant à une page en haut de l'emplacement de vos méthodes et fichiers d'en-tête), cliquez sur votre application sous objectifs (devrait être le premier), puis aller à général. En bas, vous devriez voir linked frameworks and libraries cliquer sur le petit symbole plus et ajouter le cadre StoreKit.framework. Si vous ne le faites pas, l'achat intégré à l'application fonctionnera PAS!

Si vous utilisez Objective-C comme langue pour votre application, vous pouvez ignorer ces cinq étapes. Sinon, si vous utilisez Swift, procédez comme suit:

  1. Créez un nouveau fichier .h (en-tête) en accédant à Filename__> Newname__> File... (Command ⌘ + N). Ce fichier sera appelé "Votre fichier .h" dans la suite du didacticiel.

  2. Lorsque vous y êtes invité, cliquez sur Créer un en-tête de pontage . Ce sera notre fichier d'en-tête de pontage. Si vous êtes invité et non , passez à l'étape 3. Si vous êtes invité , ignorez l'étape 3 et allez directement à l'étape 4.

  3. Créez un autre fichier .h nommé Bridge.h dans le dossier du projet principal, puis accédez au gestionnaire d'applications (l'icône bleue ressemblant à une page), puis sélectionnez votre application dans la section Targetsname__, puis cliquez sur Build Settings. Recherchez l’option indiquant Swift Compiler - Génération de code , puis définissez l’en-tête Objective-C Bridging Header option sur Bridge.h

  4. Dans votre fichier d'en-tête de pontage, ajoutez la ligne #import "MyObjectiveCHeaderFile.h", où MyObjectiveCHeaderFileest le nom du fichier d'en-tête que vous avez créé à la première étape. Ainsi, par exemple, si vous avez nommé votre fichier d'en-tête InAppPurchase.h , vous devez ajouter la ligne #import "InAppPurchase.h" à votre fichier d'en-tête de pont.

  5. Créez un nouveau fichier Méthodes Objective-C (.m) en accédant à Filename__> Newname__> File... (Command ⌘ + N). Nommez-le de la même manière que le fichier d'en-tête créé à l'étape 1. Par exemple, si vous appelez le fichier à l'étape 1 InAppPurchase.h , vous appellerez ce nouveau fichier InAppPurchase.m. Ce fichier sera appelé "Votre fichier .m" dans la suite du didacticiel.

Nous allons maintenant entrer dans le codage réel. Ajoutez le code suivant dans votre fichier .h:

BOOL areAdsRemoved;

- (IBAction)restore;
- (IBAction)tapsRemoveAds;

Ensuite, vous devez importer le framework StoreKitdans votre fichier .m, ainsi que d'ajouter SKProductsRequestDelegateet SKPaymentTransactionObserveraprès votre déclaration @interface:

#import <StoreKit/StoreKit.h>

//put the name of your view controller in place of MyViewController
@interface MyViewController() <SKProductsRequestDelegate, SKPaymentTransactionObserver>

@end

@implementation MyViewController //the name of your view controller (same as above)
  //the code below will be added here
@end

et maintenant ajoutez ce qui suit dans votre fichier .m, cette partie devient compliquée, je vous suggère donc de lire les commentaires dans le code:

//If you have more than one in-app purchase, you can define both of
//of them here. So, for example, you could define both kRemoveAdsProductIdentifier
//and kBuyCurrencyProductIdentifier with their respective product ids
//
//for this example, we will only use one product

#define kRemoveAdsProductIdentifier @"put your product id (the one that we just made in App Store Connect) in here"

- (IBAction)tapsRemoveAds{
    NSLog(@"User requests to remove ads");

    if([SKPaymentQueue canMakePayments]){
        NSLog(@"User can make payments");

        //If you have more than one in-app purchase, and would like
        //to have the user purchase a different product, simply define 
        //another function and replace kRemoveAdsProductIdentifier with 
        //the identifier for the other product

        SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:kRemoveAdsProductIdentifier]];
        productsRequest.delegate = self;
        [productsRequest start];

    }
    else{
        NSLog(@"User cannot make payments due to parental controls");
        //this is called the user cannot make payments, most likely due to parental controls
    }
}

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
    SKProduct *validProduct = nil;
    int count = [response.products count];
    if(count > 0){
        validProduct = [response.products objectAtIndex:0];
        NSLog(@"Products Available!");
        [self purchase:validProduct];
    }
    else if(!validProduct){
        NSLog(@"No products available");
        //this is called if your product id is not valid, this shouldn't be called unless that happens.
    }
}

- (void)purchase:(SKProduct *)product{
    SKPayment *payment = [SKPayment paymentWithProduct:product];

    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    [[SKPaymentQueue defaultQueue] addPayment:payment];
}

- (IBAction) restore{
    //this is called when the user restores purchases, you should hook this up to a button
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}

- (void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
    NSLog(@"received restored transactions: %i", queue.transactions.count);
    for(SKPaymentTransaction *transaction in queue.transactions){
        if(transaction.transactionState == SKPaymentTransactionStateRestored){
            //called when the user successfully restores a purchase
            NSLog(@"Transaction state -> Restored");

            //if you have more than one in-app purchase product,
            //you restore the correct product for the identifier.
            //For example, you could use
            //if(productID == kRemoveAdsProductIdentifier)
            //to get the product identifier for the
            //restored purchases, you can use
            //
            //NSString *productID = transaction.payment.productIdentifier;
            [self doRemoveAds];
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            break;
        }
    }   
}

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
    for(SKPaymentTransaction *transaction in transactions){
        //if you have multiple in app purchases in your app,
        //you can get the product identifier of this transaction
        //by using transaction.payment.productIdentifier
        //
        //then, check the identifier against the product IDs
        //that you have defined to check which product the user
        //just purchased            

        switch(transaction.transactionState){
            case SKPaymentTransactionStatePurchasing: NSLog(@"Transaction state -> Purchasing");
                //called when the user is in the process of purchasing, do not add any of your own code here.
                break;
            case SKPaymentTransactionStatePurchased:
            //this is called when the user has successfully purchased the package (Cha-Ching!)
                [self doRemoveAds]; //you can add your code for what you want to happen when the user buys the purchase here, for this tutorial we use removing ads
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                NSLog(@"Transaction state -> Purchased");
                break;
            case SKPaymentTransactionStateRestored:
                NSLog(@"Transaction state -> Restored");
                //add the same code as you did from SKPaymentTransactionStatePurchased here
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
            case SKPaymentTransactionStateFailed:
                //called when the transaction does not finish
                if(transaction.error.code == SKErrorPaymentCancelled){
                    NSLog(@"Transaction state -> Cancelled");
                    //the user cancelled the payment ;(
                }
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
        }
    }
}

Maintenant, vous voulez ajouter votre code pour ce qui se passera lorsque l'utilisateur terminera la transaction. Pour ce tutoriel, nous utilisons la suppression des ajouts. Vous devrez ajouter votre propre code pour ce qui se produit lors du chargement de la vue bannière.

- (void)doRemoveAds{
    ADBannerView *banner;
    [banner setAlpha:0];
    areAdsRemoved = YES;
    removeAdsButton.hidden = YES;
    removeAdsButton.enabled = NO;
    [[NSUserDefaults standardUserDefaults] setBool:areAdsRemoved forKey:@"areAdsRemoved"];
    //use NSUserDefaults so that you can load whether or not they bought it
    //it would be better to use KeyChain access, or something more secure
    //to store the user data, because NSUserDefaults can be changed.
    //You're average downloader won't be able to change it very easily, but
    //it's still best to use something more secure than NSUserDefaults.
    //For the purpose of this tutorial, though, we're going to use NSUserDefaults
    [[NSUserDefaults standardUserDefaults] synchronize];
}

Si vous n'avez pas d'annonces dans votre application, vous pouvez utiliser tout ce que vous voulez. Par exemple, nous pourrions rendre la couleur de l'arrière-plan bleue. Pour ce faire, nous voudrions utiliser:

- (void)doRemoveAds{
    [self.view setBackgroundColor:[UIColor blueColor]];
    areAdsRemoved = YES
    //set the bool for whether or not they purchased it to YES, you could use your own boolean here, but you would have to declare it in your .h file

    [[NSUserDefaults standardUserDefaults] setBool:areAdsRemoved forKey:@"areAdsRemoved"];
    //use NSUserDefaults so that you can load wether or not they bought it
    [[NSUserDefaults standardUserDefaults] synchronize];
}

Maintenant, quelque part dans votre méthode viewDidLoadname__, vous allez vouloir ajouter le code suivant:

areAdsRemoved = [[NSUserDefaults standardUserDefaults] boolForKey:@"areAdsRemoved"];
[[NSUserDefaults standardUserDefaults] synchronize];
//this will load wether or not they bought the in-app purchase

if(areAdsRemoved){
    [self.view setBackgroundColor:[UIColor blueColor]];
    //if they did buy it, set the background to blue, if your using the code above to set the background to blue, if your removing ads, your going to have to make your own code here
}

Maintenant que vous avez ajouté tout le code, accédez à votre fichier .xib ou storyboardet ajoutez deux boutons, l'un disant achat et l'autre disant restauration. Branchez le tapsRemoveAdsIBActionau bouton d'achat que vous venez de créer et le restoreIBActionau bouton de restauration. L'action restorevérifie si l'utilisateur a déjà acheté l'achat in-app et lui donne l'achat in-app gratuitement s'il ne l'a pas déjà.

Ensuite, allez dans App Store Connect , cliquez sur Users and Access, cliquez sur l'en-tête Sandbox Testers, puis cliquez sur le symbole + à gauche, où il est indiqué Testersname__. Vous pouvez simplement mettre des choses au hasard pour le prénom et le nom, et le courrier électronique ne doit pas nécessairement être réel, vous devez simplement pouvoir vous en souvenir. Mettez un mot de passe (que vous devrez vous rappeler) et remplissez le reste des informations. Je vous recommanderais de définir le Date of Birth comme une date permettant à l'utilisateur de 18 ans ou plus. App Store TerritoryA pour être dans le bon pays. Ensuite, déconnectez-vous de votre compte iTunes existant (vous pouvez vous reconnecter après ce tutoriel).

Maintenant, lancez votre application sur votre appareil iOS. Si vous essayez de l’exécuter sur le simulateur, l’achat restera toujours en erreur, vous DEVRAZ exécutez-le sur votre appareil iOS. Une fois l'application en cours d'exécution, appuyez sur le bouton d'achat. Lorsque vous êtes invité à vous connecter à votre compte iTunes, connectez-vous en tant qu'utilisateur test que nous venons de créer. Ensuite, lorsqu’il vous demande de confirmer l’achat de 99 ¢ ou quel que soit le type de prix que vous définissez, FAITES-EN UN INSTANTANÉ À L’ÉCRAN va utiliser pour votre screenshot for review sur App Store Connect. Maintenant, annulez le paiement.

Maintenant, allez à App Store Connect , puis allez à My Apps> the app you have the In-app purchase on> In-App Purchases. Cliquez ensuite sur votre achat intégré et cliquez sur Modifier sous les détails de l'achat intégré. Une fois que vous avez terminé, importez la photo que vous venez de prendre sur votre iPhone dans votre ordinateur et transférez-la en tant que capture d'écran pour la relire, puis, dans les notes de révision, mettez votre TEST USER e-mail et mot de passe. Cela aidera Apple dans le processus de révision.

Cela fait, revenez dans l'application sur votre appareil iOS, toujours connecté en tant que compte d'utilisateur test et cliquez sur le bouton d'achat. Cette fois, confirmez le paiement Ne vous inquiétez pas, cela ne facturera PAS votre compte, NI l'argent, les comptes d'utilisateurs test recevront tous les achats in-app gratuitement Après vous avez confirmé le paiement, assurez-vous que ce qui se passe lorsque l'utilisateur achète votre produit se produit réellement. Si ce n'est pas le cas, il s'agira d'une erreur avec votre méthode doRemoveAdsname__. Encore une fois, je recommande d'utiliser la modification de l'arrière-plan en bleu pour tester l'achat intégré, mais cela ne devrait pas être votre achat réel intégré. Si tout fonctionne et que vous êtes prêt à partir! Assurez-vous simplement d'inclure l'achat in-app dans votre nouveau fichier binaire lorsque vous le téléchargez sur App Store Connect!


Voici quelques erreurs courantes:

Connecté: No Products Available

Cela pourrait signifier trois choses:

  • Vous n'avez pas entré le bon ID d'achat intégré dans votre code (pour l'identifiant kRemoveAdsProductIdentifierdans le code ci-dessus
  • Vous n'avez pas autorisé l'achat in-app à la vente sur App Store Connect
  • Vous n'avez pas attendu que l'ID d'achat intégré à l'application soit enregistré dans App Store Connect . Attendez quelques heures après la création de l’ID et votre problème devrait être résolu.
  • Vous n'avez pas rempli vos informations relatives aux accords, aux taxes et aux services bancaires.

Si cela ne fonctionne pas la première fois, ne soyez pas frustré! N'abandonne pas! Il m'a fallu environ 5 heures d'affilée pour que cela fonctionne et environ 10 heures à la recherche du bon code! Si vous utilisez exactement le code ci-dessus, cela devrait fonctionner correctement. N'hésitez pas à commenter si vous avez des questions .

J'espère que cela aidera tous ceux qui souhaitent ajouter un achat intégré à leur application iOS. À votre santé!

540
Jojodmo

Il suffit de traduire le code Jojodmo en Swift:

class InAppPurchaseManager: NSObject , SKProductsRequestDelegate, SKPaymentTransactionObserver{





//If you have more than one in-app purchase, you can define both of
//of them here. So, for example, you could define both kRemoveAdsProductIdentifier
//and kBuyCurrencyProductIdentifier with their respective product ids
//
//for this example, we will only use one product

let kRemoveAdsProductIdentifier = "put your product id (the one that we just made in iTunesConnect) in here"

@IBAction func tapsRemoveAds() {

    NSLog("User requests to remove ads")

    if SKPaymentQueue.canMakePayments() {
        NSLog("User can make payments")

        //If you have more than one in-app purchase, and would like
        //to have the user purchase a different product, simply define
        //another function and replace kRemoveAdsProductIdentifier with
        //the identifier for the other product
        let set : Set<String> = [kRemoveAdsProductIdentifier]
        let productsRequest = SKProductsRequest(productIdentifiers: set)
        productsRequest.delegate = self
        productsRequest.start()

    }
    else {
        NSLog("User cannot make payments due to parental controls")
        //this is called the user cannot make payments, most likely due to parental controls
    }
}


func purchase(product : SKProduct) {

    let payment = SKPayment(product: product)
    SKPaymentQueue.defaultQueue().addTransactionObserver(self)
    SKPaymentQueue.defaultQueue().addPayment(payment)
}

func restore() {
    //this is called when the user restores purchases, you should hook this up to a button
    SKPaymentQueue.defaultQueue().addTransactionObserver(self)
    SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
}


func doRemoveAds() {
    //TODO: implement
}

/////////////////////////////////////////////////
//////////////// store delegate /////////////////
/////////////////////////////////////////////////
// MARK: - store delegate -


func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {

    if let validProduct = response.products.first {
        NSLog("Products Available!")
        self.purchase(validProduct)
    }
    else {
        NSLog("No products available")
        //this is called if your product id is not valid, this shouldn't be called unless that happens.
    }
}

func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue) {


    NSLog("received restored transactions: \(queue.transactions.count)")
    for transaction in queue.transactions {
        if transaction.transactionState == .Restored {
            //called when the user successfully restores a purchase
            NSLog("Transaction state -> Restored")

            //if you have more than one in-app purchase product,
            //you restore the correct product for the identifier.
            //For example, you could use
            //if(productID == kRemoveAdsProductIdentifier)
            //to get the product identifier for the
            //restored purchases, you can use
            //
            //NSString *productID = transaction.payment.productIdentifier;
            self.doRemoveAds()
            SKPaymentQueue.defaultQueue().finishTransaction(transaction)
            break;
        }
    }
}


func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {

    for transaction in transactions {
        switch transaction.transactionState {
        case .Purchasing: NSLog("Transaction state -> Purchasing")
            //called when the user is in the process of purchasing, do not add any of your own code here.
        case .Purchased:
            //this is called when the user has successfully purchased the package (Cha-Ching!)
            self.doRemoveAds() //you can add your code for what you want to happen when the user buys the purchase here, for this tutorial we use removing ads
            SKPaymentQueue.defaultQueue().finishTransaction(transaction)
            NSLog("Transaction state -> Purchased")
        case .Restored:
            NSLog("Transaction state -> Restored")
            //add the same code as you did from SKPaymentTransactionStatePurchased here
            SKPaymentQueue.defaultQueue().finishTransaction(transaction)
        case .Failed:
            //called when the transaction does not finish
            if transaction.error?.code == SKErrorPaymentCancelled {
                NSLog("Transaction state -> Cancelled")
                //the user cancelled the payment ;(
            }
            SKPaymentQueue.defaultQueue().finishTransaction(transaction)
        case .Deferred:
            // The transaction is in the queue, but its final status is pending external action.
            NSLog("Transaction state -> Deferred")

        }


    }
}
} 
12
Yedidya Reiss

RMStore est une bibliothèque iOS légère pour les achats intégrés. Il encapsule l'API StoreKit et vous fournit des blocs pratiques pour les requêtes asynchrones. L'achat d'un produit est aussi simple que d'appeler une seule méthode. 

Pour les utilisateurs avancés, cette bibliothèque fournit également une vérification des reçus, des téléchargements de contenu et la persistance des transactions. 

4
Vladimir Grigorov

Réponse rapide

Ceci est destiné à compléter ma réponse Objective-C pour les Swift utilisateurs, afin d'éviter que la réponse Objective-C ne devienne trop grande.

Installer

Tout d'abord, configurez l'achat in-app sur appstoreconnect.Apple.com . Suivez le début de ma réponse Objective-C (étapes 1 à 13, sous l'en-tête de l'App Store Connect ) pour obtenir des instructions. en faisant cela.

L'enregistrement de votre ID de produit dans App Store Connect peut prendre quelques heures. Soyez patient.

Maintenant que vous avez configuré vos informations d'achat via l'application sur App Store Connect, nous devons ajouter le cadre Apple pour les achats via l'application, StoreKit, à l'application.

Accédez à votre projet Xcode et accédez au gestionnaire d'applications (icône bleue ressemblant à une page en haut de la barre de gauche où se trouvent les fichiers de votre application). Cliquez sur votre application sous les cibles à gauche (ce devrait être la première option), puis allez dans "Capacités" en haut. Sur la liste, vous devriez voir une option "Achat intégré". Activez cette fonction et Xcode ajoutera StoreKit à votre projet.

Codage

Maintenant, nous allons commencer à coder!

Commencez par créer un nouveau fichier Swift qui gérera tous vos achats in-app. Je vais l'appeler IAPManager.Swift.

Dans ce fichier, nous allons créer une nouvelle classe, appelée IAPManager qui est une SKProductsRequestDelegate et SKPaymentTransactionObserver. En haut, assurez-vous d'importer Foundation et StoreKit

import Foundation
import StoreKit

public class IAPManager: NSObject, SKProductsRequestDelegate,
                         SKPaymentTransactionObserver {
}

Ensuite, nous allons ajouter une variable pour définir l'identifiant de notre achat intégré (vous pouvez également utiliser un enum, qui serait plus facile à gérer si vous avez plusieurs IAP).

// This should the ID of the in-app-purchase you made on AppStore Connect.
// if you have multiple IAPs, you'll need to store their identifiers in
// other variables, too (or, preferably in an enum).
let removeAdsID = "com.skiplit.removeAds"

Ajoutons un initialiseur pour notre classe ensuite:

// This is the initializer for your IAPManager class
//
// A better, and more scaleable way of doing this
// is to also accept a callback in the initializer, and call
// that callback in places like the paymentQueue function, and
// in all functions in this class, in place of calls to functions
// in RemoveAdsManager (you'll see those calls in the code below).

let productID: String
init(productID: String){
    self.productID = productID
}

Maintenant, nous allons ajouter les fonctions requises pour que SKProductsRequestDelegate et SKPaymentTransactionObserver fonctionnent:

Nous ajouterons la classe RemoveAdsManager plus tard

// This is called when a SKProductsRequest receives a response
public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse){
    // Let's try to get the first product from the response
    // to the request
    if let product = response.products.first{
        // We were able to get the product! Make a new payment
        // using this product
        let payment = SKPayment(product: product)

        // add the new payment to the queue
        SKPaymentQueue.default().add(self)
        SKPaymentQueue.default().add(payment)
    }
    else{
        // Something went wrong! It is likely that either
        // the user doesn't have internet connection, or
        // your product ID is wrong!
        //
        // Tell the user in requestFailed() by sending an alert,
        // or something of the sort

        RemoveAdsManager.removeAdsFailure()
    }
}

// This is called when the user restores their IAP sucessfully
private func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue){
    // For every transaction in the transaction queue...
    for transaction in queue.transactions{
        // If that transaction was restored
        if transaction.transactionState == .restored{
            // get the producted ID from the transaction
            let productID = transaction.payment.productIdentifier

            // In this case, we have only one IAP, so we don't need to check
            // what IAP it is. However, this is useful if you have multiple IAPs!
            // You'll need to figure out which one was restored
            if(productID.lowercased() == IAPManager.removeAdsID.lowercased()){
                // Restore the user's purchases
                RemoveAdsManager.restoreRemoveAdsSuccess()
            }

            // finish the payment
            SKPaymentQueue.default().finishTransaction(transaction)
        }
    }
}

// This is called when the state of the IAP changes -- from purchasing to purchased, for example.
// This is where the magic happens :)
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]){
    for transaction in transactions{
        // get the producted ID from the transaction
        let productID = transaction.payment.productIdentifier

        // In this case, we have only one IAP, so we don't need to check
        // what IAP it is.
        // However, if you have multiple IAPs, you'll need to use productID
        // to check what functions you should run here!

        switch transaction.transactionState{
        case .purchasing:
            // if the user is currently purchasing the IAP,
            // we don't need to do anything.
            //
            // You could use this to show the user
            // an activity indicator, or something like that
            break
        case .purchased:
            // the user successfully purchased the IAP!
            RemoveAdsManager.removeAdsSuccess()
            SKPaymentQueue.default().finishTransaction(transaction)
        case .restored:
                // the user restored their IAP!
                IAPTestingHandler.restoreRemoveAdsSuccess()
                SKPaymentQueue.default().finishTransaction(transaction)
        case .failed:
                // The transaction failed!
                RemoveAdsManager.removeAdsFailure()
                // finish the transaction
                SKPaymentQueue.default().finishTransaction(transaction)
        case .deferred:
                // This happens when the IAP needs an external action
                // in order to proceeded, like Ask to Buy
                RemoveAdsManager.removeAdsDeferred()
                break
        }
    }
}

Ajoutons maintenant quelques fonctions pouvant être utilisées pour démarrer un achat ou restaurer des achats:

// Call this when you want to begin a purchase
// for the productID you gave to the initializer
public func beginPurchase(){
    // If the user can make payments
    if SKPaymentQueue.canMakePayments(){
        // Create a new request
        let request = SKProductsRequest(productIdentifiers: [productID])
        // Set the request delegate to self, so we receive a response
        request.delegate = self
        // start the request
        request.start()
    }
    else{
        // Otherwise, tell the user that
        // they are not authorized to make payments,
        // due to parental controls, etc
    }
}

// Call this when you want to restore all purchases
// regardless of the productID you gave to the initializer
public func beginRestorePurchases(){
    // restore purchases, and give responses to self
    SKPaymentQueue.default().add(self)
    SKPaymentQueue.default().restoreCompletedTransactions()
}

Ensuite, ajoutons une nouvelle classe d’utilitaires pour gérer nos IAP. Tout ce code peut appartenir à une classe, mais son multiple le rend un peu plus propre. Je vais créer une nouvelle classe appelée RemoveAdsManager et y mettre quelques fonctions.

public class RemoveAdsManager{

    class func removeAds()
    class func restoreRemoveAds()

    class func areAdsRemoved() -> Bool

    class func removeAdsSuccess()
    class func restoreRemoveAdsSuccess()
    class func removeAdsDeferred()
    class func removeAdsFailure()
}

Les trois premières fonctions, removeAds, restoreRemoveAds et areAdsRemoved, sont des fonctions que vous appelez pour effectuer certaines actions. Les quatre derniers sont un qui sera appelé par IAPManager.

Ajoutons du code aux deux premières fonctions, removeAds et restoreRemoveAds:

// Call this when the user wants
// to remove ads, like when they
// press a "remove ads" button
class func removeAds(){
    // Before starting the purchase, you could tell the
    // user that their purchase is happening, maybe with
    // an activity indicator

    let iap = IAPManager(productID: IAPManager.removeAdsID)
    iap.beginPurchase()
}

// Call this when the user wants
// to restore their IAP purchases,
// like when they press a "restore
// purchases" button.
class func restoreRemoveAds(){
    // Before starting the purchase, you could tell the
    // user that the restore action is happening, maybe with
    // an activity indicator

    let iap = IAPManager(productID: IAPManager.removeAdsID)
    iap.beginRestorePurchases()
}

Enfin, ajoutons du code aux cinq dernières fonctions.

// Call this to check whether or not
// ads are removed. You can use the
// result of this to hide or show
// ads
class func areAdsRemoved() -> Bool{
    // This is the code that is run to check
    // if the user has the IAP.

    return UserDefaults.standard.bool(forKey: "RemoveAdsPurchased")
}

// This will be called by IAPManager
// when the user sucessfully purchases
// the IAP
class func removeAdsSuccess(){
    // This is the code that is run to actually
    // give the IAP to the user!
    //
    // I'm using UserDefaults in this example,
    // but you may want to use Keychain,
    // or some other method, as UserDefaults
    // can be modified by users using their
    // computer, if they know how to, more
    // easily than Keychain

    UserDefaults.standard.set(true, forKey: "RemoveAdsPurchased")
    UserDefaults.standard.synchronize()
}

// This will be called by IAPManager
// when the user sucessfully restores
//  their purchases
class func restoreRemoveAdsSuccess(){
    // Give the user their IAP back! Likely all you'll need to
    // do is call the same function you call when a user
    // sucessfully completes their purchase. In this case, removeAdsSuccess()

    removeAdsSuccess()
}

// This will be called by IAPManager
// when the IAP failed
class func removeAdsFailure(){
    // Send the user a message explaining that the IAP
    // failed for some reason, and to try again later
}

// This will be called by IAPManager
// when the IAP gets deferred.
class func removeAdsDeferred(){
    // Send the user a message explaining that the IAP
    // was deferred, and pending an external action, like
    // Ask to Buy.
}

En réunissant tout cela, nous obtenons quelque chose comme ceci:

import Foundation
import StoreKit

public class RemoveAdsManager{

    // Call this when the user wants
    // to remove ads, like when they
    // press a "remove ads" button
    class func removeAds(){
        // Before starting the purchase, you could tell the
        // user that their purchase is happening, maybe with
        // an activity indicator

        let iap = IAPManager(productID: IAPManager.removeAdsID)
        iap.beginPurchase()
    }

    // Call this when the user wants
    // to restore their IAP purchases,
    // like when they press a "restore
    // purchases" button.
    class func restoreRemoveAds(){
        // Before starting the purchase, you could tell the
        // user that the restore action is happening, maybe with
        // an activity indicator

        let iap = IAPManager(productID: IAPManager.removeAdsID)
        iap.beginRestorePurchases()
    }

    // Call this to check whether or not
    // ads are removed. You can use the
    // result of this to hide or show
    // ads
    class func areAdsRemoved() -> Bool{
        // This is the code that is run to check
        // if the user has the IAP.

        return UserDefaults.standard.bool(forKey: "RemoveAdsPurchased")
    }

    // This will be called by IAPManager
    // when the user sucessfully purchases
    // the IAP
    class func removeAdsSuccess(){
        // This is the code that is run to actually
        // give the IAP to the user!
        //
        // I'm using UserDefaults in this example,
        // but you may want to use Keychain,
        // or some other method, as UserDefaults
        // can be modified by users using their
        // computer, if they know how to, more
        // easily than Keychain

        UserDefaults.standard.set(true, forKey: "RemoveAdsPurchased")
        UserDefaults.standard.synchronize()
    }

    // This will be called by IAPManager
    // when the user sucessfully restores
    //  their purchases
    class func restoreRemoveAdsSuccess(){
        // Give the user their IAP back! Likely all you'll need to
        // do is call the same function you call when a user
        // sucessfully completes their purchase. In this case, removeAdsSuccess()
        removeAdsSuccess()
    }

    // This will be called by IAPManager
    // when the IAP failed
    class func removeAdsFailure(){
        // Send the user a message explaining that the IAP
        // failed for some reason, and to try again later
    }

    // This will be called by IAPManager
    // when the IAP gets deferred.
    class func removeAdsDeferred(){
        // Send the user a message explaining that the IAP
        // was deferred, and pending an external action, like
        // Ask to Buy.
    }

}

public class IAPManager: NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver{

    // This should the ID of the in-app-purchase you made on AppStore Connect.
    // if you have multiple IAPs, you'll need to store their identifiers in
    // other variables, too (or, preferably in an enum).
    static let removeAdsID = "com.skiplit.removeAds"

    // This is the initializer for your IAPManager class
    //
    // An alternative, and more scaleable way of doing this
    // is to also accept a callback in the initializer, and call
    // that callback in places like the paymentQueue function, and
    // in all functions in this class, in place of calls to functions
    // in RemoveAdsManager.
    let productID: String
    init(productID: String){
        self.productID = productID
    }

    // Call this when you want to begin a purchase
    // for the productID you gave to the initializer
    public func beginPurchase(){
        // If the user can make payments
        if SKPaymentQueue.canMakePayments(){
            // Create a new request
            let request = SKProductsRequest(productIdentifiers: [productID])
            request.delegate = self
            request.start()
        }
        else{
            // Otherwise, tell the user that
            // they are not authorized to make payments,
            // due to parental controls, etc
        }
    }

    // Call this when you want to restore all purchases
    // regardless of the productID you gave to the initializer
    public func beginRestorePurchases(){
        SKPaymentQueue.default().add(self)
        SKPaymentQueue.default().restoreCompletedTransactions()
    }

    // This is called when a SKProductsRequest receives a response
    public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse){
        // Let's try to get the first product from the response
        // to the request
        if let product = response.products.first{
            // We were able to get the product! Make a new payment
            // using this product
            let payment = SKPayment(product: product)

            // add the new payment to the queue
            SKPaymentQueue.default().add(self)
            SKPaymentQueue.default().add(payment)
        }
        else{
            // Something went wrong! It is likely that either
            // the user doesn't have internet connection, or
            // your product ID is wrong!
            //
            // Tell the user in requestFailed() by sending an alert,
            // or something of the sort

            RemoveAdsManager.removeAdsFailure()
        }
    }

    // This is called when the user restores their IAP sucessfully
    private func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue){
        // For every transaction in the transaction queue...
        for transaction in queue.transactions{
            // If that transaction was restored
            if transaction.transactionState == .restored{
                // get the producted ID from the transaction
                let productID = transaction.payment.productIdentifier

                // In this case, we have only one IAP, so we don't need to check
                // what IAP it is. However, this is useful if you have multiple IAPs!
                // You'll need to figure out which one was restored
                if(productID.lowercased() == IAPManager.removeAdsID.lowercased()){
                    // Restore the user's purchases
                    RemoveAdsManager.restoreRemoveAdsSuccess()
                }

                // finish the payment
                SKPaymentQueue.default().finishTransaction(transaction)
            }
        }
    }

    // This is called when the state of the IAP changes -- from purchasing to purchased, for example.
    // This is where the magic happens :)
    public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]){
        for transaction in transactions{
            // get the producted ID from the transaction
            let productID = transaction.payment.productIdentifier

            // In this case, we have only one IAP, so we don't need to check
            // what IAP it is.
            // However, if you have multiple IAPs, you'll need to use productID
            // to check what functions you should run here!

            switch transaction.transactionState{
            case .purchasing:
                // if the user is currently purchasing the IAP,
                // we don't need to do anything.
                //
                // You could use this to show the user
                // an activity indicator, or something like that
                break
            case .purchased:
                // the user sucessfully purchased the IAP!
                RemoveAdsManager.removeAdsSuccess()
                SKPaymentQueue.default().finishTransaction(transaction)
            case .restored:
                // the user restored their IAP!
                RemoveAdsManager.restoreRemoveAdsSuccess()
                SKPaymentQueue.default().finishTransaction(transaction)
            case .failed:
                // The transaction failed!
                RemoveAdsManager.removeAdsFailure()
                // finish the transaction
                SKPaymentQueue.default().finishTransaction(transaction)
            case .deferred:
                // This happens when the IAP needs an external action
                // in order to proceeded, like Ask to Buy
                RemoveAdsManager.removeAdsDeferred()
                break
            }
        }
    }

}

Enfin, vous devez ajouter un moyen permettant à l'utilisateur de démarrer l'achat et d'appeler RemoveAdsManager.removeAds(), de lancer une restauration et d'appeler RemoveAdsManager.restoreRemoveAds(), comme un bouton quelque part! N'oubliez pas que, conformément aux directives de l'App Store, vous devez fournir un bouton pour restaurer les achats quelque part.

Soumission pour révision

La dernière chose à faire est de soumettre votre IAP pour examen sur App Store Connect! Pour des instructions détaillées, vous pouvez suivre la dernière partie de ma réponse à Objective-C , sous Soumission pour révision entête.

0
Jojodmo

Je sais que je suis assez en retard pour poster ceci, mais je partage une expérience similaire quand j'ai appris les ficelles du modèle IAP.

L'achat intégré est l'un des flux de travail les plus complets sous iOS mis en œuvre par la structure Storekit. La documentation entière est assez claire si vous avez la patience de la lire, mais son caractère technique est un peu avancé.

Résumer:

1 - Demander les produits - utilisez les classes SKProductRequest & SKProductRequestDelegate pour émettre une demande d’ID de produit et les récupérer à partir de votre propre magasin itunesconnect.

Ces SKProducts doivent être utilisés pour renseigner l'interface utilisateur de votre magasin, que l'utilisateur peut utiliser pour acheter un produit spécifique.

2 - Demande de paiement d'émission - utilisez SKPayment & SKPaymentQueue pour ajouter un paiement à la file d'attente des transactions.

3 - Surveillez la file d'attente des transactions pour la mise à jour du statut - utilisez la méthode updatedTransactions du protocole SKPaymentTransactionObserver pour surveiller le statut:

SKPaymentTransactionStatePurchasing - don't do anything
SKPaymentTransactionStatePurchased - unlock product, finish the transaction
SKPaymentTransactionStateFailed - show error, finish the transaction
SKPaymentTransactionStateRestored - unlock product, finish the transaction

4 - Restaurer le flux de boutons - utilisez les actions restoreCompletedTransactions de SKPaymentQueue pour accomplir cela - l'étape 3 se chargera du reste, ainsi que des méthodes suivantes de SKPaymentTransactionObserver:

paymentQueueRestoreCompletedTransactionsFinished
restoreCompletedTransactionsFailedWithError

Ici est un didacticiel pas à pas (que j'ai créé à la suite de mes propres tentatives pour le comprendre) et qui l'explique. À la fin, il fournit également un exemple de code que vous pouvez directement utiliser.

Ici est un autre que j'ai créé pour expliquer certaines choses que seul le texte pourrait décrire de manière plus efficace.

0
Nirav Bhatt