diff --git a/processor/src/controllers/payment.controller.ts b/processor/src/controllers/payment.controller.ts index e1a5fc0..1f055f4 100644 --- a/processor/src/controllers/payment.controller.ts +++ b/processor/src/controllers/payment.controller.ts @@ -5,6 +5,7 @@ import { PaymentReference, Payment } from '@commercetools/platform-sdk'; import { ConnectorActions } from '../utils/constant.utils'; import { validateCommerceToolsPaymentPayload } from '../validators/payment.validators'; import CustomError from '../errors/custom.error'; +import SkipError from '../errors/skip.error'; /** * Handle the cart controller according to the action @@ -19,20 +20,28 @@ export const paymentController = async ( ): Promise => { const ctPayment: Payment = JSON.parse(JSON.stringify(resource)).obj; - validateCommerceToolsPaymentPayload(action, ctPayment); - const controllerAction = determinePaymentAction(ctPayment); + if (controllerAction.errorMessage !== '') { + throw new CustomError(400, controllerAction.errorMessage as string); + } + + if (controllerAction.action === ConnectorActions.NoAction) { + throw new SkipError('SCTM - No payment actions matched'); + } + + validateCommerceToolsPaymentPayload(action, controllerAction.action, ctPayment); + switch (controllerAction.action) { case ConnectorActions.GetPaymentMethods: return await handleListPaymentMethodsByPayment(ctPayment); case ConnectorActions.CreatePayment: return await handleCreatePayment(ctPayment); - case ConnectorActions.NoAction: - return { - statusCode: 200, - }; default: - throw new CustomError(400, controllerAction.errorMessage ?? ''); + if (controllerAction.errorMessage === '') { + throw new SkipError('SCTM - No payment actions matched'); + } + + throw new CustomError(400, controllerAction.errorMessage as string); } }; diff --git a/processor/src/controllers/processor.controller.ts b/processor/src/controllers/processor.controller.ts index b057080..0ee7f4b 100644 --- a/processor/src/controllers/processor.controller.ts +++ b/processor/src/controllers/processor.controller.ts @@ -7,6 +7,7 @@ import { paymentController } from './payment.controller'; import CustomError from '../errors/custom.error'; import SkipError from '../errors/skip.error'; import { apiError } from '../api/error.api'; +import { formatErrorResponse } from '../errors/mollie.error'; /** * Exposed service endpoint. @@ -44,8 +45,8 @@ export const post = async (request: Request, response: Response, next: NextFunct } if (error instanceof CustomError) { return apiError(response, error.errors); - } else { - next(error); } + + return apiError(response, formatErrorResponse(error).errors); } }; diff --git a/processor/src/service/payment.service.ts b/processor/src/service/payment.service.ts index b46dfa8..3dc27f1 100644 --- a/processor/src/service/payment.service.ts +++ b/processor/src/service/payment.service.ts @@ -42,7 +42,6 @@ export const handleListPaymentMethodsByPayment = async (ctPayment: Payment): Pro try { const mollieOptions = await mapCommercetoolsPaymentCustomFieldsToMollieListParams(ctPayment); const methods: List = await listPaymentMethods(mollieOptions); - const availableMethods = JSON.stringify({ count: methods.length, methods: methods.length ? methods : [], @@ -50,7 +49,7 @@ export const handleListPaymentMethodsByPayment = async (ctPayment: Payment): Pro const ctUpdateActions: UpdateAction[] = [setCustomFields(CustomFields.payment.response, availableMethods)]; - const hasCardPayment = methods.findIndex((method: Method) => method.id === PaymentMethod.creditcard); + const hasCardPayment = methods.find((method: Method) => method.id === PaymentMethod.creditcard); if (hasCardPayment) { ctUpdateActions.push(setCustomFields(CustomFields.payment.profileId, readConfiguration().mollie.profileId)); diff --git a/processor/src/utils/paymentAction.utils.ts b/processor/src/utils/paymentAction.utils.ts index ff20cf4..43a3d24 100644 --- a/processor/src/utils/paymentAction.utils.ts +++ b/processor/src/utils/paymentAction.utils.ts @@ -30,7 +30,7 @@ export const determinePaymentAction = (ctPayment?: Payment): DeterminePaymentAct }; } - const { key, transactions } = ctPayment; + const { id, key, transactions } = ctPayment; const initialChargeTransactions: CTTransaction[] = []; const pendingChargeTransactions: CTTransaction[] = []; @@ -70,7 +70,7 @@ export const determinePaymentAction = (ctPayment?: Payment): DeterminePaymentAct break; // Create Payment - case !!key && + case (!!key || !!id) && initialChargeTransactions.length === 1 && !successChargeTransactions.length && !pendingChargeTransactions.length: @@ -78,7 +78,7 @@ export const determinePaymentAction = (ctPayment?: Payment): DeterminePaymentAct break; default: action = ConnectorActions.NoAction; - errorMessage = 'SCTM - No payment actions matched'; + logger.warn('SCTM - No payment actions matched'); } return { diff --git a/processor/src/validators/payment.validators.ts b/processor/src/validators/payment.validators.ts index c150c7a..528881d 100644 --- a/processor/src/validators/payment.validators.ts +++ b/processor/src/validators/payment.validators.ts @@ -3,7 +3,7 @@ import { PaymentMethod as MolliePaymentMethods } from '@mollie/api-client'; import SkipError from '../errors/skip.error'; import CustomError from '../errors/custom.error'; import { logger } from '../utils/logger.utils'; -import { CustomFields } from '../utils/constant.utils'; +import { ConnectorActions, CustomFields } from '../utils/constant.utils'; /** * Checks if the given action is either 'Create' or 'Update'. @@ -56,7 +56,7 @@ export const hasValidPaymentMethod: (method: string | undefined) => boolean = (m * The `errorMessage` property contains the error message if the input is invalid. * @param ctPayment */ -export const checkPaymentMethodInput = (ctPayment: CTPayment): true | CustomError => { +export const checkPaymentMethodInput = (connectorAction: string, ctPayment: CTPayment): true | CustomError => { const CTPaymentMethod = ctPayment.paymentMethodInfo?.method ?? ''; const [method] = CTPaymentMethod.split(','); @@ -128,16 +128,23 @@ export const checkAmountPlanned = (ctPayment: CTPayment): true | CustomError => /** * Validates the payload of a CommerceTools payment based on the provided action and payment object. * - * @param {string} action - The action to perform on the payment. - * @param {CTPayment} CTPayment - The CommerceTools payment object to validate. + * @param {string} extensionAction - The action to perform on the payment. + * @param {string} controllerAction - The determined action that need to be done with the payment. + * @param {CTPayment} ctPayment - The CommerceTools payment object to validate. * @return {void} - An object containing the validated action and an error message if validation fails. */ -export const validateCommerceToolsPaymentPayload = (action: string, ctPayment: CTPayment): void => { - checkExtensionAction(action); +export const validateCommerceToolsPaymentPayload = ( + extensionAction: string, + connectorAction: string, + ctPayment: CTPayment, +): void => { + checkExtensionAction(extensionAction); checkPaymentInterface(ctPayment); - // checkPaymentMethodInput(ctPayment); + if (connectorAction === ConnectorActions.CreatePayment) { + checkPaymentMethodInput(connectorAction, ctPayment); + } checkAmountPlanned(ctPayment); }; diff --git a/processor/tests/controllers/payment.controller.spec.ts b/processor/tests/controllers/payment.controller.spec.ts index cb4c129..73bd968 100644 --- a/processor/tests/controllers/payment.controller.spec.ts +++ b/processor/tests/controllers/payment.controller.spec.ts @@ -53,6 +53,11 @@ describe('Test payment.controller.ts', () => { } as unknown as Payment, } as PaymentReference; + (determinePaymentAction as jest.Mock).mockReturnValue({ + action: ConnectorActions.GetPaymentMethods, + errorMessage: '', + }); + (validateCommerceToolsPaymentPayload as jest.Mock).mockImplementationOnce(() => { throw new CustomError(400, 'dummy message'); }); @@ -61,7 +66,8 @@ describe('Test payment.controller.ts', () => { expect(error).toBeInstanceOf(CustomError); expect(error.statusCode).toBe(400); expect(error.message).toBe('dummy message'); - expect(determinePaymentAction).toBeCalledTimes(0); + expect(determinePaymentAction).toBeCalledTimes(1); + expect(validateCommerceToolsPaymentPayload).toBeCalledTimes(1); expect(handleListPaymentMethodsByPayment).toBeCalledTimes(0); expect(handleCreatePayment).toBeCalledTimes(0); }); diff --git a/processor/tests/validators/payment.validators.spec.ts b/processor/tests/validators/payment.validators.spec.ts index 5931dc0..8cb4a4c 100644 --- a/processor/tests/validators/payment.validators.spec.ts +++ b/processor/tests/validators/payment.validators.spec.ts @@ -1,3 +1,4 @@ +import { ConnectorActions } from './../../src/utils/constant.utils'; import { Payment } from '@commercetools/platform-sdk'; import { checkExtensionAction, @@ -5,6 +6,7 @@ import { checkPaymentMethodInput, checkPaymentMethodSpecificParameters, hasValidPaymentMethod, + validateCommerceToolsPaymentPayload, } from './../../src/validators/payment.validators'; import { describe, it, expect, jest, afterEach } from '@jest/globals'; import CustomError from '../../src/errors/custom.error'; @@ -149,7 +151,7 @@ describe('checkPaymentMethodInput', () => { jest.clearAllMocks(); // Clear all mocks after each test case }); - it('should throw CustomError and a correct error message if the payment method is not defined', () => { + it('should throw CustomError and a correct error message if the payment method is not defined when trying to create a Mollie payment', () => { const CTPayment: Payment = { id: '5c8b0375-305a-4f19-ae8e-07806b101999', version: 1, @@ -168,7 +170,7 @@ describe('checkPaymentMethodInput', () => { }; try { - checkPaymentMethodInput(CTPayment); + checkPaymentMethodInput(ConnectorActions.CreatePayment, CTPayment); } catch (error: any) { expect(error).toBeInstanceOf(CustomError); expect(error.message).toBe( @@ -198,7 +200,7 @@ describe('checkPaymentMethodInput', () => { }; try { - checkPaymentMethodInput(CTPayment); + checkPaymentMethodInput(ConnectorActions.CreatePayment, CTPayment); } catch (error: any) { expect(error).toBeInstanceOf(CustomError); expect(error.message).toBe( @@ -227,7 +229,7 @@ describe('checkPaymentMethodInput', () => { }, }; - expect(checkPaymentMethodInput(CTPayment)).toBe(true); + expect(checkPaymentMethodInput(ConnectorActions.CreatePayment, CTPayment)).toBe(true); }); }); @@ -361,3 +363,44 @@ describe('checkPaymentMethodSpecificParameters', () => { expect(checkPaymentMethodSpecificParameters(CTPayment, CTPayment.paymentMethodInfo.method as string)).toBe(true); }); }); + +import * as paymentValidators from '../../src/validators/payment.validators'; + +describe('validateCommerceToolsPaymentPayload', () => { + jest.spyOn(paymentValidators, 'checkPaymentMethodInput'); + + it('should not call the checkPaymentMethodInput when the action is not "CreatePayment"', () => { + try { + validateCommerceToolsPaymentPayload('Update', ConnectorActions.GetPaymentMethods, {} as Payment); + } catch (error: unknown) { + expect(checkPaymentMethodInput).toBeCalledTimes(0); + } + }); + + it('should call the checkPaymentMethodInput when the action is "CreatePayment"', () => { + try { + const CTPayment: Payment = { + id: '5c8b0375-305a-4f19-ae8e-07806b101999', + version: 1, + createdAt: '2024-07-04T14:07:35.625Z', + lastModifiedAt: '2024-07-04T14:07:35.625Z', + amountPlanned: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 1000, + fractionDigits: 2, + }, + paymentStatus: {}, + transactions: [], + interfaceInteractions: [], + paymentMethodInfo: { + paymentInterface: 'Mollie', + }, + }; + + validateCommerceToolsPaymentPayload('Update', ConnectorActions.CreatePayment, CTPayment); + } catch (error: unknown) { + expect(checkPaymentMethodInput).toBeCalledTimes(1); + } + }); +});