210 lines
8.6 KiB
JavaScript
210 lines
8.6 KiB
JavaScript
|
|
const { Documenso } = require("@documenso/sdk-typescript");
|
|
const logger = require("../utils/logger");
|
|
const { QUERY_META_FOR_ESIG_COMPLETION, INSERT_ESIGNATURE_COMPLETED_DOCOUMENT, UPDATE_ESIGNATURE_DOCUMENT, DISTRIBUTE_ESIGNATURE_DOCUMENT, QUERY_DOCUMENSO_KEY, GET_DOCUMENSO_KEY_BY_JOBID } = require("../graphql-client/queries");
|
|
const { uploadFileBuffer } = require("../media/imgproxy-media");
|
|
|
|
const client = require('../graphql-client/graphql-client').client;
|
|
|
|
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) {
|
|
try {
|
|
const message = req.body
|
|
logger.log(`esig-webhook-received`, "DEBUG", "redis", "api", {
|
|
event: message.event,
|
|
body: message
|
|
});
|
|
|
|
const documentId = (message.payload?.id || message.payload?.payload?.id)?.toString()
|
|
//TODO: Implement checks to prevent this from going backwards in status? If a request fails, it retries, which could cause a document marked as completed to be marked as rejected if the rejection event is processed after the completion event.
|
|
switch (message.event) {
|
|
case webhookTypeEnums.DOCUMENT_OPENED:
|
|
//TODO: DR: Add notification for document opened.
|
|
await client.request(UPDATE_ESIGNATURE_DOCUMENT, {
|
|
external_document_id: documentId,
|
|
esig_update: {
|
|
status: "OPENED",
|
|
opened: true,
|
|
}
|
|
})
|
|
break;
|
|
case webhookTypeEnums.DOCUMENT_REJECTED:
|
|
await client.request(UPDATE_ESIGNATURE_DOCUMENT, {
|
|
external_document_id: documentId,
|
|
esig_update: {
|
|
status: "REJECTED",
|
|
rejected: true,
|
|
}
|
|
})
|
|
break;
|
|
case webhookTypeEnums.DOCUMENT_CREATED:
|
|
//This is largely a throwaway event we know it was created.
|
|
// Here you can add any additional processing you want to do when a document is created
|
|
break;
|
|
case webhookTypeEnums.DOCUMENT_COMPLETED:
|
|
//TODO: DR: Add notification for document completed.
|
|
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:
|
|
// Here you can add any additional processing you want to do when a document is signed
|
|
await client.request(UPDATE_ESIGNATURE_DOCUMENT, {
|
|
external_document_id: documentId,
|
|
esig_update: {
|
|
status: "SIGNED",
|
|
}
|
|
})
|
|
break;
|
|
default:
|
|
res.status(200).json({ message: "Unsupported event type." });
|
|
logger.log(`esig-webhook-received-unknown`, "ERROR", "redis", "api", {
|
|
event: message.event,
|
|
body: message
|
|
});
|
|
return;
|
|
}
|
|
logger.log(`esig-webhook-processed`, "INFO", "redis", "api", { event: message.event, documentId: message.payload?.payload?.id, jobid: message.payload?.payload?.externalId?.split("|")[0] || null });
|
|
res.sendStatus(200)
|
|
} catch (error) {
|
|
logger.log(`esig-webhook-error`, "ERROR", "redis", "api", {
|
|
message: error.message, stack: error.stack,
|
|
body: req.body
|
|
});
|
|
res.status(500).json({ message: "Error processing webhook event.", error: error.message });
|
|
}
|
|
}
|
|
|
|
async function handleDocumentCompleted(payload) {
|
|
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
|
|
});
|
|
|
|
//Have to use globally authed cleint since this a webhook.
|
|
const { jobs_by_pk: { bodyshop: { documenso_api_key } } } = await client.request(GET_DOCUMENSO_KEY_BY_JOBID, {
|
|
jobid,
|
|
|
|
})
|
|
const documenso = new Documenso({
|
|
apiKey: documenso_api_key,
|
|
serverURL: "https://sign.imex.online/api/v2",
|
|
});
|
|
|
|
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) {
|
|
//TODO: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
|
|
});
|
|
|
|
await client.request(DISTRIBUTE_ESIGNATURE_DOCUMENT, {
|
|
external_document_id: payload.id.toString(),
|
|
esig_update: {
|
|
status: "COMPLETED",
|
|
completed: true,
|
|
completed_at: new Date().toISOString()
|
|
},
|
|
audit: {
|
|
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_COMPLETED_DOCOUMENT, {
|
|
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
|
|
}
|
|
|
|
|
|
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;
|
|
} |