diff --git a/processor/src/commercetools/action.commercetools.ts b/processor/src/commercetools/action.commercetools.ts index 773b6cb..eaff2d2 100644 --- a/processor/src/commercetools/action.commercetools.ts +++ b/processor/src/commercetools/action.commercetools.ts @@ -139,3 +139,12 @@ export const addCustomLineItem = ( taxCategory, }; }; + +export const setTransactionCustomField = (name: string, value: string, transactionId: string) => { + return { + action: 'setTransactionCustomField', + name, + value, + transactionId, + }; +}; diff --git a/processor/src/service/payment.service.ts b/processor/src/service/payment.service.ts index 7d3a85a..ccdfad5 100644 --- a/processor/src/service/payment.service.ts +++ b/processor/src/service/payment.service.ts @@ -54,6 +54,7 @@ import { changeTransactionState, changeTransactionTimestamp, setCustomFields, + setTransactionCustomField, setTransactionCustomType, } from '../commercetools/action.commercetools'; import { readConfiguration } from '../utils/config.utils'; @@ -66,7 +67,12 @@ import { getPaymentExtension } from '../commercetools/extensions.commercetools'; import { HttpDestination } from '@commercetools/platform-sdk/dist/declarations/src/generated/models/extension'; import { cancelPaymentRefund, createPaymentRefund, getPaymentRefund } from '../mollie/refund.mollie'; import { ApplePaySessionRequest, CustomPayment, SupportedPaymentMethods } from '../types/mollie.types'; -import { calculateTotalSurchargeAmount, convertCentToEUR, parseStringToJsonObject } from '../utils/app.utils'; +import { + calculateTotalSurchargeAmount, + convertCentToEUR, + parseStringToJsonObject, + roundSurchargeAmountToCent, +} from '../utils/app.utils'; import ApplePaySession from '@mollie/api-client/dist/types/src/data/applePaySession/ApplePaySession'; import { getMethodConfigObjects, getSingleMethodConfigObject } from '../commercetools/customObjects.commercetools'; import { getCartFromPayment, updateCart } from '../commercetools/cart.commercetools'; @@ -387,7 +393,27 @@ export const handleCreatePayment = async (ctPayment: Payment): Promise { + return ( + constraint.countryCode === billingCountry && constraint.currencyCode === ctPayment.amountPlanned.currencyCode + ); + }) as PricingConstraintItem; + + logger.debug(`SCTM - handleCreatePayment - Calculating total surcharge amount`); + const surchargeAmountInCent = pricingConstraint + ? roundSurchargeAmountToCent( + calculateTotalSurchargeAmount(ctPayment, pricingConstraint.surchargeCost), + ctPayment.amountPlanned.fractionDigits, + ) + : 0; + + const paymentParams = createMollieCreatePaymentParams(ctPayment, extensionUrl, surchargeAmountInCent); let molliePayment; if (PaymentMethod[paymentParams.method as PaymentMethod]) { @@ -402,28 +428,12 @@ export const handleCreatePayment = async (ctPayment: Payment): Promise { - return ( - constraint.countryCode === billingCountry && constraint.currencyCode === ctPayment.amountPlanned.currencyCode - ); - }) as PricingConstraintItem; - - logger.debug(`SCTM - handleCreatePayment - Calculating total surcharge amount`); - const surchargeAmount = pricingConstraint - ? calculateTotalSurchargeAmount(ctPayment, pricingConstraint.surchargeCost) - : 0; - const cartUpdateActions = createCartUpdateActions(cart, ctPayment, surchargeAmount); + const cartUpdateActions = createCartUpdateActions(cart, ctPayment, surchargeAmountInCent); if (cartUpdateActions.length > 0) { await updateCart(cart, cartUpdateActions as CartUpdateAction[]); } - const ctActions = await getCreatePaymentUpdateAction(molliePayment, ctPayment); + const ctActions = await getCreatePaymentUpdateAction(molliePayment, ctPayment, surchargeAmountInCent); return { statusCode: 201, @@ -439,7 +449,11 @@ export const handleCreatePayment = async (ctPayment: Payment): Promise} A promise that resolves to an array of update actions. * @throws {Error} If the original transaction is not found. */ -export const getCreatePaymentUpdateAction = async (molliePayment: MPayment | CustomPayment, CTPayment: Payment) => { +export const getCreatePaymentUpdateAction = async ( + molliePayment: MPayment | CustomPayment, + CTPayment: Payment, + surchargeAmountInCent: number, +) => { try { // Find the original transaction which triggered create order const originalTransaction = CTPayment.transactions?.find((transaction) => { @@ -475,7 +489,7 @@ export const getCreatePaymentUpdateAction = async (molliePayment: MPayment | Cus sctm_created_at: molliePayment.createdAt, }; - return Promise.resolve([ + const actions: UpdateAction[] = [ // Add interface interaction addInterfaceInteraction(interfaceInteractionParams), // Update transaction interactionId @@ -484,7 +498,20 @@ export const getCreatePaymentUpdateAction = async (molliePayment: MPayment | Cus changeTransactionTimestamp(originalTransaction.id, molliePayment.createdAt), // Update transaction state changeTransactionState(originalTransaction.id, CTTransactionState.Pending), - ]); + ]; + + if (surchargeAmountInCent > 0) { + // Add surcharge amount to the custom field of the transaction + actions.push( + setTransactionCustomField( + CustomFields.transactionSurchargeCost, + JSON.stringify({ surchargeAmountInCent }), + originalTransaction.id, + ), + ); + } + + return Promise.resolve(actions); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { return Promise.reject(error); diff --git a/processor/src/utils/app.utils.ts b/processor/src/utils/app.utils.ts index c5d3a8b..5c30a59 100644 --- a/processor/src/utils/app.utils.ts +++ b/processor/src/utils/app.utils.ts @@ -97,3 +97,7 @@ export const calculateTotalSurchargeAmount = (ctPayment: Payment, surcharges?: S return (amount * percentageAmount) / 100 + fixedAmount; }; + +export const roundSurchargeAmountToCent = (surchargeAmountInEur: number, fractionDigits: number): number => { + return Math.round(surchargeAmountInEur * Math.pow(10, fractionDigits)); +}; diff --git a/processor/src/utils/constant.utils.ts b/processor/src/utils/constant.utils.ts index 9720900..1eea193 100644 --- a/processor/src/utils/constant.utils.ts +++ b/processor/src/utils/constant.utils.ts @@ -38,6 +38,7 @@ export const CustomFields = { response: 'sctm_apple_pay_session_response', }, }, + transactionSurchargeCost: 'sctm_transaction_surcharge_cost', }; export enum ConnectorActions { @@ -65,3 +66,5 @@ export const DEFAULT_DUE_DATE = 14; export const CUSTOM_OBJECT_CONTAINER_NAME = 'sctm-app-methods'; export const MOLLIE_SURCHARGE_CUSTOM_LINE_ITEM = 'mollie-surcharge-line-item'; + +export const MOLLIE_SURCHARGE_LINE_DESCRIPTION = 'Total surcharge amount'; diff --git a/processor/src/utils/map.utils.ts b/processor/src/utils/map.utils.ts index 91656eb..8c7c5db 100644 --- a/processor/src/utils/map.utils.ts +++ b/processor/src/utils/map.utils.ts @@ -1,11 +1,11 @@ -import { CustomFields, MOLLIE_SURCHARGE_CUSTOM_LINE_ITEM } from './constant.utils'; +import { CustomFields, MOLLIE_SURCHARGE_CUSTOM_LINE_ITEM, MOLLIE_SURCHARGE_LINE_DESCRIPTION } from './constant.utils'; import { logger } from './logger.utils'; import { calculateDueDate, makeMollieAmount } from './mollie.utils'; import { CustomPaymentMethod, ParsedMethodsRequestType } from '../types/mollie.types'; import { Cart, CartUpdateAction, Payment, TaxCategoryResourceIdentifier } from '@commercetools/platform-sdk'; import CustomError from '../errors/custom.error'; import { MethodsListParams, PaymentCreateParams, PaymentMethod } from '@mollie/api-client'; -import { parseStringToJsonObject, removeEmptyProperties } from './app.utils'; +import { convertCentToEUR, parseStringToJsonObject, removeEmptyProperties } from './app.utils'; import { readConfiguration } from './config.utils'; import { addCustomLineItem, removeCustomLineItem } from '../commercetools/action.commercetools'; @@ -105,7 +105,11 @@ const getSpecificPaymentParams = (method: PaymentMethod | CustomPaymentMethod, p } }; -export const createMollieCreatePaymentParams = (payment: Payment, extensionUrl: string): PaymentCreateParams => { +export const createMollieCreatePaymentParams = ( + payment: Payment, + extensionUrl: string, + surchargeAmountInCent: number, +): PaymentCreateParams => { const { amountPlanned, paymentMethodInfo } = payment; const [method, issuer] = paymentMethodInfo?.method?.split(',') ?? [null, null]; @@ -116,10 +120,21 @@ export const createMollieCreatePaymentParams = (payment: Payment, extensionUrl: payment.id, ); + const mollieLines = paymentRequest.lines ?? []; + if (surchargeAmountInCent > 0) { + mollieLines.push( + createMollieLineForSurchargeAmount( + surchargeAmountInCent, + amountPlanned.fractionDigits, + amountPlanned.currencyCode, + ), + ); + } + const defaultWebhookEndpoint = new URL(extensionUrl).origin + '/webhook'; const createPaymentParams = { - amount: makeMollieAmount(amountPlanned), + amount: makeMollieAmount(amountPlanned, surchargeAmountInCent), description: paymentRequest.description ?? '', redirectUrl: paymentRequest.redirectUrl ?? null, webhookUrl: defaultWebhookEndpoint, @@ -133,7 +148,7 @@ export const createMollieCreatePaymentParams = (payment: Payment, extensionUrl: applicationFee: paymentRequest.applicationFee ?? {}, include: paymentRequest.include ?? '', captureMode: paymentRequest.captureMode ?? '', - lines: paymentRequest.lines ?? [], + lines: mollieLines, ...getSpecificPaymentParams(method as PaymentMethod, paymentRequest), }; @@ -143,7 +158,7 @@ export const createMollieCreatePaymentParams = (payment: Payment, extensionUrl: export const createCartUpdateActions = ( cart: Cart, ctPayment: Payment, - surchargeAmount: number, + surchargeAmountInCent: number, ): CartUpdateAction[] => { const mollieSurchargeCustomLine = cart.customLineItems.find((item) => { return item.key === MOLLIE_SURCHARGE_CUSTOM_LINE_ITEM; @@ -155,14 +170,14 @@ export const createCartUpdateActions = ( updateActions.push(removeCustomLineItem(mollieSurchargeCustomLine.id)); } - if (surchargeAmount > 0) { + if (surchargeAmountInCent > 0) { const name = { en: MOLLIE_SURCHARGE_CUSTOM_LINE_ITEM, de: MOLLIE_SURCHARGE_CUSTOM_LINE_ITEM, }; const money = { - centAmount: Math.round(surchargeAmount * Math.pow(10, ctPayment.amountPlanned.fractionDigits)), + centAmount: surchargeAmountInCent, currencyCode: ctPayment.amountPlanned.currencyCode, }; @@ -178,3 +193,22 @@ export const createCartUpdateActions = ( return updateActions; }; + +export const createMollieLineForSurchargeAmount = ( + surchargeAmountInCent: number, + fractionDigits: number, + currency: string, +) => { + const totalSurchargeAmount = { + currency, + value: convertCentToEUR(surchargeAmountInCent, fractionDigits).toFixed(2), + }; + + return { + description: MOLLIE_SURCHARGE_LINE_DESCRIPTION, + quantity: 1, + quantityUnit: 'pcs', + unitPrice: totalSurchargeAmount, + totalAmount: totalSurchargeAmount, + }; +}; diff --git a/processor/src/utils/mollie.utils.ts b/processor/src/utils/mollie.utils.ts index 9f3cf89..afcc058 100644 --- a/processor/src/utils/mollie.utils.ts +++ b/processor/src/utils/mollie.utils.ts @@ -11,9 +11,12 @@ const convertCTToMollieAmountValue = (ctValue: number, fractionDigits = 2): stri return (ctValue / divider).toFixed(fractionDigits); }; -export const makeMollieAmount = ({ centAmount, fractionDigits, currencyCode }: CentPrecisionMoney): Amount => { +export const makeMollieAmount = ( + { centAmount, fractionDigits, currencyCode }: CentPrecisionMoney, + surchargeAmountInCent: number = 0, +): Amount => { return { - value: convertCTToMollieAmountValue(centAmount, fractionDigits), + value: convertCTToMollieAmountValue(centAmount + surchargeAmountInCent, fractionDigits), currency: currencyCode, }; }; diff --git a/processor/tests/commercetools/action.commercetools.spec.ts b/processor/tests/commercetools/action.commercetools.spec.ts index 5eaf636..3035ef1 100644 --- a/processor/tests/commercetools/action.commercetools.spec.ts +++ b/processor/tests/commercetools/action.commercetools.spec.ts @@ -1,4 +1,4 @@ -import { ConnectorActions, MOLLIE_SURCHARGE_CUSTOM_LINE_ITEM } from '../../src/utils/constant.utils'; +import { ConnectorActions, CustomFields, MOLLIE_SURCHARGE_CUSTOM_LINE_ITEM } from '../../src/utils/constant.utils'; import { describe, test, expect, jest } from '@jest/globals'; import { addCustomLineItem, @@ -8,6 +8,7 @@ import { changeTransactionTimestamp, removeCustomLineItem, setCustomFields, + setTransactionCustomField, setTransactionCustomType, } from '../../src/commercetools/action.commercetools'; import { CTTransactionState, CreateInterfaceInteractionParams } from '../../src/types/commercetools.types'; @@ -163,8 +164,6 @@ describe('Test actions.utils.ts', () => { }); test('should be able to return the correct addCustomLineItem action', () => { - const customId = 'custom-id'; - const name = { de: MOLLIE_SURCHARGE_CUSTOM_LINE_ITEM, en: MOLLIE_SURCHARGE_CUSTOM_LINE_ITEM, @@ -184,4 +183,19 @@ describe('Test actions.utils.ts', () => { slug, }); }); + + test('should be able to return the correct setTransactionCustomField action', () => { + const name = CustomFields.transactionSurchargeCost; + const surchargeInCentAmount = { + surchargeInCentAmount: 12345, + }; + const transactionId = 'test'; + + expect(setTransactionCustomField(name, JSON.stringify(surchargeInCentAmount), transactionId)).toStrictEqual({ + action: 'setTransactionCustomField', + name, + value: JSON.stringify(surchargeInCentAmount), + transactionId, + }); + }); }); diff --git a/processor/tests/commercetools/customObjects.commercetools.spec.ts b/processor/tests/commercetools/customObjects.commercetools.spec.ts index e803c47..6debd2c 100644 --- a/processor/tests/commercetools/customObjects.commercetools.spec.ts +++ b/processor/tests/commercetools/customObjects.commercetools.spec.ts @@ -58,19 +58,6 @@ describe('Test getMethodConfigObjects', () => { }); it('should throw error', async () => { - const mockWithContainer = jest.fn(); - const mockGet = jest.fn(); - const customObjects = [ - { - id: '123', - name: '123', - }, - { - id: 'test', - name: 'test', - }, - ] as unknown as CustomObject[]; - const error = new Error('dummy error'); (createApiRoot as jest.Mock).mockReturnValue({ @@ -133,12 +120,6 @@ describe('Test getSingleMethodConfigObject', () => { it('should throw error', async () => { const key = 'test'; - const customObject = { - id: '123', - key, - name: '123', - } as unknown as CustomObject[]; - const mockError = new Error('dummy error'); (createApiRoot as jest.Mock).mockReturnValue({ diff --git a/processor/tests/service/payment.service.spec.ts b/processor/tests/service/payment.service.spec.ts index 590154f..27a5147 100644 --- a/processor/tests/service/payment.service.spec.ts +++ b/processor/tests/service/payment.service.spec.ts @@ -983,11 +983,11 @@ describe('Test getCreatePaymentUpdateAction', () => { (getCreatePaymentUpdateAction as jest.Mock).mockImplementationOnce(() => { const paymentService = jest.requireActual( '../../src/service/payment.service.ts', - ) as typeof import('../../src/service/payment.service.ts'); - return paymentService.getCreatePaymentUpdateAction(molliePayment, CTPayment); + ) as typeof import('../../src/service/payment.service'); + return paymentService.getCreatePaymentUpdateAction(molliePayment, CTPayment, 0); }); - getCreatePaymentUpdateAction(molliePayment, CTPayment).catch((error) => { + getCreatePaymentUpdateAction(molliePayment, CTPayment, 0).catch((error) => { expect(error).toEqual({ status: 400, title: 'Cannot find original transaction', @@ -1048,8 +1048,8 @@ describe('Test getCreatePaymentUpdateAction', () => { (getCreatePaymentUpdateAction as jest.Mock).mockImplementationOnce(() => { const paymentService = jest.requireActual( '../../src/service/payment.service.ts', - ) as typeof import('../../src/service/payment.service.ts'); - return paymentService.getCreatePaymentUpdateAction(molliePayment, CTPayment); + ) as typeof import('../../src/service/payment.service'); + return paymentService.getCreatePaymentUpdateAction(molliePayment, CTPayment, 0); }); (changeTransactionState as jest.Mock).mockReturnValueOnce({ @@ -1058,7 +1058,7 @@ describe('Test getCreatePaymentUpdateAction', () => { transactionId: '5c8b0375-305a-4f19-ae8e-07806b101999', }); - const actual = await getCreatePaymentUpdateAction(molliePayment, CTPayment); + const actual = await getCreatePaymentUpdateAction(molliePayment, CTPayment, 0); expect(actual).toHaveLength(4); expect(actual[0]).toEqual({ @@ -1100,6 +1100,120 @@ describe('Test getCreatePaymentUpdateAction', () => { state: CTTransactionState.Pending, }); }); + + test('should return an array of actions included setTransactionCustomField when surcharge amount is not 0', async () => { + 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: [ + { + id: '5c8b0375-305a-4f19-ae8e-07806b101999', + type: 'Authorization', + amount: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 1000, + fractionDigits: 2, + }, + state: 'Initial', + }, + ], + interfaceInteractions: [], + paymentMethodInfo: { + method: 'creditcard', + }, + }; + + const molliePayment: molliePayment = { + resource: 'payment', + id: 'tr_7UhSN1zuXS', + amount: { currency: 'USD', value: '10.00' }, + createdAt: '2024-07-05T04:24:12+00:00', + _links: { + checkout: { + href: 'https://api.mollie.com/v2/payments/tr_7UhSN1zuXS', + type: 'https://api.mollie.com/v2/payments/tr_7UhSN1zuXS', + }, + documentation: { + href: 'https://api.mollie.com/v2/payments/tr_7UhSN1zuXS', + type: 'https://api.mollie.com/v2/payments/tr_7UhSN1zuXS', + }, + }, + } as molliePayment; + + (getCreatePaymentUpdateAction as jest.Mock).mockImplementationOnce(() => { + const paymentService = jest.requireActual( + '../../src/service/payment.service.ts', + ) as typeof import('../../src/service/payment.service'); + return paymentService.getCreatePaymentUpdateAction(molliePayment, CTPayment, 1000); + }); + + (changeTransactionState as jest.Mock).mockReturnValueOnce({ + action: 'changeTransactionState', + state: 'Pending', + transactionId: '5c8b0375-305a-4f19-ae8e-07806b101999', + }); + + const actual = await getCreatePaymentUpdateAction(molliePayment, CTPayment, 1000); + expect(actual).toHaveLength(5); + + expect(actual[0]).toEqual({ + action: 'addInterfaceInteraction', + type: { + key: 'sctm_interface_interaction_type', + }, + fields: { + sctm_id: uuid, + sctm_action_type: ConnectorActions.CreatePayment, + sctm_created_at: molliePayment.createdAt, + sctm_request: JSON.stringify({ + transactionId: CTPayment.transactions[0].id, + paymentMethod: CTPayment.paymentMethodInfo.method, + }), + sctm_response: JSON.stringify({ + molliePaymentId: molliePayment.id, + checkoutUrl: molliePayment._links.checkout?.href, + transactionId: CTPayment.transactions[0].id, + }), + }, + }); + + expect(actual[1]).toEqual({ + action: 'changeTransactionInteractionId', + transactionId: CTPayment.transactions[0].id, + interactionId: molliePayment.id, + }); + + expect(actual[2]).toEqual({ + action: 'changeTransactionTimestamp', + transactionId: CTPayment.transactions[0].id, + timestamp: molliePayment.createdAt, + }); + + expect(actual[3]).toEqual({ + action: 'changeTransactionState', + transactionId: CTPayment.transactions[0].id, + state: CTTransactionState.Pending, + }); + + expect(actual[4]).toEqual({ + action: 'setTransactionCustomField', + name: CustomFieldName.transactionSurchargeCost, + value: JSON.stringify({ + surchargeAmountInCent: 1000, + }), + transactionId: CTPayment.transactions[0].id, + }); + }); }); describe('Test handleCreatePayment', () => { @@ -1240,7 +1354,7 @@ describe('Test handleCreatePayment', () => { (updateCart as jest.Mock).mockReturnValue(mockedCart); - const totalSurchargeAmount = 10.2; + const totalSurchargeAmount = 1020; const actual = await handleCreatePayment(CTPayment); @@ -1257,7 +1371,7 @@ describe('Test handleCreatePayment', () => { }, quantity: 1, money: { - centAmount: Math.round(totalSurchargeAmount * Math.pow(10, CTPayment.amountPlanned.fractionDigits)), + centAmount: totalSurchargeAmount, currencyCode: CTPayment.amountPlanned.currencyCode, }, slug: MOLLIE_SURCHARGE_CUSTOM_LINE_ITEM, @@ -1270,7 +1384,9 @@ describe('Test handleCreatePayment', () => { CTPayment, methodConfig.value.pricingConstraints[0].surchargeCost, ); - expect(calculateTotalSurchargeAmount).toHaveReturnedWith(totalSurchargeAmount); + expect(calculateTotalSurchargeAmount).toHaveReturnedWith( + totalSurchargeAmount / Math.pow(10, CTPayment.amountPlanned.fractionDigits), + ); expect(createCartUpdateActions).toHaveBeenCalledTimes(1); expect(createCartUpdateActions).toHaveBeenCalledWith(mockedCart, CTPayment, totalSurchargeAmount); @@ -1304,6 +1420,14 @@ describe('Test handleCreatePayment', () => { transactionId: '5c8b0375-305a-4f19-ae8e-07806b101999', state: 'Pending', }, + { + action: 'setTransactionCustomField', + name: 'sctm_transaction_surcharge_cost', + transactionId: '5c8b0375-305a-4f19-ae8e-07806b101999', + value: JSON.stringify({ + surchargeAmountInCent: totalSurchargeAmount, + }), + }, ]; expect(actual).toEqual({ @@ -1400,7 +1524,7 @@ describe('Test handleCreatePayment', () => { (updateCart as jest.Mock).mockReturnValueOnce(cart); - const totalSurchargeAmount = 10.2; + const totalSurchargeAmount = 1020; const actual = await handleCreatePayment(CTPayment); @@ -1417,7 +1541,7 @@ describe('Test handleCreatePayment', () => { }, quantity: 1, money: { - centAmount: Math.round(totalSurchargeAmount * Math.pow(10, CTPayment.amountPlanned.fractionDigits)), + centAmount: totalSurchargeAmount, currencyCode: CTPayment.amountPlanned.currencyCode, }, slug: MOLLIE_SURCHARGE_CUSTOM_LINE_ITEM, @@ -1429,7 +1553,9 @@ describe('Test handleCreatePayment', () => { CTPayment, methodConfig.value.pricingConstraints[0].surchargeCost, ); - expect(calculateTotalSurchargeAmount).toHaveReturnedWith(totalSurchargeAmount); + expect(calculateTotalSurchargeAmount).toHaveReturnedWith( + totalSurchargeAmount / Math.pow(10, CTPayment.amountPlanned.fractionDigits), + ); expect(createCartUpdateActions).toHaveBeenCalledTimes(1); expect(createCartUpdateActions).toHaveBeenCalledWith(cart, CTPayment, totalSurchargeAmount); @@ -1463,6 +1589,14 @@ describe('Test handleCreatePayment', () => { transactionId: '5c8b0375-305a-4f19-ae8e-07806b101999', state: 'Pending', }, + { + action: 'setTransactionCustomField', + name: 'sctm_transaction_surcharge_cost', + transactionId: '5c8b0375-305a-4f19-ae8e-07806b101999', + value: JSON.stringify({ + surchargeAmountInCent: totalSurchargeAmount, + }), + }, ]; expect(createPaymentWithCustomMethod).toBeCalledTimes(1); diff --git a/processor/tests/utils/app.utils.spec.ts b/processor/tests/utils/app.utils.spec.ts index 30d499d..60eda64 100644 --- a/processor/tests/utils/app.utils.spec.ts +++ b/processor/tests/utils/app.utils.spec.ts @@ -5,6 +5,7 @@ import { createDateNowString, parseStringToJsonObject, removeEmptyProperties, + roundSurchargeAmountToCent, validateEmail, } from '../../src/utils/app.utils'; import { logger } from '../../src/utils/logger.utils'; @@ -134,3 +135,13 @@ describe('Test calculateTotalSurchargeAmount', () => { expect(calculateTotalSurchargeAmount(payment, undefined)).toBe(0); }); }); + +describe('Test roundSurchargeAmountToCent', () => { + it('should return correct surcharge amount in cent', () => { + const surchargeAmountInEur = 300.998; + + const fractionDigits = 2; + + expect(roundSurchargeAmountToCent(surchargeAmountInEur, fractionDigits)).toBe(30100); + }); +}); diff --git a/processor/tests/utils/constant.utils.spec.ts b/processor/tests/utils/constant.utils.spec.ts index 63e17c6..0355877 100644 --- a/processor/tests/utils/constant.utils.spec.ts +++ b/processor/tests/utils/constant.utils.spec.ts @@ -10,6 +10,7 @@ import { DUE_DATE_PATTERN, DEFAULT_DUE_DATE, MOLLIE_SURCHARGE_CUSTOM_LINE_ITEM, + MOLLIE_SURCHARGE_LINE_DESCRIPTION, } from '../../src/utils/constant.utils'; import { version } from '../../package.json'; @@ -84,4 +85,9 @@ describe('Test constant.utils.ts', () => { expect(MOLLIE_SURCHARGE_CUSTOM_LINE_ITEM).toBeDefined(); expect(MOLLIE_SURCHARGE_CUSTOM_LINE_ITEM).toBe('mollie-surcharge-line-item'); }); + + test('should return correct {MOLLIE_SURCHARGE_LINE_DESCRIPTION} pattern', () => { + expect(MOLLIE_SURCHARGE_LINE_DESCRIPTION).toBeDefined(); + expect(MOLLIE_SURCHARGE_LINE_DESCRIPTION).toBe('Total surcharge amount'); + }); }); diff --git a/processor/tests/utils/map.utils.spec.ts b/processor/tests/utils/map.utils.spec.ts index 52e0107..2cf3d97 100644 --- a/processor/tests/utils/map.utils.spec.ts +++ b/processor/tests/utils/map.utils.spec.ts @@ -2,13 +2,14 @@ import { describe, test, expect, it, jest } from '@jest/globals'; import { createCartUpdateActions, createMollieCreatePaymentParams, + createMollieLineForSurchargeAmount, mapCommercetoolsPaymentCustomFieldsToMollieListParams, } from '../../src/utils/map.utils'; import { Cart, Payment } from '@commercetools/platform-sdk'; import { MethodsListParams, PaymentCreateParams, PaymentMethod } from '@mollie/api-client'; import { calculateDueDate, makeMollieAmount } from '../../src/utils/mollie.utils'; import { CustomPaymentMethod } from '../../src/types/mollie.types'; -import { MOLLIE_SURCHARGE_CUSTOM_LINE_ITEM } from '../../src/utils/constant.utils'; +import { MOLLIE_SURCHARGE_CUSTOM_LINE_ITEM, MOLLIE_SURCHARGE_LINE_DESCRIPTION } from '../../src/utils/constant.utils'; jest.mock('../../src/utils/mollie.utils.ts', () => ({ // @ts-expect-error ignore type error @@ -85,7 +86,7 @@ describe('createMollieCreatePaymentParams', () => { }; const extensionUrl = 'https://example.com/webhook'; - const mollieCreatePaymentParams = createMollieCreatePaymentParams(CTPayment, extensionUrl); + const mollieCreatePaymentParams = createMollieCreatePaymentParams(CTPayment, extensionUrl, 0); const mollieAmount = makeMollieAmount(CTPayment.amountPlanned); expect(mollieCreatePaymentParams).toEqual({ @@ -99,6 +100,57 @@ describe('createMollieCreatePaymentParams', () => { }); }); + it('should able to create a mollie payment params from CommerceTools payment object for with method as creditcard and surcharge amount is not 0', async () => { + 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: { + method: 'creditcard', + }, + }; + const extensionUrl = 'https://example.com/webhook'; + + const surchargeAmountInCent = 1000; + + const mollieCreatePaymentParams = createMollieCreatePaymentParams(CTPayment, extensionUrl, surchargeAmountInCent); + const mollieAmount = { + currency: CTPayment.amountPlanned.currencyCode, + value: '20.00', + }; + + expect(mollieCreatePaymentParams).toEqual({ + method: CTPayment.paymentMethodInfo.method, + amount: mollieAmount, + webhookUrl: extensionUrl, + lines: [ + { + description: MOLLIE_SURCHARGE_LINE_DESCRIPTION, + quantity: 1, + quantityUnit: 'pcs', + unitPrice: { + currency: CTPayment.amountPlanned.currencyCode, + value: '10.00', + }, + totalAmount: { + currency: CTPayment.amountPlanned.currencyCode, + value: '10.00', + }, + }, + ], + }); + }); + it('should able to create a mollie payment params from CommerceTools payment object with method as creditcard which has custom field', async () => { const customFieldObject = { description: 'Test payment', @@ -137,7 +189,7 @@ describe('createMollieCreatePaymentParams', () => { }; const extensionUrl = 'https://example.com/webhook'; - const mollieCreatePaymentParams = createMollieCreatePaymentParams(CTPayment, extensionUrl); + const mollieCreatePaymentParams = createMollieCreatePaymentParams(CTPayment, extensionUrl, 0); expect(mollieCreatePaymentParams).toEqual({ method: 'creditcard', @@ -197,7 +249,7 @@ describe('createMollieCreatePaymentParams', () => { }; const extensionUrl = 'https://example.com/webhook'; - const mollieCreatePaymentParams: PaymentCreateParams = createMollieCreatePaymentParams(CTPayment, extensionUrl); + const mollieCreatePaymentParams: PaymentCreateParams = createMollieCreatePaymentParams(CTPayment, extensionUrl, 0); expect(mollieCreatePaymentParams).toEqual({ method: PaymentMethod.ideal, amount: { @@ -258,7 +310,7 @@ describe('createMollieCreatePaymentParams', () => { }; const extensionUrl = 'https://example.com/webhook'; - const mollieCreatePaymentParams: PaymentCreateParams = createMollieCreatePaymentParams(CTPayment, extensionUrl); + const mollieCreatePaymentParams: PaymentCreateParams = createMollieCreatePaymentParams(CTPayment, extensionUrl, 0); expect(mollieCreatePaymentParams).toEqual({ method: PaymentMethod.bancontact, amount: { @@ -317,7 +369,7 @@ describe('createMollieCreatePaymentParams', () => { const dueDate = '2024-01-01'; (calculateDueDate as jest.Mock).mockReturnValueOnce(dueDate); - const mollieCreatePaymentParams: PaymentCreateParams = createMollieCreatePaymentParams(CTPayment, extensionUrl); + const mollieCreatePaymentParams: PaymentCreateParams = createMollieCreatePaymentParams(CTPayment, extensionUrl, 0); expect(mollieCreatePaymentParams).toEqual({ method: PaymentMethod.banktransfer, @@ -373,7 +425,7 @@ describe('createMollieCreatePaymentParams', () => { }; const extensionUrl = 'https://example.com/webhook'; - const mollieCreatePaymentParams: PaymentCreateParams = createMollieCreatePaymentParams(CTPayment, extensionUrl); + const mollieCreatePaymentParams: PaymentCreateParams = createMollieCreatePaymentParams(CTPayment, extensionUrl, 0); expect(mollieCreatePaymentParams).toEqual({ method: PaymentMethod.przelewy24, amount: { @@ -426,7 +478,7 @@ describe('createMollieCreatePaymentParams', () => { }; const extensionUrl = 'https://example.com/webhook'; - const mollieCreatePaymentParams: PaymentCreateParams = createMollieCreatePaymentParams(CTPayment, extensionUrl); + const mollieCreatePaymentParams: PaymentCreateParams = createMollieCreatePaymentParams(CTPayment, extensionUrl, 0); expect(mollieCreatePaymentParams).toEqual({ method: PaymentMethod.kbc, amount: { @@ -480,7 +532,7 @@ describe('createMollieCreatePaymentParams', () => { const extensionUrl = 'https://example.com/webhook'; - const mollieCreatePaymentParams: PaymentCreateParams = createMollieCreatePaymentParams(CTPayment, extensionUrl); + const mollieCreatePaymentParams: PaymentCreateParams = createMollieCreatePaymentParams(CTPayment, extensionUrl, 0); expect(mollieCreatePaymentParams).toEqual({ method: CustomPaymentMethod.blik, amount: { @@ -534,7 +586,7 @@ describe('createMollieCreatePaymentParams', () => { }; const extensionUrl = 'https://example.com/webhook'; - const mollieCreatePaymentParams: PaymentCreateParams = createMollieCreatePaymentParams(CTPayment, extensionUrl); + const mollieCreatePaymentParams: PaymentCreateParams = createMollieCreatePaymentParams(CTPayment, extensionUrl, 0); expect(mollieCreatePaymentParams).toEqual({ method: PaymentMethod.applepay, amount: { @@ -589,7 +641,7 @@ describe('createMollieCreatePaymentParams', () => { }; const extensionUrl = 'https://example.com/webhook'; - const mollieCreatePaymentParams: PaymentCreateParams = createMollieCreatePaymentParams(CTPayment, extensionUrl); + const mollieCreatePaymentParams: PaymentCreateParams = createMollieCreatePaymentParams(CTPayment, extensionUrl, 0); expect(mollieCreatePaymentParams).toEqual({ method: PaymentMethod.paypal, amount: { @@ -645,7 +697,7 @@ describe('createMollieCreatePaymentParams', () => { }; const extensionUrl = 'https://example.com/webhook'; - const mollieCreatePaymentParams: PaymentCreateParams = createMollieCreatePaymentParams(CTPayment, extensionUrl); + const mollieCreatePaymentParams: PaymentCreateParams = createMollieCreatePaymentParams(CTPayment, extensionUrl, 0); expect(mollieCreatePaymentParams).toEqual({ method: PaymentMethod.giftcard, amount: { @@ -721,7 +773,7 @@ describe('createMollieCreatePaymentParams', () => { }; const extensionUrl = 'https://example.com/webhook'; - const mollieCreatePaymentParams: PaymentCreateParams = createMollieCreatePaymentParams(CTPayment, extensionUrl); + const mollieCreatePaymentParams: PaymentCreateParams = createMollieCreatePaymentParams(CTPayment, extensionUrl, 0); expect(mollieCreatePaymentParams).toEqual({ method: PaymentMethod.paypal, amount: { @@ -754,7 +806,7 @@ describe('Test createCartUpdateActions', () => { currencyCode: 'EUR', }, } as Payment; - const surchargeAmount = 10; + const surchargeAmountInCent = 10; const expectedResult = [ { @@ -766,8 +818,7 @@ describe('Test createCartUpdateActions', () => { quantity: 1, slug: MOLLIE_SURCHARGE_CUSTOM_LINE_ITEM, money: { - centAmount: Math.ceil(surchargeAmount * Math.pow(10, ctPayment.amountPlanned.fractionDigits)), - // centAmount: surchargeAmount, + centAmount: surchargeAmountInCent, currencyCode: ctPayment.amountPlanned.currencyCode, }, taxCategory: { @@ -776,7 +827,7 @@ describe('Test createCartUpdateActions', () => { }, ]; - expect(createCartUpdateActions(cart, ctPayment, surchargeAmount)).toEqual(expectedResult); + expect(createCartUpdateActions(cart, ctPayment, surchargeAmountInCent)).toEqual(expectedResult); }); it('should able to create cart update actions including remove the existing custom line item and insert new custom line item', async () => { @@ -800,7 +851,7 @@ describe('Test createCartUpdateActions', () => { currencyCode: 'EUR', }, } as Payment; - const surchargeAmount = 10; + const surchargeAmountInCent = 10; const expectedResult = [ { @@ -816,7 +867,7 @@ describe('Test createCartUpdateActions', () => { quantity: 1, slug: MOLLIE_SURCHARGE_CUSTOM_LINE_ITEM, money: { - centAmount: Math.ceil(surchargeAmount * Math.pow(10, ctPayment.amountPlanned.fractionDigits)), + centAmount: surchargeAmountInCent, // centAmount: surchargeAmount, currencyCode: ctPayment.amountPlanned.currencyCode, }, @@ -826,6 +877,30 @@ describe('Test createCartUpdateActions', () => { }, ]; - expect(createCartUpdateActions(cart, ctPayment, surchargeAmount)).toEqual(expectedResult); + expect(createCartUpdateActions(cart, ctPayment, surchargeAmountInCent)).toEqual(expectedResult); + }); +}); + +describe('Test createMollieLineForSurchargeAmount', () => { + it('should return a Mollie line for the surcharge amount', () => { + const surchargeAmountInCent = 1020; + const fractionDigits = 2; + const currency = 'EUR'; + + const expected = { + description: MOLLIE_SURCHARGE_LINE_DESCRIPTION, + quantity: 1, + quantityUnit: 'pcs', + unitPrice: { + currency, + value: '10.20', + }, + totalAmount: { + currency, + value: '10.20', + }, + }; + + expect(createMollieLineForSurchargeAmount(surchargeAmountInCent, fractionDigits, currency)).toStrictEqual(expected); }); }); diff --git a/processor/tests/utils/mollie.utils.spec.ts b/processor/tests/utils/mollie.utils.spec.ts index 7863481..c016a39 100644 --- a/processor/tests/utils/mollie.utils.spec.ts +++ b/processor/tests/utils/mollie.utils.spec.ts @@ -45,6 +45,23 @@ describe('Test mollie.utils.ts', () => { expect(makeMollieAmount(centPrecisionMoney)).toEqual(expected); }); + + it('should create a Mollie Amount from CentPrecisionMoney with surcharge amount is not 0', () => { + const centPrecisionMoney: CentPrecisionMoney = { + centAmount: 1234, + fractionDigits: 2, + currencyCode: 'EUR', + } as CentPrecisionMoney; + + const surchargeAmountInCent = 20; + + const expected: Amount = { + value: '12.54', + currency: 'EUR', + }; + + expect(makeMollieAmount(centPrecisionMoney, surchargeAmountInCent)).toEqual(expected); + }); }); describe('makeCTMoney', () => {