267 votes

Comment ajouter un achat in-app à une application iOS ?

Comment ajouter un achat in-app à une application iOS ? Quels sont les détails et existe-t-il un exemple de code ?

<strong>Il s'agit d'une sorte de fourre-tout sur la façon d'ajouter des achats in-app aux applications iOS.</strong>

13 votes

Et si vous lisiez le "Guide de programmation des achats intra-applicatifs" ?

4 votes

572voto

Jojodmo Points 4155

Utilisateurs de Swift

Les utilisateurs de Swift peuvent consulter Ma réponse rapide à cette question .
Ou encore, consultez Réponse de Yedidya Reiss qui traduit ce code Objective-C en Swift.

Utilisateurs d'Objective-C

Le reste de cette réponse est écrit en Objective-C.

App Store Connect

  1. Ir a appstoreconnect.apple.com et se connecter
  2. Cliquez sur My Apps puis cliquez sur l'application à laquelle vous souhaitez ajouter l'achat.
  3. Cliquez sur le bouton Features puis sélectionnez In-App Purchases à gauche
  4. Cliquez sur le bouton + icône au milieu
  5. Pour ce tutoriel, nous allons ajouter un achat in-app pour supprimer les publicités, choisissez donc non-consumable . Si vous deviez envoyer un article physique à l'utilisateur, ou lui donner quelque chose qu'il peut acheter plus d'une fois, vous choisiriez consumable .
  6. Pour le nom de la référence, mettez ce que vous voulez (mais assurez-vous de savoir de quoi il s'agit).
  7. Pour l'identifiant du produit, mettez tld.websitename.appname.referencename cela fonctionnera le mieux, donc par exemple, vous pourriez utiliser com.jojodmo.blix.removeads
  8. Choisissez cleared for sale et choisissez ensuite le niveau de prix 1 (99¢). Le niveau 2 sera 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 élevé que l'on puisse payer pour supprimer des publicités.
  9. Cliquez sur le bouton bleu add language et saisissez les informations. Toutes ces informations seront affichées au client, alors ne mettez rien que vous ne voulez pas qu'il voie.
  10. Pour hosting content with Apple choisissez no
  11. Vous pouvez laisser les notes de révision en blanc POUR MAINTENANT .
  12. Sauter le screenshot for review POUR MAINTENANT tout ce que nous sautons, nous y reviendrons.
  13. Cliquez sur "Enregistrer".

Il peut s'écouler quelques heures avant que l'identifiant de votre produit ne soit enregistré dans le système. App Store Connect alors soyez patient.

Mise en place de votre projet

Maintenant que vous avez configuré vos informations d'achat in-app sur App Store Connect, allez dans votre projet Xcode, et allez dans le gestionnaire d'applications (icône en forme de page bleue en haut de l'endroit où se trouvent vos méthodes et vos fichiers d'en-tête), cliquez sur votre application sous les cibles (ce devrait être la première) puis allez dans général. En bas, vous devriez voir linked frameworks and libraries cliquez sur le petit symbole plus et ajoutez le cadre StoreKit.framework Si vous ne faites pas cela, l'achat in-app sera PAS travail !

Si vous utilisez Objective-C comme langage pour votre application, vous doit sauter ces cinq étapes . Sinon, si vous utilisez Swift, vous pouvez suivre les instructions suivantes Ma réponse rapide à cette question, ici Si vous préférez utiliser Objective-C pour le code d'achat en ligne, mais que vous utilisez Swift dans votre application, vous pouvez procéder comme suit :

  1. Créer un nouveau .h (en-tête) en allant dans File > New > File... ( Command + N ). Ce fichier sera désigné sous le nom de "Votre .h dans le reste du tutoriel

  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 pas si vous y êtes invité, passez à l'étape 3. Si vous sont Si vous y êtes invité, passez l'étape 3 et allez directement à l'étape 4.

  3. Créer un autre .h fichier nommé Bridge.h dans le dossier principal du projet. Ensuite, allez dans le gestionnaire d'applications (l'icône en forme de page bleue), puis sélectionnez votre application dans la fenêtre Targets et cliquez sur Build Settings . Trouvez l'option qui dit Compilateur Swift - Génération de code et ensuite définir le En-tête de pontage en Objective-C option pour Bridge.h

  4. Dans votre fichier d'en-tête de pontage, ajoutez la ligne #import "MyObjectiveCHeaderFile.h" , donde MyObjectiveCHeaderFile est le nom du fichier d'en-tête que vous avez créé à l'étape 1. Donc, 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 une nouvelle méthode Objective-C ( .m ) en allant sur File > New > File... ( Command + N ). Donnez-lui le même nom que le fichier d'en-tête que vous avez créé à l'étape 1. Par exemple, si vous avez appelé le fichier de l'étape 1 InAppPurchase.h vous appelleriez ce nouveau fichier InAppPurchase.m . Ce fichier sera désigné sous le nom de "Votre .m dans le reste du tutoriel.

Codage

Maintenant, nous allons entrer dans le codage réel. Ajoutez le code suivant dans votre .h fichier :

