💰 Bonus — Monétiser une application mobile avec Ionic

⚠️ Chapitre hors scope du module, proposé en bonus pour les plus curieux. Il ne fait pas partie des objectifs évalués, mais reflète la réalité du marché et vous permettra de mettre en place une monétisation dans vos projets personnels.

Dans ce chapitre bonus, vous allez découvrir comment gagner de l'argent avec une application mobile, de la théorie jusqu'à l'implémentation concrète dans un projet Ionic-Vue.

La monétisation est une réalité du marché : même les applications gratuites doivent générer des revenus pour être viables. Comprendre comment ça fonctionne — et comment l'intégrer proprement dans un projet — est une compétence précieuse, que vous soyez futur développeur freelance, employé, ou entrepreneur.

🎯 Objectifs d'apprentissage

À la fin de ce chapitre, vous serez capables de :

  • comprendre les différents modèles de monétisation d'une application mobile ;
  • distinguer les formats publicitaires (bannière, interstitiel, rewarded) et savoir quand les utiliser ;
  • appliquer les guidelines officielles AdMob par format ;
  • intégrer le plugin @capacitor-community/admob dans un projet Ionic-Vue ;
  • implémenter une bannière, une pub interstitielle et une pub récompensée ;
  • comprendre le fonctionnement des achats intégrés (IAP) et leur intégration avec RevenueCat ;
  • comprendre ce que la monétisation implique pour la publication sur les stores.

💰 B.1 — Les modèles de monétisation mobiles

💡 B.1.1 Vue d'ensemble

Il existe plusieurs façons de gagner de l'argent avec une application mobile. Elles ne s'excluent pas et peuvent souvent être combinées.

Modèle Description Exemples
Freemium Gratuit avec fonctionnalités premium payantes Spotify, Duolingo
Publicité (ads) Affichage de pubs, revenus via impressions/clics Jeux mobiles gratuits
Abonnement Paiement récurrent mensuel ou annuel Netflix, Strava
Achat unique (paid app) L'utilisateur achète l'app une seule fois Certains jeux/outils
Achats intégrés (IAP) Contenu ou fonctionnalités achetables dans l'app V-Bucks Fortnite, skins
Affiliate / partenariats Liens sponsorisés, recommandations rémunérées Apps de voyage, finance

💬 Exemple : Clash Royale est gratuit, mais génère des centaines de millions via les achats intégrés (gemmes, coffres). Candy Crush, lui, se finance surtout via les pubs et quelques IAP.

📊 B.1.2 Lequel choisir ?

Le choix du modèle dépend du type d'app, de l'audience cible et de la valeur perçue.

Règle générale :

  • App utilitaire ou pro → abonnement ou achat unique
  • Jeu casual ou app grand public → publicité + IAP
  • App avec forte valeur ajoutée → freemium (base gratuite + premium payant)

⚠️ Attention à l'UX :

Un mauvais modèle de monétisation peut détruire une bonne application.

  • Pubs intrusives
  • Paywalls agressifs
  • Dark patterns (faux boutons "Fermer", comptes à rebours trompeurs)

… → avis négatifs et désinstallations massives.

Dans ce chapitre, nous nous concentrons sur deux approches pratiques :

  • la publicité avec Google AdMob ;
  • les achats intégrés (IAP) avec RevenueCat.

📢 B.2 — La publicité mobile avec Google AdMob

📱 B.2.1 Qu'est-ce qu'AdMob ?

Google AdMob est la régie publicitaire mobile de Google, et la plus utilisée dans le monde.

Elle sert d'intermédiaire entre :

  • les annonceurs (qui veulent diffuser leurs pubs)
  • les développeurs (qui veulent monétiser leur app).

En tant que développeur, vous :

  1. créez une unité publicitaire dans la console AdMob ;
  2. intégrez le SDK AdMob dans votre app ;
  3. recevez des revenus à chaque impression ou clic (CPM / CPC).

💬 Exemple : pour 1 000 impressions d'une bannière, vous touchez en moyenne entre 1€ et 3€ selon la région et la thématique de l'app. Les pubs vidéo rewarded rapportent généralement beaucoup plus.

