Skip to content

Commit

Permalink
fix(processor): fix the tax percentage calculations
Browse files Browse the repository at this point in the history
  • Loading branch information
joey-koster-ct committed Jan 6, 2025
1 parent 8308efa commit 5a92823
Show file tree
Hide file tree
Showing 4 changed files with 324 additions and 8 deletions.
54 changes: 47 additions & 7 deletions processor/src/services/converters/helper.converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ export const mapCoCoLineItemToAdyenLineItem = (lineItem: CoCoLineItem): LineItem
amountExcludingTax: getItemAmount(getAmountExcludingTax(lineItem), lineItem.quantity),
amountIncludingTax: getItemAmount(getAmountIncludingTax(lineItem), lineItem.quantity),
taxAmount: getItemAmount(getTaxAmount(lineItem), lineItem.quantity),
taxPercentage: convertTaxPercentageToCentAmount(lineItem.taxRate?.amount),
taxPercentage: convertTaxPercentageToAdyenMinorUnits(
lineItem.totalPrice.fractionDigits,
lineItem.totalPrice.currencyCode,
lineItem.taxRate?.amount,
),
};
};

Expand All @@ -36,7 +40,11 @@ export const mapCoCoCustomLineItemToAdyenLineItem = (customLineItem: CustomLineI
amountExcludingTax: getItemAmount(getAmountExcludingTax(customLineItem), customLineItem.quantity),
amountIncludingTax: getItemAmount(getAmountIncludingTax(customLineItem), customLineItem.quantity),
taxAmount: getItemAmount(getTaxAmount(customLineItem), customLineItem.quantity),
taxPercentage: convertTaxPercentageToCentAmount(customLineItem.taxRate?.amount),
taxPercentage: convertTaxPercentageToAdyenMinorUnits(
customLineItem.totalPrice.fractionDigits,
customLineItem.totalPrice.currencyCode,
customLineItem.taxRate?.amount,
),
};
};

Expand Down Expand Up @@ -74,7 +82,11 @@ export const mapCoCoShippingInfoToAdyenLineItem = (shippingInfo: ShippingInfo):
amountExcludingTax: amountExcludingTaxValue,
amountIncludingTax: amountIncludingTaxValue,
taxAmount: taxAmountValue,
taxPercentage: convertTaxPercentageToCentAmount(shippingInfo.taxRate?.amount),
taxPercentage: convertTaxPercentageToAdyenMinorUnits(
shippingInfo.price.fractionDigits,
shippingInfo.price.currencyCode,
shippingInfo.taxRate?.amount,
),
};
};