BOOL areAdsRemoved;

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

Ensuite, vous devez importer le StoreKit dans votre .m ainsi que d'ajouter SKProductsRequestDelegate y SKPaymentTransactionObserver après votre @interface déclaration :

#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 ajoutez maintenant ce qui suit dans votre .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
    [[SKPaymentQueuedefaultQueue] 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 passe lorsque l'utilisateur termine la transaction, pour ce tutoriel, nous utilisons la suppression des ajouts, vous devrez ajouter votre propre code pour ce qui se passe lorsque la vue de la bannière se charge.

- (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 de publicité dans votre application, vous pouvez utiliser n'importe quel autre élément. Par exemple, nous pourrions rendre la couleur de l'arrière-plan bleue. Pour ce faire, nous devons 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 viewDidLoad 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, allez dans votre fichier .xib o storyboard et ajoutez deux boutons, l'un disant achat, et l'autre disant restauration. Accrochez le tapsRemoveAds IBAction au bouton d'achat que vous venez de faire, et le restore IBAction au bouton de restauration. Le site restore vérifie si l'utilisateur a déjà effectué l'achat in-app, et lui offre gratuitement l'achat in-app s'il ne l'a pas déjà fait.

Soumission à l'examen

Ensuite, allez dans App Store Connect et cliquez sur Users and Access puis cliquez sur le Sandbox Testers et cliquez ensuite sur l'en-tête + à gauche, là où il est écrit Testers . Vous pouvez saisir des noms et des prénoms au hasard, et l'adresse électronique ne doit pas nécessairement être réelle - vous devez simplement pouvoir vous en souvenir. Entrez un mot de passe (dont vous devrez vous souvenir) et remplissez le reste des informations. Je vous recommande de faire le Date of Birth une date qui ferait que l'utilisateur aurait 18 ans ou plus. App Store Territory HAS pour être dans le bon pays. Ensuite, déconnectez-vous de votre compte iTunes existant (vous pourrez vous reconnecter après ce tutoriel).

Maintenant, exécutez votre application sur votre appareil iOS, si vous essayez de l'exécuter sur le simulateur, l'achat sera toujours erreur, vous DEVOIR l'exécuter sur votre appareil iOS. Une fois l'application lancée, 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 est demandé de confirmer l'achat de 99¢ ou du prix que vous avez fixé pour le palier de prix, PRENDRE UNE CAPTURE D'ÉCRAN DE CELUI-CI c'est ce que vous allez 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 in-app et cliquez sur modifier sous les détails de l'achat in-app. Une fois que vous avez fait cela, importez la photo que vous venez de prendre avec votre iPhone sur votre ordinateur, et téléchargez-la comme capture d'écran pour la révision, puis, dans les notes de révision, mettez votre nom d'utilisateur et votre mot de passe. UTILISATEUR DE TEST e-mail et mot de passe. Cela aidera apple dans le processus de révision.

Après avoir effectué cette opération, retournez sur l'application sur votre appareil iOS, toujours connecté en tant que compte utilisateur de test, et cliquez sur le bouton d'achat. Cette fois, confirmez le paiement Ne vous inquiétez pas, cela ne débitera PAS votre compte d'argent, les comptes d'utilisateurs de test reçoivent tous les achats in-app gratuitement. Après avoir 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, c'est qu'il y a une erreur dans votre site Web. doRemoveAds méthode. Encore une fois, je recommande de changer l'arrière-plan en bleu pour tester l'achat in-app, cela ne devrait pas être votre achat in-app réel cependant. Si tout fonctionne, vous êtes prêt à partir ! Assurez-vous simplement d'inclure l'achat in-app dans votre nouveau binaire lorsque vous le téléchargez sur App Store Connect !


Voici quelques erreurs courantes :

Logué : No Products Available

Cela peut signifier quatre choses :

  • Vous n'avez pas mis le bon identifiant d'achat in-app dans votre code (pour l'identifiant kRemoveAdsProductIdentifier dans le code ci-dessus
  • Vous n'avez pas validé votre achat in-app pour la vente sur App Store Connect
  • Vous n'avez pas attendu que l'identifiant de l'achat in-app soit enregistré dans la base de données de l'entreprise. App Store Connect . Attendez quelques heures après la création de l'identifiant, et votre problème devrait être résolu.
  • Vous n'avez pas fini de remplir vos accords, vos impôts et vos informations bancaires.

Si ça ne marche pas du premier coup, ne soyez pas frustré ! N'abandonnez pas ! Il m'a fallu environ 5 heures d'affilée avant d'y arriver, et environ 10 heures pour trouver le bon code ! Si vous utilisez exactement le code ci-dessus, cela devrait fonctionner correctement. N'hésitez pas à commenter si vous avez des questions. du tout .

J'espère que cela aidera tous ceux qui espèrent ajouter un achat in-app à leur application iOS. A la vôtre !

1 votes

Mais si je n'ajoute pas cette ligne, lorsque je clique sur le bouton de restauration, rien ne se passe de toute façon merci beaucoup pour ce tutoriel ;)

0 votes

Je vous suggère de sauvegarder les paramètres de suppression de l'annonce dans le trousseau de clés. Le site NSUserDefault est vraiment facile à modifier avec quelques connaissances simples.

0 votes

@Jojodmo2010 C'est un superbe tutoriel merci :) Il m'a aidé à ajouter l'achat en application à mon application. Bien que je sois d'accord avec rckoenes que le trousseau de clés devrait être utilisé au lieu de NSUserDefault.

14voto

Yedidya Points 1291

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

        }

    }
}
}