🎨 B.2.2 Les formats publicitaires

Il existe plusieurs formats, chacun adapté à un contexte précis.

🔲 Bannière (Banner)

  • Petite bande publicitaire, affichée de manière permanente en haut ou en bas de l'écran.
  • Non-intrusive, revenus modestes mais constants.
  • Idéale pour les apps à usage prolongé (lecture, outils, utilitaires).

💬 Exemple : une app de calculatrice affiche une bannière en bas en permanence.

⬛ Interstitiel (Interstitial)

  • Publicité plein écran qui s'affiche entre deux écrans ou moments de transition naturels.
  • Plus intrusive, mais plus rémunératrice.
  • Doit être utilisée aux bons moments — jamais en plein milieu d'une action.
  • L'utilisateur peut la fermer après quelques secondes.

💬 Exemple : dans un jeu mobile, une interstitielle s'affiche entre deux niveaux ou après un "Game Over".

🎬 Rewarded (RewardedAd)

  • Publicité vidéo volontaire : l'utilisateur choisit de la regarder en échange d'une récompense.
  • Meilleur taux d'engagement, non-intrusive car volontaire.
  • Très utilisée dans les jeux.

💬 Exemple : "Regardez une vidéo pour obtenir 50 pièces d'or !" dans un jeu mobile.

📋 B.2.2 Tableau récapitulatif des formats

Format Intrusivité Revenus Déclenchement recommandé
Bannière Faible Faibles Permanent, contenu statique
Interstitiel Élevée Moyens Transitions naturelles (fin de niveau, changement de page)
Rewarded Nulle (volontaire) Élevés Sur action explicite de l'utilisateur

⏱️ B.2.3 Guidelines officielles et bonnes pratiques UX

Google publie des guidelines officielles pour chaque format. Ne pas les respecter peut entraîner la suspension de votre compte AdMob ou le refus de votre app sur le store.

Nous allons voir les règles essentielles, format par format.

🔲 Guidelines — Bannière

  • Ne jamais placer une bannière de façon à provoquer des clics accidentels — par exemple juste sous un bouton de l'app.
  • Ne pas empiler plusieurs bannières sur le même écran.
  • Ne pas masquer la bannière sous d'autres éléments de l'interface.
  • La bannière doit rester visible sans jamais gêner l'usage principal.
  • Utiliser de préférence le format ADAPTIVE_BANNER : il s'adapte à la largeur de l'écran et est le format recommandé par Google depuis 2023.

⬛ Guidelines — Interstitiel

Ces règles sont issues de la documentation officielle AdMob.

À propos du format

  • Conçues pour des apps à expérience linéaire, avec des points de départ/arrêt clairs.
  • Si votre app n'a pas cette structure (lampe torche, calculatrice), préférez une bannière.

Certaines interstitielles peuvent avoir un délai jusqu'à 5 secondes avant d'afficher "Fermer". Avec les high-engagement ads, ce délai peut monter à 12 secondes (voire ~30 secondes via certains réseaux).

⬛ Guidelines — Interstitiel (bonnes pratiques)

✅ Implémentations recommandées :

  • Afficher après une transition naturelle (fin de niveau, changement de section).

  • Laisser l'utilisateur terminer son action avant la pub.

  • Pré-charger la pub en arrière-plan avant d'en avoir besoin (prepareInterstitial()).

  • Se poser ces questions avant chaque affichage :

    • Comment l'utilisateur interagit-il avec l'app à cet instant ?
    • L'interstitielle va-t-elle le surprendre ?
    • Est-ce vraiment le bon moment ?

⬛ Guidelines — Interstitiel (interdictions)

❌ Implémentations interdites :

  • Afficher une interstitielle au lancement de l'application.
  • Déclencher une pub en plein milieu d'une action (formulaire, lecture, gameplay actif).
  • Afficher plusieurs interstitielles à la suite sans pause.
  • Cacher ou rendre difficile d'accès le bouton "Fermer".
  • Déclencher une pub suite à un clic accidentel.