Expand Down Expand Up @@ -137,7 +149,6 @@ export const mapCoCoOrderItemsToAdyenLineItems = (
export const mapCoCoCartItemsToAdyenLineItems = (
cart: Pick<Cart, 'lineItems' | 'customLineItems' | 'shippingInfo' | 'discountOnTotalPrice'>,
): LineItem[] => {
// TODO: SCC-2800: fix amount parsing values
const aydenLineItems: LineItem[] = [];

cart.lineItems.forEach((lineItem) => aydenLineItems.push(mapCoCoLineItemToAdyenLineItem(lineItem)));
Expand Down Expand Up @@ -260,12 +271,41 @@ const getTaxAmount = (lineItem: CoCoLineItem | CustomLineItem): number => {
);
};

const convertTaxPercentageToCentAmount = (decimalTaxRate?: number): number => {
/**
* Convert the CoCo tax percentage, which ranges from 0-1 as floating point numbers to the expected Adyen minor units.
*
* This function applies the given fractionDigits to get the correct minor units. This also takes into account the deviations Adyen has with regards to the fractionDigits.
*
* @example CoCo taxRate of 0.21, normalized is 21% with currencyCode EUR and fractionDigits of 2 = expressed in Adyen minor units value as 2100
* @example CoCo taxRate of 0.21, normalized is 21% with currencyCode CLP, according to ISO standard has fractionDigits of 0 but Adyen deviats from it and so it has fractionDigits of 2 = expressed in Adyen minor units value as 2100
* @example CoCo taxRate of 0.21, normalized is 21% with currencyCode JPY and fractionDigits of 0 = expressed in Adyen minor units value as 21
*
* @param fractionDigits the fractionDigits that are applicable for the currencyCode according to ISO_4217
* @param currencyCode the applicable currencyCode
* @param decimalTaxRate the tax rate expressed in decimals between 0-1 as floating point numbers taken from the CoCo taxRate (see docs below)
*
* @see https://docs.commercetools.com/api/projects/taxCategories#ctp:api:type:TaxRate
* @see https://docs.adyen.com/api-explorer/Checkout/latest/post/sessions#request-lineItems-taxPercentage
* @see https://docs.adyen.com/development-resources/currency-codes/#minor-units
*/
const convertTaxPercentageToAdyenMinorUnits = (
fractionDigits: number,
currencyCode: string,
decimalTaxRate?: number,
): number => {
if (!decimalTaxRate) {
return 0;
}
// First go from the range of 0 - 1 (floating numbers) to value expressed as "non-decimals". I.e. 0.15% from CoCo becomes 15% tax rate value.
const normalizedTaxRate = decimalTaxRate * 100;

// TODO: SCC-2800: figure out what to do in this function
// Apply the fractionDigits for the cart (i.e. currencyCode) that is in CoCo according to the ISO_4217 standard but overrule the given fractionDigits from the CoCo if Adyen requires it based on the given mapping.
const result = MoneyConverters.convertWithOverrulingMapping(
CURRENCIES_FROM_ISO_TO_ADYEN_MAPPING,
normalizedTaxRate,
currencyCode,
-fractionDigits,
);

return decimalTaxRate * 100 * 100;
return result;
};
256 changes: 256 additions & 0 deletions processor/test/data/coco-cart-clp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
{
"type": "Cart",
"id": "00cdf819-8c36-46bd-a938-22c2712c6461",
"version": 7,
"versionModifiedAt": "2025-01-06T11:43:59.506Z",
"lastMessageSequenceNumber": 1,
"createdAt": "2025-01-06T11:43:48.883Z",
"lastModifiedAt": "2025-01-06T11:43:59.506Z",
"lastModifiedBy": {
"clientId": "1ZgJGJmwvAdOf8w8KcOOLmMX",
"isPlatformClient": false
},
"createdBy": {
"clientId": "X_zFOeNrElgLiF4yuswXQOuR",
"isPlatformClient": false
},
"customerEmail": "asd@asd.com",
"lineItems": [
{
"id": "c4700e7e-5b9c-42c6-a02e-322ea8c0fc8e",
"productId": "ee2fa33e-0a9e-4728-9a32-2e024a88b71d",
"productKey": "teak-serving-platter",
"name": {
"en-US": "Teak Serving Platter",
"en-GB": "Teak Serving Platter",
"de-DE": "Servierplatte aus Teakholz"
},
"productType": {
"typeId": "product-type",
"id": "c6446851-767c-4378-8e8a-ec0ca91eebc9",
"version": 1
},
"productSlug": {
"en-US": "teak-serving-platter",
"en-GB": "teak-serving-platter",
"de-DE": "servierplatte-aus-teakholz"
},
"variant": {
"id": 1,
"sku": "TST-02",
"prices": [
{
"id": "108fde77-8df6-4ac6-ba02-f482ec5794a0",
"value": {
"type": "centPrecision",
"currencyCode": "EUR",
"centAmount": 1299,
"fractionDigits": 2
}
},
{
"id": "1a09ad24-6bf4-47a2-939a-72b65921cbf8",
"value": {
"type": "centPrecision",
"currencyCode": "GBP",
"centAmount": 1299,
"fractionDigits": 2
},
"country": "GB"
},
{
"id": "d2862da8-cd2f-4ba9-8427-d323af3d74de",
"value": {
"type": "centPrecision",
"currencyCode": "USD",
"centAmount": 1299,
"fractionDigits": 2
},
"country": "US"
},
{
"id": "d677024b-6900-4f55-af5b-8779ee6ddc50",
"value": {
"type": "centPrecision",
"currencyCode": "CLP",
"centAmount": 150,
"fractionDigits": 0
},
"key": "clp"
}
],
"images": [
{
"url": "https://storage.googleapis.com/merchant-center-europe/sample-data/goodstore/Teak_Serving_Platter-1.1.jpeg",
"dimensions": {
"w": 4331,
"h": 2389
}
}
],
"attributes": [
{
"name": "productspec",
"value": {
"en-GB": "- Made of natural teak\n- Hand wash only",
"en-US": "- Made of natural teak\n- Hand wash only",
"de-DE": "- Hergestellt aus natürlichem Teakholz\n- Handwäsche nur"
}
}
],
"assets": [],
"availability": {
"isOnStock": false,
"availableQuantity": 0,
"version": 1,
"id": "10f0f9ce-06c9-4d2a-812c-e5b0c266bc00"
}
},
"price": {
"id": "d677024b-6900-4f55-af5b-8779ee6ddc50",
"value": {
"type": "centPrecision",
"currencyCode": "CLP",
"centAmount": 150,
"fractionDigits": 0
},
"key": "clp"
},
"quantity": 1,
"discountedPricePerQuantity": [],
"taxRate": {
"name": "Chili",
"amount": 0.12,
"includedInPrice": true,
"country": "CL",
"id": "gxuuA8ZL",
"key": "chili-clp",
"subRates": []
},
"perMethodTaxRate": [],
"addedAt": "2025-01-06T11:43:48.878Z",
"lastModifiedAt": "2025-01-06T11:43:48.878Z",
"state": [
{
"quantity": 1,
"state": {
"typeId": "state",
"id": "26f1f5f6-ae77-4314-a7bb-02ebf05b6203"
}
}
],
"priceMode": "Platform",
"lineItemMode": "Standard",
"totalPrice": {
"type": "centPrecision",
"currencyCode": "CLP",
"centAmount": 150,
"fractionDigits": 0
},
"taxedPrice": {
"totalNet": {
"type": "centPrecision",
"currencyCode": "CLP",
"centAmount": 134,
"fractionDigits": 0
},
"totalGross": {
"type": "centPrecision",
"currencyCode": "CLP",
"centAmount": 150,
"fractionDigits": 0
},
"taxPortions": [
{
"rate": 0.12,
"amount": {
"type": "centPrecision",
"currencyCode": "CLP",
"centAmount": 16,
"fractionDigits": 0
},
"name": "Chili"
}
],
"totalTax": {
"type": "centPrecision",
"currencyCode": "CLP",
"centAmount": 16,
"fractionDigits": 0
}
},
"taxedPricePortions": []
}
],
"cartState": "Active",
"totalPrice": {
"type": "centPrecision",
"currencyCode": "CLP",
"centAmount": 150,
"fractionDigits": 0
},
"taxedPrice": {
"totalNet": {
"type": "centPrecision",
"currencyCode": "CLP",
"centAmount": 134,
"fractionDigits": 0
},
"totalGross": {
"type": "centPrecision",
"currencyCode": "CLP",
"centAmount": 150,
"fractionDigits": 0
},
"taxPortions": [
{
"rate": 0.12,
"amount": {
"type": "centPrecision",
"currencyCode": "CLP",
"centAmount": 16,
"fractionDigits": 0
},
"name": "Chili"
}
],
"totalTax": {
"type": "centPrecision",
"currencyCode": "CLP",
"centAmount": 16,
"fractionDigits": 0
}
},
"shippingMode": "Single",
"shippingAddress": {
"firstName": "asd",
"lastName": "asd",
"streetName": "asd",
"postalCode": "1111aa",
"city": "asd",
"country": "CL",
"email": "asd@asd.com"
},
"shipping": [],
"customLineItems": [],
"discountCodes": [],
"directDiscounts": [],
"inventoryMode": "None",
"taxMode": "Platform",
"taxRoundingMode": "HalfEven",
"taxCalculationMode": "LineItemLevel",
"deleteDaysAfterLastModification": 90,
"refusedGifts": [],
"origin": "Customer",
"billingAddress": {
"firstName": "asd",
"lastName": "asd",
"streetName": "asd",
"postalCode": "1111aa",
"city": "asd",
"country": "CL",
"email": "asd@asd.com"
},
"itemShippingAddresses": [],
"totalLineItemQuantity": 1
}
21 changes: 20 additions & 1 deletion processor/test/services/converters/helper.converter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
ShippingInfo,
} from '@commercetools/connect-payments-sdk';
import CoCoCartJSON from '../../data/coco-cart.json';
import CoCoCartCLPJSON from '../../data/coco-cart-clp.json';

describe('helper.converter', () => {
beforeEach(() => {
Expand Down Expand Up @@ -131,7 +132,7 @@ describe('helper.converter', () => {
expect(actual).toEqual(expected);
});

test('should map CoCo shipping info to Adyen line item', () => {
test('should map CoCo discount info to Adyen line item', () => {
const input = CoCoCartJSON.discountOnTotalPrice as any;

const actual = mapCoCoDiscountOnTotalPriceToAdyenLineItem({ discountOnTotalPrice: input });
Expand Down Expand Up @@ -188,4 +189,22 @@ describe('helper.converter', () => {

expect(actual).toEqual(expected);
});

test('should map the CoCo line items to Adyen line items taking into account Adyen deviations', () => {
// CLP currencyCode according to ISO_4217 has 0 fractionDigits but Adyen expects 2 fractionDigits
const input = CoCoCartCLPJSON.lineItems as CoCoLineItem[];

const actual = mapCoCoLineItemToAdyenLineItem(input[0]);
const expected: LineItem = {
id: 'TST-02',
description: 'Teak Serving Platter',
quantity: 1,
amountExcludingTax: 13400,
amountIncludingTax: 15000,
taxAmount: 1600,
taxPercentage: 1200,
};

expect(actual).toEqual(expected);
});
});
1 change: 1 addition & 0 deletions processor/test/utils/mock-payment-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ export const mockAdyenRefundPaymentResponse: PaymentRefundResponse = {
export const mockGetPaymentAmount: PaymentAmount = {
centAmount: 150000,
currencyCode: 'USD',
fractionDigits: 2,
};

export const mockAdyenCreatePaymentResponse: PaymentResponse = {
Expand Down

0 comments on commit 5a92823

Please sign in to comment.