adobe-to-docusign-migrator/web/routers/verify.py

147 lines
4.6 KiB
Python

"""
web/routers/verify.py
---------------------
Verification endpoints: send test envelopes, poll status, void.
Uses DocuSign Envelopes API to confirm migrated templates work end-to-end.
"""
from typing import Optional
import httpx
from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from web.config import settings
from web.session import get_session
router = APIRouter()
class SendRequest(BaseModel):
template_id: str
recipient_name: str
recipient_email: str
class VoidRequest(BaseModel):
reason: str = "Test envelope — voided after verification"
def _require_docusign(session: dict) -> Optional[JSONResponse]:
if not session.get("docusign_access_token"):
return JSONResponse({"error": "not authenticated to DocuSign"}, status_code=401)
return None
@router.post("/send")
async def send_test_envelope(body: SendRequest, request: Request):
"""Send a test envelope using a migrated DocuSign template."""
session = get_session(request)
err = _require_docusign(session)
if err:
return err
headers = {
"Authorization": f"Bearer {session['docusign_access_token']}",
"Content-Type": "application/json",
}
base = f"{settings.docusign_base_url}/v2.1/accounts/{settings.docusign_account_id}"
async with httpx.AsyncClient() as client:
# Fetch template to discover actual role names
tpl_resp = await client.get(f"{base}/templates/{body.template_id}", headers=headers)
role_names = []
if tpl_resp.is_success:
tpl = tpl_resp.json()
recipients = tpl.get("recipients", {})
for group in recipients.values():
if isinstance(group, list):
for r in group:
rn = r.get("roleName")
if rn and rn not in role_names:
role_names.append(rn)
# Fall back to generic role name if template fetch failed
if not role_names:
role_names = ["Signer"]
template_roles = [
{"email": body.recipient_email, "name": body.recipient_name, "roleName": rn}
for rn in role_names
]
payload = {
"templateId": body.template_id,
"status": "sent",
"templateRoles": template_roles,
"emailSubject": "[Verification Test] Please sign this document",
}
resp = await client.post(f"{base}/envelopes", headers=headers, json=payload)
if not resp.is_success:
return JSONResponse(
{"error": "DocuSign API error", "detail": resp.text},
status_code=502,
)
data = resp.json()
return {"envelope_id": data.get("envelopeId"), "roles": role_names}
@router.get("/status/{envelope_id}")
async def envelope_status(envelope_id: str, request: Request):
"""Get the current status of a test envelope."""
session = get_session(request)
err = _require_docusign(session)
if err:
return err
async with httpx.AsyncClient() as client:
resp = await client.get(
f"{settings.docusign_base_url}/v2.1/accounts/{settings.docusign_account_id}/envelopes/{envelope_id}",
headers={"Authorization": f"Bearer {session['docusign_access_token']}"},
)
if not resp.is_success:
return JSONResponse(
{"error": "DocuSign API error", "detail": resp.text},
status_code=502,
)
data = resp.json()
return {
"envelope_id": envelope_id,
"status": data.get("status"),
"completed_at": data.get("completedDateTime"),
"sent_at": data.get("sentDateTime"),
}
@router.post("/void/{envelope_id}")
async def void_envelope(envelope_id: str, body: VoidRequest, request: Request):
"""Void a test envelope after verification is complete."""
session = get_session(request)
err = _require_docusign(session)
if err:
return err
async with httpx.AsyncClient() as client:
resp = await client.put(
f"{settings.docusign_base_url}/v2.1/accounts/{settings.docusign_account_id}/envelopes/{envelope_id}",
headers={
"Authorization": f"Bearer {session['docusign_access_token']}",
"Content-Type": "application/json",
},
json={"status": "voided", "voidedReason": body.reason},
)
if not resp.is_success:
return JSONResponse(
{"error": "DocuSign API error", "detail": resp.text},
status_code=502,
)
return {"voided": True, "envelope_id": envelope_id}