FlaskPython 3.9+ · moncashconnect · Flask 3+

Intégrer MonCash avec Flask

Une application Flask complète : créer un paiement, vérifier le statut sur la page de retour et recevoir les confirmations par webhook — le tout sans dépendances tierces pour la partie MonCash.

Sandbox

Commencez en Sandbox

Avant d'implémenter le code ci-dessous avec votre clé live (sk_proj_…), nous recommandons de tester d'abord en mode Sandbox avec sk_test_proj_…. Le code est identique — vous ne changez que la valeur de la variable d'environnement. Lisez le Guide Sandbox avant de continuer.

Prérequis

  • Python 3.9+
  • pip ou uv
  • Un projet MonCashConnect avec clé secrète — créer un projet

Installation

pip install flask moncashconnect python-dotenv

Ou avec uv : uv add flask moncashconnect python-dotenv

Variables d'environnement

Créez un fichier .env à la racine :

MCC_SECRET_KEY=sk_proj_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
MCC_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
APP_URL=https://votresite.com
FLASK_ENV=development

Application complète (app.py)

Application Flask fonctionnelle couvrant les trois routes essentielles. La clé est request.get_data() dans la route webhook — elle retourne les octets bruts sans consommer le stream.

from flask import Flask, request, redirect, jsonify, abort
from moncashconnect import MonCashClient, construct_event, MonCashError
from dotenv import load_dotenv
import os

load_dotenv()

app    = Flask(__name__)
client = MonCashClient(os.environ["MCC_SECRET_KEY"])


# ── POST /pay ────────────────────────────────────────────────────────
# Corps attendu : {"orderId": "...", "amount": 500}
@app.post("/pay")
def initiate_payment():
    data     = request.get_json(force=True)
    order_id = data.get("orderId")
    amount   = data.get("amount")

    if not order_id or not amount:
        abort(400, description="orderId et amount sont requis.")

    try:
        payment = client.create_payment(
            amount=int(amount),
            reference_id=str(order_id),
            return_url=f"{os.environ['APP_URL']}/pay/return",
        )
    except MonCashError as exc:
        return jsonify({"error": str(exc)}), exc.status_code

    return redirect(payment["paymentUrl"])


# ── GET /pay/return ──────────────────────────────────────────────────
# Page de retour — vérifier le statut réel via l'API
@app.get("/pay/return")
def payment_return():
    ref = request.args.get("ref")
    if not ref:
        abort(400, description="Référence manquante.")

    try:
        tx = client.get_payment_status(ref)
    except MonCashError as exc:
        return jsonify({"error": str(exc)}), exc.status_code

    # Remplacez par render_template("return.html", tx=tx) si vous utilisez Jinja2
    return jsonify(tx)


# ── POST /webhooks/moncash ────────────────────────────────────────────
# Pas de @csrf — Flask n'a pas de protection CSRF native sur les routes POST
# La sécurité est assurée par la vérification HMAC-SHA256
@app.post("/webhooks/moncash")
def moncash_webhook():
    raw_body  = request.get_data()              # bytes — AVANT tout parsing
    signature = request.headers.get("X-MCC-Signature", "")
    timestamp = request.headers.get("X-MCC-Timestamp", "")

    try:
        event = construct_event(
            raw_body,
            signature,
            timestamp,
            os.environ["MCC_WEBHOOK_SECRET"],
        )
    except MonCashError as exc:
        return str(exc), exc.status_code

    if event["event"] == "payment.completed":
        # Mettez à jour votre BDD ici
        print(f"✅ Paiement confirmé : {event['reference']} — {event['amount']} HTG")

    elif event["event"] == "payment.failed":
        print(f"❌ Paiement échoué : {event['reference']}")

    return "OK", 200


if __name__ == "__main__":
    app.run(debug=os.environ.get("FLASK_ENV") == "development")
Flask n'a pas de protection CSRF native sur les routes POST (contrairement à Django). La vérification HMAC-SHA256 assure la sécurité du webhook — pas besoin de décorateur supplémentaire.

Structure avec Blueprints (application plus grande)

Pour une application Flask de taille réelle, organisez les routes MonCash dans un Blueprint :

# payments/blueprint.py
from flask import Blueprint, request, redirect, jsonify
from moncashconnect import MonCashClient, construct_event, MonCashError
import os

payments_bp = Blueprint("payments", __name__, url_prefix="/payments")
client      = MonCashClient(os.environ["MCC_SECRET_KEY"])


@payments_bp.post("/pay")
def pay(): ...

@payments_bp.get("/return")
def return_page(): ...

@payments_bp.post("/webhook")
def webhook(): ...


# app.py — enregistrement du blueprint
from flask import Flask
from payments.blueprint import payments_bp

app = Flask(__name__)
app.register_blueprint(payments_bp)

Avec cette structure, l'URL webhook devient /payments/webhook. Pensez à mettre à jour la configuration dans le tableau de bord MonCashConnect.

Liste de contrôle avant mise en production

MCC_SECRET_KEY et MCC_WEBHOOK_SECRET sont dans les variables d'environnement du serveur (pas dans le code ou le dépôt Git)

request.get_data() est utilisé dans la route webhook — pas request.json ou request.data après get_json()

FLASK_ENV=production (désactive le mode debug)

Utilisez un serveur WSGI en production : gunicorn app:app (pas flask run)

La route webhook répond toujours 200 après traitement réussi

Votre handler webhook est idempotent — vérifiez si la commande est déjà traitée avant de la modifier

L'URL https://votresite.com/webhooks/moncash est configurée dans le tableau de bord MonCashConnect

Ne déployez jamais avec flask run en production — c'est le serveur de développement. Utilisez gunicorn app:app ou uvicorn (avec ASGI via Quart si vous passez à async).