Accueil

Gérer les achats intégrés avec InAppPurchaseLib - partie 2

Tutoriel Swift

Posté par Véronique le 10 May 2020

Dans cet article, nous verrons comment intégrer, simplement, des achats in-app dans une application Swift grâce à InAppPurchaseLib. Nous parlerons plus particulièrement des abonnements avec renouvellement automatique, mais la librairie supporte tous les types de produits. Le gros point fort de la librairie, c'est qu'elle intègre une vérification des achats auprès de Fovea.Billing, ce qui permet de sécuriser les transactions.

iap-swift-lib

Dans la première partie, nous avons vu comment configurer un projet sur App Store Connect et dans Xcode pour pouvoir intégrer des achats in-app. Pour finir, nous verrons dans la troisième partie, comment créer et utiliser un compte de tests pour effectuer des achats, sans être facturé, et avec des durées réduites pour les abonnements avec renouvellement automatique.

Mais passons à présent à la seconde partie, dans laquelle nous allons voir comment intégrer la librairie dans le code de l'application.


1. Ajouter InAppPurchaseLib au projet

  1. Sélectionnez votre projet dans Xcode
  2. Allez dans la section Swift Package
  3. Cliquez sur le bouton (+) Add Package Dependency
Add Package Dependency
  1. Copiez l'URL Git du projet: https://github.com/iridescent-dev/iap-swift-lib.git
  2. Cliquez sur Next
  3. Dans Rules sélectionner Version: Up to Next Major …
  4. Cliquez sur Next
Add Package Dependency
  1. Assurez-vous que votre projet soit sélectionné dans Add to target
  2. Cliquez sur Finish

2. Initialiser la librairie

Avant toute chose, la librairie doit être initialisée, ce qui doit être fait le plus tôt possible. Une bonne méthode consiste a appeler la fonction InAppPurchase.initialize() lors du lancement de l'application, c'est-à-dire dans le didFinishLaunchingWithOptions de votre fichier AppDelegate.swift.

Les produits seront chargé depuis l'App Store et le statut des achats de l'utilisateur sera actualisé.

Lors de l'appel à InAppPurchase.initialize(), vous devez fournir les paramètres suivants :

  • iapProducts: Array<IAPProduct> - La liste de vos produits, avec pour chacun :
    • productIdentifier: String - L'identifiant du produit.
    • productType: IAPProductType - Le type de produit (consumable, nonConsumable, nonRenewingSubscription ou autoRenewableSubscription).
  • validatorUrlString: String - votre URL de validation de Fovea.Billing.
Exemple
import InAppPurchaseLib

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    InAppPurchase.initialize(
      iapProducts: [
        IAPProduct(productIdentifier: "monthly_plan", productType: .autoRenewableSubscription),
        IAPProduct(productIdentifier: "yearly_plan",  productType: .autoRenewableSubscription)
      ],
      validatorUrlString: "https://validator.fovea.cc/v1/validate?appName=demo&apiKey=12345678"
    )
    return true
  }
}

Vous devez également stopper la librairie lorsque l'application se termine, dans la fonction applicationWillTerminate de votre fichier AppDelegate.swift.

Exemple
import InAppPurchaseLib

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
  ...
  func applicationWillTerminate(_ application: UIApplication) {
      InAppPurchase.stop()
  }
}

3. Afficher les produits

InAppPurchase.getProductBy(identifier: "monthly_plan") permet de récupérer le SKProduct retourné par l'App Store et étendu par InAppPurchaseLib, avec des variables/fonctions utilisaires, permettant notamment de faciliter l'affichage des informations d'un produit.

Exemple
@IBOutlet weak var monthlyTitleLabel: UILabel!
@IBOutlet weak var monthlyDescriptionLabel: UILabel!
@IBOutlet weak var monthlyButton: UIButton!

override func viewWillAppear(_ animated: Bool) {
  self.refreshView()
  InAppPurchase.refresh(callback: { result in
      if result.state == .succeeded {
        self.refreshView()
      }
  })
}

