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

257

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

Ceci est censé être une sorte de fourre-tout pour savoir comment ajouter des achats intégrés à des applications iOS

Jojodmo
la source
11
Qu'en est-il de la lecture du "Guide de programmation des achats intégrés"?
rmaddy

Réponses:

554

Utilisateurs Swift

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

Utilisateurs Objective-C

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

App Store Connect

  1. Accédez à appstoreconnect.apple.com et connectez-vous
  2. Cliquez sur My Appspuis sur l'application à laquelle vous souhaitez ajouter l'achat
  3. Cliquez sur l'en- Featurestête, 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é pour supprimer les publicités, alors choisissez 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 référence, mettez ce que vous voulez (mais assurez-vous de savoir de quoi il s'agit)
  7. Pour l'id produit mis, tld.websitename.appname.referencenamecela fonctionnera le mieux, donc par exemple, vous pouvez utilisercom.jojodmo.blix.removeads
  8. Choisissez cleared for sale, puis choisissez le niveau de prix comme 1 (99 ¢). Le niveau 2 serait de 1,99 $ et le niveau 3 serait de 2,99 $. La liste complète est disponible si vous cliquez sur view pricing matrixJe vous recommande d'utiliser le niveau 1, car c'est généralement le plus que quiconque paiera pour supprimer les annonces.
  9. Cliquez sur le add languagebouton bleu et saisissez les informations. Tout cela sera montré au client, alors ne mettez rien que vous ne voulez pas qu'il voit
  10. Pour hosting content with Applechoisir non
  11. Vous pouvez laisser les notes de révision vides POUR MAINTENANT .
  12. Sautez le screenshot for review POUR MAINTENANT , tout ce que nous sautons, nous y reviendrons.
  13. Cliquez sur «enregistrer»

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

Mettre en place 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'application (icône bleue semblable à une page en haut de l'emplacement de vos méthodes et fichiers d'en-tête) cliquez sur votre application sous les cibles (devrait être la première), puis accédez à Général. En bas, vous devriez voir linked frameworks and librariescliquer sur le petit symbole plus et ajouter le cadre StoreKit.frameworkSi vous ne le faites pas, l'achat dans l'application ne fonctionnera PAS !

Si vous utilisez Objective-C comme langue pour votre application, vous devez ignorer ces cinq étapes . Sinon, si vous utilisez Swift, vous pouvez suivre Ma réponse Swift pour cette question, ici , ou, si vous préférez utiliser Objective-C pour le code d'achat In-App mais utilisez Swift dans votre application, vous pouvez faire ce qui suit :

  1. Créer un nouveau .hfichier ( en- tête) en allant à File> New> File...( Command ⌘+ N). Ce fichier sera appelé "Votre .hfichier" 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 n'y êtes pas invité, passez à l'étape 3. Si vous y êtes invité, ignorez l'étape 3 et passez directement à l'étape 4.

  3. Créez un autre .hfichier nommé Bridge.hdans le dossier principal du projet, puis accédez au Gestionnaire d'applications (l'icône bleue semblable à une page), puis sélectionnez votre application dans la Targetssection et cliquez sur Build Settings. Recherchez l'option indiquant Swift Compiler - Génération de code , puis définissez l' option Objective-C Bridging Header surBridge.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éé à l'étape un. Ainsi, par exemple, si vous avez nommé votre fichier d'en-tête InAppPurchase.h , vous ajouteriez la ligne #import "InAppPurchase.h"à votre fichier d'en-tête de pont.

  5. Créer une nouvelle méthodes (Objective-C .mfichier) en allant à File> New> File...( Command ⌘+ N). Nommez-le de la même manière que le fichier d'en-tête que vous avez créé à l'étape 1. Par exemple, si vous avez appelé le fichier à l'étape 1 InAppPurchase.h , vous appelleriez ce nouveau fichier InAppPurchase.m . Ce fichier sera appelé «votre .mfichier» dans le reste du didacticiel.

Codage

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

BOOL areAdsRemoved;

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

Ensuite, vous devez importer le StoreKitframework dans votre .mfichier, ainsi qu'ajouter SKProductsRequestDelegateet SKPaymentTransactionObserveraprès votre @interfacedé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 maintenant ajoutez ce qui suit dans votre .mfichier, cette partie devient compliquée, donc je vous suggère 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 passera lors du chargement de la 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 viewDidLoadméthode, 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 .xibou storyboardet ajoutez deux boutons, l'un disant achat et l'autre disant restauration. Branchez le tapsRemoveAds IBActionbouton d'achat que vous venez de faire et le restore IBActionbouton de restauration. L' restoreaction vérifiera si l'utilisateur a déjà acheté l'achat in-app et lui donnera l'achat in-app gratuitement s'il ne l'a pas déjà.

