Documentation API Intégration Mobile

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

  1. L'utilisateur tape "Payer" dans l'app

    L'app envoie une requête à VOTRE backend (pas à GerminoPay).

  2. Votre backend signe et appelle GerminoPay

    Le backend crée le paiement et reçoit une payment_url.

  3. Le backend renvoie l'URL à l'app

    L'app l'ouvre dans une WebView ou un navigateur externe.

  4. 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'),
        ),
      ),
    );
  }
}