Documentation API Intégration Mobile

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

  1. User taps "Pay" in the app

    The app sends a request to YOUR backend (not GerminoPay).

  2. Your backend signs and calls GerminoPay

    Backend creates the payment and receives a payment_url.

  3. Backend returns the URL to the app

    The app opens it in a WebView or external browser.

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