💬 Règle des intervalles : implémentez toujours un cooldown (par ex. 3 à 5 minutes) entre deux interstitielles.

🎬 Guidelines — Rewarded

  • La pub doit toujours être volontaire : l'utilisateur choisit explicitement de la regarder.
  • La récompense doit être clairement annoncée avant le visionnage ("Regardez 30 secondes pour obtenir 50 pièces").
  • Ne jamais forcer un utilisateur à regarder une rewarded pour continuer à utiliser l'app normalement.
  • La récompense doit être délivrée uniquement après visionnage complet (événement Rewarded).
  • Pour les grosses récompenses (argent réel, contenu premium), utiliser la vérification côté serveur (SSV).

💬 Les pubs rewarded ont les meilleurs eCPM et un engagement maximal.

📊 B.2.4 Estimations de revenus publicitaires (2024–2025)

Format eCPM moyen (UE/US) Engagement Risque UX
Bannière 1€ – 3€ / 1 000 impressions Faible Très faible
Interstitiel 5€ – 15€ / 1 000 impressions Moyen Élevé si mal placé
Rewarded 10€ – 30€ / 1 000 impressions Très élevé Nul (volontaire)

💬 Ces valeurs sont indicatives et varient selon la région, la thématique et la qualité de l’audience.

🛒 B.3 — Les achats intégrés (In-App Purchases)

🛒 B.3.1 Principe général

Les achats intégrés (IAP) permettent à l'utilisateur d'acheter du contenu ou des fonctionnalités directement dans l'app.

Types principaux :

Type Description Exemple
Consommable Acheté et "consommé" (disparaît après usage) 100 pièces d'or, 5 vies
Non-consommable Acheté une fois, disponible à vie Supprimer les pubs, déverrouiller un niveau
Abonnement Accès récurrent, renouvelable automatiquement Accès premium mensuel

🏪 B.3.2 Le rôle des stores