Soumission pour examen

Ensuite, allez dans App Store Connect , puis cliquez sur Users and Accesspuis cliquez sur l'en- Sandbox Testerstête, puis cliquez sur le +symbole à gauche où il est indiqué Testers. Vous pouvez simplement mettre des choses au hasard pour le prénom et le nom de famille, et l'e-mail n'a pas à être réel - vous devez simplement pouvoir vous en souvenir. Entrez un mot de passe (dont vous devrez vous souvenir) et remplissez le reste des informations. Je recommanderais que vous fixiez Date of Birthune date qui rendrait l'utilisateur 18 ans ou plus. App Store Territory DOIT être dans le bon pays. Ensuite, déconnectez-vous de votre compte iTunes existant (vous pouvez vous reconnecter après ce didacticiel).

Maintenant, exécutez votre application sur votre appareil iOS, si vous essayez de l'exécuter sur le simulateur, l'achat sera toujours une erreur, vous DEVEZ l' exécuter 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 de test que nous venons de créer. Ensuite, quand il vous demande de confirmer l'achat de 99 ¢ ou tout autre niveau de prix, PRENEZ UN INSTANTANÉ D'ÉCRAN, c'est ce que vous allez utiliser pour votre screenshot for reviewsur App Store Connect. Annulez maintenant le paiement.

Maintenant, allez à Connect App Store , puis passez à 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 fait cela, importez la photo que vous venez de prendre sur votre iPhone dans votre ordinateur et téléchargez-la comme capture d'écran pour examen, puis, dans les notes de révision, mettez votre e-mail et votre mot de passe TEST USER . Cela aidera Apple dans le processus d'examen.

Après avoir fait cela, revenez sur l'application sur votre appareil iOS, toujours connecté en tant que compte d'utilisateur de test, et cliquez sur le bouton d'achat. Cette fois, confirmez le paiement Ne vous inquiétez pas, cela ne facturera PAS votre compte, testez les comptes utilisateur et obtenez tous les achats in-app gratuitement Après avoir confirmé le paiement, assurez-vous que ce qui se passe lorsque l'utilisateur achète réellement votre produit arrive. Si ce n'est pas le cas, ce sera une erreur avec votre doRemoveAdsméthode. Encore une fois, je vous recommande de changer l'arrière-plan en bleu pour tester l'achat dans l'application, mais cela ne devrait pas être votre achat réel dans l'application. Si tout fonctionne et que vous êtes prêt à partir! Assurez-vous simplement d'inclure l'achat dans l'application 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 quatre choses:

  • Vous n'avez pas mis l'ID d'achat intégré dans votre code (pour l'identifiant kRemoveAdsProductIdentifierdans le code ci-dessus)
  • Vous n'avez pas effacé votre achat intégré à la vente sur l' App Store Connect
  • Vous n'avez pas attendu que l'ID d'achat intégré soit enregistré dans l' App Store Connect . Attendez quelques heures avant de créer l'ID et votre problème devrait être résolu.
  • Vous n'avez pas terminé de renseigner vos accords, taxes et informations bancaires.

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

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

Jojodmo
la source
1
mais si je n'ajoute pas cette ligne, quand je clique sur le bouton de restauration, rien ne se passe .. de toute façon merci beaucoup pour ce tutoriel;)
Ilario
1
"if ( * transaction * == SKPaymentTransactionStateRestored) {" devrait être if ( * transaction.transactionState * == SKPaymentTransactionStateRestored) {
Massmaker
13
Les meilleures pratiques d'Apple vous recommandent d'ajouter l'observateur de transactions à l'AppDelegate, et non les actions du contrôleur de vue. developer.apple.com/library/ios/technotes/tn2387/_index.html
Craig Pickering du
3
Je reçois un compte de 0 produits, mais j'ai déjà vérifié les 3 raisons possibles que vous avez énumérées. La seule chose qui me vient à l'esprit si je n'ai pas configuré les coordonnées, les coordonnées bancaires et les informations fiscales sur le «contrat d'application payante ios» dans itunes connect, est-ce la raison?
Christopher Francisco
4
Vous devez expliquer qu'à l'étape 9, le nom d'affichage est ce qui est présenté à l'utilisateur. Et il est présenté de la manière suivante: "Voulez-vous acheter un NOM D'AFFICHAGE pour 0,99 $?". Ceci est important car j'ai fait mon nom d'affichage "Supprimer les publicités", puis mon application a été rejetée parce que j'utilisais une grammaire incorrecte dans la fenêtre contextuelle! J'ai dû changer mon nom d'affichage en "Package de suppression d'annonces".
Alan Scarpa,
13

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

        }


    }
}
} 
Yedidya Reiss
la source
6

Réponse rapide

