refactor(api): simplify Universign transaction flow and error handling
This commit is contained in:
parent
f357e9614b
commit
d921c7100a
1 changed files with 137 additions and 180 deletions
317
api.py
317
api.py
|
|
@ -1190,21 +1190,21 @@ templates_signature_email = {
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
""",
|
""",
|
||||||
"variables_disponibles": [
|
"variables_disponibles": [
|
||||||
"NOM_SIGNATAIRE",
|
"NOM_SIGNATAIRE",
|
||||||
"TYPE_DOC",
|
"TYPE_DOC",
|
||||||
"NUMERO",
|
"NUMERO",
|
||||||
"DATE",
|
"DATE",
|
||||||
"MONTANT_TTC",
|
"MONTANT_TTC",
|
||||||
"SIGNER_URL",
|
"SIGNER_URL",
|
||||||
"CONTACT_EMAIL",
|
"CONTACT_EMAIL",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"signature_confirmee": {
|
"signature_confirmee": {
|
||||||
"id": "signature_confirmee",
|
"id": "signature_confirmee",
|
||||||
"nom": "Confirmation de Signature",
|
"nom": "Confirmation de Signature",
|
||||||
"sujet": "✅ Document signé - {{TYPE_DOC}} {{NUMERO}}",
|
"sujet": "✅ Document signé - {{TYPE_DOC}} {{NUMERO}}",
|
||||||
"corps_html": """
|
"corps_html": """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
|
@ -1301,20 +1301,20 @@ templates_signature_email = {
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
""",
|
""",
|
||||||
"variables_disponibles": [
|
"variables_disponibles": [
|
||||||
"NOM_SIGNATAIRE",
|
"NOM_SIGNATAIRE",
|
||||||
"TYPE_DOC",
|
"TYPE_DOC",
|
||||||
"NUMERO",
|
"NUMERO",
|
||||||
"DATE_SIGNATURE",
|
"DATE_SIGNATURE",
|
||||||
"TRANSACTION_ID",
|
"TRANSACTION_ID",
|
||||||
"CONTACT_EMAIL",
|
"CONTACT_EMAIL",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"relance_signature": {
|
"relance_signature": {
|
||||||
"id": "relance_signature",
|
"id": "relance_signature",
|
||||||
"nom": "Relance Signature en Attente",
|
"nom": "Relance Signature en Attente",
|
||||||
"sujet": "⏰ Rappel - Signature en attente {{TYPE_DOC}} {{NUMERO}}",
|
"sujet": "⏰ Rappel - Signature en attente {{TYPE_DOC}} {{NUMERO}}",
|
||||||
"corps_html": """
|
"corps_html": """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
|
@ -1431,240 +1431,170 @@ async def universign_envoyer_avec_email(
|
||||||
auth = (api_key, "")
|
auth = (api_key, "")
|
||||||
|
|
||||||
logger.info(f"🔐 Démarrage processus Universign pour {email}")
|
logger.info(f"🔐 Démarrage processus Universign pour {email}")
|
||||||
logger.info(f"📌 API URL: {api_url}")
|
logger.info(f"📌 Document: {doc_id} ({doc_data.get('type_label')})")
|
||||||
logger.info(f"📌 Document: {doc_id} - Type: {doc_data.get('type_label')}")
|
|
||||||
|
|
||||||
# ========================================
|
# ========================================
|
||||||
# VÉRIFICATION PRÉLIMINAIRE : PDF valide ?
|
# VÉRIFICATION : PDF valide ?
|
||||||
# ========================================
|
# ========================================
|
||||||
if not pdf_bytes or len(pdf_bytes) == 0:
|
if not pdf_bytes or len(pdf_bytes) == 0:
|
||||||
logger.error(f"❌ PDF vide ou None")
|
raise Exception("Le PDF généré est vide")
|
||||||
raise Exception("Le PDF généré est vide. Vérifiez la génération du PDF.")
|
|
||||||
|
logger.info(f"✅ PDF valide : {len(pdf_bytes)} octets")
|
||||||
logger.info(f"✅ PDF reçu : {len(pdf_bytes)} octets")
|
|
||||||
|
|
||||||
# ========================================
|
# ========================================
|
||||||
# ÉTAPE 1 : Créer la transaction
|
# ÉTAPE 1 : Créer la transaction
|
||||||
# ========================================
|
# ========================================
|
||||||
logger.info(f"📝 ÉTAPE 1/7 : Création transaction")
|
logger.info(f"📝 ÉTAPE 1/7 : Création transaction")
|
||||||
|
|
||||||
transaction_payload = {
|
|
||||||
"name": f"{doc_data.get('type_label', 'Document')} {doc_id}",
|
|
||||||
"language": "fr",
|
|
||||||
}
|
|
||||||
|
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{api_url}/transactions",
|
f"{api_url}/transactions",
|
||||||
auth=auth,
|
auth=auth,
|
||||||
json=transaction_payload,
|
json={
|
||||||
|
"name": f"{doc_data.get('type_label', 'Document')} {doc_id}",
|
||||||
|
"language": "fr",
|
||||||
|
},
|
||||||
timeout=30,
|
timeout=30,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"Status création transaction: {response.status_code}")
|
|
||||||
|
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
logger.error(f"❌ Réponse: {response.text}")
|
logger.error(f"❌ Erreur création transaction: {response.text}")
|
||||||
raise Exception(f"Erreur création transaction: {response.status_code} - {response.text}")
|
raise Exception(f"Erreur création transaction: {response.status_code}")
|
||||||
|
|
||||||
transaction_data = response.json()
|
transaction_id = response.json().get("id")
|
||||||
transaction_id = transaction_data.get("id")
|
|
||||||
|
|
||||||
if not transaction_id:
|
|
||||||
logger.error(f"❌ Pas de transaction_id dans la réponse: {transaction_data}")
|
|
||||||
raise Exception("Transaction créée mais ID manquant")
|
|
||||||
|
|
||||||
logger.info(f"✅ Transaction créée: {transaction_id}")
|
logger.info(f"✅ Transaction créée: {transaction_id}")
|
||||||
|
|
||||||
# ========================================
|
# ========================================
|
||||||
# ÉTAPE 2 : Upload du fichier PDF
|
# ÉTAPE 2 : Upload du fichier PDF
|
||||||
# ========================================
|
# ========================================
|
||||||
logger.info(f"📄 ÉTAPE 2/7 : Upload PDF")
|
logger.info(f"📄 ÉTAPE 2/7 : Upload PDF")
|
||||||
|
|
||||||
# Préparer le fichier multipart
|
|
||||||
filename = f"{doc_data.get('type_label', 'Document')}_{doc_id}.pdf"
|
|
||||||
files = {
|
files = {
|
||||||
"file": (filename, pdf_bytes, "application/pdf")
|
"file": (
|
||||||
|
f"{doc_data.get('type_label', 'Document')}_{doc_id}.pdf",
|
||||||
|
pdf_bytes,
|
||||||
|
"application/pdf",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug(f"Upload fichier: {filename}, taille: {len(pdf_bytes)} octets")
|
|
||||||
|
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{api_url}/files",
|
f"{api_url}/files",
|
||||||
auth=auth,
|
auth=auth,
|
||||||
files=files,
|
files=files,
|
||||||
timeout=60, # Plus de timeout pour gros fichiers
|
timeout=60,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"Status upload fichier: {response.status_code}")
|
|
||||||
|
|
||||||
if response.status_code not in [200, 201]:
|
if response.status_code not in [200, 201]:
|
||||||
logger.error(f"❌ Erreur upload: {response.text}")
|
logger.error(f"❌ Erreur upload: {response.text}")
|
||||||
raise Exception(f"Erreur upload fichier: {response.status_code} - {response.text}")
|
raise Exception(f"Erreur upload fichier: {response.status_code}")
|
||||||
|
|
||||||
# Parser la réponse
|
file_id = response.json().get("id")
|
||||||
try:
|
|
||||||
file_data = response.json()
|
|
||||||
logger.debug(f"Réponse upload: {file_data}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"❌ Erreur parsing réponse upload: {e}")
|
|
||||||
logger.error(f"Réponse brute: {response.text[:500]}")
|
|
||||||
raise Exception(f"Réponse upload invalide: {e}")
|
|
||||||
|
|
||||||
file_id = file_data.get("id")
|
|
||||||
|
|
||||||
if not file_id:
|
if not file_id:
|
||||||
logger.error(f"❌ Pas de file_id dans la réponse")
|
logger.error(f"❌ Pas de file_id retourné")
|
||||||
logger.error(f"Réponse complète: {file_data}")
|
|
||||||
raise Exception("Upload réussi mais file_id manquant")
|
raise Exception("Upload réussi mais file_id manquant")
|
||||||
|
|
||||||
logger.info(f"✅ Fichier uploadé: {file_id}")
|
logger.info(f"✅ Fichier uploadé: {file_id}")
|
||||||
|
|
||||||
# ========================================
|
# ========================================
|
||||||
# ÉTAPE 3 : Ajouter le document à la transaction
|
# ÉTAPE 3 : Ajouter le document (form-data)
|
||||||
# ========================================
|
# ========================================
|
||||||
logger.info(f"📋 ÉTAPE 3/7 : Ajout document à transaction {transaction_id}")
|
logger.info(f"📋 ÉTAPE 3/7 : Ajout document à transaction")
|
||||||
|
|
||||||
document_payload = {
|
|
||||||
"document": file_id # ✅ Utiliser "document" pas "file"
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug(f"Payload document: {document_payload}")
|
|
||||||
logger.debug(f"URL: {api_url}/transactions/{transaction_id}/documents")
|
|
||||||
|
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{api_url}/transactions/{transaction_id}/documents",
|
f"{api_url}/transactions/{transaction_id}/documents",
|
||||||
auth=auth,
|
auth=auth,
|
||||||
json=document_payload,
|
data={"document": file_id}, # ✅ UTILISER data= (form-data)
|
||||||
timeout=30,
|
timeout=30,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"Status ajout document: {response.status_code}")
|
|
||||||
|
|
||||||
if response.status_code not in [200, 201]:
|
if response.status_code not in [200, 201]:
|
||||||
logger.error(f"❌ Erreur ajout document: {response.text}")
|
logger.error(f"❌ Erreur ajout document: {response.text}")
|
||||||
logger.error(f"Payload envoyé: {document_payload}")
|
raise Exception(f"Erreur ajout document: {response.status_code}")
|
||||||
logger.error(f"file_id utilisé: '{file_id}' (type: {type(file_id)})")
|
|
||||||
raise Exception(f"Erreur ajout document: {response.status_code} - {response.text}")
|
document_id = response.json().get("id")
|
||||||
|
|
||||||
try:
|
|
||||||
document_data = response.json()
|
|
||||||
logger.debug(f"Réponse ajout document: {document_data}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"❌ Erreur parsing réponse document: {e}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
document_id = document_data.get("id")
|
|
||||||
|
|
||||||
if not document_id:
|
|
||||||
logger.error(f"❌ Pas de document_id dans la réponse")
|
|
||||||
logger.error(f"Réponse complète: {document_data}")
|
|
||||||
raise Exception("Document ajouté mais ID manquant")
|
|
||||||
|
|
||||||
logger.info(f"✅ Document ajouté: {document_id}")
|
logger.info(f"✅ Document ajouté: {document_id}")
|
||||||
|
|
||||||
# ========================================
|
# ========================================
|
||||||
# ÉTAPE 4 : Créer le signataire
|
# ÉTAPE 4 : Créer le signataire
|
||||||
# ========================================
|
# ========================================
|
||||||
logger.info(f"👤 ÉTAPE 4/7 : Création signataire")
|
logger.info(f"👤 ÉTAPE 4/7 : Création signataire")
|
||||||
|
|
||||||
nom_parts = nom.split()
|
nom_parts = nom.split()
|
||||||
first_name = nom_parts[0] if len(nom_parts) > 0 else nom
|
first_name = nom_parts[0] if len(nom_parts) > 0 else nom
|
||||||
last_name = " ".join(nom_parts[1:]) if len(nom_parts) > 1 else ""
|
last_name = " ".join(nom_parts[1:]) if len(nom_parts) > 1 else ""
|
||||||
|
|
||||||
signer_payload = {
|
|
||||||
"email": email,
|
|
||||||
"firstName": first_name,
|
|
||||||
"lastName": last_name,
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug(f"Payload signataire: {signer_payload}")
|
|
||||||
|
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{api_url}/transactions/{transaction_id}/signers",
|
f"{api_url}/transactions/{transaction_id}/signers",
|
||||||
auth=auth,
|
auth=auth,
|
||||||
json=signer_payload,
|
data={ # ✅ UTILISER data= (form-data)
|
||||||
|
"email": email,
|
||||||
|
"firstName": first_name,
|
||||||
|
"lastName": last_name,
|
||||||
|
},
|
||||||
timeout=30,
|
timeout=30,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"Status création signataire: {response.status_code}")
|
|
||||||
|
|
||||||
if response.status_code not in [200, 201]:
|
if response.status_code not in [200, 201]:
|
||||||
logger.error(f"❌ Erreur création signataire: {response.text}")
|
logger.error(f"❌ Erreur création signataire: {response.text}")
|
||||||
raise Exception(f"Erreur création signataire: {response.status_code} - {response.text}")
|
raise Exception(f"Erreur création signataire: {response.status_code}")
|
||||||
|
|
||||||
signer_data = response.json()
|
signer_id = response.json().get("id")
|
||||||
signer_id = signer_data.get("id")
|
|
||||||
|
|
||||||
if not signer_id:
|
|
||||||
logger.error(f"❌ Pas de signer_id dans la réponse")
|
|
||||||
raise Exception("Signataire créé mais ID manquant")
|
|
||||||
|
|
||||||
logger.info(f"✅ Signataire créé: {signer_id}")
|
logger.info(f"✅ Signataire créé: {signer_id}")
|
||||||
|
|
||||||
# ========================================
|
# ========================================
|
||||||
# ÉTAPE 5 : Créer le champ de signature
|
# ÉTAPE 5 : Créer le champ de signature
|
||||||
# ========================================
|
# ========================================
|
||||||
logger.info(f"✍️ ÉTAPE 5/7 : Création champ signature")
|
logger.info(f"✍️ ÉTAPE 5/7 : Création champ signature")
|
||||||
|
|
||||||
field_payload = {
|
|
||||||
"type": "signature",
|
|
||||||
"page": 1,
|
|
||||||
"signer": signer_id,
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug(f"Payload champ: {field_payload}")
|
|
||||||
|
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{api_url}/transactions/{transaction_id}/documents/{document_id}/fields",
|
f"{api_url}/transactions/{transaction_id}/documents/{document_id}/fields",
|
||||||
auth=auth,
|
auth=auth,
|
||||||
json=field_payload,
|
data={ # ✅ UTILISER data= (form-data)
|
||||||
|
"type": "signature",
|
||||||
|
"page": "1",
|
||||||
|
"signer": signer_id,
|
||||||
|
},
|
||||||
timeout=30,
|
timeout=30,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"Status création champ: {response.status_code}")
|
|
||||||
|
|
||||||
if response.status_code not in [200, 201]:
|
if response.status_code not in [200, 201]:
|
||||||
logger.error(f"❌ Erreur création champ: {response.text}")
|
logger.error(f"❌ Erreur création champ: {response.text}")
|
||||||
raise Exception(f"Erreur création champ: {response.status_code} - {response.text}")
|
raise Exception(f"Erreur création champ: {response.status_code}")
|
||||||
|
|
||||||
field_data = response.json()
|
field_id = response.json().get("id")
|
||||||
field_id = field_data.get("id")
|
|
||||||
|
|
||||||
logger.info(f"✅ Champ créé: {field_id}")
|
logger.info(f"✅ Champ créé: {field_id}")
|
||||||
|
|
||||||
# ========================================
|
# ========================================
|
||||||
# ÉTAPE 6 : Démarrer la transaction
|
# ÉTAPE 6 : Démarrer la transaction
|
||||||
# ========================================
|
# ========================================
|
||||||
logger.info(f"🚀 ÉTAPE 6/7 : Démarrage transaction")
|
logger.info(f"🚀 ÉTAPE 6/7 : Démarrage transaction")
|
||||||
|
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{api_url}/transactions/{transaction_id}/start",
|
f"{api_url}/transactions/{transaction_id}/start", auth=auth, timeout=30
|
||||||
auth=auth,
|
|
||||||
json={},
|
|
||||||
timeout=30
|
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"Status démarrage: {response.status_code}")
|
|
||||||
|
|
||||||
if response.status_code not in [200, 201]:
|
if response.status_code not in [200, 201]:
|
||||||
logger.error(f"❌ Erreur démarrage: {response.text}")
|
logger.error(f"❌ Erreur démarrage: {response.text}")
|
||||||
raise Exception(f"Erreur démarrage: {response.status_code} - {response.text}")
|
raise Exception(f"Erreur démarrage: {response.status_code}")
|
||||||
|
|
||||||
final_data = response.json()
|
final_data = response.json()
|
||||||
|
logger.info(f"✅ Transaction démarrée")
|
||||||
|
|
||||||
# ========================================
|
# ========================================
|
||||||
# ÉTAPE 7 : Récupérer l'URL de signature
|
# ÉTAPE 7 : Récupérer l'URL de signature
|
||||||
# ========================================
|
# ========================================
|
||||||
logger.info(f"🔗 ÉTAPE 7/7 : Récupération URL")
|
logger.info(f"🔗 ÉTAPE 7/7 : Récupération URL")
|
||||||
|
|
||||||
signer_url = ""
|
signer_url = ""
|
||||||
|
|
||||||
|
# Chercher dans la réponse du /start
|
||||||
if final_data.get("signers"):
|
if final_data.get("signers"):
|
||||||
for signer in final_data["signers"]:
|
for signer in final_data["signers"]:
|
||||||
if signer.get("email") == email:
|
if signer.get("email") == email:
|
||||||
signer_url = signer.get("url", "")
|
signer_url = signer.get("url", "")
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# Si pas trouvée, interroger le signataire directement
|
||||||
if not signer_url:
|
if not signer_url:
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
f"{api_url}/transactions/{transaction_id}/signers/{signer_id}",
|
f"{api_url}/transactions/{transaction_id}/signers/{signer_id}",
|
||||||
|
|
@ -1676,16 +1606,24 @@ async def universign_envoyer_avec_email(
|
||||||
|
|
||||||
if not signer_url:
|
if not signer_url:
|
||||||
logger.error(f"❌ URL de signature introuvable")
|
logger.error(f"❌ URL de signature introuvable")
|
||||||
raise ValueError("URL de signature non retournée")
|
raise ValueError("URL de signature non retournée par Universign")
|
||||||
|
|
||||||
logger.info(f"✅ URL récupérée")
|
logger.info(f"✅ URL récupérée avec succès")
|
||||||
|
|
||||||
# ========================================
|
# ========================================
|
||||||
# Créer l'email de notification
|
# Créer l'email de notification
|
||||||
# ========================================
|
# ========================================
|
||||||
|
logger.info(f"📧 Préparation email de notification")
|
||||||
|
|
||||||
template = templates_signature_email["demande_signature"]
|
template = templates_signature_email["demande_signature"]
|
||||||
|
|
||||||
type_labels = {0: "Devis", 10: "Commande", 30: "Bon de Livraison", 60: "Facture", 50: "Avoir"}
|
type_labels = {
|
||||||
|
0: "Devis",
|
||||||
|
10: "Commande",
|
||||||
|
30: "Bon de Livraison",
|
||||||
|
60: "Facture",
|
||||||
|
50: "Avoir",
|
||||||
|
}
|
||||||
|
|
||||||
variables = {
|
variables = {
|
||||||
"NOM_SIGNATAIRE": nom,
|
"NOM_SIGNATAIRE": nom,
|
||||||
|
|
@ -1704,6 +1642,7 @@ async def universign_envoyer_avec_email(
|
||||||
sujet = sujet.replace(f"{{{{{var}}}}}", str(valeur))
|
sujet = sujet.replace(f"{{{{{var}}}}}", str(valeur))
|
||||||
corps = corps.replace(f"{{{{{var}}}}}", str(valeur))
|
corps = corps.replace(f"{{{{{var}}}}}", str(valeur))
|
||||||
|
|
||||||
|
# Créer log email
|
||||||
email_log = EmailLog(
|
email_log = EmailLog(
|
||||||
id=str(uuid.uuid4()),
|
id=str(uuid.uuid4()),
|
||||||
destinataire=email,
|
destinataire=email,
|
||||||
|
|
@ -1718,9 +1657,12 @@ async def universign_envoyer_avec_email(
|
||||||
|
|
||||||
session.add(email_log)
|
session.add(email_log)
|
||||||
await session.flush()
|
await session.flush()
|
||||||
|
|
||||||
|
# Enqueue l'email
|
||||||
email_queue.enqueue(email_log.id)
|
email_queue.enqueue(email_log.id)
|
||||||
|
|
||||||
logger.info(f"✅ Processus terminé avec succès")
|
logger.info(f"✅ Email mis en file pour {email}")
|
||||||
|
logger.info(f"🎉 Processus Universign terminé avec succès")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"transaction_id": transaction_id,
|
"transaction_id": transaction_id,
|
||||||
|
|
@ -1730,10 +1672,25 @@ async def universign_envoyer_avec_email(
|
||||||
"email_sent": True,
|
"email_sent": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
logger.error(f"❌ Erreur HTTP Universign: {e}")
|
||||||
|
if e.response:
|
||||||
|
logger.error(f"Status: {e.response.status_code}")
|
||||||
|
logger.error(f"Body: {e.response.text}")
|
||||||
|
return {
|
||||||
|
"error": f"Erreur Universign: {e.response.status_code if e.response else 'Unknown'} - {e.response.text if e.response else str(e)}",
|
||||||
|
"statut": "ERREUR",
|
||||||
|
"email_sent": False,
|
||||||
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ Erreur Universign: {e}", exc_info=True)
|
logger.error(f"❌ Erreur Universign: {e}", exc_info=True)
|
||||||
return {"error": str(e), "statut": "ERREUR", "email_sent": False}
|
return {
|
||||||
|
"error": str(e),
|
||||||
|
"statut": "ERREUR",
|
||||||
|
"email_sent": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def universign_statut(transaction_id: str) -> Dict:
|
async def universign_statut(transaction_id: str) -> Dict:
|
||||||
"""Récupération statut signature"""
|
"""Récupération statut signature"""
|
||||||
import requests
|
import requests
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue