Intégration Mobile
Integrate GerminoPay into your mobile app with a secure server-side architecture.
Principle
NEVER put your secret key in the mobile app
Mobile apps can be decompiled. Always do the signed requests from YOUR backend. The app only talks to your backend, never to GerminoPay directly with the secret.
Recommended architecture
-
User taps "Pay" in the app
The app sends a request to YOUR backend (not GerminoPay).
-
Your backend signs and calls GerminoPay
Backend creates the payment and receives a payment_url.
-
Backend returns the URL to the app
The app opens it in a WebView or external browser.
-
Webhook notifies your backend
Your backend updates the order and can push a notification to the 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'),
),
),
);
}
}