-
Notifications
You must be signed in to change notification settings - Fork 87
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(payment): invoice through s3 (#6733)
* feat: payment proof through s3 * feat: add memory of upload to s3 to payments model * chore: add s3 bucket url to config * feat: refetch from stripe.charges * chore: add debug logs * chore: switch redirect to json message * test: update test config * fix: increase nginx proxy buffer size * Revert "chore: switch redirect to json message" This reverts commit e0214db. * fix: include .platform in ECR * chore: refactor with refined completed payment schema * chore: follow repo convention of returning true for success result * refactor: move invoice generation code to payment-proof folder * test: add cases for payment-proof * chore: remove stray comments * feat: remove ebs .platform config * refactor: uppercase for global constants * test: update mock http to https * chore: fix duplicates, empty imports, terser mock return statements * test: fix missing mock for s3upload
- Loading branch information
Showing
21 changed files
with
670 additions
and
259 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const DAY_IN_SECONDS = 24 * 60 * 60 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
267 changes: 267 additions & 0 deletions
267
src/app/modules/payments/__tests__/payment-proof.controller.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,267 @@ | ||
import dbHandler from '__tests__/unit/backend/helpers/jest-db' | ||
import expressHandler from '__tests__/unit/backend/helpers/jest-express' | ||
import axios from 'axios' | ||
import { ObjectId } from 'bson' | ||
import mongoose from 'mongoose' | ||
import { errAsync, ok, okAsync } from 'neverthrow' | ||
import { PaymentStatus, SubmissionType } from 'shared/types' | ||
|
||
import getPaymentModel from 'src/app/models/payment.server.model' | ||
import { getEncryptPendingSubmissionModel } from 'src/app/models/pending_submission.server.model' | ||
import * as ConvertHtmlToPdf from 'src/app/utils/convert-html-to-pdf' | ||
import { | ||
IPaymentSchema, | ||
IPopulatedEncryptedForm, | ||
IPopulatedForm, | ||
} from 'src/types' | ||
|
||
import * as FormService from '../../form/form.service' | ||
import * as EncryptSubmissionService from '../../submission/encrypt-submission/encrypt-submission.service' | ||
import * as PaymentProofController from '../payment-proof.controller' | ||
import { PaymentProofUploadS3Error } from '../payment-proof.errors' | ||
import * as PaymentProofService from '../payment-proof.service' | ||
import { StripeFetchError } from '../stripe.errors' | ||
import * as StripeUtils from '../stripe.utils' | ||
|
||
const Payment = getPaymentModel(mongoose) | ||
const EncryptPendingSubmission = getEncryptPendingSubmissionModel(mongoose) | ||
|
||
const MOCK_FORM_ID = new ObjectId().toHexString() | ||
|
||
jest.mock('axios') | ||
jest.mock('src/app/modules/payments/stripe.utils') | ||
jest.mock('src/app/utils/convert-html-to-pdf') | ||
|
||
jest.mock( | ||
'src/app/modules/submission/encrypt-submission/encrypt-submission.service', | ||
) | ||
const MockEncryptSubmissionService = jest.mocked(EncryptSubmissionService) | ||
|
||
jest.mock('../../form/form.service') | ||
const MockFormService = jest.mocked(FormService) | ||
|
||
describe('stripe.controller', () => { | ||
beforeAll(async () => await dbHandler.connect()) | ||
afterAll(async () => await dbHandler.closeDatabase()) | ||
beforeEach(() => jest.clearAllMocks()) | ||
|
||
describe('downloadPaymentInvoice', () => { | ||
const mockBusinessInfo = { | ||
address: 'localhost', | ||
gstRegNo: 'G123456', | ||
} | ||
const mockFormTitle = 'Mock Form Title' | ||
const mockSubmissionId = 'MOCK_SUBMISSION_ID' | ||
const mockForm = { | ||
_id: MOCK_FORM_ID, | ||
admin: { | ||
agency: { | ||
business: mockBusinessInfo, | ||
}, | ||
}, | ||
title: mockFormTitle, | ||
} as IPopulatedForm | ||
|
||
let payment: IPaymentSchema | ||
|
||
beforeEach(async () => { | ||
await dbHandler.clearCollection(Payment.collection.name) | ||
await dbHandler.clearCollection(EncryptPendingSubmission.collection.name) | ||
const pendingSubmission = await EncryptPendingSubmission.create({ | ||
submissionType: SubmissionType.Encrypt, | ||
form: MOCK_FORM_ID, | ||
encryptedContent: 'some random encrypted content', | ||
version: 1, | ||
}) | ||
|
||
payment = await Payment.create({ | ||
formId: mockForm._id, | ||
targetAccountId: 'acct_MOCK_ACCOUNT_ID', | ||
pendingSubmissionId: pendingSubmission._id, | ||
amount: 12345, | ||
status: PaymentStatus.Succeeded, | ||
paymentIntentId: 'pi_MOCK_PAYMENT_INTENT', | ||
email: '[email protected]', | ||
completedPayment: { | ||
receiptUrl: 'https://form.gov.sg', | ||
submissionId: mockSubmissionId, | ||
}, | ||
gstEnabled: false, | ||
}) | ||
}) | ||
|
||
it('should reject when receipt url is not present', async () => { | ||
// Arrange | ||
MockFormService.retrieveFullFormById.mockReturnValue(okAsync(mockForm)) | ||
MockEncryptSubmissionService.checkFormIsEncryptMode.mockReturnValue( | ||
ok(mockForm as IPopulatedEncryptedForm), | ||
) | ||
const mockReq = expressHandler.mockRequest({ | ||
params: { formId: mockForm._id, paymentId: payment._id }, | ||
}) | ||
const mockRes = expressHandler.mockResponse() | ||
const checkStripeReceiptIsReadySpy = jest | ||
.spyOn(PaymentProofService, 'checkStripeReceiptIsReady') | ||
.mockReturnValueOnce( | ||
errAsync(new StripeFetchError('Receipt url not ready')), | ||
) | ||
|
||
// Act | ||
await PaymentProofController.downloadPaymentInvoice( | ||
mockReq, | ||
mockRes, | ||
jest.fn(), | ||
) | ||
|
||
// Assert | ||
expect(checkStripeReceiptIsReadySpy).toHaveBeenCalledOnce() | ||
expect(mockRes.status).toHaveBeenCalledWith(404) | ||
}) | ||
|
||
it('should reject when receipt download from stripe fails', async () => { | ||
// Arrange | ||
MockFormService.retrieveFullFormById.mockReturnValue(okAsync(mockForm)) | ||
MockEncryptSubmissionService.checkFormIsEncryptMode.mockReturnValue( | ||
ok(mockForm as IPopulatedEncryptedForm), | ||
) | ||
const mockReq = expressHandler.mockRequest({ | ||
params: { formId: mockForm._id, paymentId: payment._id }, | ||
}) | ||
const mockRes = expressHandler.mockResponse() | ||
const axiosSpy = jest | ||
.spyOn(axios, 'get') | ||
.mockRejectedValueOnce({ data: 'missing resource' }) | ||
|
||
const retrieveReceiptUrlFromStripeSpy = jest | ||
.spyOn(PaymentProofService, '_retrieveReceiptUrlFromStripe') | ||
.mockReturnValueOnce(okAsync('https://form.gov.sg')) | ||
|
||
// Act | ||
await PaymentProofController.downloadPaymentInvoice( | ||
mockReq, | ||
mockRes, | ||
jest.fn(), | ||
) | ||
|
||
// Assert | ||
expect(MockFormService.retrieveFullFormById).toHaveBeenCalledWith( | ||
mockForm._id, | ||
) | ||
expect(retrieveReceiptUrlFromStripeSpy).toHaveBeenCalledOnce() | ||
expect(axiosSpy).toHaveBeenCalledOnce() | ||
expect(mockRes.status).toHaveBeenCalledWith(404) | ||
}) | ||
|
||
it('should return with error if upload to s3 fails', async () => { | ||
// Arrange | ||
MockFormService.retrieveFullFormById.mockReturnValue(okAsync(mockForm)) | ||
MockEncryptSubmissionService.checkFormIsEncryptMode.mockReturnValue( | ||
ok(mockForm as IPopulatedEncryptedForm), | ||
) | ||
|
||
const mockReq = expressHandler.mockRequest({ | ||
params: { formId: mockForm._id, paymentId: payment._id }, | ||
}) | ||
const mockRes = expressHandler.mockResponse() | ||
const axiosSpy = jest | ||
.spyOn(axios, 'get') | ||
.mockResolvedValueOnce({ data: '<html>some html</html>' }) | ||
|
||
const convertInvoiceSpy = jest | ||
.spyOn(StripeUtils, 'convertToProofOfPaymentFormat') | ||
.mockReturnValueOnce('<html>some converted html</html>') | ||
|
||
const generatePdfFromHtmlSpy = jest | ||
.spyOn(ConvertHtmlToPdf, 'generatePdfFromHtml') | ||
.mockResolvedValueOnce(Buffer.from('123')) | ||
|
||
const retrieveReceiptUrlFromStripeSpy = jest | ||
.spyOn(PaymentProofService, '_retrieveReceiptUrlFromStripe') | ||
.mockReturnValueOnce(okAsync('https://form.gov.sg')) | ||
|
||
const storePaymentProofInS3Spy = jest | ||
.spyOn(PaymentProofService, '_storePaymentProofInS3') | ||
.mockReturnValueOnce(errAsync(new PaymentProofUploadS3Error())) | ||
|
||
const mockRedirectUrl = 'mockRedirectUrl' | ||
const getPaymentProofPresignedS3UrlSpy = jest | ||
.spyOn(PaymentProofService, '_getPaymentProofPresignedS3Url') | ||
.mockReturnValueOnce(okAsync(mockRedirectUrl)) | ||
|
||
// Act | ||
await PaymentProofController.downloadPaymentInvoice( | ||
mockReq, | ||
mockRes, | ||
jest.fn(), | ||
) | ||
|
||
// Assert | ||
expect(MockFormService.retrieveFullFormById).toHaveBeenCalledWith( | ||
mockForm._id, | ||
) | ||
expect(retrieveReceiptUrlFromStripeSpy).toHaveBeenCalledOnce() | ||
expect(axiosSpy).toHaveBeenCalledOnce() | ||
expect(convertInvoiceSpy).toHaveBeenCalledOnce() | ||
expect(generatePdfFromHtmlSpy).toHaveBeenCalledOnce() | ||
expect(storePaymentProofInS3Spy).toHaveBeenCalledOnce() | ||
expect(getPaymentProofPresignedS3UrlSpy).not.toHaveBeenCalled() | ||
expect(mockRes.status).toHaveBeenCalledWith(404) | ||
}) | ||
|
||
it('should return with redirect link', async () => { | ||
// Arrange | ||
MockFormService.retrieveFullFormById.mockReturnValue(okAsync(mockForm)) | ||
MockEncryptSubmissionService.checkFormIsEncryptMode.mockReturnValue( | ||
ok(mockForm as IPopulatedEncryptedForm), | ||
) | ||
|
||
const mockReq = expressHandler.mockRequest({ | ||
params: { formId: mockForm._id, paymentId: payment._id }, | ||
}) | ||
const mockRes = expressHandler.mockResponse() | ||
const axiosSpy = jest | ||
.spyOn(axios, 'get') | ||
.mockResolvedValueOnce({ data: '<html>some html</html>' }) | ||
|
||
const convertInvoiceSpy = jest | ||
.spyOn(StripeUtils, 'convertToProofOfPaymentFormat') | ||
.mockReturnValueOnce('<html>some converted html</html>') | ||
|
||
const generatePdfFromHtmlSpy = jest | ||
.spyOn(ConvertHtmlToPdf, 'generatePdfFromHtml') | ||
.mockResolvedValueOnce(Buffer.from('123')) | ||
|
||
const retrieveReceiptUrlFromStripeSpy = jest | ||
.spyOn(PaymentProofService, '_retrieveReceiptUrlFromStripe') | ||
.mockReturnValueOnce(okAsync('https://form.gov.sg')) | ||
|
||
const storePaymentProofInS3Spy = jest | ||
.spyOn(PaymentProofService, '_storePaymentProofInS3') | ||
.mockReturnValueOnce(okAsync(true)) | ||
|
||
const mockRedirectUrl = 'mockRedirectUrl' | ||
const getPaymentProofPresignedS3UrlSpy = jest | ||
.spyOn(PaymentProofService, '_getPaymentProofPresignedS3Url') | ||
.mockReturnValueOnce(okAsync(mockRedirectUrl)) | ||
|
||
// Act | ||
await PaymentProofController.downloadPaymentInvoice( | ||
mockReq, | ||
mockRes, | ||
jest.fn(), | ||
) | ||
|
||
// Assert | ||
expect(MockFormService.retrieveFullFormById).toHaveBeenCalledWith( | ||
mockForm._id, | ||
) | ||
expect(retrieveReceiptUrlFromStripeSpy).toHaveBeenCalledOnce() | ||
expect(axiosSpy).toHaveBeenCalledOnce() | ||
expect(convertInvoiceSpy).toHaveBeenCalledOnce() | ||
expect(generatePdfFromHtmlSpy).toHaveBeenCalledOnce() | ||
expect(storePaymentProofInS3Spy).toHaveBeenCalledOnce() | ||
expect(getPaymentProofPresignedS3UrlSpy).toHaveBeenCalledOnce() | ||
expect(mockRes.redirect).toHaveBeenCalledWith(mockRedirectUrl) | ||
}) | ||
}) | ||
}) |
Oops, something went wrong.