11voto

Jojodmo Points 4155

Réponse rapide

Il s'agit de compléter ma réponse en Objective-C pour les utilisateurs de Swift, afin d'éviter que la réponse en Objective-C ne devienne trop grosse.

Configuration

D'abord, configurez l'achat in-app sur appstoreconnect.apple.com . Suivez la partie initiale de ma réponse en Objective-C (étapes 1 à 13, sous l'onglet App Store Connect ) pour obtenir des instructions à ce sujet.

L'enregistrement de l'ID de votre produit dans App Store Connect peut prendre quelques heures, alors soyez patient.

Maintenant que vous avez configuré les informations relatives aux achats in-app sur App Store Connect, nous devons ajouter le cadre d'Apple pour les achats in-app, StoreKit dans l'application.

Allez dans votre projet Xcode, et allez dans le gestionnaire d'applications (icône en forme de page bleue en haut de la barre de gauche où se trouvent les fichiers de votre application). Cliquez sur votre application sous les cibles sur la gauche (ce devrait être la première option), puis allez à "Capacités" en haut. Dans la liste, vous devriez voir une option "In-App Purchase". Activez cette capacité, et Xcode ajoutera StoreKit à votre projet.

Codage

Maintenant, nous allons commencer à coder !

Tout d'abord, créez 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 un SKProductsRequestDelegate y SKPaymentTransactionObserver . En haut, assurez-vous que vous importez Foundation y 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 in-app (vous pouvez également utiliser une variable de type enum (ce qui serait plus facile à gérer si vous avez plusieurs PEI).

// 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 ensuite un initialisateur pour notre classe :

// 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 nécessaires pour SKProductsRequestDelegate y SKPaymentTransactionObserver pour travailler :

Nous allons ajouter le <code>RemoveAdsManager</code> classe 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 qui peuvent être utilisées pour lancer 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 PEI. Tout ce code pourrait être dans une seule classe, mais le fait d'en avoir plusieurs rend les choses un peu plus propres. Je vais créer une nouvelle classe appelée RemoveAdsManager et dans celui-ci, mettez 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 y areAdsRemoved sont des fonctions que vous appelez pour effectuer certaines actions. Les quatre dernières sont des fonctions qui seront appelées par IAPManager .

Ajoutons un peu de code aux deux premières fonctions, removeAds y 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()
}

Et 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 mettant tout ça ensemble, on obtient quelque chose comme ça :

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 pour l'utilisateur de lancer l'achat et d'appeler RemoveAdsManager.removeAds() et lancer une restauration et 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 à l'examen

La dernière chose à faire est de soumettre votre PAI pour examen sur App Store Connect ! Pour obtenir des instructions détaillées sur la façon de procéder, vous pouvez suivre la dernière partie de l'article intitulé ma réponse en Objective-C en vertu de la Soumettre pour examen en-tête.

4voto

Vladimir Grigorov Points 3306

RMStore est une bibliothèque iOS légère pour les Achats In-App. Elle enveloppe l'API StoreKit et vous fournit des blocs pratiques pour les requêtes asynchrones. L'achat d'un produit est aussi simple que l'appel d'une seule méthode.

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

-2voto

Nirav Bhatt Points 2850

Je sais que j'interviens un peu tard, mais je partage une expérience similaire lorsque j'ai appris les ficelles du modèle IAP.

L'achat in-app est l'un des flux de travail les plus complets d'iOS mis en œuvre par le cadre Storekit. Le site documentation complète est assez clair si vous avez la patience de le lire, mais est quelque peu avancé dans la nature de la technicité.

Pour résumer :

1 - Demandez les produits - utilisez les classes SKProductRequest & SKProductRequestDelegate pour demander les ID des produits et les recevoir en retour de votre propre magasin itunesconnect.

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

2 - Émettez une demande de paiement - utilisez SKPayment & SKPaymentQueue pour ajouter le 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 - Restaurez le flux de boutons - utilisez la méthode restoreCompletedTransactions de SKPaymentQueue pour y parvenir - l'étape 3 s'occupera du reste, ainsi que les méthodes suivantes de SKPaymentTransactionObserver :

paymentQueueRestoreCompletedTransactionsFinished
restoreCompletedTransactionsFailedWithError

Ici est un tutoriel étape par étape (rédigé par moi suite à mes propres tentatives de compréhension) qui l'explique. À la fin, il fournit également un exemple de code que vous pouvez utiliser directement.

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

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