Skip to content

Commit

Permalink
Bugfix: Add surcharge amount to Mollie payment + Update surcharge amo…
Browse files Browse the repository at this point in the history
…unt to CT Transaction custom fields

Bugfix: Add surcharge amount to Mollie payment + Update surcharge amount to CT Transaction custom fields
  • Loading branch information
NghiaDTr authored Nov 20, 2024
2 parents 7ad12b8 + c15a623 commit beb7acd
Show file tree
Hide file tree
Showing 13 changed files with 405 additions and 87 deletions.
9 changes: 9 additions & 0 deletions processor/src/commercetools/action.commercetools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,12 @@ export const addCustomLineItem = (
taxCategory,
};
};

export const setTransactionCustomField = (name: string, value: string, transactionId: string) => {
return {
action: 'setTransactionCustomField',
name,
value,
transactionId,
};
};
73 changes: 50 additions & 23 deletions processor/src/service/payment.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import {
changeTransactionState,
changeTransactionTimestamp,
setCustomFields,
setTransactionCustomField,
setTransactionCustomType,
} from '../commercetools/action.commercetools';
import { readConfiguration } from '../utils/config.utils';
Expand All @@ -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';
Expand Down Expand Up @@ -387,7 +393,27 @@ export const handleCreatePayment = async (ctPayment: Payment): Promise<Controlle

const cart = await getCartFromPayment(ctPayment.id);

const paymentParams = createMollieCreatePaymentParams(ctPayment, extensionUrl);
const [method] = ctPayment?.paymentMethodInfo?.method?.split(',') ?? [null, null];

logger.debug(`SCTM - handleCreatePayment - Getting customized configuration for payment method: ${method}`);
const paymentMethodConfig = await getSingleMethodConfigObject(method as string);
const billingCountry = getBillingCountry(ctPayment);

const pricingConstraint = paymentMethodConfig.value.pricingConstraints?.find((constraint: PricingConstraintItem) => {
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]) {
Expand All @@ -402,28 +428,12 @@ export const handleCreatePayment = async (ctPayment: Payment): Promise<Controlle
molliePayment = await createPaymentWithCustomMethod(paymentParams);
}

logger.debug(
`SCTM - handleCreatePayment - Getting customized configuration for payment method: ${paymentParams.method}`,
);
const paymentMethodConfig = await getSingleMethodConfigObject(paymentParams.method as string);
const billingCountry = getBillingCountry(ctPayment);

const pricingConstraint = paymentMethodConfig.value.pricingConstraints?.find((constraint: PricingConstraintItem) => {
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,
Expand All @@ -439,7 +449,11 @@ export const handleCreatePayment = async (ctPayment: Payment): Promise<Controlle
* @return {Promise<UpdateAction[]>} 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) => {
Expand Down Expand Up @@ -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
Expand All @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions processor/src/utils/app.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
};
3 changes: 3 additions & 0 deletions processor/src/utils/constant.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const CustomFields = {
response: 'sctm_apple_pay_session_response',
},
},
transactionSurchargeCost: 'sctm_transaction_surcharge_cost',
};

export enum ConnectorActions {
Expand Down Expand Up @@ -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';
50 changes: 42 additions & 8 deletions processor/src/utils/map.utils.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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];
Expand All @@ -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,
Expand All @@ -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),
};

Expand All @@ -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;
Expand All @@ -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,
};

Expand All @@ -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,
};
};
7 changes: 5 additions & 2 deletions processor/src/utils/mollie.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
};
Expand Down
20 changes: 17 additions & 3 deletions processor/tests/commercetools/action.commercetools.spec.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -8,6 +8,7 @@ import {
changeTransactionTimestamp,
removeCustomLineItem,
setCustomFields,
setTransactionCustomField,
setTransactionCustomType,
} from '../../src/commercetools/action.commercetools';
import { CTTransactionState, CreateInterfaceInteractionParams } from '../../src/types/commercetools.types';
Expand Down Expand Up @@ -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,
Expand All @@ -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,
});
});
});
19 changes: 0 additions & 19 deletions processor/tests/commercetools/customObjects.commercetools.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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({
Expand Down
Loading

0 comments on commit beb7acd

Please sign in to comment.