Files
wortis_tpe/lib/tpe.py

2651 lines
100 KiB
Python

import random
import uuid
import logging
from flask import Flask, request, jsonify, Blueprint
from werkzeug.security import generate_password_hash, check_password_hash
from datetime import datetime, timezone
import requests
from flask_cors import CORS
import os
from bson import ObjectId
from pymongo import MongoClient, DESCENDING
import json
from datetime import timedelta
import string
# Configuration Blueprint
tpe_bp = Blueprint('tpe', __name__, url_prefix='/tpe')
@tpe_bp.route('/')
def hello():
return jsonify({"message": "Hello from TPE"})
# Configuration MongoDB
client = MongoClient("mongodb://dipadmin:Cgt*2020#@31.207.36.187,62.210.100.14,62.210.101.31/admin?replicaSet=rs0&readPreference=secondaryPreferred&authSource=admin&connectTimeoutMS=30000", connect=False)
mongo = client
BD_e2c = MongoClient("mongodb://e2c:6GpxLTPhe8JkF83J@mongodb.wortis.cg/e2c")
clientpaiements = BD_e2c.e2c.clientpaiements
# Collections principales
users_collection = mongo.TPE.users
transactions_collection = mongo.TPE.transactions
services_collection = mongo.TPE.services
recharges_collection = mongo.TPE.recharges
activities_collection = mongo.TPE.activities
# Collections entreprises
enterprises_collection = mongo.TPE.enterprises
enterprise_members_collection = mongo.TPE.enterprise_members
commission_withdrawals_collection = mongo.TPE.commission_withdrawals
# Configuration logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# ===== CLASSES UTILITAIRES =====
class JSONEncoder(json.JSONEncoder):
"""Encodeur JSON personnalisé pour gérer ObjectId"""
def default(self, obj):
if isinstance(obj, ObjectId):
return str(obj)
if isinstance(obj, datetime):
return obj.isoformat()
return super().default(obj)
class User:
def __init__(self, nom, agent_id, pin, role="agent", solde=0.0):
self.nom = nom
self.agent_id = agent_id
self.pin_hash = generate_password_hash(pin)
self.role = role
self.solde = solde
self.date_creation = datetime.utcnow()
def to_dict(self):
return {
"nom": self.nom,
"agent_id": self.agent_id,
"pin_hash": self.pin_hash,
"role": self.role,
"solde": self.solde,
"date_creation": self.date_creation
}
@staticmethod
def verify_pin(pin_hash, pin):
return check_password_hash(pin_hash, pin)
class Enterprise:
"""Modèle pour les comptes entreprise"""
def __init__(self, nom_entreprise, email_entreprise, telephone_entreprise,
adresse=None, secteur_activite=None):
self.enterprise_id = self.generate_enterprise_id()
self.nom_entreprise = nom_entreprise
self.email_entreprise = email_entreprise
self.telephone_entreprise = telephone_entreprise
self.adresse = adresse
self.secteur_activite = secteur_activite
self.solde_entreprise = 0.0
self.commission_entreprise = 0.0
self.type = 'enterprise'
self.status = 'active'
self.created_at = datetime.now(timezone.utc)
self.updated_at = datetime.now(timezone.utc)
@staticmethod
def generate_enterprise_id():
"""Génère un ID entreprise unique au format ENT + 4 chiffres"""
while True:
random_number = random.randint(1000, 9999)
enterprise_id = f"ENT{random_number}"
existing = enterprises_collection.find_one({'enterprise_id': enterprise_id})
if not existing:
return enterprise_id
def to_dict(self):
return {
'enterprise_id': self.enterprise_id,
'nom_entreprise': self.nom_entreprise,
'email_entreprise': self.email_entreprise,
'telephone_entreprise': self.telephone_entreprise,
'adresse': self.adresse,
'secteur_activite': self.secteur_activite,
'solde_entreprise': self.solde_entreprise,
'commission_entreprise': self.commission_entreprise,
'type': self.type,
'status': self.status,
'created_at': self.created_at,
'updated_at': self.updated_at,
'total_members': 0,
'total_transactions': 0,
'total_volume': 0.0
}
# ===== FONCTIONS UTILITAIRES =====
def serialize_objectid(obj):
"""Convertit les ObjectId MongoDB en strings pour JSON"""
if isinstance(obj, ObjectId):
return str(obj)
elif isinstance(obj, dict):
return {key: serialize_objectid(value) for key, value in obj.items()}
elif isinstance(obj, list):
return [serialize_objectid(item) for item in obj]
return obj
def generate_agent_id():
"""Génère un ID agent unique au format WRT + 4 chiffres"""
while True:
random_number = random.randint(1000, 9999)
agent_id = f"WRT{random_number}"
existing_user = users_collection.find_one({'agent_id': agent_id})
if not existing_user:
return agent_id
def log_activity(activity_type, title, description, user_id=None, metadata=None):
"""Enregistre une activité dans les logs"""
try:
activity = {
'type': activity_type,
'title': title,
'description': description,
'user_id': user_id,
'metadata': metadata or {},
'created_at': datetime.now(),
'icon': get_activity_icon(activity_type),
'color': get_activity_color(activity_type)
}
activities_collection.insert_one(activity)
except Exception as e:
logger.error(f"❌ Erreur log activité: {e}")
def get_effective_balance(agent_id):
"""
Retourne le solde effectif selon le type de compte (agent ou membre entreprise)
"""
user = users_collection.find_one({'agent_id': agent_id})
if not user:
raise ValueError("AGENT_NOT_FOUND")
account_type = user.get('type', 'agent')
if account_type == 'enterprise_member':
enterprise_id = user.get('enterprise_id')
if not enterprise_id:
raise ValueError("ENTERPRISE_ID_MISSING")
enterprise = enterprises_collection.find_one({'enterprise_id': enterprise_id})
if not enterprise:
raise ValueError("ENTERPRISE_NOT_FOUND")
return {
'source': 'enterprise',
'balance': float(enterprise.get('solde_entreprise', 0)),
'enterprise_id': enterprise_id,
'nom_entreprise': enterprise.get('nom_entreprise', '')
}
return {
'source': 'personal',
'balance': float(user.get('solde', 0)),
'agent_id': agent_id,
'nom_agent': user.get('nom', '')
}
def get_activity_icon(activity_type):
"""Retourne l'icône selon le type d'activité"""
icons = {
'agent_created': 'person_add',
'agent_updated': 'person',
'agent_status_changed': 'toggle_on',
'recharge_approved': 'check_circle',
'recharge_rejected': 'cancel',
'export_generated': 'download',
'login': 'login',
'logout': 'logout',
'system_error': 'error',
'enterprise_created': 'business',
'enterprise_member_added': 'group_add',
}
return icons.get(activity_type, 'info')
def get_activity_color(activity_type):
"""Retourne la couleur selon le type d'activité"""
colors = {
'agent_created': 'green',
'agent_updated': 'blue',
'agent_status_changed': 'orange',
'recharge_approved': 'green',
'recharge_rejected': 'red',
'export_generated': 'blue',
'login': 'green',
'logout': 'grey',
'system_error': 'red',
'enterprise_created': 'blue',
'enterprise_member_added': 'green',
}
return colors.get(activity_type, 'grey')
def calculate_agent_commission(agent_id):
"""Calculer la commission disponible d'un agent"""
try:
commission_pipeline = [
{'$match': {
'agent_id': agent_id,
'status': 'completed'
}},
{'$group': {
'_id': None,
'total_commission': {'$sum': {'$toDouble': '$commission_agent'}}
}}
]
commission_result = list(transactions_collection.aggregate(commission_pipeline))
total_commission_earned = commission_result[0]['total_commission'] if commission_result else 0.0
withdrawal_pipeline = [
{'$match': {
'agent_id': agent_id,
'status': 'completed'
}},
{'$group': {
'_id': None,
'total_withdrawn': {'$sum': {'$toDouble': '$montant'}}
}}
]
withdrawal_result = list(commission_withdrawals_collection.aggregate(withdrawal_pipeline))
total_commission_withdrawn = withdrawal_result[0]['total_withdrawn'] if withdrawal_result else 0.0
available_commission = total_commission_earned - total_commission_withdrawn
return max(0.0, available_commission)
except Exception as e:
logger.error(f"❌ Erreur calcul commission: {e}")
return 0.0
# ===== FONCTION PRINCIPALE DE PAIEMENT =====
def process_agent_payment(agent_id, service_name, montant_total, montant_reel, client_reference, additional_data=None):
"""
Fonction réutilisable pour débiter un agent (individuel ou membre entreprise)
Gère automatiquement le type de compte
"""
try:
# 1. VÉRIFIER LE COMPTE UTILISATEUR
user = users_collection.find_one({
"agent_id": agent_id,
"status": "active"
})
if not user:
raise ValueError("AGENT_NOT_FOUND")
# 2. DÉTERMINER LE TYPE DE COMPTE ET LE SOLDE
account_type = user.get('type', 'agent')
if account_type == 'enterprise_member':
# Membre d'entreprise - utiliser le solde de l'entreprise
enterprise_id = user.get('enterprise_id')
if not enterprise_id:
raise ValueError("ENTERPRISE_ID_MISSING")
enterprise = enterprises_collection.find_one({'enterprise_id': enterprise_id})
if not enterprise:
raise ValueError("ENTERPRISE_NOT_FOUND")
if enterprise.get('status') != 'active':
raise ValueError("ENTERPRISE_INACTIVE")
current_balance = float(enterprise.get('solde_entreprise', 0))
balance_source = 'enterprise'
balance_id = enterprise_id
else:
# Agent individuel - utiliser son propre solde
current_balance = float(user.get("solde", 0))
balance_source = 'personal'
balance_id = agent_id
# 3. VÉRIFIER LE SOLDE SUFFISANT
if current_balance < montant_total:
deficit = montant_total - current_balance
raise ValueError(f"INSUFFICIENT_BALANCE|{deficit}")
# 4. CALCULER LA COMMISSION ET LES FRAIS
frais_totaux_rate = 0.02
commission_agent_rate = 0.01
frais_plateforme_rate = 0.01
frais_totaux = montant_reel * frais_totaux_rate
commission_agent = montant_reel * commission_agent_rate
frais_plateforme = montant_reel * frais_plateforme_rate
montant_total_calcule = montant_reel + frais_totaux
if abs(montant_total - montant_total_calcule) > 0.01:
logger.warning(f"⚠️ Montant total ajusté: {montant_total}{montant_total_calcule}")
montant_total = montant_total_calcule
# 5. CRÉER LA TRANSACTION
now = datetime.now(timezone.utc)
trans_id = f"WTPE{now.strftime('%Y%m%d%H%M%S')}"
transaction_data = {
"_id": trans_id,
"transID": trans_id,
"user_id": str(user["_id"]),
"agent_id": agent_id,
"agent_name": user["nom"],
"phone_agent": user.get("telephone", ""),
"service_name": service_name,
"client_reference": str(client_reference),
# Informations entreprise (si applicable)
"account_type": account_type,
"enterprise_id": user.get('enterprise_id') if account_type == 'enterprise_member' else None,
"balance_source": balance_source,
# Montants
"montant_reel": float(montant_reel),
"frais_totaux": round(float(frais_totaux), 2),
"frais_plateforme": round(float(frais_plateforme), 2),
"commission_agent": round(float(commission_agent), 2),
"montant_total_debite": round(float(montant_total), 2),
"solde_avant": float(current_balance),
"solde_apres": round(float(current_balance - montant_total), 2),
"status": "pending",
"created_at": now,
"updated_at": now,
"additional_data": additional_data or {}
}
# 6. INSÉRER LA TRANSACTION
transactions_collection.insert_one(transaction_data)
# 7. DÉBITER LE COMPTE APPROPRIÉ
if balance_source == 'enterprise':
# Débiter l'entreprise
debit_result = enterprises_collection.update_one(
{
"enterprise_id": enterprise_id,
"status": "active",
"solde_entreprise": {"$gte": montant_total}
},
{
"$inc": {
"solde_entreprise": -montant_total,
"total_transactions": 1,
"commission_entreprise": commission_agent,
"total_volume": montant_total
},
"$set": {
"updated_at": now
}
}
)
else:
# Débiter l'agent individuel
debit_result = users_collection.update_one(
{
"agent_id": agent_id,
"status": "active",
"solde": {"$gte": montant_total}
},
{
"$inc": {
"solde": -montant_total,
"total_transactions": 1,
"total_commission_earned": commission_agent,
"total_volume": montant_total
},
"$set": {
"last_transaction_date": now,
"updated_at": now
}
}
)
# 8. VÉRIFIER QUE LE DÉBIT A RÉUSSI
if debit_result.modified_count == 0:
transactions_collection.update_one(
{"_id": trans_id},
{
"$set": {
"status": "failed",
"failure_reason": "debit_failed",
"updated_at": datetime.now(timezone.utc)
}
}
)
raise ValueError("DEBIT_FAILED")
# 9. MARQUER LA TRANSACTION COMME RÉUSSIE
transactions_collection.update_one(
{"_id": trans_id},
{
"$set": {
"status": "completed",
"debited_at": datetime.now(timezone.utc),
"updated_at": datetime.now(timezone.utc)
}
}
)
logger.info(f"✅ Paiement traité - Transaction: {trans_id} - Type: {account_type}")
return {
"success": True,
"transaction_id": trans_id,
"agent_id": agent_id,
"agent_name": user["nom"],
"account_type": account_type,
"enterprise_id": user.get('enterprise_id') if account_type == 'enterprise_member' else None,
"montant_debite": round(float(montant_total), 2),
"commission_gagnee": round(float(commission_agent), 2),
"nouveau_solde": round(float(current_balance - montant_total), 2),
"balance_source": balance_source,
"details": transaction_data
}
except ValueError as e:
logger.error(f"❌ Erreur de validation: {e}")
raise
except Exception as e:
logger.error(f"❌ Erreur inattendue: {e}")
if 'trans_id' in locals():
transactions_collection.update_one(
{"_id": trans_id},
{
"$set": {
"status": "error",
"error_message": str(e),
"updated_at": datetime.now(timezone.utc)
}
}
)
raise
def rollback_agent_payment(trans_id):
"""Fonction pour annuler une transaction et rembourser l'agent"""
try:
transaction = transactions_collection.find_one({"_id": trans_id})
if not transaction:
raise ValueError("TRANSACTION_NOT_FOUND")
# Déterminer le type de compte
balance_source = transaction.get('balance_source', 'personal')
if balance_source == 'enterprise':
# Rembourser l'entreprise
enterprises_collection.update_one(
{"enterprise_id": transaction.get("enterprise_id")},
{
"$inc": {
"solde_entreprise": transaction["montant_total_debite"],
"total_transactions": -1,
"commission_entreprise": -transaction["commission_agent"],
"total_volume": -transaction["montant_total_debite"]
},
"$set": {"updated_at": datetime.now(timezone.utc)}
}
)
else:
# Rembourser l'agent
users_collection.update_one(
{"agent_id": transaction["agent_id"]},
{
"$inc": {
"solde": transaction["montant_total_debite"],
"total_transactions": -1,
"total_commission_earned": -transaction["commission_agent"],
"total_volume": -transaction["montant_total_debite"]
},
"$set": {"updated_at": datetime.now(timezone.utc)}
}
)
# Marquer la transaction comme remboursée
transactions_collection.update_one(
{"_id": trans_id},
{
"$set": {
"status": "refunded",
"refunded_at": datetime.now(timezone.utc),
"updated_at": datetime.now(timezone.utc)
}
}
)
return {"success": True, "message": "Transaction remboursée"}
except Exception as e:
logger.error(f"❌ Erreur rollback: {e}")
raise
# ===== ROUTES AUTHENTIFICATION =====
@tpe_bp.route('/users/register', methods=['POST'])
def register_user():
"""Créer un nouveau compte utilisateur avec ID auto-généré"""
try:
data = request.get_json()
required_fields = ['nom', 'pin']
for field in required_fields:
if field not in data or not data[field]:
return jsonify({
'success': False,
'message': f'Le champ {field} est requis'
}), 400
if len(data['pin']) < 4:
return jsonify({
'success': False,
'message': 'Le PIN doit contenir au moins 4 caractères'
}), 400
agent_id = generate_agent_id()
user = User(
nom=data['nom'].strip(),
agent_id=agent_id,
pin=data['pin'],
role=data.get('role', 'agent'),
solde=0.0
)
result = users_collection.insert_one(user.to_dict())
created_user = users_collection.find_one({'_id': result.inserted_id})
del created_user['pin_hash']
return jsonify({
'success': True,
'message': 'Utilisateur créé avec succès',
'user': json.loads(json.dumps(created_user, cls=JSONEncoder)),
'generated_agent_id': agent_id
}), 201
except Exception as e:
return jsonify({
'success': False,
'message': f'Erreur lors de la création de l\'utilisateur: {str(e)}'
}), 500
@tpe_bp.route('/login', methods=['POST'])
def login():
"""Authentification utilisateur - Gère agents ET membres entreprise"""
try:
data = request.get_json()
agent_id = data.get('agent_id')
pin_input = data.get('pin') or data.get('token')
if not agent_id or not pin_input:
return jsonify({
'success': False,
'message': 'ID agent et PIN requis'
}), 400
user = users_collection.find_one({'agent_id': agent_id})
if not user:
logger.info(f"❌ Tentative de connexion - Agent introuvable: {agent_id}")
return jsonify({
'success': False,
'message': 'ID agent ou PIN incorrect'
}), 401
# Vérification du PIN
pin_valid = False
if user.get('token') and data.get('token'):
pin_valid = (user['token'] == pin_input)
elif user.get('pin_hash'):
pin_valid = User.verify_pin(user['pin_hash'], pin_input)
elif user.get('pin'):
pin_valid = (user['pin'] == pin_input)
else:
return jsonify({
'success': False,
'message': 'Configuration utilisateur invalide'
}), 500
if not pin_valid:
logger.info(f"❌ PIN incorrect pour {agent_id}")
return jsonify({
'success': False,
'message': 'ID agent ou PIN incorrect'
}), 401
# Vérifier le statut
if user.get('status') == 'blocked' or user.get('is_blocked'):
return jsonify({
'success': False,
'message': 'Compte bloqué. Contactez l\'administrateur.'
}), 403
if user.get('status') == 'inactive':
return jsonify({
'success': False,
'message': 'Compte inactif. Contactez l\'administrateur.'
}), 403
# Mettre à jour les informations de connexion
users_collection.update_one(
{'agent_id': agent_id},
{
'$set': {
'last_login': datetime.now(),
'login_attempts': 0
},
'$inc': {
'total_logins': 1
}
}
)
# Préparer les données utilisateur
user_data = {
'id': str(user['_id']),
'nom': user.get('nom', ''),
'agent_id': user['agent_id'],
'role': user.get('role', 'agent'),
'solde': user.get('solde', 0),
'date_creation': user.get('date_creation', user.get('created_at')),
'telephone': user.get('telephone', ''),
'email': user.get('email', ''),
'type': user.get('type', 'agent'),
'status': user.get('status', 'active')
}
# Ajouter le token si il existe
if user.get('token'):
user_data['token'] = user['token']
# Ajouter informations entreprise si membre
if user.get('type') == 'enterprise_member' and user.get('enterprise_id'):
enterprise = enterprises_collection.find_one({'enterprise_id': user.get('enterprise_id')})
if enterprise:
user_data['enterprise'] = {
'enterprise_id': enterprise.get('enterprise_id'),
'nom_entreprise': enterprise.get('nom_entreprise'),
'solde_entreprise': enterprise.get('solde_entreprise', 0),
'status': enterprise.get('status')
}
user_data['is_admin_entreprise'] = user.get('is_admin_entreprise', False)
logger.info(f"✅ Connexion réussie pour {agent_id} - {user.get('nom')}")
return jsonify({
'success': True,
'message': 'Connexion réussie',
'user': user_data
}), 200
except Exception as e:
logger.error(f"❌ Erreur lors de l'authentification: {e}")
return jsonify({
'success': False,
'message': f'Erreur lors de l\'authentification: {str(e)}'
}), 500
@tpe_bp.route('/agent/<agent_id>/effective_balance', methods=['GET'])
def get_effective_balance_route(agent_id):
try:
balance_info = get_effective_balance(agent_id)
return jsonify({
'success': True,
'balance_info': balance_info
}), 200
except Exception as e:
return jsonify({
'success': False,
'message': str(e)
}), 400
# ===== ROUTES UTILISATEURS =====
@tpe_bp.route('/users', methods=['GET'])
def get_users():
"""Récupérer la liste des utilisateurs"""
try:
users = list(users_collection.find({}, {'pin_hash': 0}))
return jsonify({
'success': True,
'users': serialize_objectid(users),
'count': len(users)
}), 200
except Exception as e:
return jsonify({
'success': False,
'message': f'Erreur lors de la récupération des utilisateurs: {str(e)}'
}), 500
@tpe_bp.route('/users/<agent_id>', methods=['GET'])
def get_user_by_agent_id(agent_id):
"""Récupérer un utilisateur par son ID agent"""
try:
user = users_collection.find_one({'agent_id': agent_id}, {'pin_hash': 0})
if not user:
return jsonify({
'success': False,
'message': 'Utilisateur non trouvé'
}), 404
return jsonify({
'success': True,
'user': serialize_objectid(user)
}), 200
except Exception as e:
return jsonify({
'success': False,
'message': f'Erreur lors de la récupération de l\'utilisateur: {str(e)}'
}), 500
@tpe_bp.route('/agent/<agent_id>/balance', methods=['GET'])
def get_agent_balance(agent_id):
"""Récupérer le solde d'un agent"""
try:
user = users_collection.find_one({'agent_id': agent_id})
if not user:
return jsonify({
'success': False,
'message': 'Agent non trouvé'
}), 404
total_transactions = transactions_collection.count_documents({'agent_id': agent_id})
commission_pipeline = [
{'$match': {'agent_id': agent_id}},
{'$group': {
'_id': None,
'total_commission': {'$sum': {'$toDouble': '$commission_agent'}}
}}
]
commission_result = list(transactions_collection.aggregate(commission_pipeline))
total_commission = commission_result[0]['total_commission'] if commission_result else 0.0
response_data = {
'success': True,
'agent_id': agent_id,
'solde': float(user.get('solde', 0)),
'nom': user['nom'],
'role': user.get('role'),
'type': user.get('type', 'agent'),
'total_transactions': total_transactions,
'total_commission': round(total_commission, 2),
'date_creation': user.get('date_creation', user.get('created_at')),
'last_transaction_date': user.get('last_transaction_date')
}
# Si membre entreprise, ajouter infos entreprise
if user.get('type') == 'enterprise_member' and user.get('enterprise_id'):
enterprise = enterprises_collection.find_one({'enterprise_id': user.get('enterprise_id')})
if enterprise:
response_data['enterprise'] = {
'enterprise_id': enterprise.get('enterprise_id'),
'nom_entreprise': enterprise.get('nom_entreprise'),
'solde_entreprise': enterprise.get('solde_entreprise', 0)
}
return jsonify(response_data), 200
except Exception as e:
logger.error(f"❌ Erreur get_agent_balance: {e}")
return jsonify({
'success': False,
'message': f'Erreur: {str(e)}'
}), 500
# ===== ROUTES RECHARGES =====
@tpe_bp.route('/agent/<agent_id>/recharge', methods=['POST'])
def recharge_agent_balance(agent_id):
"""Recharger le solde d'un agent (ajouter un montant)"""
try:
data = request.get_json()
if 'montant' not in data:
return jsonify({
'success': False,
'message': 'Le champ montant est requis'
}), 400
montant = float(data['montant'])
if montant <= 0:
return jsonify({
'success': False,
'message': 'Le montant doit être positif'
}), 400
user = users_collection.find_one({'agent_id': agent_id})
if not user:
return jsonify({
'success': False,
'message': 'Agent non trouvé'
}), 404
# Vérifier si c'est un membre entreprise
if user.get('type') == 'enterprise_member':
return jsonify({
'success': False,
'message': 'Utilisez l\'endpoint de recharge entreprise pour les membres d\'entreprise',
'enterprise_recharge_endpoint': f'/tpe/enterprise/{user.get("enterprise_id")}/recharge'
}), 400
ancien_solde = user.get('solde', 0.0)
nouveau_solde = ancien_solde + montant
users_collection.update_one(
{'agent_id': agent_id},
{'$set': {'solde': nouveau_solde}}
)
return jsonify({
'success': True,
'message': 'Recharge effectuée avec succès',
'agent_id': agent_id,
'montant_recharge': montant,
'ancien_solde': ancien_solde,
'nouveau_solde': nouveau_solde
}), 200
except ValueError:
return jsonify({
'success': False,
'message': 'Le montant doit être un nombre valide'
}), 400
except Exception as e:
return jsonify({
'success': False,
'message': f'Erreur lors de la recharge: {str(e)}'
}), 500
@tpe_bp.route('/recharge/create/agent/<agent_id>', methods=['POST'])
def create_recharge(agent_id):
"""Créer une nouvelle demande de recharge"""
try:
data = request.get_json()
required_fields = ['pin', 'montant']
for field in required_fields:
if field not in data or not data[field]:
return jsonify({
'success': False,
'message': f'Champ requis manquant: {field}'
}), 400
montant = data['montant']
requested_by = data.get('requested_by', agent_id)
try:
montant_float = float(montant)
if montant_float <= 0:
return jsonify({
'success': False,
'message': 'Le montant doit être supérieur à 0'
}), 400
if montant_float > 10000000:
return jsonify({
'success': False,
'message': 'Le montant ne peut pas dépasser 10,000,000 FCFA'
}), 400
except ValueError:
return jsonify({
'success': False,
'message': 'Montant invalide'
}), 400
agent = users_collection.find_one({'agent_id': agent_id})
if not agent:
return jsonify({
'success': False,
'message': f'Agent {agent_id} introuvable dans le système'
}), 404
if not User.verify_pin(agent['pin_hash'], data['pin']):
return jsonify({
'success': False,
'message': 'ID agent ou PIN incorrect'
}), 401
recharge_data = {
'agent_id': agent_id,
'montant': str(int(montant_float)),
'status': 'pending',
'requested_by': requested_by,
'created_at': datetime.now(),
'updated_at': datetime.now(),
}
insert_result = recharges_collection.insert_one(recharge_data)
if insert_result.inserted_id:
created_recharge = recharges_collection.find_one({'_id': insert_result.inserted_id})
recharge_response = {
'_id': {'$oid': str(created_recharge['_id'])},
'agent_id': created_recharge['agent_id'],
'montant': created_recharge['montant'],
'status': created_recharge['status'],
'requested_by': created_recharge['requested_by'],
'description': created_recharge.get('description', ''),
'created_at': created_recharge['created_at'].isoformat(),
}
return jsonify({
'success': True,
'message': f'Demande de recharge créée avec succès pour l\'agent {agent_id}',
'recharge': recharge_response,
'recharge_id': str(insert_result.inserted_id)
}), 201
else:
return jsonify({
'success': False,
'message': 'Erreur lors de la création de la recharge'
}), 500
except Exception as e:
return jsonify({
'success': False,
'message': f'Erreur lors de la création: {str(e)}'
}), 500
@tpe_bp.route('/recharge/get_recharges', methods=['GET'])
def get_recharges():
"""Récupérer toutes les recharges"""
try:
status_filter = request.args.get('status')
filter_query = {}
if status_filter:
filter_query['status'] = status_filter
recharges = list(recharges_collection.find(filter_query).sort("created_at", -1))
recharges_serialized = []
for recharge in recharges:
recharge_data = {
'_id': {'$oid': str(recharge['_id'])},
'agent_id': recharge.get('agent_id', ''),
'enterprise_id': recharge.get('enterprise_id', ''),
'montant': recharge.get('montant', '0'),
'status': recharge.get('status', 'pending'),
'type': recharge.get('type', 'personal'),
'created_at': recharge.get('created_at', datetime.now()).isoformat(),
'processed_at': recharge.get('processed_at'),
'processed_by': recharge.get('processed_by'),
'rejection_reason': recharge.get('rejection_reason'),
'approved_by': recharge.get('approved_by'),
'rejected_by': recharge.get('rejected_by')
}
recharges_serialized.append(recharge_data)
return jsonify({
'success': True,
'all_recharges': recharges_serialized,
'total': len(recharges_serialized)
}), 200
except Exception as e:
return jsonify({
'success': False,
'message': f'Erreur lors de la récupération: {str(e)}'
}), 500
@tpe_bp.route('/recharge/approve/<recharge_id>/agent/<agent_id>', methods=['PUT'])
def approve_recharge(recharge_id, agent_id):
"""Approuver une recharge"""
try:
data = request.get_json()
approved_by = data.get('approved_by')
if not approved_by:
return jsonify({
'success': False,
'message': 'ID du rechargeur requis'
}), 400
recharge = recharges_collection.find_one({'_id': ObjectId(recharge_id)})
if not recharge:
return jsonify({
'success': False,
'message': 'Recharge introuvable'
}), 404
agent = users_collection.find_one({'agent_id': agent_id})
if not agent:
return jsonify({
'success': False,
'message': f'Agent {agent_id} introuvable dans le système'
}), 404
if not User.verify_pin(agent['pin_hash'], data['pin']):
return jsonify({
'success': False,
'message': 'ID agent ou PIN incorrect'
}), 401
if recharge.get('status') != 'pending':
return jsonify({
'success': False,
'message': f'Impossible d\'approuver une recharge avec le statut: {recharge.get("status")}'
}), 400
update_result = recharges_collection.update_one(
{'_id': ObjectId(recharge_id)},
{
'$set': {
'status': 'approved',
'approved_by': approved_by,
'approved_at': datetime.now(),
'processed_at': datetime.now()
}
}
)
if update_result.modified_count > 0:
target_agent_id = recharge.get('agent_id')
target_enterprise_id = recharge.get('enterprise_id')
montant = float(recharge.get('montant', 0))
if target_enterprise_id:
# Recharge entreprise
enterprises_collection.update_one(
{'enterprise_id': target_enterprise_id},
{'$inc': {'solde_entreprise': montant}}
)
elif target_agent_id and montant > 0:
# Recharge agent individuel
users_collection.update_one(
{'agent_id': target_agent_id},
{'$inc': {'solde': montant}}
)
return jsonify({
'success': True,
'message': 'Recharge approuvée avec succès',
'data': {
'recharge_id': recharge_id,
'agent_id': target_agent_id,
'enterprise_id': target_enterprise_id,
'montant': montant,
'approved_by': approved_by
}
}), 200
else:
return jsonify({
'success': False,
'message': 'Erreur lors de la mise à jour'
}), 500
except Exception as e:
return jsonify({
'success': False,
'message': f'Erreur lors de l\'approbation: {str(e)}'
}), 500
# ===== ROUTES TRANSACTIONS =====
@tpe_bp.route('/agent/<agent_id>/transactions', methods=['GET'])
def get_agent_transactions(agent_id):
"""Récupérer les transactions d'un agent"""
try:
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 50))
skip = (page - 1) * limit
date_debut = request.args.get('date_debut')
date_fin = request.args.get('date_fin')
type_transaction = request.args.get('type_transaction')
query = {'agent_id': agent_id}
if date_debut and date_fin:
query['created_at'] = {
'$gte': datetime.fromisoformat(date_debut.replace('Z', '+00:00')),
'$lte': datetime.fromisoformat(date_fin.replace('Z', '+00:00'))
}
if type_transaction:
query['service_name'] = {'$regex': type_transaction, '$options': 'i'}
transactions = list(transactions_collection.find(query, {
'_id': 1,
'transID': 1,
'agent_id': 1,
'service_name': 1,
'client_reference': 1,
'montant_total_debite': 1,
'montant_reel': 1,
'commission_agent': 1,
'status': 1,
'account_type': 1,
'enterprise_id': 1,
'balance_source': 1,
'created_at': 1,
'updated_at': 1
}).sort('created_at', -1).skip(skip).limit(limit))
total = transactions_collection.count_documents(query)
serialized_transactions = []
for trans in transactions:
trans_data = {
'_id': str(trans['_id']),
'transID': trans.get('transID', str(trans['_id'])),
'agent_id': trans.get('agent_id'),
'service_name': trans.get('service_name'),
'client_reference': trans.get('client_reference'),
'montant_total_debite': float(trans.get('montant_total_debite', 0)),
'montant_reel': float(trans.get('montant_reel', 0)),
'commission_agent': float(trans.get('commission_agent', 0)),
'status': trans.get('status'),
'account_type': trans.get('account_type', 'agent'),
'enterprise_id': trans.get('enterprise_id'),
'balance_source': trans.get('balance_source', 'personal'),
'created_at': trans.get('created_at').isoformat() if trans.get('created_at') else None,
'updated_at': trans.get('updated_at').isoformat() if trans.get('updated_at') else None
}
serialized_transactions.append(trans_data)
return jsonify({
'success': True,
'transactions': serialized_transactions,
'pagination': {
'page': page,
'limit': limit,
'total': total,
'pages': (total + limit - 1) // limit if limit > 0 else 1
}
}), 200
except Exception as e:
logger.error(f"❌ Erreur get_agent_transactions: {e}")
return jsonify({
'success': False,
'message': f'Erreur lors de la récupération des transactions: {str(e)}'
}), 500
@tpe_bp.route('/agent/<agent_id>/stats', methods=['GET'])
def get_agent_stats(agent_id):
"""Récupérer les statistiques d'un agent"""
try:
agent = users_collection.find_one({'agent_id': agent_id})
if not agent:
return jsonify({
'success': False,
'message': 'Agent non trouvé'
}), 404
today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
tomorrow = today + timedelta(days=1)
this_month_start = today.replace(day=1)
stats_jour = list(transactions_collection.aggregate([
{'$match': {
'agent_id': agent_id,
'created_at': {'$gte': today, '$lt': tomorrow}
}},
{'$group': {
'_id': None,
'total_transactions': {'$sum': 1},
'total_montant': {'$sum': {'$toDouble': '$montant_total_debite'}},
'total_commissions': {'$sum': {'$toDouble': '$commission_agent'}}
}}
]))
stats_mois = list(transactions_collection.aggregate([
{'$match': {
'agent_id': agent_id,
'created_at': {'$gte': this_month_start}
}},
{'$group': {
'_id': None,
'total_transactions': {'$sum': 1},
'total_montant': {'$sum': {'$toDouble': '$montant_total_debite'}},
'total_commissions': {'$sum': {'$toDouble': '$commission_agent'}}
}}
]))
top_services = list(transactions_collection.aggregate([
{'$match': {'agent_id': agent_id}},
{'$group': {
'_id': '$service_name',
'count': {'$sum': 1},
'total_montant': {'$sum': {'$toDouble': '$montant_total_debite'}}
}},
{'$sort': {'count': -1}},
{'$limit': 5}
]))
result = {
'solde_actuel': float(agent.get('solde', 0)),
'account_type': agent.get('type', 'agent'),
'statistiques_jour': stats_jour[0] if stats_jour else {
'total_transactions': 0,
'total_montant': 0.0,
'total_commissions': 0.0
},
'statistiques_mois': stats_mois[0] if stats_mois else {
'total_transactions': 0,
'total_montant': 0.0,
'total_commissions': 0.0
},
'services_favoris': top_services
}
for stat_period in ['statistiques_jour', 'statistiques_mois']:
if result[stat_period] and '_id' in result[stat_period]:
result[stat_period].pop('_id')
# Si membre entreprise, ajouter le solde entreprise
if agent.get('type') == 'enterprise_member' and agent.get('enterprise_id'):
enterprise = enterprises_collection.find_one({'enterprise_id': agent.get('enterprise_id')})
if enterprise:
result['solde_entreprise'] = float(enterprise.get('solde_entreprise', 0))
result['enterprise_id'] = enterprise.get('enterprise_id')
result['nom_entreprise'] = enterprise.get('nom_entreprise')
return jsonify({
'success': True,
'stats': result
}), 200
except Exception as e:
logger.error(f"❌ Erreur get_agent_stats: {e}")
return jsonify({
'success': False,
'message': f'Erreur lors de la récupération des statistiques: {str(e)}'
}), 500
# ===== ROUTES SERVICES =====
@tpe_bp.route('/service/<service_name>', methods=['GET'])
def get_service_fields(service_name):
"""Récupérer les champs d'un service"""
service = services_collection.find_one({"name": service_name})
if service is None:
return jsonify({"error": "Service not found"}), 404
service['_id'] = str(service['_id'])
return jsonify({"service": service})
@tpe_bp.route('/get_services_tpe', methods=['GET'])
def get_services_all_back():
"""Récupérer tous les services"""
services = list(services_collection.find({}))
for service in services:
service['_id'] = str(service['_id'])
services.sort(key=lambda x: x.get('rang', '').lower())
random_services = random.sample(services, min(3, len(services)))
return jsonify({
"random_services": random_services,
"all_services": services
})
# ===== ROUTES PAIEMENTS INTERNET =====
@tpe_bp.route('/get_abn_info_tpe', methods=['POST'])
def get_abn_info_tpe():
"""Récupérer les informations d'un abonnement Internet"""
try:
data = request.json
if not data or not data.get("numabn"):
return jsonify({"error": "Numéro d'abonnement requis"}), 400
logger.info(f"🔍 Récupération infos abonnement TPE: {data.get('numabn')}")
url = "https://apingbss-prod.speedpro.cg/api/get/customer"
payload = json.dumps({
"mode": "GET",
"numabn": data.get("numabn")
})
headers = {
'Content-Type': 'application/json',
}
response = requests.post(url, headers=headers, data=payload)
response.raise_for_status()
api_response = response.json()
if not api_response or 'prix' not in api_response or 'name' not in api_response:
return jsonify({
"error": "Abonnement non trouvé",
"details": "Vérifiez le numéro d'abonnement",
"numAbn": data.get("numabn")
}), 404
prix = float(api_response.get("prix"))
frais_totaux = prix * 0.02
commission_agent = prix * 0.01
frais_plateforme = prix * 0.01
montant_total = prix + frais_totaux
response_data = {
"Numéro d'Abonnement": data.get("numabn"),
"Abonné": api_response.get("name", "N/A"),
"Adresse": api_response.get("adresse", "Pas d'adresse"),
"Offre": api_response.get("offer", api_response.get("formule", "Standard")),
"Prix": prix,
"Frais": round(frais_totaux, 2),
"Commission": round(commission_agent, 2),
"Total": round(montant_total, 2)
}
logger.info(f"✅ Infos abonnement TPE récupérées: {data.get('numabn')} - {api_response.get('name')}")
return jsonify(response_data), 200
except requests.exceptions.RequestException as e:
logger.error(f"❌ Erreur API Congo Télécom: {e}")
return jsonify({
"error": "Service temporairement indisponible",
"details": "Impossible de contacter le service d'abonnement"
}), 503
except Exception as e:
logger.error(f"❌ Erreur get_abn_info_tpe: {e}")
return jsonify({
"error": "Erreur interne",
"details": str(e)
}), 500
@tpe_bp.route('/speed_paiement_tpe', methods=['POST'])
def speed_paiement_tpe():
"""Route principale pour le paiement Internet via solde agent"""
try:
data = request.json
logger.info(f"📥 Données reçues pour paiement: {data}")
if not data or not data.get("numAbn"):
return jsonify({
"success": False,
"error": "Numéro d'abonnement requis"
}), 400
agent_id = data.get("agent_id")
if not agent_id:
agent_id = request.headers.get("X-Agent-ID")
if not agent_id:
return jsonify({
"success": False,
"error": "agent_id requis pour le paiement"
}), 400
logger.info(f"🔄 Début paiement Internet - Agent: {agent_id}, Abonnement: {data.get('numAbn')}")
url = "https://apingbss-prod.speedpro.cg/api/get/customer"
payload = json.dumps({
"mode": "GET",
"numabn": data.get("numAbn")
})
headers = {
'Content-Type': 'application/json',
}
response = requests.post(url, headers=headers, data=payload)
response.raise_for_status()
api_response = response.json()
if not api_response or 'prix' not in api_response or 'name' not in api_response:
return jsonify({
"success": False,
"error": "Abonnement non trouvé"
}), 404
prix = float(api_response.get("prix"))
frais_totaux = prix * 0.02
commission_prevue = prix * 0.01
frais_plateforme = prix * 0.01
montant_total = prix + frais_totaux
logger.info(f"💰 Calcul tarification - Prix: {prix} FCFA, Total: {montant_total} FCFA")
additional_data = {
"numAbn": data.get("numAbn"),
"subscriber_name": api_response.get("name"),
"subscriber_address": api_response.get("address", "Pas d'adresse"),
"service_provider": "Congo Télécom",
"subscription_plan": api_response.get("plan", "Standard"),
"api_response": api_response,
"payment_method": "agent_balance"
}
try:
payment_result = process_agent_payment(
agent_id=agent_id,
service_name="Internet",
montant_total=montant_total,
montant_reel=prix,
client_reference=data.get("numAbn"),
additional_data=additional_data
)
logger.info(f"💰 Agent débité avec succès: {payment_result}")
except ValueError as e:
error_msg = str(e)
if "INSUFFICIENT_BALANCE" in error_msg:
deficit = error_msg.split("|")[1] if "|" in error_msg else "N/A"
return jsonify({
"success": False,
"error": "Solde insuffisant",
"details": f"Il manque {deficit} FCFA",
"required_amount": montant_total
}), 400
elif "AGENT_NOT_FOUND" in error_msg:
return jsonify({
"success": False,
"error": "Agent non trouvé ou inactif"
}), 404
else:
return jsonify({
"success": False,
"error": f"Erreur de paiement: {error_msg}"
}), 400
try:
confirmation_code = f"WTPE{datetime.now().strftime('%Y%m%d')}{data.get('numAbn')[-4:]}"
transactions_collection.update_one(
{"_id": payment_result["transaction_id"]},
{
"$set": {
"tel": data.get("tel"),
"status": "completed",
"confirmation_code": confirmation_code,
"completed_at": datetime.now(timezone.utc),
"updated_at": datetime.now(timezone.utc)
}
}
)
logger.info(f"✅ Paiement Internet réussi - Transaction: {payment_result['transaction_id']}")
return jsonify({
"success": True,
"message": "Paiement Internet effectué avec succès",
"data": {
"transaction_id": payment_result["transaction_id"],
"confirmation_code": confirmation_code,
"agent_id": agent_id,
"agent_name": payment_result["agent_name"],
"account_type": payment_result.get("account_type", "agent"),
"enterprise_id": payment_result.get("enterprise_id"),
"subscriber_name": api_response.get("name"),
"subscription_number": data.get("numAbn"),
"amount_paid": prix,
"total_debited": round(montant_total, 2),
"commission_earned": payment_result["commission_gagnee"],
"new_balance": payment_result["nouveau_solde"],
"balance_source": payment_result.get("balance_source", "personal"),
"payment_date": datetime.now(timezone.utc).isoformat()
}
}), 200
except Exception as completion_error:
logger.error(f"❌ Erreur finalisation: {completion_error}")
return jsonify({
"success": False,
"error": "Erreur lors de la finalisation du paiement"
}), 500
except Exception as e:
logger.error(f"❌ Erreur inattendue: {e}")
return jsonify({
"success": False,
"error": f"Erreur inattendue: {str(e)}"
}), 500
# ===== ROUTES PAIEMENTS PELISA =====
@tpe_bp.route('/get_pelisa_info_tpe', methods=['POST'])
def get_pelisa_info_tpe():
"""Récupérer les informations d'un compteur Pelisa"""
try:
data = request.json
if not data or not data.get("ref_meter"):
return jsonify({"error": "Numéro de compteur requis"}), 400
url = "https://api.live.wortis.cg/get_user_pelissa"
payload = json.dumps({"ref_meter": data.get("ref_meter")})
headers = {'Content-Type': 'application/json'}
response = requests.post(url, headers=headers, data=payload)
response.raise_for_status()
api_response = response.json()
if not api_response or api_response.get('Ustatus') != 1:
return jsonify({
"error": "Compteur non trouvé ou inactif",
"ref_meter": data.get("ref_meter")
}), 404
response_data = {
"Numéro de Compteur": data.get("ref_meter"),
"Nom complet": api_response.get("first_name", "N/A").strip(),
"Adresse": api_response.get("addresse", "Pas d'adresse").strip(),
"Profil Tarifaire": api_response.get("profil_tarif", "Standard"),
"Numéro de Police": api_response.get("n_police", "N/A"),
"Référence Contrat": api_response.get("ref_contract", "N/A"),
"Numéro de Branchement": str(api_response.get("branchement", "N/A")),
"Téléphone": api_response.get("tel", "").strip() or "Non renseigné",
"Statut Compteur": "Actif" if api_response.get('status') == 0 else "Inactif",
"Statut Résiliation": "Normal" if api_response.get('status_resiliation') == 0 else "Résilié"
}
return jsonify(response_data), 200
except Exception as e:
logger.error(f"❌ Erreur get_pelisa_info_tpe: {e}")
return jsonify({"error": "Erreur interne", "details": str(e)}), 500
@tpe_bp.route('/pelisa_paiement_tpe', methods=['POST'])
def pelisa_paiement_tpe():
"""Route principale pour le paiement Pelisa via solde agent"""
try:
data = request.json
required_fields = ["ref_meter", "montantinitial"]
for field in required_fields:
if not data or not data.get(field):
return jsonify({"success": False, "error": f"{field} requis"}), 400
agent_id = data.get("agent_id") or request.headers.get("X-Agent-ID")
if not agent_id:
return jsonify({"success": False, "error": "agent_id requis"}), 400
# Récupérer infos compteur
url = "https://api.live.wortis.cg/get_user_pelissa"
payload = json.dumps({"ref_meter": data.get("ref_meter")})
response = requests.post(url, headers={'Content-Type': 'application/json'}, data=payload)
response.raise_for_status()
api_response = response.json()
if not api_response or api_response.get('Ustatus') != 1:
return jsonify({"success": False, "error": "Compteur non trouvé ou inactif"}), 404
montant_initial = float(data.get("montantinitial"))
if montant_initial <= 0:
return jsonify({"success": False, "error": "Le montant doit être supérieur à zéro"}), 400
prix = montant_initial
frais_totaux = prix * 0.02
montant_total = prix + frais_totaux
additional_data = {
"ref_meter": data.get("ref_meter"),
"subscriber_name": api_response.get("first_name", "").strip(),
"service_provider": "Pelisa",
"montant_initial": montant_initial
}
try:
payment_result = process_agent_payment(
agent_id=agent_id,
service_name="Recharge de Compteur Pelisa",
montant_total=montant_total,
montant_reel=prix,
client_reference=str(data.get("ref_meter")),
additional_data=additional_data
)
except ValueError as e:
error_msg = str(e)
if "INSUFFICIENT_BALANCE" in error_msg:
deficit = error_msg.split("|")[1] if "|" in error_msg else "N/A"
return jsonify({
"success": False,
"error": "Solde insuffisant",
"details": f"Il manque {deficit} FCFA"
}), 400
return jsonify({"success": False, "error": str(e)}), 400
confirmation_code = f"WPEL{datetime.now().strftime('%Y%m%d')}{str(data.get('ref_meter'))[-4:]}"
transactions_collection.update_one(
{"_id": payment_result["transaction_id"]},
{"$set": {
"tel": data.get("tel"),
"status": "completed",
"confirmation_code": confirmation_code,
"completed_at": datetime.now(timezone.utc)
}}
)
# Envoi WhatsApp
try:
reqUrl = "https://api.live.wortis.cg/send_whatsapp_backoffice_api"
payload_wa = json.dumps({
"sms": (
f"🪙 TPE : {data.get('num_operation', 'N/A')}\n"
f"Marchand : Pelissa\n"
f"Compteur : {data.get('ref_meter')}\n"
f"Client : {api_response.get('first_name', '').strip()}\n"
f"Tel.: {data.get('tel', 'N/A')}\n"
f"Montant : {prix}\n"
f"Agent : {agent_id}\n"
f"TransID : {confirmation_code}"
)
})
requests.post(reqUrl, data=payload_wa, headers={'Content-Type': 'application/json'})
except:
pass
return jsonify({
"success": True,
"message": "Paiement Pelisa effectué avec succès",
"data": {
"transaction_id": payment_result["transaction_id"],
"confirmation_code": confirmation_code,
"agent_id": agent_id,
"account_type": payment_result.get("account_type"),
"enterprise_id": payment_result.get("enterprise_id"),
"amount_paid": prix,
"total_debited": round(montant_total, 2),
"commission_earned": payment_result["commission_gagnee"],
"new_balance": payment_result["nouveau_solde"],
"balance_source": payment_result.get("balance_source")
}
}), 200
except Exception as e:
logger.error(f"❌ Erreur Pelisa: {e}")
return jsonify({"success": False, "error": str(e)}), 500
# ===== ROUTES PAIEMENTS E2C =====
@tpe_bp.route('/get_e2c_info_tpe', methods=['POST'])
def get_e2c_info_tpe():
"""Récupérer les informations d'un client E2C"""
try:
data = request.json
if not data or not data.get("numc") or not data.get("ville"):
return jsonify({"error": "Numéro client et ville requis"}), 400
url = "https://api.live.wortis.cg/get_e2c_apk"
payload = json.dumps({"numc": data.get("numc"), "ville": data.get("ville")})
response = requests.post(url, headers={'Content-Type': 'application/json'}, data=payload)
response.raise_for_status()
api_response = response.json()
if not api_response or not api_response.get('NomClient'):
return jsonify({"error": "Client non trouvé"}), 404
dette_totale = float(api_response.get("SoldeDetteTotale", 0))
response_data = {
"Numéro Client": data.get("numc"),
"Nom Complet": api_response.get("NomClient", "N/A").strip(),
"Adresse": api_response.get("Adresse", "Pas d'adresse").strip(),
"Ref de Paiement": api_response.get("RefPaiement", "N/A"),
"Numéro de Branchement": api_response.get("NumBranchement", "N/A"),
"Dette Totale": f"{dette_totale:.0f} FCFA",
"Ville": data.get("ville"),
"MontantAPayer": dette_totale,
"StatutFacture": "Impayée" if dette_totale > 0 else "Soldée"
}
return jsonify(response_data), 200
except Exception as e:
logger.error(f"❌ Erreur get_e2c_info_tpe: {e}")
return jsonify({"error": "Erreur interne"}), 500
@tpe_bp.route('/e2c_paiement_tpe', methods=['POST'])
def e2c_paiement_tpe():
"""Route principale pour le paiement E2C via solde agent"""
try:
data = request.json
required_fields = ["numc", "ville", "montantinitial"]
for field in required_fields:
if not data or not data.get(field):
return jsonify({"success": False, "error": f"{field} requis"}), 400
agent_id = data.get("agent_id") or request.headers.get("X-Agent-ID")
if not agent_id:
return jsonify({"success": False, "error": "agent_id requis"}), 400
url = "https://api.live.wortis.cg/get_e2c_apk"
payload = json.dumps({"numc": data.get("numc"), "ville": data.get("ville")})
response = requests.post(url, headers={'Content-Type': 'application/json'}, data=payload)
response.raise_for_status()
api_response = response.json()
if not api_response or not api_response.get('NomClient'):
return jsonify({"success": False, "error": "Client non trouvé"}), 404
dette_totale = float(api_response.get("SoldeDetteTotale", 0))
montant_initial = float(data.get("montantinitial"))
if montant_initial <= 0:
return jsonify({"success": False, "error": "Le montant doit être supérieur à zéro"}), 400
prix = montant_initial
frais_totaux = prix * 0.02
montant_total = prix + frais_totaux
additional_data = {
"numc": data.get("numc"),
"ville": data.get("ville"),
"client_name": api_response.get("NomClient", "").strip(),
"service_provider": "E2C",
"dette_totale_avant": dette_totale
}
try:
payment_result = process_agent_payment(
agent_id=agent_id,
service_name="Facture électricité",
montant_total=montant_total,
montant_reel=prix,
client_reference=data.get("numc"),
additional_data=additional_data
)
except ValueError as e:
error_msg = str(e)
if "INSUFFICIENT_BALANCE" in error_msg:
deficit = error_msg.split("|")[1] if "|" in error_msg else "N/A"
return jsonify({"success": False, "error": "Solde insuffisant", "details": f"Il manque {deficit} FCFA"}), 400
return jsonify({"success": False, "error": str(e)}), 400
confirmation_code = f"WE2C{datetime.now().strftime('%Y%m%d')}{data.get('numc')[-4:]}"
transactions_collection.update_one(
{"_id": payment_result["transaction_id"]},
{"$set": {
"status": "completed",
"confirmation_code": confirmation_code,
"completed_at": datetime.now(timezone.utc)
}}
)
return jsonify({
"success": True,
"message": "Paiement facture E2C effectué avec succès",
"data": {
"transaction_id": payment_result["transaction_id"],
"confirmation_code": confirmation_code,
"agent_id": agent_id,
"account_type": payment_result.get("account_type"),
"enterprise_id": payment_result.get("enterprise_id"),
"client_name": api_response.get("NomClient", "").strip(),
"amount_paid": prix,
"total_debited": round(montant_total, 2),
"commission_earned": payment_result["commission_gagnee"],
"new_balance": payment_result["nouveau_solde"],
"balance_source": payment_result.get("balance_source"),
"dette_restante": dette_totale - montant_initial
}
}), 200
except Exception as e:
logger.error(f"❌ Erreur E2C: {e}")
return jsonify({"success": False, "error": str(e)}), 500
# ===== ROUTES ENTREPRISES =====
@tpe_bp.route('/enterprise/create', methods=['POST'])
def create_enterprise():
"""Créer un nouveau compte entreprise"""
try:
data = request.get_json()
required_fields = ['nom_entreprise', 'email_entreprise', 'telephone_entreprise']
for field in required_fields:
if field not in data or not data[field]:
return jsonify({
'success': False,
'message': f'Champ requis manquant: {field}'
}), 400
existing_email = enterprises_collection.find_one({'email_entreprise': data['email_entreprise']})
if existing_email:
return jsonify({
'success': False,
'message': 'Cette adresse email est déjà utilisée'
}), 409
enterprise = Enterprise(
nom_entreprise=data['nom_entreprise'].strip(),
email_entreprise=data['email_entreprise'].strip(),
telephone_entreprise=data['telephone_entreprise'].strip(),
adresse=data.get('adresse', '').strip(),
secteur_activite=data.get('secteur_activite', '').strip()
)
result = enterprises_collection.insert_one(enterprise.to_dict())
if result.inserted_id:
log_activity(
'enterprise_created',
'Entreprise créée',
f'{enterprise.enterprise_id} - {data["nom_entreprise"]}',
data.get('created_by', 'admin'),
{'enterprise_id': enterprise.enterprise_id}
)
created_enterprise = enterprise.to_dict()
created_enterprise['_id'] = str(result.inserted_id)
return jsonify({
'success': True,
'message': f'Entreprise {enterprise.enterprise_id} créée avec succès',
'enterprise': serialize_objectid(created_enterprise)
}), 201
else:
return jsonify({
'success': False,
'message': 'Erreur lors de la création de l\'entreprise'
}), 500
except Exception as e:
logger.error(f"❌ Erreur création entreprise: {e}")
return jsonify({
'success': False,
'message': f'Erreur lors de la création: {str(e)}'
}), 500
@tpe_bp.route('/enterprise/<enterprise_id>/add-member', methods=['POST'])
def add_enterprise_member(enterprise_id):
"""Ajouter un membre à une entreprise"""
try:
data = request.get_json()
required_fields = ['nom', 'telephone', 'role_dans_entreprise']
for field in required_fields:
if field not in data or not data[field]:
return jsonify({
'success': False,
'message': f'Champ requis manquant: {field}'
}), 400
enterprise = enterprises_collection.find_one({'enterprise_id': enterprise_id})
if not enterprise:
return jsonify({
'success': False,
'message': f'Entreprise {enterprise_id} introuvable'
}), 404
existing_phone = users_collection.find_one({'telephone': data['telephone']})
if existing_phone:
return jsonify({
'success': False,
'message': 'Ce numéro de téléphone est déjà utilisé'
}), 409
agent_id = generate_agent_id()
default_pin = ''.join(random.choices(string.digits, k=4))
member_data = {
'agent_id': agent_id,
'nom': data['nom'].strip(),
'telephone': data['telephone'].strip(),
'email': data.get('email', '').strip(),
'pin': default_pin,
'pin_hash': generate_password_hash(default_pin),
'type': 'enterprise_member',
'enterprise_id': enterprise_id,
'role_dans_entreprise': data['role_dans_entreprise'],
'is_admin_entreprise': data.get('is_admin_entreprise', False),
'status': 'active',
'solde': 0.0,
'created_at': datetime.now(timezone.utc),
'updated_at': datetime.now(timezone.utc),
'created_by': data.get('created_by', 'admin'),
'last_login': None
}
result = users_collection.insert_one(member_data)
if result.inserted_id:
membership = {
'enterprise_id': enterprise_id,
'agent_id': agent_id,
'role': data['role_dans_entreprise'],
'is_admin': data.get('is_admin_entreprise', False),
'joined_at': datetime.now(timezone.utc),
'status': 'active'
}
enterprise_members_collection.insert_one(membership)
enterprises_collection.update_one(
{'enterprise_id': enterprise_id},
{
'$inc': {'total_members': 1},
'$set': {'updated_at': datetime.now(timezone.utc)}
}
)
log_activity(
'enterprise_member_added',
'Membre ajouté à l\'entreprise',
f'{agent_id} ajouté à {enterprise_id}',
data.get('created_by', 'admin'),
{'enterprise_id': enterprise_id, 'agent_id': agent_id}
)
created_member = member_data.copy()
created_member.pop('pin')
created_member.pop('pin_hash')
created_member['_id'] = str(result.inserted_id)
return jsonify({
'success': True,
'message': f'Membre {agent_id} ajouté à l\'entreprise {enterprise_id}',
'member': serialize_objectid(created_member),
'default_pin': default_pin,
'enterprise': {
'enterprise_id': enterprise_id,
'nom_entreprise': enterprise.get('nom_entreprise')
}
}), 201
else:
return jsonify({
'success': False,
'message': 'Erreur lors de l\'ajout du membre'
}), 500
except Exception as e:
logger.error(f"❌ Erreur ajout membre entreprise: {e}")
return jsonify({
'success': False,
'message': f'Erreur lors de l\'ajout: {str(e)}'
}), 500
@tpe_bp.route('/enterprise/<enterprise_id>/members', methods=['GET'])
def get_enterprise_members(enterprise_id):
"""Récupérer tous les membres d'une entreprise"""
try:
enterprise = enterprises_collection.find_one({'enterprise_id': enterprise_id})
if not enterprise:
return jsonify({
'success': False,
'message': f'Entreprise {enterprise_id} introuvable'
}), 404
members = list(users_collection.find({
'enterprise_id': enterprise_id,
'type': 'enterprise_member'
}, {'pin': 0, 'pin_hash': 0}))
for member in members:
member_transactions = transactions_collection.count_documents({
'agent_id': member['agent_id']
})
volume_result = list(transactions_collection.aggregate([
{'$match': {'agent_id': member['agent_id']}},
{'$group': {
'_id': None,
'total_volume': {'$sum': {'$toDouble': '$montant_total_debite'}}
}}
]))
member['total_transactions'] = member_transactions
member['total_volume'] = volume_result[0]['total_volume'] if volume_result else 0.0
return jsonify({
'success': True,
'enterprise': {
'enterprise_id': enterprise_id,
'nom_entreprise': enterprise.get('nom_entreprise'),
'solde_entreprise': enterprise.get('solde_entreprise', 0.0),
'status': enterprise.get('status')
},
'members': serialize_objectid(members),
'total_members': len(members)
}), 200
except Exception as e:
logger.error(f"❌ Erreur récupération membres: {e}")
return jsonify({
'success': False,
'message': f'Erreur: {str(e)}'
}), 500
@tpe_bp.route('/enterprise/<enterprise_id>/balance', methods=['GET'])
def get_enterprise_balance(enterprise_id):
"""Récupérer le solde et les statistiques d'une entreprise"""
try:
enterprise = enterprises_collection.find_one({'enterprise_id': enterprise_id})
if not enterprise:
return jsonify({
'success': False,
'message': f'Entreprise {enterprise_id} introuvable'
}), 404
members = list(users_collection.find({
'enterprise_id': enterprise_id,
'type': 'enterprise_member'
}, {'agent_id': 1}))
member_ids = [m['agent_id'] for m in members]
total_transactions = transactions_collection.count_documents({
'agent_id': {'$in': member_ids}
})
financial_stats = list(transactions_collection.aggregate([
{'$match': {'agent_id': {'$in': member_ids}}},
{'$group': {
'_id': None,
'total_volume': {'$sum': {'$toDouble': '$montant_total_debite'}},
'total_commissions': {'$sum': {'$toDouble': '$commission_agent'}}
}}
]))
total_volume = financial_stats[0]['total_volume'] if financial_stats else 0.0
total_commissions = financial_stats[0]['total_commissions'] if financial_stats else 0.0
today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
tomorrow = today + timedelta(days=1)
daily_stats = list(transactions_collection.aggregate([
{
'$match': {
'agent_id': {'$in': member_ids},
'created_at': {'$gte': today, '$lt': tomorrow}
}
},
{
'$group': {
'_id': None,
'daily_transactions': {'$sum': 1},
'daily_volume': {'$sum': {'$toDouble': '$montant_total_debite'}},
'daily_commissions': {'$sum': {'$toDouble': '$commission_agent'}}
}
}
]))
return jsonify({
'success': True,
'enterprise_id': enterprise_id,
'nom_entreprise': enterprise.get('nom_entreprise'),
'solde_entreprise': float(enterprise.get('solde_entreprise', 0)),
'commission_entreprise': float(enterprise.get('commission_entreprise', 0)),
'status': enterprise.get('status'),
'total_members': len(member_ids),
'statistics': {
'total_transactions': total_transactions,
'total_volume': round(total_volume, 2),
'total_commissions': round(total_commissions, 2),
'daily_transactions': daily_stats[0]['daily_transactions'] if daily_stats else 0,
'daily_volume': round(daily_stats[0]['daily_volume'] if daily_stats else 0, 2),
'daily_commissions': round(daily_stats[0]['daily_commissions'] if daily_stats else 0, 2)
},
'created_at': enterprise.get('created_at').isoformat() if enterprise.get('created_at') else None
}), 200
except Exception as e:
logger.error(f"❌ Erreur récupération solde entreprise: {e}")
return jsonify({
'success': False,
'message': f'Erreur: {str(e)}'
}), 500
@tpe_bp.route('/enterprise/<enterprise_id>/recharge', methods=['POST'])
def recharge_enterprise_balance(enterprise_id):
"""Recharger le solde d'une entreprise"""
try:
data = request.get_json()
if 'montant' not in data:
return jsonify({
'success': False,
'message': 'Le champ montant est requis'
}), 400
montant = float(data['montant'])
if montant <= 0:
return jsonify({
'success': False,
'message': 'Le montant doit être positif'
}), 400
enterprise = enterprises_collection.find_one({'enterprise_id': enterprise_id})
if not enterprise:
return jsonify({
'success': False,
'message': f'Entreprise {enterprise_id} introuvable'
}), 404
ancien_solde = enterprise.get('solde_entreprise', 0.0)
nouveau_solde = ancien_solde + montant
enterprises_collection.update_one(
{'enterprise_id': enterprise_id},
{
'$set': {
'solde_entreprise': nouveau_solde,
'updated_at': datetime.now(timezone.utc)
},
'$inc': {'total_volume': montant}
}
)
recharge_record = {
'enterprise_id': enterprise_id,
'montant': str(int(montant)),
'type': 'enterprise_recharge',
'ancien_solde': ancien_solde,
'nouveau_solde': nouveau_solde,
'recharged_by': data.get('recharged_by', 'admin'),
'description': data.get('description', 'Recharge solde entreprise'),
'created_at': datetime.now(timezone.utc),
'status': 'completed'
}
recharges_collection.insert_one(recharge_record)
log_activity(
'enterprise_recharged',
'Recharge entreprise',
f'{enterprise_id} - {montant} FCFA',
data.get('recharged_by', 'admin'),
{'enterprise_id': enterprise_id, 'montant': montant}
)
return jsonify({
'success': True,
'message': f'Recharge de {montant} FCFA effectuée avec succès',
'enterprise_id': enterprise_id,
'montant_recharge': montant,
'ancien_solde': ancien_solde,
'nouveau_solde': nouveau_solde,
'recharge_id': str(recharge_record.get('_id'))
}), 200
except ValueError:
return jsonify({
'success': False,
'message': 'Le montant doit être un nombre valide'
}), 400
except Exception as e:
logger.error(f"❌ Erreur recharge entreprise: {e}")
return jsonify({
'success': False,
'message': f'Erreur: {str(e)}'
}), 500
@tpe_bp.route('/enterprises', methods=['GET'])
def get_all_enterprises():
"""Récupérer toutes les entreprises"""
try:
status_filter = request.args.get('status')
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 50))
filter_query = {}
if status_filter:
filter_query['status'] = status_filter
total = enterprises_collection.count_documents(filter_query)
skip = (page - 1) * limit
enterprises = list(
enterprises_collection.find(filter_query)
.sort("created_at", -1)
.skip(skip)
.limit(limit)
)
return jsonify({
'success': True,
'enterprises': serialize_objectid(enterprises),
'pagination': {
'page': page,
'limit': limit,
'total': total,
'total_pages': (total + limit - 1) // limit
}
}), 200
except Exception as e:
return jsonify({
'success': False,
'message': f'Erreur: {str(e)}'
}), 500
@tpe_bp.route('/member/<agent_id>/info', methods=['GET'])
def get_member_info(agent_id):
"""Récupérer les informations complètes d'un membre"""
try:
member = users_collection.find_one({'agent_id': agent_id}, {'pin': 0, 'pin_hash': 0})
if not member:
return jsonify({
'success': False,
'message': 'Membre non trouvé'
}), 404
response_data = {
'member': serialize_objectid(member)
}
if member.get('type') == 'enterprise_member' and member.get('enterprise_id'):
enterprise = enterprises_collection.find_one(
{'enterprise_id': member.get('enterprise_id')}
)
if enterprise:
response_data['enterprise'] = {
'enterprise_id': enterprise.get('enterprise_id'),
'nom_entreprise': enterprise.get('nom_entreprise'),
'solde_entreprise': enterprise.get('solde_entreprise', 0),
'status': enterprise.get('status'),
'total_members': enterprise.get('total_members', 0)
}
trans_count = transactions_collection.count_documents({'agent_id': agent_id})
volume_result = list(transactions_collection.aggregate([
{'$match': {'agent_id': agent_id}},
{'$group': {
'_id': None,
'total_volume': {'$sum': {'$toDouble': '$montant_total_debite'}},
'total_commissions': {'$sum': {'$toDouble': '$commission_agent'}}
}}
]))
response_data['member_stats'] = {
'total_transactions': trans_count,
'total_volume': round(volume_result[0]['total_volume'] if volume_result else 0, 2),
'total_commissions': round(volume_result[0]['total_commissions'] if volume_result else 0, 2)
}
return jsonify({
'success': True,
**response_data
}), 200
except Exception as e:
logger.error(f"❌ Erreur récupération info membre: {e}")
return jsonify({
'success': False,
'message': f'Erreur: {str(e)}'
}), 500
# ===== ROUTES ADMIN =====
@tpe_bp.route('/admin/system/stats', methods=['GET'])
def get_system_stats():
"""Récupérer les statistiques système"""
try:
agents_stats = users_collection.aggregate([
{'$group': {'_id': '$status', 'count': {'$sum': 1}}}
])
active_agents = 0
for stat in agents_stats:
if stat['_id'] == 'active':
active_agents = stat['count']
today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
tomorrow = today + timedelta(days=1)
daily_transactions = transactions_collection.count_documents({
'created_at': {'$gte': today, '$lt': tomorrow}
})
volume_result = list(transactions_collection.aggregate([
{'$group': {
'_id': None,
'total_volume': {'$sum': {'$toDouble': '$montant_total_debite'}},
'total_commissions': {'$sum': {'$toDouble': '$commission_agent'}}
}}
]))
total_volume = volume_result[0]['total_volume'] if volume_result else 0.0
total_commissions = volume_result[0]['total_commissions'] if volume_result else 0.0
total_transactions = transactions_collection.count_documents({})
success_transactions = transactions_collection.count_documents({'status': 'completed'})
success_rate = (success_transactions / total_transactions * 100) if total_transactions > 0 else 0.0
pending_recharges = recharges_collection.count_documents({'status': 'pending'})
# Stats entreprises
total_enterprises = enterprises_collection.count_documents({})
total_enterprise_members = users_collection.count_documents({'type': 'enterprise_member'})
return jsonify({
'success': True,
'stats': {
'active_agents': active_agents,
'daily_transactions': daily_transactions,
'total_volume': total_volume,
'total_commissions': total_commissions,
'success_rate': round(success_rate, 2),
'pending_recharges': pending_recharges,
'total_agents': users_collection.count_documents({}),
'total_enterprises': total_enterprises,
'total_enterprise_members': total_enterprise_members,
'last_updated': datetime.now().isoformat()
}
}), 200
except Exception as e:
return jsonify({
'success': False,
'message': f'Erreur: {str(e)}'
}), 500
@tpe_bp.route('/admin/agents', methods=['GET'])
def get_all_agents():
"""Récupérer tous les agents"""
try:
status_filter = request.args.get('status')
search = request.args.get('search')
agent_type = request.args.get('type')
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 50))
filter_query = {}
if status_filter:
filter_query['status'] = status_filter
if agent_type:
filter_query['type'] = agent_type
if search:
filter_query['$or'] = [
{'nom': {'$regex': search, '$options': 'i'}},
{'agent_id': {'$regex': search, '$options': 'i'}},
{'email': {'$regex': search, '$options': 'i'}}
]
total = users_collection.count_documents(filter_query)
skip = (page - 1) * limit
agents = list(
users_collection.find(filter_query)
.sort("created_at", -1)
.skip(skip)
.limit(limit)
)
# Enrichir avec infos entreprise si nécessaire
for agent in agents:
if agent.get('type') == 'enterprise_member' and agent.get('enterprise_id'):
enterprise = enterprises_collection.find_one({'enterprise_id': agent.get('enterprise_id')})
if enterprise:
agent['enterprise_name'] = enterprise.get('nom_entreprise')
return jsonify({
'success': True,
'agents': serialize_objectid(agents),
'pagination': {
'page': page,
'limit': limit,
'total': total,
'total_pages': (total + limit - 1) // limit,
'has_next': skip + limit < total,
'has_prev': page > 1
}
}), 200
except Exception as e:
return jsonify({
'success': False,
'message': f'Erreur: {str(e)}'
}), 500
@tpe_bp.route('/admin/dashboard/enterprises', methods=['GET'])
def get_enterprises_dashboard():
"""Dashboard récapitulatif pour toutes les entreprises"""
try:
total_enterprises = enterprises_collection.count_documents({})
active_enterprises = enterprises_collection.count_documents({'status': 'active'})
total_balance_result = list(enterprises_collection.aggregate([
{'$group': {
'_id': None,
'total_balance': {'$sum': {'$toDouble': '$solde_entreprise'}},
'total_commissions': {'$sum': {'$toDouble': '$commission_entreprise'}}
}}
]))
total_members = users_collection.count_documents({'type': 'enterprise_member'})
top_enterprises = list(enterprises_collection.aggregate([
{'$sort': {'total_volume': -1}},
{'$limit': 10},
{'$project': {
'enterprise_id': 1,
'nom_entreprise': 1,
'solde_entreprise': 1,
'total_transactions': 1,
'total_volume': 1,
'total_members': 1
}}
]))
today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
tomorrow = today + timedelta(days=1)
all_enterprise_members = list(users_collection.find(
{'type': 'enterprise_member'},
{'agent_id': 1}
))
all_member_ids = [m['agent_id'] for m in all_enterprise_members]
daily_enterprise_transactions = transactions_collection.count_documents({
'agent_id': {'$in': all_member_ids},
'created_at': {'$gte': today, '$lt': tomorrow}
})
return jsonify({
'success': True,
'dashboard': {
'total_enterprises': total_enterprises,
'active_enterprises': active_enterprises,
'total_balance': round(total_balance_result[0]['total_balance'] if total_balance_result else 0, 2),
'total_commissions': round(total_balance_result[0]['total_commissions'] if total_balance_result else 0, 2),
'total_members': total_members,
'daily_transactions': daily_enterprise_transactions,
'top_enterprises': serialize_objectid(top_enterprises)
},
'generated_at': datetime.now().isoformat()
}), 200
except Exception as e:
logger.error(f"❌ Erreur dashboard entreprises: {e}")
return jsonify({
'success': False,
'message': f'Erreur: {str(e)}'
}), 500
# ===== ROUTES UTILITAIRES =====
@tpe_bp.route('/health', methods=['GET'])
def health_check():
"""Vérification de l'état de l'API"""
try:
mongo.TPE.command('ping')
total_users = users_collection.count_documents({})
total_transactions = transactions_collection.count_documents({})
total_enterprises = enterprises_collection.count_documents({})
return jsonify({
'success': True,
'message': 'API opérationnelle',
'database': 'MongoDB connecté',
'stats': {
'total_users': total_users,
'total_transactions': total_transactions,
'total_enterprises': total_enterprises
},
'timestamp': datetime.now(timezone.utc).isoformat()
}), 200
except Exception as e:
return jsonify({
'success': False,
'message': 'Erreur de connexion à la base de données',
'error': str(e)
}), 500
@tpe_bp.route('/api/docs', methods=['GET'])
def api_documentation():
"""Documentation complète de l'API TPE"""
docs = {
"version": "2.0.0",
"description": "API TPE avec support des comptes entreprise",
"endpoints": {
"Authentification": {
"POST /tpe/login": "Connexion agent ou membre entreprise",
"POST /tpe/users/register": "Créer un compte agent individuel"
},
"Gestion Agents Individuels": {
"GET /tpe/agent/{agent_id}/balance": "Récupérer le solde",
"POST /tpe/agent/{agent_id}/recharge": "Recharger le solde",
"GET /tpe/agent/{agent_id}/transactions": "Historique transactions",
"GET /tpe/agent/{agent_id}/stats": "Statistiques agent"
},
"Gestion Entreprises": {
"POST /tpe/enterprise/create": "Créer une entreprise",
"GET /tpe/enterprises": "Liste des entreprises",
"GET /tpe/enterprise/{id}/balance": "Solde entreprise",
"POST /tpe/enterprise/{id}/recharge": "Recharger entreprise",
"POST /tpe/enterprise/{id}/add-member": "Ajouter un membre",
"GET /tpe/enterprise/{id}/members": "Liste des membres",
"GET /tpe/member/{agent_id}/info": "Info membre"
},
"Paiements (Fonctionne pour agents ET membres entreprise)": {
"POST /tpe/speed_paiement_tpe": "Paiement Internet",
"POST /tpe/pelisa_paiement_tpe": "Paiement Pelisa",
"POST /tpe/e2c_paiement_tpe": "Paiement E2C"
},
"Services": {
"GET /tpe/get_services_tpe": "Liste des services",
"POST /tpe/get_abn_info_tpe": "Info abonnement Internet",
"POST /tpe/get_pelisa_info_tpe": "Info compteur Pelisa",
"POST /tpe/get_e2c_info_tpe": "Info client E2C"
},
"Recharges": {
"POST /tpe/recharge/create/agent/{agent_id}": "Créer demande recharge",
"GET /tpe/recharge/get_recharges": "Liste des recharges",
"PUT /tpe/recharge/approve/{recharge_id}/agent/{agent_id}": "Approuver recharge"
},
"Administration": {
"GET /tpe/admin/system/stats": "Statistiques système",
"GET /tpe/admin/agents": "Liste agents (avec filtres)",
"GET /tpe/admin/dashboard/enterprises": "Dashboard entreprises"
},
"Utilitaires": {
"GET /tpe/health": "Vérifier l'état de l'API",
"GET /tpe/api/docs": "Documentation de l'API"
}
},
"features": {
"Comptes Entreprise": "Support complet des comptes entreprise avec solde partagé",
"Rétrocompatibilité": "Les agents individuels fonctionnent exactement comme avant",
"Détection Automatique": "Le système détecte automatiquement le type de compte",
"Traçabilité": "Toutes les transactions indiquent la source du solde (personnel/entreprise)"
},
"exemples": {
"Créer entreprise et ajouter 2 membres": {
"1_create_enterprise": {
"method": "POST",
"url": "/tpe/enterprise/create",
"body": {
"nom_entreprise": "TechCorp SARL",
"email_entreprise": "contact@techcorp.cg",
"telephone_entreprise": "+242 06 123 45 67"
}
},
"2_add_member_1": {
"method": "POST",
"url": "/tpe/enterprise/ENT1234/add-member",
"body": {
"nom": "Jean Dupont",
"telephone": "+242 06 111 11 11",
"role_dans_entreprise": "Caissier"
}
},
"3_add_member_2": {
"method": "POST",
"url": "/tpe/enterprise/ENT1234/add-member",
"body": {
"nom": "Marie Martin",
"telephone": "+242 06 222 22 22",
"role_dans_entreprise": "Manager"
}
},
"4_recharge_enterprise": {
"method": "POST",
"url": "/tpe/enterprise/ENT1234/recharge",
"body": {
"montant": 100000,
"recharged_by": "admin"
}
},
"5_member_payment": {
"method": "POST",
"url": "/tpe/speed_paiement_tpe",
"body": {
"agent_id": "WRT5001",
"numAbn": "12345"
},
"note": "Débite automatiquement le solde de l'entreprise ENT1234"
}
}
}
}
return jsonify(docs), 200
# ===== GESTION DES ERREURS =====
@tpe_bp.errorhandler(404)
def not_found(error):
return jsonify({'success': False, 'message': 'Endpoint non trouvé'}), 404
@tpe_bp.errorhandler(500)
def internal_error(error):
return jsonify({'success': False, 'message': 'Erreur interne du serveur'}), 500
@tpe_bp.errorhandler(400)
def bad_request(error):
return jsonify({'success': False, 'message': 'Requête invalide'}), 400
# ===== MESSAGE DE DÉMARRAGE =====
logger.info("=" * 60)
logger.info("🚀 API TPE avec Support Entreprise chargée")
logger.info("=" * 60)
logger.info("✅ Collections:")
logger.info(" - users_collection (agents individuels + membres entreprise)")
logger.info(" - enterprises_collection")
logger.info(" - enterprise_members_collection")
logger.info(" - transactions_collection")
logger.info(" - recharges_collection")
logger.info(" - services_collection")
logger.info(" - activities_collection")
logger.info(" - commission_withdrawals_collection")
logger.info("")
logger.info("✅ Fonctionnalités:")
logger.info(" - Agents individuels: Fonctionnement INCHANGÉ")
logger.info(" - Comptes entreprise: Solde partagé entre membres")
logger.info(" - Détection automatique du type de compte")
logger.info(" - Toutes vos routes existantes compatibles")
logger.info("")
logger.info("📚 Documentation disponible: GET /tpe/api/docs")
logger.info("=" * 60)