Ceci est destiné à compléter ma réponse Objective-C pour les utilisateurs Swift, pour empêcher la réponse Objective-C de devenir trop grande.

Installer

Tout d'abord, configurez l'achat dans l'application sur appstoreconnect.apple.com . Suivez la première partie de ma réponse Objective-C (étapes 1 à 13, sous l'en- tête App Store Connect ) pour obtenir des instructions sur cette opération.

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

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

Allez dans votre projet Xcode, et allez dans le gestionnaire d'application (icône bleue semblable à 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 (cela devrait être la première option), puis allez dans "Capacités" en haut. Dans la liste, vous devriez voir une option "Achat In-App". Activez cette fonctionnalité et Xcode s'ajoutera StoreKità votre projet.

Codage

Maintenant, nous allons commencer à coder!

Tout d'abord, créez un nouveau fichier rapide qui gérera tous vos achats intégrés. Je vais l'appeler IAPManager.swift.

Dans ce fichier, nous allons créer une nouvelle classe, appelée IAPManagera SKProductsRequestDelegateet SKPaymentTransactionObserver. En haut, assurez-vous d'importer FoundationetStoreKit

import Foundation
import StoreKit

public class IAPManager: NSObject, SKProductsRequestDelegate,
                         SKPaymentTransactionObserver {
}

Ensuite, nous allons ajouter une variable pour définir l'identifiant de notre achat dans l'application (vous pouvez également utiliser un enum, qui serait plus facile à maintenir 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 SKProductsRequestDelegateet SKPaymentTransactionObservertravailler:

Nous ajouterons la RemoveAdsManagerclasse 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 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 pourrait être dans une seule classe, mais en avoir plusieurs 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, restoreRemoveAdset areAdsRemoved, sont des fonctions que vous appelez à faire certaines actions. Les quatre derniers seront appelés par IAPManager.

Ajoutons du code aux deux premières fonctions removeAdset 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 cela ensemble, 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 pour l'utilisateur de commencer l'achat et d'appeler RemoveAdsManager.removeAds()et de démarrer une restauration et d'appeler RemoveAdsManager.restoreRemoveAds(), comme un bouton quelque part! Gardez à l'esprit que, conformément aux directives de l'App Store, vous devez fournir un bouton pour restaurer les achats quelque part.

Soumission pour examen

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

Jojodmo
la source
4

RMStore est une bibliothèque iOS légère pour les achats intégrés . Il enveloppe l'API StoreKit et vous fournit des blocs pratiques pour les demandes 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 la vérification des reçus, les téléchargements de contenu et la persistance des transactions.

Vladimir Grigorov
la source
-1

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

L'achat dans l'application est l'un des flux de travail les plus complets d'iOS mis en œuvre par le framework Storekit. La documentation entière est assez claire si vous avez la patience de la lire, mais elle est quelque peu avancée en termes de technicité.

Résumer:

1 - Demandez les produits - utilisez les classes SKProductRequest et SKProductRequestDelegate pour émettre une demande d'ID de produit et les recevoir de votre propre boutique itunesconnect.

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

2 - Émettez une demande de paiement - utilisez SKPayment & SKPaymentQueue pour ajouter un paiement à la file d'attente de transactions.

3 - Surveiller 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 - Restore flow button - utilisez SKPaymentQueue restoreCompletedTransactions pour accomplir cela - l'étape 3 s'occupe du reste, avec les méthodes suivantes de SKPaymentTransactionObserver:

paymentQueueRestoreCompletedTransactionsFinished
restoreCompletedTransactionsFailedWithError

Voici un tutoriel étape par étape (créé par moi à la suite de mes propres tentatives pour le comprendre) qui l'explique. À la fin, il fournit également un exemple de code que vous pouvez utiliser directement.

En voici une autre que j'ai créée pour expliquer certaines choses que seul le texte pourrait décrire de manière plus efficace.

Nirav Bhatt
la source
21
StackOverflow est un site Web pour aider les autres, et non pour essayer de gagner de l'argent avec eux. Vous devez soit supprimer l'avant-dernier lien, soit simplement publier gratuitement ce qui est fait dans ce didacticiel ici.
Jojodmo
@Jojodmo pouvez-vous étayer votre réclamation avec des directives de SO? Je vois beaucoup de gens commercialiser leur propre SDK (même payé) avec une clause de non-responsabilité, qui je pense est très présente ici aussi.
Nirav Bhatt
12
Il n'y a pas de directives contre cela, mais si vous êtes ici pour gagner de l'argent, vous êtes probablement ici pour de mauvaises raisons. OMI, votre réponse semble viser à inciter les gens à s'inscrire à vos didacticiels vidéo, et non à aider les autres
Jojodmo
3
Ce n'est rien d'autre que de la gêne.
durazno