Les stores (Google Play, App Store) sont obligatoirement impliqués :

  • hébergent et valident les produits achetables ;
  • gèrent le paiement (sécurisé, l'app ne voit jamais les données bancaires) ;
  • prélèvent une commission de 15% à 30% sur chaque achat.

💬 Sur un achat à 1.00 CHF, vous recevez 0.70 à 0.85 CHF selon les politiques en vigueur.

🔄 B.3.3 Le flux d'un achat

Quand l'utilisateur achète :

  1. L'utilisateur appuie sur "Acheter" dans l'app.
  2. Le store natif (Google Play / App Store) affiche sa fenêtre de paiement.
  3. L'utilisateur confirme (empreinte, Face ID, mot de passe).
  4. Le store envoie un receipt (reçu cryptographique) à l'app.
  5. L'app valide ce receipt (idéalement côté serveur) et délivre le contenu.
  6. Le store verse les revenus mensuellement (commission déduite).

⚠️ Validation côté serveur : recommandée pour les achats sensibles (abonnements, monnaie premium). RevenueCat gère cela automatiquement.

⚙️ B.3.4 RevenueCat — pourquoi et comment ça marche

Pour Ionic + Capacitor, la solution la plus fiable en production est RevenueCat.

Sans RevenueCat Avec RevenueCat
Deux APIs différentes (StoreKit / Billing) Une seule API unifiée
Receipts à valider soi-même Validation côté serveur automatique
Pas de dashboard Dashboard revenus / rétention / MRR / LTV
Gestion complexe des abonnements Gestion automatique
Gratuit Gratuit jusqu'à 2 500$/mois gérés

Concepts :

  • Product : produit store (ex : premium_monthly)
  • Entitlement : accès débloqué (ex : premium)
  • Offering : ensemble d’offres à présenter (mensuel, annuel…)

💬 RevenueCat écoute les stores, valide les reçus, expose une API unifiée.

⚠️ B.3.5 Prérequis concrets pour tester les IAP

Contrairement à AdMob, les IAP :

  • ne fonctionnent pas en local pur ;
  • nécessitent une app sur piste de test interne (Google / Apple).

Prérequis :

  • compte RevenueCat configuré avec votre app ;
  • produits créés dans Google Play / App Store ;
  • app publiée sur une piste de test interne.

👉 Dans cet exercice, concentrez-vous sur AdMob. Les IAP sont là pour vos projets personnels quand vous aurez les comptes et stores configurés.

📋 B.4 — Implications pour la publication

📋 B.4.1 Ce qu'il faut prévoir à l'avance

Élément Obligatoire ? Description
Politique de confidentialité ✅ Oui Obligatoire dès que vous collectez des données (AdMob le fait)
Consentement RGPD (UMP) ✅ Oui (UE) Formulaire de consentement pour les pubs personnalisées
Déclaration des pubs dans le store ✅ Oui Google / Apple demandent de déclarer l'usage d'AdMob
Compte AdMob créé et vérifié ✅ Oui Doit être lié au projet avant publication
Compte développeur Google Play ✅ Oui (Android) 25$ unique
Compte développeur Apple ✅ Oui (iOS) 99$/an
Informations fiscales ✅ Oui Obligatoires pour recevoir les revenus
Déclaration COPPA (enfants) ✅ Si <13 ans Pubs non-personnalisées obligatoires
Bouton "Restaurer les achats" ✅ Oui (iOS + IAP) Exigé par Apple

🔐 B.4.2 Consentement RGPD et UMP

Depuis 2024, Google exige pour les pubs en UE :

  • un formulaire de consentement via UMP (User Messaging Platform) ;
  • l'utilisateur choisit pubs personnalisées ou non.

Si l’utilisateur refuse :

  • pubs non-personnalisées (npa: true) ;
  • revenus plus faibles, mais conformité légale.

L’implémentation du formulaire est intégrée dans useAdMob.ts via requestConsentInfo() et showConsentForm().

📊 B.4.3 IDs de test vs production

AdMob fournit des IDs de test officiels :

Plateforme ID de test Banner ID de test Interstitiel ID de test Rewarded
Android ca-app-pub-3940256099942544/6300978111 ca-app-pub-3940256099942544/1033173712 ca-app-pub-3940256099942544/5224354917
iOS ca-app-pub-3940256099942544/2934735716 ca-app-pub-3940256099942544/4411468910 ca-app-pub-3940256099942544/1712485313

⛔ Ne jamais utiliser vos IDs de prod pour tester :

  • faux clics = suspension immédiate du compte AdMob.

Toujours utiliser les IDs de test en développement.

🔨 B.5 — Mise en pratique : projet AdMob de A à Z

B.5.1 — Créer et préparer le projet

ionic start quizflash-admob tabs --type=vue --capacitor
cd quizflash-admob

Vérifiez votre version de Capacitor :

npx cap --version

Installez les plugins :

npm install @capacitor-community/admob@6
npm install @revenuecat/purchases-capacitor

Ajoutez Android et synchronisez :

npx cap add android
ionic build
npx cap sync

⚠️ npx cap add android une seule fois ; ensuite ionic build + npx cap sync.

B.5.2 — Configuration Android

Dans android/app/src/main/AndroidManifest.xml, à l'intérieur de <application> :

<meta-data
  android:name="com.google.android.gms.ads.APPLICATION_ID"
  android:value="@string/admob_app_id"/>

Assurez-vous que launchMode est compatible avec les IAP :

<activity
  android:name="com.yourapp.MainActivity"
  android:launchMode="singleTop" />

Dans android/app/src/main/res/values/strings.xml :

<string name="admob_app_id">ca-app-pub-3940256099942544~3347511713</string>

💬 App ID de test à remplacer par le vôtre en prod.

B.5.3 — Composable useAdMob.ts (1/3)

// src/composables/useAdMob.ts
import {
    AdMob,
    BannerAdOptions, BannerAdSize, BannerAdPosition,
    AdmobConsentStatus,
} from '@capacitor-community/admob'

// ─── IDs publicitaires ──────────────────────────────────────────
const IS_TESTING = true

const AD_IDS = {
    banner:       IS_TESTING ? 'ca-app-pub-3940256099942544/6300978111'  : 'VOTRE_ID_BANNER',
    interstitial: IS_TESTING ? 'ca-app-pub-3940256099942544/1033173712' : 'VOTRE_ID_INTERSTITIAL',
    rewarded:     IS_TESTING ? 'ca-app-pub-3940256099942544/5224354917' : 'VOTRE_ID_REWARDED',
}

// ─── Cooldown interstitiel ──────────────────────────────────────
let lastInterstitialTime = 0
const INTERSTITIAL_COOLDOWN_MS = 3 * 60 * 1000 // 3 minutes

export function useAdMob() {

B.5.3 — Composable useAdMob.ts (2/3)

  async function initialize(): Promise<void> {
    await AdMob.initialize()

    const consentInfo = await AdMob.requestConsentInfo()
    if (consentInfo.isConsentFormAvailable && consentInfo.status === AdmobConsentStatus.REQUIRED) {
        await AdMob.showConsentForm()
    }

    const trackingInfo = await AdMob.trackingAuthorizationStatus()
    if (trackingInfo.status === 'notDetermined') {
        await AdMob.requestTrackingAuthorization()
    }
  }

  async function showBanner(): Promise<void> {
    const options: BannerAdOptions = {
        adId:     AD_IDS.banner,
        adSize:   BannerAdSize.ADAPTIVE_BANNER,
        position: BannerAdPosition.BOTTOM_CENTER,
    }
    await AdMob.showBanner(options)
  }

  async function hideBanner(): Promise<void> { await AdMob.hideBanner() }
  async function removeBanner(): Promise<void> { await AdMob.removeBanner() }

B.5.3 — Composable useAdMob.ts (3/3)

  async function prepareInterstitial(): Promise<void> {
    await AdMob.prepareInterstitial({ adId: AD_IDS.interstitial })
  }

  async function showInterstitial(): Promise<boolean> {
    const now = Date.now()
    if (now - lastInterstitialTime < INTERSTITIAL_COOLDOWN_MS) {
        console.log('[AdMob] Cooldown actif — interstitielle ignorée.')
        return false
    }
    try {
        await AdMob.showInterstitial()
        lastInterstitialTime = now
        return true
    } catch (e) {
        console.warn('[AdMob] Interstitiale non disponible :', e)
        return false
    }
  }

  async function showRewardedAd(): Promise<{ type: string; amount: number } | null> {
    try {
        await AdMob.prepareRewardVideoAd({ adId: AD_IDS.rewarded })
        const reward = await AdMob.showRewardVideoAd()
        return { type: reward.type, amount: reward.amount }
    } catch (e) {
        console.warn('[AdMob] Rewarded non disponible :', e)
        return null
    }
  }

  return {
    initialize,
    showBanner, hideBanner, removeBanner,
    prepareInterstitial, showInterstitial,
    showRewardedAd,
  }
}

B.5.4 — Initialiser AdMob dans main.ts

// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { IonicVue } from '@ionic/vue'
import { useAdMob } from '@/composables/useAdMob'

async function bootstrap() {
  const app = createApp(App).use(IonicVue).use(router)

  const { initialize } = useAdMob()
  await initialize()

  router.isReady().then(() => {
    app.mount('#app')
  })
}

bootstrap()

B.5.5 — Tab 1 : Quiz + Ads (1/2)

<!-- src/views/Tab1Page.vue -->
<template>
  <ion-page>
    <ion-header>
      <ion-toolbar>
        <ion-title>QuizFlash 🧠</ion-title>
      </ion-toolbar>
    </ion-header>

    <ion-content class="ion-padding">
      <ion-card>
        <ion-card-header>
          <ion-card-title>
            Question {{ currentQuestion + 1 }} / {{ questions.length }}
          </ion-card-title>
        </ion-card-header>
        <ion-card-content>
          <p>{{ questions[currentQuestion].text }}</p>

          <ion-button expand="block" @click="nextQuestion">
            Question suivante →
          </ion-button>

          <ion-button
            expand="block"
            fill="outline"
            color="warning"
            @click="getHint"
          >
            💡 Obtenir un indice (regarder une vidéo)
          </ion-button>
        </ion-card-content>
      </ion-card>

      <ion-toast
        :is-open="toastOpen"
        :message="toastMessage"
        :duration="3000"
        @didDismiss="toastOpen = false"
      />
    </ion-content>
  </ion-page>
</template>

B.5.5 — Tab 1 : Quiz + Ads (2/2)

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import {
  IonPage, IonHeader, IonToolbar, IonTitle, IonContent,
  IonCard, IonCardHeader, IonCardTitle, IonCardContent,
  IonButton, IonToast
} from '@ionic/vue'
import { useAdMob } from '@/composables/useAdMob'

const { showBanner, removeBanner, prepareInterstitial, showInterstitial, showRewardedAd } = useAdMob()

const questions = [
  { text: 'Quelle est la capitale de la Suisse ?' },
  { text: 'Combien font 7 × 8 ?' },
  { text: 'En quelle année a eu lieu la Révolution française ?' },
  { text: 'Quel est le symbole chimique de l\'or ?' },
  { text: 'Combien de côtés a un hexagone ?' },
]
const currentQuestion = ref(0)
const toastOpen = ref(false)
const toastMessage = ref('')

function showToast(msg: string) {
  toastMessage.value = msg
  toastOpen.value = true
}

async function nextQuestion() {
  currentQuestion.value = (currentQuestion.value + 1) % questions.length
  const shown = await showInterstitial()
  if (!shown) await prepareInterstitial()
}

async function getHint() {
  showToast('⏳ Chargement de la vidéo...')
  const reward = await showRewardedAd()
  if (reward) {
    showToast(`🎉 Indice débloqué ! (récompense : ${reward.amount} ${reward.type})`)
  } else {
    showToast('❌ Vidéo non disponible, réessayez plus tard.')
  }
}

onMounted(async () => {
  await showBanner()
  await prepareInterstitial()
})

onUnmounted(async () => {
  await removeBanner()
})
</script>

⚙️ B.5.6 — Composable useIAP.ts (1/2)

// src/composables/useIAP.ts
import { Purchases, PurchasesOffering } from '@revenuecat/purchases-capacitor'
import { ref, toRaw } from 'vue'

export function useIAP() {
  const offering = ref<PurchasesOffering | null>(null)
  const isPremium = ref(false)
  const isLoading = ref(false)
  const error = ref<string | null>(null)

  async function loadOffering(): Promise<void> {
    isLoading.value = true
    error.value = null
    try {
      const result = await Purchases.getOfferings()
      offering.value = result.current ?? null
    } catch (e: any) {
      error.value = e?.message ?? 'Impossible de charger les offres'
    } finally {
      isLoading.value = false
    }
  }

  async function checkPremiumStatus(): Promise<void> {
    try {
      const { customerInfo } = await Purchases.getCustomerInfo()
      isPremium.value = customerInfo.entitlements.active['premium'] !== undefined
    } catch (e: any) {
      console.warn('[IAP] Impossible de vérifier le statut premium :', e)
    }
  }

⚙️ B.5.6 — Composable useIAP.ts (2/2)

  async function purchasePackage(packageToPurchase: any): Promise<boolean> {
    isLoading.value = true
    error.value = null
    try {
      const { customerInfo } = await Purchases.purchasePackage({
        aPackage: toRaw(packageToPurchase)
      })
      isPremium.value = customerInfo.entitlements.active['premium'] !== undefined
      return isPremium.value
    } catch (e: any) {
      if (e?.code !== 'PURCHASE_CANCELLED') {
        error.value = e?.message ?? 'Erreur lors de l\'achat'
      }
      return false
    } finally {
      isLoading.value = false
    }
  }

  async function restorePurchases(): Promise<void> {
    isLoading.value = true
    error.value = null
    try {
      const { customerInfo } = await Purchases.restorePurchases()
      isPremium.value = customerInfo.entitlements.active['premium'] !== undefined
    } catch (e: any) {
      error.value = e?.message ?? 'Impossible de restaurer les achats'
    } finally {
      isLoading.value = false
    }
  }

  return {
    offering,
    isPremium,
    isLoading,
    error,
    loadOffering,
    checkPremiumStatus,
    purchasePackage,
    restorePurchases,
  }
}

B.5.7 — Tab 2 : Écran Paywall (IAP)

<!-- src/views/Tab2Page.vue -->
<template>
  <ion-page>
    <ion-header>
      <ion-toolbar>
        <ion-title>Premium ⭐</ion-title>
      </ion-toolbar>
    </ion-header>

    <ion-content class="ion-padding">
      <div v-if="isLoading" class="ion-text-center ion-padding">
        <ion-spinner />
        <p>Chargement des offres...</p>
      </div>

      <ion-card v-else-if="error" color="danger">
        <ion-card-content>{{ error }}</ion-card-content>
      </ion-card>

      <ion-card v-else-if="isPremium" color="success">
        <ion-card-header>
          <ion-card-title>⭐ Vous êtes Premium !</ion-card-title>
        </ion-card-header>
        <ion-card-content>
          Vous avez accès à toutes les fonctionnalités.
        </ion-card-content>
      </ion-card>

      <template v-else-if="offering">
        <ion-card>
          <ion-card-header>
            <ion-card-title>Passer Premium</ion-card-title>
          </ion-card-header>
          <ion-card-content>
            <p>✅ Sans publicité</p>
            <p>✅ Accès illimité au contenu</p>
            <p>✅ Fonctionnalités exclusives</p>
          </ion-card-content>
        </ion-card>

        <ion-list>
          <ion-item
            v-for="pkg in offering.availablePackages"
            :key="pkg.identifier"
            button
            @click="purchasePackage(pkg)"
          >
            <ion-label>
              <h2>{{ pkg.product.title }}</h2>
              <p>{{ pkg.product.description }}</p>
            </ion-label>
            <ion-note slot="end">{{ pkg.product.priceString }}</ion-note>
          </ion-item>
        </ion-list>

        <ion-button expand="block" fill="clear" @click="restorePurchases">
          Restaurer mes achats
        </ion-button>
      </template>

      <ion-card v-else>
        <ion-card-content class="ion-text-center">
          <p>Aucune offre disponible.</p>
          <p><small>RevenueCat doit être configuré avec un vrai compte et des produits publiés sur le store.</small></p>
        </ion-card-content>
      </ion-card>
    </ion-content>
  </ion-page>
</template>

🤝 B.6 — Bonnes pratiques et éthique

✅ À faire

  • Afficher le consentement RGPD (UMP) pour les utilisateurs UE.
  • Respecter les cooldowns et guidelines par format.
  • Proposer une option "Supprimer les pubs" (IAP) si possible.
  • Ajouter un bouton "Restaurer les achats" (obligatoire sur iOS).
  • Tester l'expérience en se mettant dans la peau d'un vrai utilisateur.

❌ À ne jamais faire

  • Afficher des pubs au lancement de l’app.
  • Cacher le bouton de fermeture d’une interstitielle.
  • Générer de faux clics (suspension AdMob).
  • Diffuser des pubs personnalisées dans une app ciblant < 13 ans.
  • Utiliser des dark patterns pour pousser à l’achat.

💬 Les conséquences peuvent aller jusqu'à la dépublication de l'app et clôture des comptes.

🧩 B.7 — Activité : Ajouter une bannière

🎯 Objectif : intégrer une bannière de test dans le projet Ionic développé durant l’atelier.

  1. Installez @capacitor-community/admob@6 dans votre projet existant.
  2. Lancez npx cap add android si nécessaire, puis ionic build et npx cap sync.
  3. Configurez AndroidManifest.xml et strings.xml avec l’App ID de test.
  4. Créez le composable useAdMob.ts en vous basant sur celui du cours.
  5. Ajoutez une bannière sur la page principale — pensez au margin si votre app a des tabs.
  6. Vérifiez son affichage sur émulateur ou appareil.

🏆 Bonus : ajoutez une interstitielle déclenchée après une action, avec un cooldown de 2 minutes.

🔗 B.8 — Références et ressources

💰 Bonus — Monétiser une application mobile avec Ionic

By tirtho

💰 Bonus — Monétiser une application mobile avec Ionic

  • 18