Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix: Add surcharge amount to Mollie payment + Update surcharge amount to CT Transaction custom fields #99

Merged
merged 4 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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