Intégration Mobile
Intégrez GerminoPay dans votre app mobile avec une architecture sécurisée côté serveur.
Principe
NE JAMAIS mettre la clé secrète dans l'app mobile
Les apps mobiles peuvent être décompilées. Faites toujours les requêtes signées depuis VOTRE backend. L'app ne parle qu'à votre backend, jamais à GerminoPay directement avec le secret.
Architecture recommandée
-
L'utilisateur tape "Payer" dans l'app
L'app envoie une requête à VOTRE backend (pas à GerminoPay).
-
Votre backend signe et appelle GerminoPay
Le backend crée le paiement et reçoit une payment_url.
-
Le backend renvoie l'URL à l'app
L'app l'ouvre dans une WebView ou un navigateur externe.
-
Le webhook notifie votre backend
Votre backend met à jour la commande et peut notifier l'app.
Android (Kotlin)
CheckoutActivity.kt
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*
import org.json.JSONObject
import java.net.HttpURLConnection
import java.net.URL
class CheckoutActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Appel à VOTRE backend (jamais à GerminoPay directement)
CoroutineScope(Dispatchers.IO).launch {
try {
val url = URL("https://votre-backend.com/api/creer-paiement")
val conn = url.openConnection() as HttpURLConnection
conn.requestMethod = "POST"
conn.setRequestProperty("Content-Type", "application/json")
conn.doOutput = true
val payload = JSONObject().apply {
put("montant", 5000)
put("devise", "XOF")
put("reference", "CMD-${System.currentTimeMillis()}")
}
conn.outputStream.write(payload.toString().toByteArray())
val response = conn.inputStream.bufferedReader().readText()
val paymentUrl = JSONObject(response).getString("payment_url")
withContext(Dispatchers.Main) {
// Ouvrir le paiement dans le navigateur
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(paymentUrl)))
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}
iOS (Swift)
CheckoutView.swift
import UIKit
class CheckoutViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func payerTapped(_ sender: UIButton) {
// Appel à VOTRE backend, pas à GerminoPay
guard let url = URL(string: "https://votre-backend.com/api/creer-paiement") else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let payload: [String: Any] = [
"montant": 5000,
"devise": "XOF",
"reference": "CMD-\(Int(Date().timeIntervalSince1970))"
]
request.httpBody = try? JSONSerialization.data(withJSONObject: payload)
URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data,
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let paymentUrl = json["payment_url"] as? String,
let url = URL(string: paymentUrl) else { return }
DispatchQueue.main.async {
UIApplication.shared.open(url)
}
}.resume()
}
}
React Native
CheckoutScreen.js
import React from 'react';
import { View, Button, Alert, Linking } from 'react-native';
import { WebView } from 'react-native-webview';
export default function CheckoutScreen() {
const [paymentUrl, setPaymentUrl] = React.useState(null);
const payer = async () => {
try {
// Appel à VOTRE backend
const res = await fetch('https://votre-backend.com/api/creer-paiement', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
montant: 5000,
devise: 'XOF',
reference: `CMD-${Date.now()}`,
}),
});
const data = await res.json();
setPaymentUrl(data.payment_url);
} catch (err) {
Alert.alert('Erreur', err.message);
}
};
if (paymentUrl) {
return (
<WebView
source={{ uri: paymentUrl }}
onNavigationStateChange={({ url }) => {
if (url.includes('/succes')) {
Alert.alert('Paiement réussi !');
setPaymentUrl(null);
}
}}
/>
);
}
return (
<View style={{ padding: 20 }}>
<Button title="Payer 5 000 F CFA" onPress={payer} />
</View>
);
}
Flutter
checkout_screen.dart
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:webview_flutter/webview_flutter.dart';
class CheckoutScreen extends StatefulWidget {
@override
_CheckoutScreenState createState() => _CheckoutScreenState();
}
class _CheckoutScreenState extends State<CheckoutScreen> {
String? paymentUrl;
Future<void> payer() async {
try {
// Appel à VOTRE backend
final res = await http.post(
Uri.parse('https://votre-backend.com/api/creer-paiement'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'montant': 5000,
'devise': 'XOF',
'reference': 'CMD-${DateTime.now().millisecondsSinceEpoch}',
}),
);
final data = jsonDecode(res.body);
setState(() {
paymentUrl = data['payment_url'];
});
} catch (e) {
print(e);
}
}
@override
Widget build(BuildContext context) {
if (paymentUrl != null) {
return Scaffold(
body: WebViewWidget(
controller: WebViewController()
..loadRequest(Uri.parse(paymentUrl!)),
),
);
}
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: payer,
child: Text('Payer 5 000 F CFA'),
),
),
);
}
}