func refreshView() {
  guard let product: SKProduct = InAppPurchase.getProductBy(identifier: "monthly_plan") else {
    self.monthlyTitleLabel.text = "Product unavailable"
    return
  }
  self.monthlyTitleLabel.text = product.localizedTitle
  self.monthlyDescriptionLabel.text = product.localizedDescription

  // Format price text. Example: "0,99€ / month for 3 months (then 3,99 € / month)"
  var priceText = "\(product.localizedPrice) / \(product.localizedSubscriptionPeriod!)"
  if product.hasIntroductoryPriceEligible() {
      if product.introductoryPrice!.numberOfPeriods == 1 {
          priceText = "\(product.localizedIntroductoryPrice!) for \(product.localizedIntroductoryDuration!)" +
          " (then \(priceText))"
      } else {
          priceText = "\(product.localizedIntroductoryPrice!) / \(product.localizedIntroductoryPeriod!)" +
          " for \(product.localizedIntroductoryDuration!) (then \(priceText))"
      }
  }
  self.monthlyButton.setTitle(priceText, for: .normal)
}

4. Effectuer un achat

4.1 Réaliser l'achat

Pour effectuer un achat, vous devez utiliser la méthode InAppPurchase.purchase() avec l'identifiant du produit à acheter et un callback qui sera appelé lorsque la demande d'achat aura été traitée.

Qu'il s'agisse de produits consommables ou non, ce n'est pas dans le callback qu'il faut gérer le déblocage du contenu. Nous verrons cela plus tard. Ici, nous gérons uniquement la vue : masquer le loader et afficher (ou pas) un message à l'utilisateur. La plupart des messages sont déjà affichés par Apple, rien ne sert de les dupliquer.

Exemple
override func viewDidLoad() {
  self.monthlyButton.addTarget(self, action: #selector(self.purchaseMonthlyPlan), for: .touchUpInside)
}

@objc func purchaseMonthlyPlan(sender: UIButton) {
  self.loaderView.show()
  InAppPurchase.purchase(
      productIdentifier: "monthly_plan",
      callback: { result in
          self.loaderView.hide()
          switch result.state {
          case .purchased:
              print("Product purchased successful.")
              // Do not process the purchase here

          case .failed:
              print("Purchase failed: \(result.localizedDescription).")

          case .cancelled:
              print("The user canceled the payment request.")

          case .deferred:
              print("The purchase was deferred.")
          }
  })
}

4.2 Débloquer le contenu acheté

Pour notre exemple, nous avons créées 2 souscriptions qui font parties du même groupe et pour lesquelles le contenu a débloquer est le même. Ce qui nous intéresse donc, c'est de savoir si l'utilisateur a une souscription active ou non, peu importe de quel produit il s'agit. Le meilleur moyen est d'appeler InAppPurchase.hasActiveSubscription() chaque fois qu'il y a du contenu payant pour conditionner son affichage.

Exemple
if InAppPurchase.hasActiveSubscription() {
  // Show the subscription related content.
} else {
  // Show "locked" indicator.
}

5. Restaurer des achats

Si vous vendez des produits non consommables et/ou des abonnements auto-renouvelables, alors Apple impose d'avoir un bouton permettant aux utilisateurs de restaurer leurs achats.

Lorsque l'utilisateur appui sur ce bouton, vous devez appeler InAppPurchase.restorePurchases(). Tout comme pour les achats, la fonction productPurchased() de votre IAPPurchaseDelegate sera appelé afin de débloquer le contenu, puis le callback sera appelé pour vous permettre d'afficher l'information adaptée à l'utilisateur.

Exemple
@IBAction func restorePurchases(_ sender: Any) {
  self.loaderView.show()
  InAppPurchase.restorePurchases(callback: { result in
      self.loaderView.hide()
      switch result.state {
      case .succeeded:
          if result.addedPurchases > 0 {
              print("Restore purchases successful.")
          } else {
              print("No purchase to restore.")
          }
      case .failed:
          print("Restore purchases failed.")
      }
  })
}

Vous trouverez le code du projet de démo sur GitHub.

Plus d'info sur la documentation Apple développeur :