Article technique · Tontini

Tontini : les places, le calendrier et le journal d’une tontine

Publié le , mis à jour le

Tontini est une application de tenue de tontine, dans laquelle un organisateur constitue son cercle, fixe la cotisation et l’ordre des tours, puis suit le calendrier des versements et l’historique des opérations.

Rôle
Auteur unique de l’API et de l’application
Période
De juin 2024 à décembre 2025, pour l’essentiel en juillet 2025
État
Prototype. Prototype non publié, sans paiement intégré
Technologies
Django 4.2, Django REST Framework, PostgreSQL 15, Flutter, flutter_bloc
Dépôts
Deux
Architecture de TontiniUne application Flutter appelle une API Django servie par nginx et Daphne, qui enregistre ses données dans PostgreSQL.ApplicationFlutter, flutter_blocpaquets partagésAPIDjango 4.2, REST Frameworknginx, DaphnePostgreSQL 15groupes, placesversements, journalHTTPS, jetonORM
Figure 1. Architecture de Tontini. Aucun prestataire de paiement n’y figure : l’application tient les comptes, elle ne déplace pas d’argent.

Contexte

Une tontine est un cercle d’épargne tournant : chaque membre verse la même cotisation à échéance fixe, et la somme réunie revient à l’un d’eux, à tour de rôle. Tontini en assure la tenue, avec le montant de la cotisation, sa périodicité, l’ordre des bénéficiaires et l’historique de ce qui a été versé.

Une première version, écrite en juin 2024, n’était qu’une maquette alimentée par des données simulées. L’API a été développée en juillet 2025, en quatre journées de travail, et l’application lui a été raccordée dans le même temps.

Problème

L’organisateur connaît les membres de son cercle avant qu’aucun d’eux n’ait installé l’application, et il doit pouvoir préparer la tontine entière sans les attendre. L’ordre des tours change par ailleurs souvent avant le premier versement, lorsqu’un membre s’ajoute ou demande à passer plus tôt, et le calendrier doit suivre sans que personne ait à le corriger date par date.

Réponse apportée

Une place plutôt qu’un compte

Un membre est d’abord une place dans le cercle, qui porte un nom, une position et un code d’invitation, et qui n’est rattachée à un compte que plus tard. L’organisateur crée ainsi toutes les places d’emblée, puis transmet à chacun son code, sous la forme d’un texte, d’un code QR ou d’un lien. L’adhésion se fait en deux appels : le premier montre le groupe correspondant au code, le second rattache le compte à la place.

Un calendrier reconstruit à chaque changement

Le calendrier des versements est conservé en base, à raison d’une ligne par bénéficiaire, et il est supprimé puis recréé après chaque modification du groupe ou de ses membres. La date de chaque versement se déduit de la position du membre et de la périodicité, et son montant du produit de la cotisation par le nombre de membres. Dès qu’un premier versement est confirmé, le calendrier n’est plus régénéré.

members = Member.objects.filter(group=group).order_by('position')
amount = group.contribution * members.count()

try:
    paid_distribution = Distribution.objects.get(is_paid=True, round=current_round, group=group)
except Distribution.DoesNotExist:
    distributions = Distribution.objects.filter(round=current_round, group=group)
    distributions.delete()
    for member in members:
        distribution_date = current_round.start_date + get_interval_duration(group.interval, member.position)
        Distribution.objects.create(
            amount=amount,
            date=distribution_date,
            currency_code=group.currency_code,
            is_paid=False,
            position=member.position,
            round=current_round,
            group=group,
            member=member
        )
Extrait 1. Reconstruction du calendrier d’un tour à partir de l’ordre des membres.

Un journal écrit à la confirmation

Lorsque l’organisateur confirme un versement, le serveur écrit le journal d’un seul geste : une opération de distribution pour le bénéficiaire et une opération de cotisation pour chaque membre. L’application ne suit donc pas les cotisations une à une.

Tous les articles