"""Generic eSign helper — create signing records and send signing links. Usage from any service handler: from scripts.workers.services.telecom.esign_helper import request_esign request_esign( conn=conn, order_number="CO-ABC12345", document_type="rmd", document_title="RMD Certification Letter", entity_name="Acme Telecom LLC", customer_email="john@example.com", customer_name="John Smith", document_minio_key="compliance/CO-ABC12345/rmd_letter.pdf", requires_perjury=True, metadata={"frn": "0015341902"}, ) This will: 1. INSERT a row into esign_records (status = 'pending') 2. Generate a JWT portal token 3. Send a signing email with the portal link """ import logging import os import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText logger = logging.getLogger("esign_helper") def request_esign( conn, order_number: str, document_type: str, document_title: str, entity_name: str, customer_email: str, customer_name: str = "", document_minio_key: str = "", requires_perjury: bool = False, metadata: dict | None = None, expires_hours: int = 72, ) -> int | None: """Create an esign record and email the signing link. Returns the esign_records.id on success, None on failure. """ import json from datetime import datetime, timedelta, timezone try: import jwt as pyjwt except ImportError: try: import PyJWT as pyjwt # type: ignore except ImportError: logger.error("No JWT library available — cannot create esign link") return None secret = os.environ.get("CUSTOMER_JWT_SECRET", "changeme") domain = os.environ.get("DOMAIN", "performancewest.net") expires_at = datetime.now(timezone.utc) + timedelta(hours=expires_hours) # 1. Upsert into esign_records (replace any existing pending record) try: with conn.cursor() as cur: cur.execute( """INSERT INTO esign_records (order_number, document_type, document_title, entity_name, document_minio_key, document_metadata, requires_perjury, status, expires_at) VALUES (%s, %s, %s, %s, %s, %s, %s, 'pending', %s) ON CONFLICT (order_number, document_type) WHERE status IN ('pending', 'signed') DO UPDATE SET document_title = EXCLUDED.document_title, entity_name = EXCLUDED.entity_name, document_minio_key = EXCLUDED.document_minio_key, document_metadata = EXCLUDED.document_metadata, requires_perjury = EXCLUDED.requires_perjury, expires_at = EXCLUDED.expires_at, updated_at = NOW() RETURNING id""", ( order_number, document_type, document_title, entity_name, document_minio_key, json.dumps(metadata or {}), requires_perjury, expires_at, ), ) row = cur.fetchone() esign_id = row[0] if row else None conn.commit() except Exception as exc: logger.error("Failed to insert esign record for %s: %s", order_number, exc) conn.rollback() return None # 2. Generate JWT portal token token = pyjwt.encode( { "order_id": order_number, "order_type": document_type, "email": customer_email, }, secret, algorithm="HS256", ) sign_url = f"https://{domain}/portal/esign/?token={token}" # 3. Send signing email try: _send_signing_email( to_email=customer_email, to_name=customer_name or entity_name, entity_name=entity_name, document_title=document_title, sign_url=sign_url, order_number=order_number, ) logger.info( "eSign link sent to %s for %s (%s)", customer_email, order_number, document_type, ) except Exception as exc: logger.warning("Could not send eSign email for %s: %s", order_number, exc) # Non-fatal — record exists, admin can resend return esign_id def _send_signing_email( to_email: str, to_name: str, entity_name: str, document_title: str, sign_url: str, order_number: str, ) -> None: """Send the signing invitation email.""" smtp_host = os.environ.get("SMTP_HOST", "co.carrierone.com") smtp_port = int(os.environ.get("SMTP_PORT", "587")) smtp_user = os.environ.get("SMTP_USER", "") smtp_pass = os.environ.get("SMTP_PASS", "") if not smtp_user or not smtp_pass: logger.warning("SMTP credentials not configured — skipping eSign email") return subject = f"Action Required: Sign Your {document_title}" body = f"""\

Document Ready for Signature

{entity_name}

Hi {to_name},

Your {document_title} is ready for review and signature. Please click the button below to review the document and provide your electronic signature.

Review & Sign Document

This link expires in 72 hours. If it expires, contact us and we'll send a new one.


Performance West Inc. — (888) 411-0383
Order: {order_number}

""" msg = MIMEMultipart("alternative") msg["From"] = "Performance West " msg["To"] = f"{to_name} <{to_email}>" msg["Subject"] = subject msg["Reply-To"] = "info@performancewest.net" msg.attach(MIMEText(body, "html")) with smtplib.SMTP(smtp_host, smtp_port, timeout=30) as server: server.starttls() server.login(smtp_user, smtp_pass) server.send_message(msg) def check_esign_status(conn, order_number: str, document_type: str) -> dict | None: """Check if a document has been signed. Returns the record dict or None.""" with conn.cursor() as cur: cur.execute( """SELECT id, status, signed_at, signer_email, signature_type FROM esign_records WHERE order_number = %s AND document_type = %s AND status IN ('pending', 'signed') ORDER BY created_at DESC LIMIT 1""", (order_number, document_type), ) row = cur.fetchone() if not row: return None return { "id": row[0], "status": row[1], "signed_at": row[2], "signer_email": row[3], "signature_type": row[4], }