Skip to content

Commit 29ae11f

Browse files
committed
chore: remove growthbook from payment functions
1 parent f60334f commit 29ae11f

File tree

4 files changed

+230
-44
lines changed

4 files changed

+230
-44
lines changed

src/app/modules/submission/multirespondent-submission/multirespondent-submission.service.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import {
4343
AutoReplyMailData,
4444
AutoreplySummaryRenderData,
4545
} from '../../../services/mail/mail.types'
46-
import { generateAutoreplyPdf } from '../../../services/mail/mail.utils'
46+
import { AutoReplyData, generateAutoreplyPdf } from '../../../services/mail/mail.utils'
4747
import { transformMongoError } from '../../../utils/handle-mongo-error'
4848
import { DatabaseError } from '../../core/core.errors'
4949
import { isFormMultirespondent } from '../../form/form.utils'
@@ -486,11 +486,6 @@ const sendMrfRespondentCopyEmails = ({
486486
InvalidWorkflowTypeError | MailSendError | AutoreplyPdfGenerationError
487487
> => {
488488
const submissionId: string = submission.id
489-
const submissionTime = moment(submission.created)
490-
.tz('Asia/Singapore')
491-
.format('ddd, DD MMM YYYY hh:mm:ss A')
492-
const formUrl: string = `${config.app.appUrl}/${form._id}`
493-
494489
const formQuestionAnswers = getQuestionTitleAnswerString({
495490
formFields: form.form_fields,
496491
responses,
@@ -504,20 +499,21 @@ const sendMrfRespondentCopyEmails = ({
504499
const hasFormSummary = respondentCopyRecipientData.some(
505500
(autoReplyMailData) => autoReplyMailData.includeFormSummary,
506501
)
507-
const renderData: AutoreplySummaryRenderData = {
502+
503+
const autoReplyData: AutoReplyData = {
508504
refNo: submissionId,
509505
formTitle: form.title,
510-
submissionTime: submissionTime,
511-
formData: pdfFormData,
512-
formUrl: formUrl,
506+
submissionDateTime: submission.created || new Date(),
507+
responsesData: pdfFormData,
508+
formUrl: `${config.app.appUrl}/${form._id}`
513509
}
514510

515511
// Step 1: generate PDF if needed
516512
const pdfResult: ResultAsync<
517513
Mail.Attachment | undefined,
518514
AutoreplyPdfGenerationError
519515
> = hasFormSummary
520-
? generateAutoreplyPdf(renderData, true).map((pdfBuffer) => ({
516+
? generateAutoreplyPdf(autoReplyData, true).map((pdfBuffer) => ({
521517
filename: 'response.pdf',
522518
content: Buffer.copyBytesFrom(pdfBuffer),
523519
}))
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import { ObjectId } from 'bson'
2+
import { BasicField } from 'shared/types'
3+
import { SIGNATURE_CAPTURED_STRING } from 'shared/utils/signature'
4+
5+
import * as ConvertHtmlToPdf from '../../../utils/convert-html-to-pdf'
6+
import { generateAutoreplyPdf, safeRenderFileForTest } from '../mail.utils'
7+
8+
jest.mock('../../../utils/convert-html-to-pdf.ts')
9+
const MockConvertHtmlToPdf = jest.mocked(ConvertHtmlToPdf)
10+
11+
const MOCK_PDF_BUFFER = Buffer.from('mock pdf buffer')
12+
const MOCK_SUBMISSION_DATE_TIME = new Date('2025-01-01T00:00:00.000Z')
13+
14+
describe('mail.utils', () => {
15+
const AUTOREPLY_PDF_TEMPLATE_FILEPATH = `${__dirname}/../../../views/templates/submit-form-summary-pdf.server.view.html`
16+
beforeEach(() => {
17+
jest.clearAllMocks()
18+
MockConvertHtmlToPdf.generatePdfFromHtml.mockResolvedValue(MOCK_PDF_BUFFER)
19+
})
20+
21+
describe('generateAutoreplyPdf', () => {
22+
it('should generate a pdf from html', async () => {
23+
// Arrange
24+
const mockFormId = new ObjectId().toHexString()
25+
const mockSubmissionId = new ObjectId().toHexString()
26+
const autoReplyData = {
27+
refNo: mockSubmissionId,
28+
formTitle: 'Test Form',
29+
submissionDateTime: MOCK_SUBMISSION_DATE_TIME,
30+
responsesData: [],
31+
formUrl: `https://form.gov.sg/${mockFormId}`,
32+
}
33+
34+
// Act
35+
const result = await generateAutoreplyPdf(autoReplyData, true)
36+
37+
// Assert
38+
expect(MockConvertHtmlToPdf.generatePdfFromHtml).toHaveBeenCalledOnce()
39+
expect(result._unsafeUnwrap()).toEqual(MOCK_PDF_BUFFER)
40+
})
41+
42+
it('should generate pdfRender data correctly for signature field', async () => {
43+
// Arrange
44+
const mockFormId = new ObjectId().toHexString()
45+
const mockSubmissionId = new ObjectId().toHexString()
46+
const MOCK_SIGNATURE_PNG_DATAURI = 'datauri://signature.png'
47+
const autoReplyData = {
48+
refNo: mockSubmissionId,
49+
formTitle: 'Test Form',
50+
submissionDateTime: MOCK_SUBMISSION_DATE_TIME,
51+
responsesData: [
52+
{
53+
question: 'Signature',
54+
answer: MOCK_SIGNATURE_PNG_DATAURI,
55+
answerTemplate: [SIGNATURE_CAPTURED_STRING],
56+
fieldType: BasicField.Signature,
57+
},
58+
],
59+
formUrl: `https://form.gov.sg/${mockFormId}`,
60+
}
61+
62+
const formattedSubmissionTimeString = 'Wed, 01 Jan 2025 08:00:00 AM'
63+
64+
const pdfRenderData = {
65+
refNo: mockSubmissionId,
66+
formTitle: 'Test Form',
67+
submissionTime: formattedSubmissionTimeString,
68+
formData: [
69+
{
70+
question: 'Signature',
71+
answer: MOCK_SIGNATURE_PNG_DATAURI, // should be defined for signature fields
72+
answerTemplate: [SIGNATURE_CAPTURED_STRING],
73+
},
74+
],
75+
formUrl: `https://form.gov.sg/${mockFormId}`,
76+
}
77+
78+
const expectedHtml = await safeRenderFileForTest(
79+
AUTOREPLY_PDF_TEMPLATE_FILEPATH,
80+
pdfRenderData,
81+
)
82+
83+
// Act
84+
const result = await generateAutoreplyPdf(autoReplyData, true)
85+
86+
// Assert
87+
expect(MockConvertHtmlToPdf.generatePdfFromHtml).toHaveBeenCalledOnce()
88+
expect(MockConvertHtmlToPdf.generatePdfFromHtml).toHaveBeenCalledWith(
89+
expectedHtml._unsafeUnwrap(),
90+
true,
91+
)
92+
expect(result._unsafeUnwrap()).toEqual(MOCK_PDF_BUFFER)
93+
})
94+
95+
it('should generate pdfRender data correctly for non-signature fields', async () => {
96+
// Arrange
97+
const mockFormId = new ObjectId().toHexString()
98+
const mockSubmissionId = new ObjectId().toHexString()
99+
const autoReplyData = {
100+
refNo: mockSubmissionId,
101+
formTitle: 'Test Form',
102+
submissionDateTime: MOCK_SUBMISSION_DATE_TIME,
103+
responsesData: [
104+
{
105+
question: 'Table field',
106+
answer: 'Table answer',
107+
answerTemplate: ['Table answer'],
108+
fieldType: BasicField.Table,
109+
},
110+
],
111+
formUrl: `https://form.gov.sg/${mockFormId}`,
112+
}
113+
114+
const formattedSubmissionTimeString = 'Wed, 01 Jan 2025 08:00:00 AM'
115+
116+
const pdfRenderData = {
117+
refNo: mockSubmissionId,
118+
formTitle: 'Test Form',
119+
submissionTime: formattedSubmissionTimeString,
120+
formData: [
121+
{
122+
question: 'Table field',
123+
answer: undefined, // should be undefined for non-signature fields
124+
answerTemplate: ['Table answer'],
125+
},
126+
],
127+
formUrl: `https://form.gov.sg/${mockFormId}`,
128+
}
129+
const expectedHtml = await safeRenderFileForTest(
130+
AUTOREPLY_PDF_TEMPLATE_FILEPATH,
131+
pdfRenderData,
132+
)
133+
134+
// Act
135+
const result = await generateAutoreplyPdf(autoReplyData, true)
136+
137+
// Assert
138+
expect(MockConvertHtmlToPdf.generatePdfFromHtml).toHaveBeenCalledOnce()
139+
expect(MockConvertHtmlToPdf.generatePdfFromHtml).toHaveBeenCalledWith(
140+
expectedHtml._unsafeUnwrap(),
141+
true,
142+
)
143+
expect(result._unsafeUnwrap()).toEqual(MOCK_PDF_BUFFER)
144+
})
145+
})
146+
})

src/app/services/mail/mail.service.ts

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import {
6262
SubmissionToAdminHtmlData,
6363
} from './mail.types'
6464
import {
65+
AutoReplyData,
6566
generateAutoreplyHtml,
6667
generateAutoreplyPdf,
6768
generateIssueReportedNotificationHtml,
@@ -796,18 +797,6 @@ export class MailService {
796797
>
797798
>[]
798799
> => {
799-
// Data to render both the submission details mail HTML body and PDF.
800-
801-
const renderData: AutoreplySummaryRenderData = {
802-
refNo: submission.id,
803-
formTitle: form.title,
804-
submissionTime: moment(submission.created)
805-
.tz('Asia/Singapore')
806-
.format('ddd, DD MMM YYYY hh:mm:ss A'),
807-
formData: responsesData,
808-
formUrl: `${this.#appUrl}/${form._id}`,
809-
}
810-
811800
// Create a copy of attachments for attaching of autoreply pdf if needed.
812801
const attachmentsWithAutoreplyPdf = [...attachments]
813802
const isEncryptForm = form?.responseMode === FormResponseMode.Encrypt
@@ -817,14 +806,21 @@ export class MailService {
817806
encryptFormDef.payments_channel.channel !== PaymentChannel.Unconnected &&
818807
encryptFormDef.payments_field.enabled === true
819808

809+
const autoReplyData: AutoReplyData = {
810+
refNo: submission.id,
811+
formTitle: form.title,
812+
submissionDateTime: submission.created || new Date(),
813+
responsesData,
814+
formUrl: `${this.#appUrl}/${form._id}`,
815+
}
820816
// Generate autoreply pdf and append into attachments if any of the mail has
821817
// to include a form summary.
822818
if (
823819
autoReplyMailDatas.some((data) => data.includeFormSummary) &&
824820
!isPaymentEnabled
825821
) {
826822
const pdfBufferResult = await generateAutoreplyPdf(
827-
renderData,
823+
autoReplyData,
828824
isUseLambdaOutput,
829825
)
830826
if (pdfBufferResult.isErr()) {
@@ -844,9 +840,14 @@ export class MailService {
844840
}),
845841
)
846842

847-
const strippedRenderData = {
848-
...renderData,
843+
const strippedRenderData: AutoreplySummaryRenderData = {
844+
refNo: submission.id,
845+
formTitle: form.title,
846+
submissionTime: moment(submission.created)
847+
.tz('Asia/Singapore')
848+
.format('ddd, DD MMM YYYY hh:mm:ss A'),
849849
formData: strippedResponsesData,
850+
formUrl: `${this.#appUrl}/${form._id}`,
850851
}
851852

852853
// Prepare mail sending for each autoreply mail.

src/app/services/mail/mail.utils.ts

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import dedent from 'dedent-js'
22
import ejs, { Data } from 'ejs'
33
import { flattenDeep } from 'lodash'
4+
import moment from 'moment-timezone'
45
import { ResultAsync } from 'neverthrow'
56
import validator from 'validator'
67

7-
import { BounceType } from '../../../types'
8+
import { BasicField } from '../../../../shared/types'
9+
import { BounceType, EmailRespondentConfirmationField } from '../../../types'
810
import { paymentConfig } from '../../config/features/payment.config'
911
import { createLoggerWithLabel } from '../../config/logger'
1012
import { generatePdfFromHtml } from '../../utils/convert-html-to-pdf'
@@ -47,6 +49,8 @@ const safeRenderFile = (
4749
)
4850
}
4951

52+
export const safeRenderFileForTest = safeRenderFile
53+
5054
export const generateLoginOtpHtml = (htmlData: {
5155
otpPrefix: string
5256
otp: string
@@ -111,8 +115,43 @@ export const generateBounceNotificationHtml = (
111115
return safeRenderFile(pathToTemplate, htmlData)
112116
}
113117

118+
export interface AutoReplyData {
119+
refNo: string
120+
formTitle: string
121+
submissionDateTime: Date
122+
responsesData: EmailRespondentConfirmationField[]
123+
formUrl: string
124+
}
125+
126+
const generatePdfRenderData = ({
127+
refNo,
128+
formTitle,
129+
submissionDateTime,
130+
responsesData,
131+
formUrl,
132+
}: AutoReplyData): AutoreplySummaryRenderData => {
133+
const pdfResponsesData = responsesData.map(
134+
({ question, answerTemplate, answer, fieldType }) => ({
135+
question,
136+
answerTemplate,
137+
// In the submit-form-summary-pdf.server.view.html, for signature field, it expects the answer key, while for others, it expects the answerTemplate key.
138+
answer: fieldType === BasicField.Signature ? answer : undefined,
139+
}),
140+
)
141+
142+
return {
143+
refNo,
144+
formTitle,
145+
submissionTime: moment(submissionDateTime)
146+
.tz('Asia/Singapore')
147+
.format('ddd, DD MMM YYYY hh:mm:ss A'),
148+
formData: pdfResponsesData,
149+
formUrl,
150+
}
151+
}
152+
114153
export const generateAutoreplyPdf = (
115-
renderData: AutoreplySummaryRenderData,
154+
autoReplyData: AutoReplyData,
116155
isUseLambdaOutput: boolean,
117156
): ResultAsync<Buffer, AutoreplyPdfGenerationError> => {
118157
const pathToTemplate = `${__dirname}/../../views/templates/submit-form-summary-pdf.server.view.html`
@@ -127,22 +166,26 @@ export const generateAutoreplyPdf = (
127166
},
128167
})
129168

130-
return safeRenderFile(pathToTemplate, renderData).andThen((summaryHtml) => {
131-
return ResultAsync.fromPromise(
132-
generatePdfFromHtml(summaryHtml, isUseLambdaOutput),
133-
(error) => {
134-
logger.error({
135-
meta: {
136-
action: 'generateAutoreplyPdf',
137-
},
138-
message: 'Error occurred whilst generating autoreply PDF',
139-
error,
140-
})
141-
142-
return new AutoreplyPdfGenerationError()
143-
},
144-
)
145-
})
169+
const pdfRenderData = generatePdfRenderData(autoReplyData)
170+
171+
return safeRenderFile(pathToTemplate, pdfRenderData).andThen(
172+
(summaryHtml) => {
173+
return ResultAsync.fromPromise(
174+
generatePdfFromHtml(summaryHtml, isUseLambdaOutput),
175+
(error) => {
176+
logger.error({
177+
meta: {
178+
action: 'generateAutoreplyPdf',
179+
},
180+
message: 'Error occurred whilst generating autoreply PDF',
181+
error,
182+
})
183+
184+
return new AutoreplyPdfGenerationError()
185+
},
186+
)
187+
},
188+
)
146189
}
147190

148191
export const generateAutoreplyHtml = (

0 commit comments

Comments
 (0)