132 lines
3.9 KiB
Python
132 lines
3.9 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
|
|
|
|
payload = {
|
|
"templateId": body.template_id,
|
|
"status": "sent",
|
|
"templateRoles": [
|
|
{
|
|
"email": body.recipient_email,
|
|
"name": body.recipient_name,
|
|
"roleName": "Signer",
|
|
}
|
|
],
|
|
"emailSubject": f"[Verification Test] Please sign this document",
|
|
}
|
|
|
|
async with httpx.AsyncClient() as client:
|
|
resp = await client.post(
|
|
f"{settings.docusign_base_url}/v2.1/accounts/{settings.docusign_account_id}/envelopes",
|
|
headers={
|
|
"Authorization": f"Bearer {session['docusign_access_token']}",
|
|
"Content-Type": "application/json",
|
|
},
|
|
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")}
|
|
|
|
|
|
@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}
|