import { env } from '@/lib/env'; import { logger } from '@/lib/logger'; import { getPortDocumensoConfig } from '@/lib/services/port-config'; interface DocumensoCreds { baseUrl: string; apiKey: string; } async function resolveCreds(portId?: string): Promise { if (!portId) return { baseUrl: env.DOCUMENSO_API_URL, apiKey: env.DOCUMENSO_API_KEY }; const cfg = await getPortDocumensoConfig(portId); return { baseUrl: cfg.apiUrl, apiKey: cfg.apiKey }; } async function documensoFetch( path: string, options?: RequestInit, portId?: string, ): Promise { const { baseUrl, apiKey } = await resolveCreds(portId); const res = await fetch(`${baseUrl}${path}`, { ...options, headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json', ...options?.headers, }, }); if (!res.ok) { const err = await res.text(); logger.error({ path, status: res.status, err, portId }, 'Documenso API error'); throw new Error(`Documenso API error: ${res.status}`); } return res.json(); } // Documenso 2.x renamed top-level `id` → `documentId` and recipient `id` → // `recipientId`; v1.13 still uses `id`. Normalize both shapes to the legacy // `id` form that this codebase consumes everywhere downstream. function normalizeDocument(raw: unknown): DocumensoDocument { const r = (raw ?? {}) as Record; const id = String(r.documentId ?? r.id ?? ''); const status = String(r.status ?? 'PENDING'); const recipientsRaw = (r.recipients as Array> | undefined) ?? []; const recipients = recipientsRaw.map((rec) => ({ id: String(rec.recipientId ?? rec.id ?? ''), name: String(rec.name ?? ''), email: String(rec.email ?? ''), role: String(rec.role ?? ''), signingOrder: Number(rec.signingOrder ?? 0), status: String(rec.signingStatus ?? rec.status ?? 'PENDING'), signingUrl: typeof rec.signingUrl === 'string' ? rec.signingUrl : undefined, embeddedUrl: typeof rec.embeddedUrl === 'string' ? rec.embeddedUrl : undefined, })); return { id, status, recipients }; } export interface DocumensoRecipient { name: string; email: string; role: string; signingOrder: number; } export interface DocumensoDocument { id: string; status: string; recipients: Array<{ id: string; name: string; email: string; role: string; signingOrder: number; status: string; signingUrl?: string; embeddedUrl?: string; }>; } export async function createDocument( title: string, pdfBase64: string, recipients: DocumensoRecipient[], portId?: string, ): Promise { return documensoFetch( '/api/v1/documents', { method: 'POST', body: JSON.stringify({ title, document: pdfBase64, recipients }), }, portId, ).then(normalizeDocument); } export async function generateDocumentFromTemplate( templateId: number, payload: Record, portId?: string, ): Promise { return documensoFetch( `/api/v1/templates/${templateId}/generate-document`, { method: 'POST', body: JSON.stringify(payload), }, portId, ).then(normalizeDocument); } export async function sendDocument(docId: string, portId?: string): Promise { return documensoFetch( `/api/v1/documents/${docId}/send`, { method: 'POST', }, portId, ).then(normalizeDocument); } export async function getDocument(docId: string, portId?: string): Promise { return documensoFetch(`/api/v1/documents/${docId}`, undefined, portId).then(normalizeDocument); } export async function sendReminder( docId: string, signerId: string, portId?: string, ): Promise { await documensoFetch( `/api/v1/documents/${docId}/recipients/${signerId}/remind`, { method: 'POST', }, portId, ); } export async function downloadSignedPdf(docId: string, portId?: string): Promise { const { baseUrl, apiKey } = await resolveCreds(portId); const res = await fetch(`${baseUrl}/api/v1/documents/${docId}/download`, { headers: { Authorization: `Bearer ${apiKey}` }, }); if (!res.ok) { const err = await res.text(); logger.error({ docId, status: res.status, err, portId }, 'Documenso download error'); throw new Error(`Documenso download error: ${res.status}`); } const arrayBuffer = await res.arrayBuffer(); return Buffer.from(arrayBuffer); } /** Convenience health-check used by the admin "Test connection" button. */ export async function checkDocumensoHealth( portId?: string, ): Promise<{ ok: boolean; status?: number; error?: string }> { try { const { baseUrl, apiKey } = await resolveCreds(portId); const res = await fetch(`${baseUrl}/api/v1/health`, { headers: { Authorization: `Bearer ${apiKey}` }, }); return { ok: res.ok, status: res.status }; } catch (err) { return { ok: false, error: err instanceof Error ? err.message : 'Unknown error' }; } }