393 lines
16 KiB
JavaScript
393 lines
16 KiB
JavaScript
|
||
const { Documenso } = require("@documenso/sdk-typescript");
|
||
const fs = require("fs");
|
||
const path = require("path");
|
||
const logger = require("../utils/logger");
|
||
const { QUERY_META_FOR_ESIG_COMPLETION, INSERT_ESIGNATURE_DOCUMENT, INSERT_ESIG_AUDIT_TRAIL } = require("../graphql-client/queries");
|
||
const { uploadFileBuffer } = require("../media/imgproxy-media");
|
||
const client = require('../graphql-client/graphql-client').client;
|
||
const documenso = new Documenso({
|
||
apiKey: "api_asojim0czruv13ud",//Done on a by team basis,
|
||
serverURL: "https://stg-app.documenso.com/api/v2",
|
||
});
|
||
|
||
const webhookTypeEnums = {
|
||
DOCUMENT_CREATED: "DOCUMENT_CREATED",
|
||
DOCUMENT_SENT: "DOCUMENT_SENT",
|
||
DOCUMENT_COMPLETED: "DOCUMENT_COMPLETED",
|
||
DOCUMENT_REJECTED: "DOCUMENT_REJECTED",
|
||
DOCUMENT_CANCELLED: "DOCUMENT_CANCELLED",
|
||
DOCUMENT_OPENED: "DOCUMENT_OPENED",
|
||
DOCUMENT_SIGNED: "DOCUMENT_SIGNED",
|
||
}
|
||
|
||
async function esignWebhook(req, res) {
|
||
console.log("Esign Webhook Received:", req.body);
|
||
try {
|
||
const message = req.body
|
||
logger.log(`esig-webhook-received`, "DEBUG", "redis", "api", {
|
||
event: message.event,
|
||
body: message
|
||
});
|
||
|
||
switch (message.event) {
|
||
case webhookTypeEnums.DOCUMENT_CREATED:
|
||
//This is largely a throwaway event we know it was created.
|
||
console.log("Document created event received. Document ID:", message.payload.documentId);
|
||
// Here you can add any additional processing you want to do when a document is created
|
||
break;
|
||
case webhookTypeEnums.DOCUMENT_COMPLETED:
|
||
console.log("Document completed event received. Document ID:", message.payload.documentId);
|
||
await handleDocumentCompleted(message.payload);
|
||
// Here you can add any additional processing you want to do when a document is completed
|
||
break;
|
||
case webhookTypeEnums.DOCUMENT_SIGNED:
|
||
console.log("Document signed event received. Document ID:", message.payload.documentId);
|
||
// Here you can add any additional processing you want to do when a document is signed
|
||
break;
|
||
default:
|
||
console.log(`Unhandled event type: ${message.event}`);
|
||
}
|
||
|
||
// const result = await documenso.documents.download({
|
||
// documentId: req.body.payload.id,
|
||
// });
|
||
// result.resultingBuffer = Buffer.from(result.resultingArrayBuffer);
|
||
// // Save the document to a file for testing purposes
|
||
// const downloadsDir = path.join(__dirname, '../downloads');
|
||
// if (!fs.existsSync(downloadsDir)) {
|
||
// fs.mkdirSync(downloadsDir, { recursive: true });
|
||
// }
|
||
// const filePath = path.join(downloadsDir, `document_${req.body.payload.id}.pdf`);
|
||
// fs.writeFileSync(filePath, result.resultingBuffer);
|
||
|
||
// console.log(result)
|
||
|
||
res.sendStatus(200)
|
||
} catch (error) {
|
||
logger.log(`esig-webhook-error`, "ERROR", "redis", "api", {
|
||
message: error.message, stack: error.stack,
|
||
body: req.body
|
||
});
|
||
// const downloadsDir = path.join(__dirname, '../downloads');
|
||
// if (!fs.existsSync(downloadsDir)) {
|
||
// fs.mkdirSync(downloadsDir, { recursive: true });
|
||
// }
|
||
// const filePath = path.join(downloadsDir, `document_${req.body.payload.id}.pdf`);
|
||
// fs.writeFileSync(filePath, Buffer.from(err.body));
|
||
// console.error("Error handling esign webhook:", err);
|
||
res.sendStatus(500)
|
||
}
|
||
}
|
||
|
||
async function handleDocumentCompleted(payload = sampleComplete) {
|
||
|
||
|
||
//Check if the bodyshop is on image proxy or not
|
||
try {
|
||
//Split the external id to get the uploaded user.
|
||
const [jobid, uploaded_by] = payload.externalId.split("|");
|
||
|
||
if (!jobid || !uploaded_by) {
|
||
throw new Error(`Invalid externalId format. Expected "jobid|uploaded_by", got "${payload.externalId}"`);
|
||
}
|
||
const { jobs_by_pk } = await client.request(QUERY_META_FOR_ESIG_COMPLETION, {
|
||
jobid
|
||
});
|
||
const document = await documenso.document.documentDownload({
|
||
documentId: payload.id,
|
||
});
|
||
|
||
const response = await fetch(document.downloadUrl);
|
||
const arrayBuffer = await response.arrayBuffer();
|
||
const buffer = Buffer.from(arrayBuffer);
|
||
|
||
|
||
let key = `${jobs_by_pk.bodyshop.id}/${jobs_by_pk.id}/${replaceAccents(document.filename).replace(/[^A-Z0-9]+/gi, "_")}-${new Date().getTime()}.pdf`;
|
||
|
||
if (jobs_by_pk?.bodyshop?.uselocalmediaserver) {
|
||
//LMS not yet implemented.
|
||
|
||
} else {
|
||
//S3 Upload
|
||
const uploadResult = await uploadFileBuffer({ key, buffer, contentType: "application/pdf" });
|
||
if (!uploadResult.success) {
|
||
logger.log(`esig-webhook-s3-upload-error`, "ERROR", "redis", "api", {
|
||
message: uploadResult.message,
|
||
stack: uploadResult.stack,
|
||
jobid: jobid,
|
||
documentId: payload.id
|
||
});
|
||
} else {
|
||
logger.log(`esig-webhook-s3-upload-success`, "INFO", "redis", "api", {
|
||
jobid: jobid,
|
||
documentId: payload.id,
|
||
s3Key: key,
|
||
bucket: uploadResult.bucket
|
||
});
|
||
const auditEntry = await client.request(INSERT_ESIG_AUDIT_TRAIL, {
|
||
obj: {
|
||
jobid: jobs_by_pk.id,
|
||
bodyshopid: jobs_by_pk.bodyshop.id,
|
||
operation: `Esignature document with title ${payload.title} (ID: ${payload.documentMeta.id}) has been completed.`,
|
||
useremail: uploaded_by,
|
||
type: 'esig-complete'
|
||
}
|
||
})
|
||
//insert the document record with the s3 key and bucket info.
|
||
await client.request(INSERT_ESIGNATURE_DOCUMENT, {
|
||
docInput: {
|
||
jobid: jobs_by_pk.id,
|
||
uploaded_by: uploaded_by,
|
||
key,
|
||
type: "application/pdf",
|
||
extension: "pdf",
|
||
bodyshopid: jobs_by_pk.bodyshop.id,
|
||
size: buffer.length,
|
||
takenat: new Date().toISOString(),
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
} catch (error) {
|
||
logger.log(`esig-webhook-event-completed-error`, "ERROR", "redis", "api", {
|
||
message: error.message, stack: error.stack,
|
||
payload
|
||
});
|
||
}
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
module.exports = {
|
||
esignWebhook
|
||
}
|
||
|
||
|
||
const sampleComplete = {
|
||
"id": 10929,
|
||
"title": "CASL Title set in JSR",
|
||
"source": "DOCUMENT",
|
||
"status": "COMPLETED",
|
||
"teamId": 742,
|
||
"userId": 654,
|
||
"Recipient": [
|
||
{
|
||
"id": 24997,
|
||
"name": "James Tschetter",
|
||
"role": "SIGNER",
|
||
"email": "patrick@imexsystems.ca",
|
||
"token": "uMom0GwL29NBqMfohGpUE",
|
||
"signedAt": "2026-02-27T22:11:52.835Z",
|
||
"expiresAt": "2026-05-28T22:10:48.991Z",
|
||
"documentId": 10929,
|
||
"readStatus": "OPENED",
|
||
"sendStatus": "SENT",
|
||
"templateId": null,
|
||
"authOptions": {
|
||
"accessAuth": [],
|
||
"actionAuth": []
|
||
},
|
||
"signingOrder": null,
|
||
"signingStatus": "SIGNED",
|
||
"rejectionReason": null,
|
||
"documentDeletedAt": null,
|
||
"expirationNotifiedAt": null
|
||
}
|
||
],
|
||
"createdAt": "2026-02-27T22:10:10.580Z",
|
||
"deletedAt": null,
|
||
"updatedAt": "2026-02-27T22:11:57.753Z",
|
||
"externalId": null,
|
||
"formValues": null,
|
||
"recipients": [
|
||
{
|
||
"id": 24997,
|
||
"name": "James Tschetter",
|
||
"role": "SIGNER",
|
||
"email": "patrick@imexsystems.ca",
|
||
"token": "uMom0GwL29NBqMfohGpUE",
|
||
"signedAt": "2026-02-27T22:11:52.835Z",
|
||
"expiresAt": "2026-05-28T22:10:48.991Z",
|
||
"documentId": 10929,
|
||
"readStatus": "OPENED",
|
||
"sendStatus": "SENT",
|
||
"templateId": null,
|
||
"authOptions": {
|
||
"accessAuth": [],
|
||
"actionAuth": []
|
||
},
|
||
"signingOrder": null,
|
||
"signingStatus": "SIGNED",
|
||
"rejectionReason": null,
|
||
"documentDeletedAt": null,
|
||
"expirationNotifiedAt": null
|
||
}
|
||
],
|
||
"templateId": null,
|
||
"visibility": "EVERYONE",
|
||
"authOptions": {
|
||
"globalAccessAuth": [],
|
||
"globalActionAuth": []
|
||
},
|
||
"completedAt": "2026-02-27T22:11:57.752Z",
|
||
"documentMeta": {
|
||
"id": "cmm5g3y7u00ecad1sv3ague1w",
|
||
"message": "CASL Message set in JSR",
|
||
"subject": "CASL Auth Set in JSR",
|
||
"language": "en",
|
||
"timezone": "Etc/UTC",
|
||
"dateFormat": "yyyy-MM-dd hh:mm a",
|
||
"redirectUrl": null,
|
||
"signingOrder": "PARALLEL",
|
||
"emailSettings": {
|
||
"documentDeleted": true,
|
||
"documentPending": true,
|
||
"recipientSigned": true,
|
||
"recipientRemoved": true,
|
||
"documentCompleted": true,
|
||
"ownerDocumentCompleted": true,
|
||
"recipientSigningRequest": true
|
||
},
|
||
"distributionMethod": "EMAIL",
|
||
"drawSignatureEnabled": true,
|
||
"typedSignatureEnabled": true,
|
||
"allowDictateNextSigner": false,
|
||
"uploadSignatureEnabled": true
|
||
}
|
||
}
|
||
// const sampleBody = {
|
||
// event: "DOCUMENT_COMPLETED",
|
||
// payload: {
|
||
// Recipient: [
|
||
// {
|
||
// authOptions: {
|
||
// accessAuth: [
|
||
// ],
|
||
// actionAuth: [
|
||
// ],
|
||
// },
|
||
// documentDeletedAt: null,
|
||
// documentId: 9827,
|
||
// email: "patrick@imexsystems.ca",
|
||
// expired: null,
|
||
// id: 13311,
|
||
// name: "Customer Fullname",
|
||
// readStatus: "OPENED",
|
||
// rejectionReason: null,
|
||
// role: "SIGNER",
|
||
// sendStatus: "SENT",
|
||
// signedAt: "2026-01-30T18:29:12.648Z",
|
||
// signingOrder: null,
|
||
// signingStatus: "SIGNED",
|
||
// templateId: null,
|
||
// token: "uiEWIsXUPTbWHd7QedVgt",
|
||
// },
|
||
// ],
|
||
// authOptions: {
|
||
// globalAccessAuth: [
|
||
// ],
|
||
// globalActionAuth: [
|
||
// ],
|
||
// },
|
||
// completedAt: "2026-01-30T18:29:16.279Z",
|
||
// createdAt: "2026-01-30T18:28:48.861Z",
|
||
// deletedAt: null,
|
||
// documentMeta: {
|
||
// allowDictateNextSigner: false,
|
||
// dateFormat: "yyyy-MM-dd hh:mm a",
|
||
// distributionMethod: "EMAIL",
|
||
// drawSignatureEnabled: true,
|
||
// emailSettings: {
|
||
// documentCompleted: true,
|
||
// documentDeleted: true,
|
||
// documentPending: true,
|
||
// ownerDocumentCompleted: true,
|
||
// recipientRemoved: false,
|
||
// recipientSigned: true,
|
||
// recipientSigningRequest: true,
|
||
// },
|
||
// id: "cml17vfb200qjad1t2spxnc1n",
|
||
// language: "en",
|
||
// message: "To perform repairs on your vehicle, we must receive digital authorization. Please review and sign the document to proceed with repairs. ",
|
||
// redirectUrl: null,
|
||
// signingOrder: "PARALLEL",
|
||
// subject: "Repair Authorization for ABC Collision",
|
||
// timezone: "Etc/UTC",
|
||
// typedSignatureEnabled: true,
|
||
// uploadSignatureEnabled: true,
|
||
// },
|
||
// externalId: null,
|
||
// formValues: null,
|
||
// id: 9827,
|
||
// recipients: [
|
||
// {
|
||
// authOptions: {
|
||
// accessAuth: [
|
||
// ],
|
||
// actionAuth: [
|
||
// ],
|
||
// },
|
||
// documentDeletedAt: null,
|
||
// documentId: 9827,
|
||
// email: "patrick@imexsystems.ca",
|
||
// expired: null,
|
||
// id: 13311,
|
||
// name: "Customer Fullname",
|
||
// readStatus: "OPENED",
|
||
// rejectionReason: null,
|
||
// role: "SIGNER",
|
||
// sendStatus: "SENT",
|
||
// signedAt: "2026-01-30T18:29:12.648Z",
|
||
// signingOrder: null,
|
||
// signingStatus: "SIGNED",
|
||
// templateId: null,
|
||
// token: "uiEWIsXUPTbWHd7QedVgt",
|
||
// },
|
||
// ],
|
||
// source: "DOCUMENT",
|
||
// status: "COMPLETED",
|
||
// teamId: 742,
|
||
// templateId: null,
|
||
// title: "Repair Authorization - 1/30/2026, 6:28:48 PM",
|
||
// updatedAt: "2026-01-30T18:29:16.280Z",
|
||
// userId: 654,
|
||
// visibility: "EVERYONE",
|
||
// },
|
||
// createdAt: "2026-01-30T18:29:18.504Z",
|
||
// webhookEndpoint: "https://dev.patrickfic.com/esign/webhook",
|
||
// }
|
||
|
||
function replaceAccents(str) {
|
||
// Verifies if the String has accents and replace them
|
||
if (str.search(/[\xC0-\xFF]/g) > -1) {
|
||
str = str
|
||
.replace(/[\xC0-\xC5]/g, "A")
|
||
.replace(/[\xC6]/g, "AE")
|
||
.replace(/[\xC7]/g, "C")
|
||
.replace(/[\xC8-\xCB]/g, "E")
|
||
.replace(/[\xCC-\xCF]/g, "I")
|
||
.replace(/[\xD0]/g, "D")
|
||
.replace(/[\xD1]/g, "N")
|
||
.replace(/[\xD2-\xD6\xD8]/g, "O")
|
||
.replace(/[\xD9-\xDC]/g, "U")
|
||
.replace(/[\xDD]/g, "Y")
|
||
.replace(/[\xDE]/g, "P")
|
||
.replace(/[\xE0-\xE5]/g, "a")
|
||
.replace(/[\xE6]/g, "ae")
|
||
.replace(/[\xE7]/g, "c")
|
||
.replace(/[\xE8-\xEB]/g, "e")
|
||
.replace(/[\xEC-\xEF]/g, "i")
|
||
.replace(/[\xF1]/g, "n")
|
||
.replace(/[\xF2-\xF6\xF8]/g, "o")
|
||
.replace(/[\xF9-\xFC]/g, "u")
|
||
.replace(/[\xFE]/g, "p")
|
||
.replace(/[\xFD\xFF]/g, "y");
|
||
}
|
||
return str;
|
||
}
|
||
|
||
`Unexpected Status or Content-Type: Status 200 Content-Type application/pdf\nBody: %PDF-1.7\n%<25><><EFBFBD><EFBFBD>\n1 0 obj\n<<\n/Type /Catalog\n/Pages 2 0 R\n/Names 74 0 R\n/Dests 75 0 R\n/Info 77 0 R\n/Lang (en-US)\n/Version /1.7\n>>\nendobj\n77 0 obj\n<<\n/Type /Info\n/CreationDate (D:20260227230617Z00'00')\n/Producer <FEFF007000640066002D006C006900620020002800680074007400700073003A002F002F006700690074006800750062002E0063006F006D002F0048006F007000640069006E0067002F007000640066002D006C006900620029>\n/ModDate (D:20260227231057Z)…<>5[<5B>><3E>Wu7<><37>V<EFBFBD><56><EFBFBD><EFBFBD><EFBFBD>Pw<50>WX<57>ܮJ'6NWg<57>vYϳ<><CFB3><EFBFBD><EFBFBD><EFBFBD>Щr<D0A9>\n\t+<2B>1<EFBFBD><10>m{휑<0C>hwb<><62><EFBFBD>8<EFBFBD><38>qy<>1e<31>)۱<>5m<35><6D><08><>MVM!<21>m<EFBFBD>[A<><41><10>{l<><6C>\t<EFBFBD>hia4<61><34>Tm<54><6D>8<><38>a<>e<EFBFBD>}<7D>߫<><DFAB><15>]MVpяG<D18F><47>֏<EFBFBD>jJ<"<22>A<EFBFBD>mO*<2A>P<EFBFBD><0B><><><7F><EFBFBD><EFBFBD>ѧЛ\nendstream\nendobj\n26 0 obj\n<<\n/Length 478/Filter /FlateDecode\n>>\nstream\nx<EFBFBD>MSK<EFBFBD>9<08><>)<29><>*<04>O<EFBFBD>i<EFBFBD><69>,<2C><>o <20><>kS%<25>$<EFBFBD><EFBFBD>hR\rS'<27>I<EFBFBD><49>~<7E><03><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>T[/<2F>{<05>k<EFBFBD>FC#<23><>֛<><D69B><EFBFBD>;Ӏ<>[<5B>⫀m<E2AB80>|Q<1F><>\x1b<EFBFBD><16>><3E>R<><52><EFBFBD><EFBFBD><EFBFBD>a<EFBFBD>E#<23>pI<70><49>._H<5F>ᆫt<E186AB>k<EFBFBD>D3p<33>I<EFBFBD><49><EFBFBD><EFBFBD><01>W2<57><32><EFBFBD>oJ0<4A>j<EFBFBD><6A><EFBFBD>j#<23><>!<21>$<EFBFBD><EFBFBD>-<2D><08><><EFBFBD><EFBFBD><EFBFBD>.Ϋ<><CEAB><EFBFBD>TI|8D<38>H<1C><>Y<EFBFBD><59>x<EFBFBD><78><EFBFBD><EFBFBD>1<EFBFBD>73%<25>u<EFBFBD>T<EFBFBD><54>Ӑ.rcb<63>x<EFBFBD><78>Dd6=<3D><>Oڏ1^<5E>-<2D>...and 252354 more chars` |