import type { ESignAdapter, SendParams, WebhookResult } from "./types" // DocuSign eSignature REST API. Uses a pre-obtained access token (via JWT grant // or OAuth) — set DOCUSIGN_ACCESS_TOKEN / DOCUSIGN_ACCOUNT_ID / DOCUSIGN_BASE_URI. // Docs: https://developers.docusign.com/docs/esign-rest-api/reference/envelopes/envelopes/create/ const ACCESS_TOKEN = process.env.DOCUSIGN_ACCESS_TOKEN ?? "" const ACCOUNT_ID = process.env.DOCUSIGN_ACCOUNT_ID ?? "" const BASE_URI = (process.env.DOCUSIGN_BASE_URI ?? "https://demo.docusign.net").replace(/\/+$/, "") function extOf(name: string): string { const e = name.split(".").pop()?.toLowerCase() return e && /^(pdf|docx?|png|jpe?g)$/.test(e) ? e : "pdf" } export const docusign: ESignAdapter = { id: "docusign", label: "DocuSign", configured: () => Boolean(ACCESS_TOKEN && ACCOUNT_ID), async send({ document, documentName, signerEmail, signerName, subject }: SendParams) { const envelope = { emailSubject: subject, status: "sent", documents: [{ documentBase64: document.toString("base64"), name: documentName, fileExtension: extOf(documentName), documentId: "1" }], recipients: { signers: [ { email: signerEmail, name: signerName, recipientId: "1", routingOrder: "1", // Default sign placement (bottom of page 1). Use a template/anchor // string for precise field placement in production. tabs: { signHereTabs: [{ documentId: "1", pageNumber: "1", xPosition: "100", yPosition: "650" }] }, }, ], }, } const res = await fetch(`${BASE_URI}/restapi/v2.1/accounts/${ACCOUNT_ID}/envelopes`, { method: "POST", headers: { Authorization: `Bearer ${ACCESS_TOKEN}`, "Content-Type": "application/json" }, body: JSON.stringify(envelope), }) if (!res.ok) throw new Error(`DocuSign ${res.status}: ${(await res.text()).slice(0, 300)}`) const j = (await res.json()) as { envelopeId?: string } if (!j.envelopeId) throw new Error("DocuSign did not return an envelopeId") return { externalId: j.envelopeId } }, parseWebhook(body): WebhookResult | null { // DocuSign Connect (JSON format) payload. try { const j = JSON.parse(body) as { event?: string; data?: { envelopeId?: string; envelopeSummary?: { status?: string } } } const externalId = j.data?.envelopeId const status = (j.data?.envelopeSummary?.status ?? j.event ?? "").toLowerCase() if (!externalId) return null if (status.includes("completed") || status.includes("signed")) return { externalId, status: "signed" } if (status.includes("declined")) return { externalId, status: "declined" } if (status.includes("voided")) return { externalId, status: "voided" } return null } catch { return null } }, }