Intégrer le paiement dans une application mobile : l'architecture sûre pour iOS et Android
Intégrer le paiement dans une application mobile : le serveur crée la session, l'app ouvre le checkout hébergé, le webhook signé confirme, sans clé dans l'app.
Intégrer le paiement dans une application mobile, iOS ou Android, repose sur une règle : l'application ne parle jamais directement à l'API de paiement. Elle envoie la commande à votre serveur ; votre serveur crée une session de paiement avec sa clé d'API et ne renvoie à l'application que l'adresse du checkout hébergé ; le client y paie par carte avec 3-D Secure ; puis un webhook signé confirme le paiement à votre serveur, que l'application interroge pour afficher le résultat. Aucune clé dans l'application, aucun numéro de carte entre vos mains. Ce guide détaille ce parcours étape par étape, avec le code côté serveur, les retours, la confirmation et les tests.
Intégrer le paiement dans une application mobile : de quoi parle-t-on ?
La requête « application de paiement » recouvre deux choses très différentes. Il y a les applications que les particuliers installent pour payer ; et il y a l'infrastructure qu'une application — de livraison, de réservation, de formation, de services — utilise pour encaisser ses propres utilisateurs. ChariPay est du second type : ChariPay est une plateforme de paiement marocaine, un PSP opéré par Chari Money, établissement de paiement agréé par Bank Al-Maghrib. Vos utilisateurs n'installent rien de plus : ils paient depuis votre application, sur une page hébergée par ChariPay.
L'intégration documentée ne demande aucun SDK mobile. Elle passe par trois briques que vous maîtrisez déjà si vous avez un backend : votre serveur, l'API REST de ChariPay et le checkout hébergé. Rien n'est donc à embarquer dans le binaire, et un même serveur sert votre application iOS, votre application Android et votre site.
Le point d'entrée technique est l'espace développeurs ; la collection Postman et la spécification OpenAPI se téléchargent depuis la documentation de l'API, et le guide de l'API de paiement au Maroc reprend l'intégration côté serveur.
Le parcours en sept temps, de l'application au webhook
Voici l'architecture complète d'un paiement dans une application, sans raccourci :
- 1L'application envoie la commande à votre serveur : le panier, l'identifiant de l'utilisateur, rien d'autre. Ni clé, ni donnée de carte.
- 2Votre serveur crée la session auprès de ChariPay, avec l'en-tête
X-CHARI-PAY-API-KEY: la clé ne quitte jamais le serveur. - 3Le serveur renvoie à l'application le seul `checkoutUrl`, l'adresse de la page de paiement de cette session.
- 4L'application ouvre la page hébergée à cette adresse.
- 5Le client paie : carte avec 3-D Secure, sur la page hébergée, en confirmant auprès de sa banque.
- 6Le client est renvoyé vers votre URL
acceptoudecline, selon l'issue. - 7Le webhook signé arrive sur votre serveur, qui marque la commande payée ; l'application interroge votre serveur et affiche le résultat.
| Étape | Acteur | Ce qui transite |
|---|---|---|
| 1 | Application → votre serveur | La commande et l'utilisateur, sans clé ni carte |
| 2 | Votre serveur → API ChariPay | Montant, orderId, client, URL de retour, metadata |
| 3 | Votre serveur → application | Le checkoutUrl, rien d'autre |
| 4–5 | Client ↔ page hébergée et sa banque | La carte et l'authentification 3-D Secure |
| 6 | Page hébergée → URL de retour | Le retour de l'utilisateur, pas la preuve du paiement |
| 7 | ChariPay → votre serveur ; application → votre serveur | Le webhook signé ; puis l'état de la commande |
Chaque flèche a une raison d'être. La carte est recueillie par la page hébergée, pas par un formulaire de votre application, et votre serveur n'en voit jamais le numéro. La clé ne circule qu'entre votre serveur et ChariPay. Et la vérité sur le paiement arrive par le seul canal signé : le webhook.
Ce schéma se monte de bout en bout sans argent réel : Commencer en mode test. Trois champs — nom, e-mail professionnel, entreprise —, un lien d'activation reçu par e-mail pour choisir votre mot de passe, puis le portail, où vous créez vous-même votre clé de test après une ré-authentification ; la clé complète ne s'affiche qu'une seule fois. Vous la posez sur votre serveur de développement et payez avec la carte de test. C'est gratuit, sans limite de durée et sans validation à attendre : la vérification de votre entreprise (KYB) ne conditionne que le passage en production.
Pourquoi la clé d'API ne doit jamais entrer dans l'application
Une clé par entreprise et par environnement, avec des permissions (scopes) par module ; vous la créez vous-même depuis le portail, après une ré-authentification ; la clé complète ne s'affiche qu'une seule fois et n'est jamais envoyée par e-mail ; deux clés peuvent coexister le temps d'une rotation, sans coupure. Une clé est un secret : jamais dans le navigateur, une application mobile, un dépôt git ou une capture d'écran.
Pour une application, la raison est concrète : une application publiée peut être téléchargée et décompilée par n'importe qui. Une clé cachée dans le code, dans un fichier de configuration ou dans une variable de build finit lisible. Avec elle, un tiers pourrait créer des sessions, lire vos transactions ou déclencher des remboursements au nom de votre entreprise.
La clé choisit aussi l'environnement : une seule base, https://api-psp.charipay.ma, sert la sandbox comme la production, et c'est la clé qui fait la différence — chari_sk_test_… en sandbox, chari_sk_live_… en production. Une clé de test mal rangée est donc moins grave qu'une clé de production, mais la règle est la même pour les deux. Si une clé a pu fuiter, créez-en une nouvelle, basculez votre serveur dessus, puis retirez l'ancienne. Le détail est dans protéger ses clés d'API.
Côté serveur : créer la session et ne renvoyer que le checkoutUrl
Quand l'application envoie une commande, votre serveur crée la session. La requête, en curl :
curl -X POST 'https://api-psp.charipay.ma/v1/payment-sessions' \
-H 'X-CHARI-PAY-API-KEY: chari_sk_test_...' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: order-2026-0421-session' \
-d '{
"amount": 250.00,
"orderId": "ORD-2026-0421",
"externalId": "order-2026-0421",
"config": {
"customer": {
"email": "amine.bennani@example.com",
"firstName": "Amine",
"lastName": "Bennani",
"phone": "+212600000000"
},
"urls": {
"accept": "https://votre-boutique.ma/paiement/succes",
"decline": "https://votre-boutique.ma/paiement/echec",
"notification": "https://votre-boutique.ma/webhooks/charipay"
}
},
"metadata": { "cartId": "c_987", "source": "web" }
}'La réponse (201, ou 200 au rejeu) contient ces cinq champs, et rien d'autre :
{
"sessionId": "ps_5Kd0Rn",
"sessionToken": "st_…",
"verifyKey": "vk_5Kd0Rn",
"expiresAt": "2026-10-05T10:15:00Z",
"checkoutUrl": "https://…/checkout/ps_5Kd0Rn"
}Votre serveur garde sessionId à côté de la commande et transmet à l'application le seul checkoutUrl. L'état et le montant de la session se lisent ensuite par GET /v1/payment-sessions/{sessionId}. Dans une application, mettez dans metadata ce qui relie la session à votre utilisateur, comme l'identifiant du panier et la plateforme ("source": "ios" ou "android" à la place de "web") : c'est un objet plat, de 4 Ko au plus, renvoyé tel quel dans le webhook.
Trois conventions à respecter : les montants sont en MAD, en unités majeures (250.00) ; toute URL déclarée est en https:// publique, sous peine de 400 ; un champ optionnel que vous n'utilisez pas est absent, jamais null. La référence complète des champs est dans le module « Sessions de paiement » de la documentation.
Le retour vers l'application : des URL https publiques, rien de plus
Après le paiement, la page hébergée renvoie l'utilisateur vers l'URL accept ou decline de la session. Ces URL sont en https:// publiques ; si vous ne les fournissez pas, ce sont les valeurs par défaut configurées sur votre compte qui s'appliquent, puis celles de la plateforme. En pratique, pointez-les vers des pages de votre domaine prévues pour l'application : « paiement en cours de vérification » et « paiement non abouti ».
Le point essentiel : ces URL ramènent l'utilisateur, elles ne prouvent pas le paiement. Confirmez la commande sur réception du webhook, jamais sur la seule redirection : la redirection dit que l'acheteur est revenu, pas que le paiement est acquis. Une application qui affiche « payé » parce qu'elle a vu passer l'URL accept affichera un jour « payé » pour un paiement qui ne l'est pas.
Comment l'application ouvre la page et récupère la main dépend de vos choix techniques et des systèmes visés : nous n'en décrivons pas les réglages ici. Restez au principe — page hébergée ouverte à l'adresse reçue, jamais dans une iframe cachée, retour par une URL https — et testez le retour sur vos appareils cibles. Une session vit 72 heures par défaut (expiresAt pour la raccourcir) et n'est consommée qu'une fois ; ce qui se passe si l'utilisateur ferme la page est traité dans expiration et retour d'une session de paiement.
Confirmer côté serveur : l'application interroge votre serveur
L'application n'appelle jamais l'API ChariPay, même pour demander un statut. Elle demande l'état de la commande à votre serveur, qui le tient du webhook signé :
- 1Au retour de la page hébergée, l'application affiche « paiement en cours de vérification ».
- 2Elle interroge votre serveur sur la commande, à intervalles courts et pendant un temps limité.
- 3Votre serveur répond « payé » dès qu'il a reçu, vérifié et enregistré le webhook
payment.succeededpour cette commande, retrouvée par votremetadata; pour une session, l'événement porte aussi votreorderId. - 4Si rien n'est arrivé, votre serveur peut lire lui-même
GET /v1/payment-sessions/{sessionId}.
Le sondage par le serveur reste un repli, pas la source de vérité. Après une indisponibilité, réconciliez par GET /v1/transactions. Le récepteur lui-même — signature sur le corps brut, fenêtre anti-rejeu, dédoublonnage sur Chari-Event-Id — est détaillé dans réussir son intégration des webhooks.
Côté expérience, prévoyez le cas où l'utilisateur quitte l'application avant la confirmation : à sa réouverture, l'application redemande l'état de la commande au serveur, qui a pu recevoir le webhook entre-temps.
Et si l'application affichait son propre formulaire de carte ?
C'est possible, avec le checkout direct. Le checkout direct vous laisse construire votre propre formulaire de carte : vérification de la session, envoi des données de carte, retour 3-D Secure, état du paiement. Ces appels ne sont pas authentifiés par votre clé d'API mais par la session et sa clé de vérification à usage unique (vk) — n'y envoyez jamais votre clé d'API — et ils vous placent dans le périmètre de conformité des données de carte.
Dans une application, ce n'est pas l'application qui exécute ces appels. En checkout direct, c'est votre serveur qui exécute les appels /checkout/*, de serveur à serveur ; sur le checkout hébergé, c'est la page de paiement de ChariPay qui les exécute. Dans les deux cas, ils sont authentifiés par la session et sa vk, jamais par votre clé d'API. Le formulaire de votre application transmet donc la carte à votre serveur, qui soumet le paiement : les données de carte passent par votre application et par votre serveur.
Comparez avec la page hébergée : votre client saisit sa carte sur le checkout hébergé, dans un périmètre certifié PCI DSS niveau 1 : vous ne voyez jamais de numéro de carte, et votre questionnaire d'auto-évaluation s'en trouve réduit — à confirmer avec votre conseil ou votre acquéreur.
Dans une application, le checkout direct n'a de sens que si vous avez une raison précise de ne pas utiliser la page hébergée. Les quatre appels, l'authentification par vk et la grille de décision sont dans l'article sur le checkout direct.
Tester le parcours mobile en mode test
Avant de soumettre une version, déroulez cette liste sur un vrai téléphone :
- Clé :
chari_sk_test_…sur le serveur seulement ; cherchez-la dans le binaire et dans le dépôt de l'application, elle ne doit y être nulle part. - URL de retour :
acceptetdeclineenhttps://publiques, qui ramènent bien l'utilisateur sur vos appareils cibles. - Paiement réussi : carte de test 4918 9141 0719 5005, CVV 123, n'importe quelle date d'expiration future, code 3-D Secure 555 ; seule cette carte est acceptée, et aucun argent réel ne circule.
- Confirmation : l'application n'affiche « payé » qu'après le webhook reçu par votre serveur ; en sandbox, les webhooks sont envoyés normalement.
- Abandon : fermez la page au milieu du paiement et vérifiez que la commande reste « en attente ».
- Double tap : un utilisateur qui tape deux fois sur « Payer » ne doit pas créer deux sessions ; envoyez un
Idempotency-Keystable par commande, ou unexternalIdunique : une création avec unexternalIdexistant renvoie la session existante en200 OKau lieu de201 Created.
Questions fréquentes
Faut-il un SDK pour accepter le paiement dans une application mobile ?
Non. L'intégration documentée ne demande aucun SDK mobile : elle passe par votre serveur, qui appelle l'API REST, et par le checkout hébergé, que l'application ouvre à l'adresse reçue. Rien n'est embarqué dans le binaire, et le même serveur sert vos applications iOS et Android comme votre site. La documentation de l'API propose aussi une collection Postman pour vos essais.
Peut-on appeler l'API de paiement directement depuis l'application ?
Non. Les appels de l'API sont authentifiés par votre clé, et une clé est un secret : jamais dans le navigateur, une application mobile, un dépôt git ou une capture d'écran. Une application publiée peut être décompilée. L'application parle à votre serveur ; seul votre serveur parle à l'API ChariPay, et il ne renvoie à l'application que le checkoutUrl de la session.
Faut-il un serveur pour encaisser dans une application mobile ?
Pour une intégration par l'API, oui : c'est lui qui garde la clé, crée les sessions et reçoit les webhooks signés. Sans serveur, il n'y a pas d'intégration : vous pouvez envoyer à un client un lien de paiement créé dans le portail, à usage unique, un lien par vente, et suivre le paiement dans le portail. C'est un dépannage pour démarrer, pas une intégration rattachée à vos commandes.
Comment l'application sait-elle que le paiement a réussi ?
Elle le demande à votre serveur. Celui-ci le sait par le webhook signé payment.succeeded, qui fait foi ; la redirection vers l'URL accept dit seulement que l'utilisateur est revenu. En repli, votre serveur peut lire la session par GET /v1/payment-sessions/{sessionId}. L'application affiche « en cours de vérification » tant que le serveur n'a pas confirmé.
Où ranger la clé de test pendant le développement ?
Sur votre serveur de développement, dans une variable d'environnement ou un coffre à secrets, jamais dans le code de l'application ni dans son dépôt. La clé de test chari_sk_test_… ne déplace aucun argent réel, mais elle donne accès à votre sandbox : traitez-la comme un secret, et gardez deux jeux de variables, sandbox et production, sur le serveur.
L'application peut-elle afficher son propre formulaire de carte ?
Oui, avec le checkout direct : le formulaire de l'application envoie la carte à votre serveur, et votre serveur exécute les appels /checkout/*, de serveur à serveur, authentifiés par la session et sa vk, jamais par votre clé d'API. Il vous place dans le périmètre de conformité des données de carte. Il n'a de sens que si vous avez une raison précise de ne pas utiliser la page hébergée, et l'article sur le checkout direct en détaille les compromis.
Le numéro de carte passe-t-il par mon application ?
Avec la page hébergée, aucun formulaire de votre application ne recueille la carte : votre client la saisit sur le checkout hébergé, dans un périmètre certifié PCI DSS niveau 1, et vous ne voyez jamais de numéro de carte, ni dans l'application ni sur votre serveur. Avec le checkout direct, en revanche, les données de carte passent par votre application et par votre serveur, avec les obligations de conformité qui vont avec.
Prochaine étape
Montez votre serveur de test, déclarez votre endpoint de webhooks, puis déroulez le parcours complet depuis l'application avec la carte de test : Commencer en mode test. Au passage en production, côté code, vous ne changez que la clé et le secret de webhook : les URL, les payloads et les codes d'erreur sont identiques. Pour aller plus loin, lisez expiration et retour d'une session de paiement et protéger ses clés d'API.
Écrit par Équipe ChariPay.