Paiements

PaymentController v2 — Architecture des passerelles de paiement

Guide technique pour intégrer une passerelle de paiement utilisant l'interface PaymentController v2 dans Dokos.

PaymentController v2 — Architecture des passerelles de paiement

Cette page s'adresse aux développeurs qui souhaitent créer ou intégrer une passerelle de paiement compatible avec l'interface PaymentController (version 2) introduite dans frappe/payments.


Contexte

Jusqu'à présent, les passerelles de paiement dans Dokos suivaient une interface dite v1 : chaque intégration implémentait sa propre logique d'initialisation de session et de retour de paiement, directement dans le code de la Demande de paiement.

L'interface v2 (PaymentController) centralise cette logique dans l'application payments et expose un contrat clair via PaymentController.initiate(). La Demande de paiement délègue entièrement à cette classe dès qu'elle détecte une passerelle v2.


Détection automatique de la version

Lors de la validation d'une Demande de paiement, Dokos appelle _is_v2_gateway(payment_gateway) pour déterminer quelle branche exécuter :

  • v2 détecté_process_v2_gateway() est appelé
  • v1 ou fallback → comportement legacy inchangé

La détection repose sur payments.utils.is_v2_gateway(). Si l'application payments ne dispose pas de cette fonction (version ancienne), le système bascule silencieusement en mode v1.


Implémenter une passerelle v2

Pour qu'une passerelle soit reconnue comme v2, elle doit :

  1. Être déclarée v2 dans payments.utils.is_v2_gateway() — cette logique est gérée côté application payments.
  2. Hériter de PaymentController (depuis payments.controllers.payment_controller) et implémenter la méthode initiate(tx_data).

Méthode initiate(tx_data)

Cette méthode reçoit un dictionnaire TxData et doit :

  • Créer un Payment Session Log dans Frappe
  • Retourner un objet contenant au minimum payment_url (l'URL vers laquelle le client sera redirigé)

Structure de TxData

Lors d'une demande de paiement v2, Dokos construit un objet TxData via get_tx_data(). Voici les champs transmis :

ChampValeurDescription
reference_doctypePayment RequestType du document de référence
reference_docnameNom de la Demande de paiementIdentifiant unique de la demande
payer_nameNom du tiers (client/fournisseur)Nom affiché au payeur
payer_emailEmail du contactEmail du payeur
amountRésultat de get_request_amount()Montant partiel ou total
currencyDevise de la demandeISO 4217
contactChamps contact filtrésVoir ci-dessous
addressChamps adresse filtrésVoir ci-dessous
Montant partiel : get_request_amount() est utilisé (et non grand_total) afin de prendre en charge les scénarios de paiement partiel. Une facture peut faire l'objet de plusieurs demandes de paiement successives.
Référence : reference_doctype pointe vers Payment Request (et non la facture ou la commande sous-jacente) car c'est la Demande de paiement qui gère les callbacks et la réconciliation comptable.

Champs contact transmis

Seuls les champs pertinents pour le paiement sont transmis (pas le document Contact complet) :

  • email_id
  • mobile_no
  • phone

Champs adresse transmis

  • address_line1
  • address_line2
  • city
  • state
  • pincode
  • country

Exemple d'implémentation

Voici la structure minimale d'une passerelle v2 :

from payments.controllers.payment_controller import PaymentController

class MaPasserelleController(PaymentController):

    def initiate(self, tx_data: dict) -> dict:
        """
        Initialise une session de paiement.

        Args:
            tx_data: Dictionnaire TxData fourni par Dokos.

        Returns:
            dict avec au minimum 'payment_url'.
        """
        # 1. Créer la session côté passerelle externe
        session = self._create_remote_session(
            amount=tx_data["amount"],
            currency=tx_data["currency"],
            customer_email=tx_data["contact"].get("email_id"),
        )

        # 2. Créer le Payment Session Log dans Frappe
        self.create_payment_session_log(
            reference_doctype=tx_data["reference_doctype"],
            reference_docname=tx_data["reference_docname"],
            session_id=session.id,
        )

        # 3. Retourner l'URL de paiement
        return {"payment_url": session.checkout_url}

Compatibilité ascendante

Les passerelles v1 existantes ne sont pas affectées. Le code de la Demande de paiement vérifie d'abord si la passerelle est v2 avant toute modification de comportement. En l'absence de l'application payments dans la version requise, le système bascule automatiquement sur le flux v1.


Dépendances

Cette fonctionnalité nécessite :

  • frappe/payments avec le support PaymentController (PR #192)
  • Dokos ≥ version incluant cette MR

Voir aussi