diff --git a/apps/web/src/app/(unauthenticated)/articles/signature-disclosure/page.tsx b/apps/web/src/app/(unauthenticated)/articles/signature-disclosure/page.tsx index 878332f35..c56f53702 100644 --- a/apps/web/src/app/(unauthenticated)/articles/signature-disclosure/page.tsx +++ b/apps/web/src/app/(unauthenticated)/articles/signature-disclosure/page.tsx @@ -5,7 +5,7 @@ import { Button } from '@documenso/ui/primitives/button'; export default function SignatureDisclosure() { return (
-
+

Electronic Signature Disclosure

Welcome

diff --git a/apps/web/src/pages/api/auth/[...nextauth].ts b/apps/web/src/pages/api/auth/[...nextauth].ts index 365b6ec40..31f6e9ea3 100644 --- a/apps/web/src/pages/api/auth/[...nextauth].ts +++ b/apps/web/src/pages/api/auth/[...nextauth].ts @@ -2,6 +2,8 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import NextAuth from 'next-auth'; +import { getStripeCustomerByUser } from '@documenso/ee/server-only/stripe/get-customer'; +import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app'; import { NEXT_AUTH_OPTIONS } from '@documenso/lib/next-auth/auth-options'; import { extractNextApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata'; import { prisma } from '@documenso/prisma'; @@ -18,15 +20,29 @@ export default async function auth(req: NextApiRequest, res: NextApiResponse) { error: '/signin', }, events: { - signIn: async ({ user }) => { - await prisma.userSecurityAuditLog.create({ - data: { - userId: user.id, - ipAddress, - userAgent, - type: UserSecurityAuditLogType.SIGN_IN, - }, - }); + signIn: async ({ user: { id: userId } }) => { + const [user] = await Promise.all([ + await prisma.user.findFirstOrThrow({ + where: { + id: userId, + }, + }), + await prisma.userSecurityAuditLog.create({ + data: { + userId, + ipAddress, + userAgent, + type: UserSecurityAuditLogType.SIGN_IN, + }, + }), + ]); + + // Create the Stripe customer and attach it to the user if it doesn't exist. + if (user.customerId === null && IS_BILLING_ENABLED()) { + await getStripeCustomerByUser(user).catch((err) => { + console.error(err); + }); + } }, signOut: async ({ token }) => { const userId = typeof token.id === 'string' ? parseInt(token.id) : token.id; diff --git a/packages/lib/server-only/document/complete-document-with-token.ts b/packages/lib/server-only/document/complete-document-with-token.ts index d16b83ea1..29d17cc50 100644 --- a/packages/lib/server-only/document/complete-document-with-token.ts +++ b/packages/lib/server-only/document/complete-document-with-token.ts @@ -137,7 +137,7 @@ export const completeDocumentWithToken = async ({ await sendPendingEmail({ documentId, recipientId: recipient.id }); } - const documents = await prisma.document.updateMany({ + const haveAllRecipientsSigned = await prisma.document.findFirst({ where: { id: document.id, Recipient: { @@ -146,13 +146,9 @@ export const completeDocumentWithToken = async ({ }, }, }, - data: { - status: DocumentStatus.COMPLETED, - completedAt: new Date(), - }, }); - if (documents.count > 0) { + if (haveAllRecipientsSigned) { await sealDocument({ documentId: document.id, requestMetadata }); } diff --git a/packages/lib/server-only/document/delete-document.ts b/packages/lib/server-only/document/delete-document.ts index a097d76e9..bf4f8aa06 100644 --- a/packages/lib/server-only/document/delete-document.ts +++ b/packages/lib/server-only/document/delete-document.ts @@ -75,18 +75,20 @@ export const deleteDocument = async ({ } // Continue to hide the document from the user if they are a recipient. + // Dirty way of doing this but it's faster than refetching the document. if (userRecipient?.documentDeletedAt === null) { - await prisma.recipient.update({ - where: { - documentId_email: { - documentId: document.id, - email: user.email, + await prisma.recipient + .update({ + where: { + id: userRecipient.id, }, - }, - data: { - documentDeletedAt: new Date().toISOString(), - }, - }); + data: { + documentDeletedAt: new Date().toISOString(), + }, + }) + .catch(() => { + // Do nothing. + }); } // Return partial document for API v1 response. diff --git a/packages/lib/server-only/document/seal-document.ts b/packages/lib/server-only/document/seal-document.ts index 3e366dc81..564dfc049 100644 --- a/packages/lib/server-only/document/seal-document.ts +++ b/packages/lib/server-only/document/seal-document.ts @@ -40,6 +40,11 @@ export const sealDocument = async ({ const document = await prisma.document.findFirstOrThrow({ where: { id: documentId, + Recipient: { + every: { + signingStatus: SigningStatus.SIGNED, + }, + }, }, include: { documentData: true, @@ -53,10 +58,6 @@ export const sealDocument = async ({ throw new Error(`Document ${document.id} has no document data`); } - if (document.status !== DocumentStatus.COMPLETED) { - throw new Error(`Document ${document.id} has not been completed`); - } - const recipients = await prisma.recipient.findMany({ where: { documentId: document.id, @@ -92,9 +93,9 @@ export const sealDocument = async ({ // !: Need to write the fields onto the document as a hard copy const pdfData = await getFile(documentData); - const certificate = await getCertificatePdf({ documentId }).then(async (doc) => - PDFDocument.load(doc), - ); + const certificate = await getCertificatePdf({ documentId }) + .then(async (doc) => PDFDocument.load(doc)) + .catch(() => null); const doc = await PDFDocument.load(pdfData); @@ -103,11 +104,13 @@ export const sealDocument = async ({ doc.getForm().flatten(); flattenAnnotations(doc); - const certificatePages = await doc.copyPages(certificate, certificate.getPageIndices()); + if (certificate) { + const certificatePages = await doc.copyPages(certificate, certificate.getPageIndices()); - certificatePages.forEach((page) => { - doc.addPage(page); - }); + certificatePages.forEach((page) => { + doc.addPage(page); + }); + } for (const field of fields) { await insertFieldInPDF(doc, field); @@ -138,6 +141,16 @@ export const sealDocument = async ({ } await prisma.$transaction(async (tx) => { + await tx.document.update({ + where: { + id: document.id, + }, + data: { + status: DocumentStatus.COMPLETED, + completedAt: new Date(), + }, + }); + await tx.documentData.update({ where: { id: documentData.id, diff --git a/packages/lib/server-only/htmltopdf/get-certificate-pdf.ts b/packages/lib/server-only/htmltopdf/get-certificate-pdf.ts index dee40d41a..1b6150fb9 100644 --- a/packages/lib/server-only/htmltopdf/get-certificate-pdf.ts +++ b/packages/lib/server-only/htmltopdf/get-certificate-pdf.ts @@ -35,6 +35,7 @@ export const getCertificatePdf = async ({ documentId }: GetCertificatePdfOptions await page.goto(`${NEXT_PUBLIC_WEBAPP_URL()}/__htmltopdf/certificate?d=${encryptedId}`, { waitUntil: 'networkidle', + timeout: 10_000, }); const result = await page.pdf({ diff --git a/packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx b/packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx index e415f1aac..d285fbe44 100644 --- a/packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx +++ b/packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx @@ -282,6 +282,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({