Files
property-management-network/lib/esign/docusign.ts
T

66 lines
2.8 KiB
TypeScript
Raw Normal View History

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
}
},
}