From 7db65c1bdf502caf90310b26b57304e2bfab9ffd Mon Sep 17 00:00:00 2001 From: Nghia Tran Date: Tue, 15 Oct 2024 21:10:45 +0700 Subject: [PATCH] MOL-448/PICT-258 Update processor logic --- .../src/commercetools/cart.commercetools.ts | 25 + processor/src/service/payment.service.ts | 40 +- .../commercetools/cart.commercetools.spec.ts | 88 +++ .../tests/service/payment.service.spec.ts | 499 +++++++++++++++++- 4 files changed, 648 insertions(+), 4 deletions(-) create mode 100644 processor/src/commercetools/cart.commercetools.ts create mode 100644 processor/tests/commercetools/cart.commercetools.spec.ts diff --git a/processor/src/commercetools/cart.commercetools.ts b/processor/src/commercetools/cart.commercetools.ts new file mode 100644 index 0000000..05fd818 --- /dev/null +++ b/processor/src/commercetools/cart.commercetools.ts @@ -0,0 +1,25 @@ +import { createApiRoot } from '../client/create.client'; +import CustomError from '../errors/custom.error'; +import { logger } from '../utils/logger.utils'; + +export const getCartFromPayment = async (paymentId: string) => { + const carts = await createApiRoot() + .carts() + .get({ + queryArgs: { + where: `paymentInfo(payments(id= "${paymentId}"))`, + }, + }) + .execute(); + + const results = carts.body.results; + if (results.length !== 1) { + logger.error('There is no cart which attached this target payment'); + + throw new CustomError(400, 'There is no cart which attached this target payment'); + } + + logger.info(`Found cart with id ${results[0].id}`); + + return results[0]; +}; diff --git a/processor/src/service/payment.service.ts b/processor/src/service/payment.service.ts index 248e578..c005402 100644 --- a/processor/src/service/payment.service.ts +++ b/processor/src/service/payment.service.ts @@ -59,6 +59,7 @@ import { ApplePaySessionRequest, CustomPayment, SupportedPaymentMethods } from ' import { parseStringToJsonObject } from '../utils/app.utils'; import ApplePaySession from '@mollie/api-client/dist/types/src/data/applePaySession/ApplePaySession'; import { getMethodConfigObjects } from '../commercetools/customObjects.commercetools'; +import { getCartFromPayment } from '../commercetools/cart.commercetools'; type CustomMethod = { id: string; @@ -66,6 +67,16 @@ type CustomMethod = { description: Record; image: string; order: number; + pricingConstraints?: PricingConstraintItem[]; +}; + +type PricingConstraintItem = { + id?: number; + countryCode: string; + currencyCode: string; + minAmount: number; + maxAmount: number; + surchargeCost?: string; }; /** @@ -146,6 +157,8 @@ export const handleListPaymentMethodsByPayment = async (ctPayment: Payment): Pro const mollieOptions = await mapCommercetoolsPaymentCustomFieldsToMollieListParams(ctPayment); const methods: List = await listPaymentMethods(mollieOptions); const configObjects: CustomObject[] = await getMethodConfigObjects(); + + const cart = await getCartFromPayment(ctPayment.id); const customMethods = methods.map((method) => ({ id: method.id, name: { 'en-GB': method.description }, @@ -153,10 +166,10 @@ export const handleListPaymentMethodsByPayment = async (ctPayment: Payment): Pro image: method.image.svg, order: 0, })); + const validatedMethods = validateAndSortMethods(customMethods, configObjects); const enableCardComponent = shouldEnableCardComponent(validatedMethods); - const ctUpdateActions: UpdateAction[] = []; if (enableCardComponent) { validatedMethods.splice( @@ -165,11 +178,36 @@ export const handleListPaymentMethodsByPayment = async (ctPayment: Payment): Pro ); } + if (cart.country) { + const currencyCode = ctPayment.amountPlanned.currencyCode; + + configObjects.forEach((item: CustomObject) => { + const pricingConstraint = item.value.pricingConstraints?.find((pricingConstraint: PricingConstraintItem) => { + return pricingConstraint.countryCode === cart.country && pricingConstraint.currencyCode === currencyCode; + }) as PricingConstraintItem; + + if (pricingConstraint) { + const amount = ctPayment.amountPlanned.centAmount / Math.pow(10, ctPayment.amountPlanned.fractionDigits); + + if ( + (pricingConstraint.minAmount && amount < pricingConstraint.minAmount) || + (pricingConstraint.maxAmount && amount > pricingConstraint.maxAmount) + ) { + const index: number = validatedMethods.findIndex((method) => method.id === item.value.id); + + validatedMethods.splice(index, 1); + } + } + }); + } + const availableMethods = JSON.stringify({ count: validatedMethods.length, methods: validatedMethods.length ? validatedMethods : [], }); + const ctUpdateActions: UpdateAction[] = []; + ctUpdateActions.push( setCustomFields(CustomFields.payment.profileId, enableCardComponent ? readConfiguration().mollie.profileId : ''), ); diff --git a/processor/tests/commercetools/cart.commercetools.spec.ts b/processor/tests/commercetools/cart.commercetools.spec.ts new file mode 100644 index 0000000..d810bf6 --- /dev/null +++ b/processor/tests/commercetools/cart.commercetools.spec.ts @@ -0,0 +1,88 @@ +import { getCartFromPayment } from './../../src/commercetools/cart.commercetools'; +import { afterEach, describe, expect, jest, it } from '@jest/globals'; +import { Cart } from '@commercetools/platform-sdk'; +import { createApiRoot } from '../../src/client/create.client'; +import { logger } from '../../src/utils/logger.utils'; +import CustomError from '../../src/errors/custom.error'; + +const cart: Cart = { + id: '5c8b0375-305a-4f19-ae8e-07806b101999', + country: 'DE', +} as Cart; + +jest.mock('../../src/client/create.client', () => ({ + createApiRoot: jest.fn(), +})); + +describe('Test getCartFromPayment', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should return the correct cart', async () => { + const getCarts = jest.fn(); + + (createApiRoot as jest.Mock).mockReturnValue({ + carts: jest.fn().mockReturnValue({ + get: getCarts, + }), + }); + + getCarts.mockReturnValue({ + execute: jest.fn().mockReturnValue({ + body: { + results: [cart], + }, + }), + }); + + const paymentId = 'test-payment-id'; + const result = await getCartFromPayment(paymentId); + + expect(getCarts).toHaveBeenCalledTimes(1); + expect(getCarts).toHaveBeenCalledWith({ + queryArgs: { + where: `paymentInfo(payments(id= "${paymentId}"))`, + }, + }); + expect(result).toStrictEqual(cart); + expect(logger.info).toHaveBeenCalledTimes(1); + expect(logger.info).toHaveBeenCalledWith(`Found cart with id ${cart.id}`); + }); + + it('should throw exception', async () => { + const getCarts = jest.fn(); + + (createApiRoot as jest.Mock).mockReturnValue({ + carts: jest.fn().mockReturnValue({ + get: getCarts, + }), + }); + + getCarts.mockReturnValue({ + execute: jest.fn().mockReturnValue({ + body: { + results: [], + }, + }), + }); + + const paymentId = 'test-payment-id'; + + try { + await getCartFromPayment(paymentId); + } catch (error: any) { + expect(getCarts).toHaveBeenCalledTimes(1); + expect(getCarts).toHaveBeenCalledWith({ + queryArgs: { + where: `paymentInfo(payments(id= "${paymentId}"))`, + }, + }); + expect(error).toBeInstanceOf(CustomError); + expect(error.statusCode).toBe(400); + expect(error.message).toBe('There is no cart which attached this target payment'); + expect(logger.error).toHaveBeenCalledTimes(1); + expect(logger.error).toHaveBeenCalledWith('There is no cart which attached this target payment'); + } + }); +}); diff --git a/processor/tests/service/payment.service.spec.ts b/processor/tests/service/payment.service.spec.ts index 43dc30a..15624b9 100644 --- a/processor/tests/service/payment.service.spec.ts +++ b/processor/tests/service/payment.service.spec.ts @@ -1,5 +1,6 @@ +import { getMethodConfigObjects } from './../../src/commercetools/customObjects.commercetools'; import { afterEach, beforeEach, describe, expect, it, jest, test } from '@jest/globals'; -import { CustomFields, Payment } from '@commercetools/platform-sdk'; +import { Cart, CustomFields, Payment } from '@commercetools/platform-sdk'; import { getCreatePaymentUpdateAction, getPaymentCancelActions, @@ -40,6 +41,7 @@ import { createMollieCreatePaymentParams } from '../../src/utils/map.utils'; import { CustomPayment } from '../../src/types/mollie.types'; import { changeTransactionState } from '../../src/commercetools/action.commercetools'; import { makeCTMoney, shouldRefundStatusUpdate } from '../../src/utils/mollie.utils'; +import { getCartFromPayment } from '../../src/commercetools/cart.commercetools'; const uuid = '5c8b0375-305a-4f19-ae8e-07806b101999'; jest.mock('uuid', () => ({ @@ -55,6 +57,14 @@ jest.mock('../../src/commercetools/payment.commercetools', () => ({ updatePayment: jest.fn(), })); +jest.mock('../../src/commercetools/cart.commercetools', () => ({ + getCartFromPayment: jest.fn(), +})); + +jest.mock('../../src/commercetools/customObjects.commercetools', () => ({ + getMethodConfigObjects: jest.fn(), +})); + jest.mock('../../src/service/payment.service.ts', () => ({ ...(jest.requireActual('../../src/service/payment.service.ts') as object), getCreatePaymentUpdateAction: jest.fn(), @@ -144,8 +154,150 @@ describe('Test listPaymentMethodsByPayment', () => { }, }, }, + { + resource: 'method', + id: 'bancontact', + description: 'Bancontact', + minimumAmount: { value: '0.01', currency: 'EUR' }, + maximumAmount: null, + image: { + size1x: 'https://www.mollie.com/external/icons/payment-methods/paypal.png', + size2x: 'https://www.mollie.com/external/icons/payment-methods/paypal%402x.png', + svg: 'https://www.mollie.com/external/icons/payment-methods/paypal.svg', + }, + status: 'activated', + _links: { + self: { + href: 'https://api.mollie.com/v2/methods/paypal', + type: 'application/hal+json', + }, + }, + }, + { + resource: 'method', + id: 'banktransfer', + description: 'banktransfer', + minimumAmount: { value: '0.01', currency: 'EUR' }, + maximumAmount: null, + image: { + size1x: 'https://www.mollie.com/external/icons/payment-methods/paypal.png', + size2x: 'https://www.mollie.com/external/icons/payment-methods/paypal%402x.png', + svg: 'https://www.mollie.com/external/icons/payment-methods/paypal.svg', + }, + status: 'activated', + _links: { + self: { + href: 'https://api.mollie.com/v2/methods/paypal', + type: 'application/hal+json', + }, + }, + }, ]); + (getMethodConfigObjects as jest.Mock).mockReturnValueOnce([ + { + id: 'e561ae8b-5d55-4659-b4a7-3bf13a177eaf', + version: 2, + versionModifiedAt: '2024-10-15T10:04:28.910Z', + createdAt: '2024-10-15T04:32:39.858Z', + lastModifiedAt: '2024-10-15T10:04:28.910Z', + lastModifiedBy: '', + createdBy: '', + container: 'sctm-app-methods', + key: 'paypal', + value: { + id: 'paypal', + name: { + 'en-GB': 'PayPal', + }, + description: { + 'en-GB': '', + }, + imageUrl: 'https://www.mollie.com/external/icons/payment-methods/applepay.svg', + status: 'Active', + displayOrder: 0, + pricingConstraints: [ + { + currencyCode: 'EUR', + countryCode: 'DE', + minAmount: 1000, + maxAmount: 3000, + surchargeCost: '2%', + }, + ], + }, + }, + { + id: 'a8cfe2c6-c40e-4611-900d-faf3a0fc517c', + version: 1, + versionModifiedAt: '2024-10-15T04:32:39.962Z', + createdAt: '2024-10-15T04:32:39.962Z', + lastModifiedAt: '2024-10-15T04:32:39.962Z', + lastModifiedBy: '', + createdBy: '', + container: 'sctm-app-methods', + key: 'bancontact', + value: { + id: 'bancontact', + name: { + 'en-GB': 'bancontact', + }, + description: { + 'en-GB': '', + }, + imageUrl: 'https://www.mollie.com/external/icons/payment-methods/bancontact.svg', + status: 'Active', + displayOrder: 0, + pricingConstraints: [ + { + currencyCode: 'EUR', + countryCode: 'DE', + minAmount: 1000, + maxAmount: 44000, + surchargeCost: '2%', + }, + ], + }, + }, + { + id: '5ca6f09f-2e10-4d0e-8397-467925a28568', + version: 3, + versionModifiedAt: '2024-10-15T10:05:33.573Z', + createdAt: '2024-10-15T04:32:39.923Z', + lastModifiedAt: '2024-10-15T10:05:33.573Z', + lastModifiedBy: '', + createdBy: '', + container: 'sctm-app-methods', + key: 'banktransfer', + value: { + id: 'banktransfer', + name: { + 'en-GB': 'Bank Transfer', + }, + description: { + 'en-GB': '', + }, + imageUrl: 'https://www.mollie.com/external/icons/payment-methods/banktransfer.svg', + status: 'Active', + displayOrder: 0, + pricingConstraints: [ + { + currencyCode: 'EUR', + countryCode: 'DE', + minAmount: 1000, + maxAmount: 300000, + surchargeCost: '2%', + }, + ], + }, + }, + ]); + + (getCartFromPayment as jest.Mock).mockReturnValueOnce({ + id: 'cart-id', + country: 'DE', + } as Cart); + mockResource = { id: 'RANDOMID_12345', paymentMethodInfo: { @@ -155,7 +307,7 @@ describe('Test listPaymentMethodsByPayment', () => { amountPlanned: { type: 'centPrecision', currencyCode: 'EUR', - centAmount: 1000, + centAmount: 500000, fractionDigits: 2, }, custom: { @@ -172,11 +324,144 @@ describe('Test listPaymentMethodsByPayment', () => { expect(response.statusCode).toBe(200); expect(response?.actions?.length).toBeGreaterThan(0); expect(response?.actions?.[0]?.action).toBe('setCustomField'); + expect((response?.actions?.[1] as any)?.value).toBe( + JSON.stringify({ + count: 2, + methods: [ + { + id: 'bancontact', + name: { + 'en-GB': 'bancontact', + }, + description: { + 'en-GB': '', + }, + image: 'https://www.mollie.com/external/icons/payment-methods/bancontact.svg', + order: 0, + }, + { + id: 'banktransfer', + name: { + 'en-GB': 'Bank Transfer', + }, + description: { + 'en-GB': '', + }, + image: 'https://www.mollie.com/external/icons/payment-methods/banktransfer.svg', + order: 0, + }, + ], + }), + ); }); test('call listPaymentMethodsByPayment with no object reference', async () => { (listPaymentMethods as jest.Mock).mockReturnValueOnce([]); + (getMethodConfigObjects as jest.Mock).mockReturnValueOnce([ + { + id: 'e561ae8b-5d55-4659-b4a7-3bf13a177eaf', + version: 2, + versionModifiedAt: '2024-10-15T10:04:28.910Z', + createdAt: '2024-10-15T04:32:39.858Z', + lastModifiedAt: '2024-10-15T10:04:28.910Z', + lastModifiedBy: '', + createdBy: '', + container: 'sctm-app-methods', + key: 'paypal', + value: { + id: 'paypal', + name: { + 'en-GB': 'PayPal', + }, + description: { + 'en-GB': '', + }, + imageUrl: 'https://www.mollie.com/external/icons/payment-methods/applepay.svg', + status: 'Active', + displayOrder: 0, + pricingConstraints: [ + { + currencyCode: 'EUR', + countryCode: 'DE', + minAmount: 1000, + maxAmount: 3000, + surchargeCost: '2%', + }, + ], + }, + }, + { + id: 'a8cfe2c6-c40e-4611-900d-faf3a0fc517c', + version: 1, + versionModifiedAt: '2024-10-15T04:32:39.962Z', + createdAt: '2024-10-15T04:32:39.962Z', + lastModifiedAt: '2024-10-15T04:32:39.962Z', + lastModifiedBy: '', + createdBy: '', + container: 'sctm-app-methods', + key: 'bancontact', + value: { + id: 'bancontact', + name: { + 'en-GB': 'bancontact', + }, + description: { + 'en-GB': '', + }, + imageUrl: 'https://www.mollie.com/external/icons/payment-methods/bancontact.svg', + status: 'Active', + displayOrder: 0, + pricingConstraints: [ + { + currencyCode: 'EUR', + countryCode: 'DE', + minAmount: 1000, + maxAmount: 44000, + surchargeCost: '2%', + }, + ], + }, + }, + { + id: '5ca6f09f-2e10-4d0e-8397-467925a28568', + version: 3, + versionModifiedAt: '2024-10-15T10:05:33.573Z', + createdAt: '2024-10-15T04:32:39.923Z', + lastModifiedAt: '2024-10-15T10:05:33.573Z', + lastModifiedBy: '', + createdBy: '', + container: 'sctm-app-methods', + key: 'banktransfer', + value: { + id: 'banktransfer', + name: { + 'en-GB': 'Bank Transfer', + }, + description: { + 'en-GB': '', + }, + imageUrl: 'https://www.mollie.com/external/icons/payment-methods/banktransfer.svg', + status: 'Active', + displayOrder: 0, + pricingConstraints: [ + { + currencyCode: 'EUR', + countryCode: 'DE', + minAmount: 1000, + maxAmount: 300000, + surchargeCost: '2%', + }, + ], + }, + }, + ]); + + (getCartFromPayment as jest.Mock).mockReturnValueOnce({ + id: 'cart-id', + country: 'DE', + } as Cart); + mockResource = { typeId: 'payment', paymentMethodInfo: { @@ -272,6 +557,110 @@ describe('Test listPaymentMethodsByPayment', () => { }, ]); + (getMethodConfigObjects as jest.Mock).mockReturnValueOnce([ + { + id: 'e561ae8b-5d55-4659-b4a7-3bf13a177eaf', + version: 2, + versionModifiedAt: '2024-10-15T10:04:28.910Z', + createdAt: '2024-10-15T04:32:39.858Z', + lastModifiedAt: '2024-10-15T10:04:28.910Z', + lastModifiedBy: '', + createdBy: '', + container: 'sctm-app-methods', + key: 'paypal', + value: { + id: 'paypal', + name: { + 'en-GB': 'PayPal', + }, + description: { + 'en-GB': '', + }, + imageUrl: 'https://www.mollie.com/external/icons/payment-methods/applepay.svg', + status: 'Active', + displayOrder: 0, + pricingConstraints: [ + { + currencyCode: 'EUR', + countryCode: 'DE', + minAmount: 1000, + maxAmount: 3000, + surchargeCost: '2%', + }, + ], + }, + }, + { + id: 'a8cfe2c6-c40e-4611-900d-faf3a0fc517c', + version: 1, + versionModifiedAt: '2024-10-15T04:32:39.962Z', + createdAt: '2024-10-15T04:32:39.962Z', + lastModifiedAt: '2024-10-15T04:32:39.962Z', + lastModifiedBy: '', + createdBy: '', + container: 'sctm-app-methods', + key: 'creditcard', + value: { + id: 'creditcard', + name: { + 'en-GB': 'creditcard', + }, + description: { + 'en-GB': '', + }, + imageUrl: 'https://www.mollie.com/external/icons/payment-methods/bancontact.svg', + status: 'Active', + displayOrder: 0, + pricingConstraints: [ + { + currencyCode: 'EUR', + countryCode: 'DE', + minAmount: 0, + maxAmount: 100000, + surchargeCost: '2%', + }, + ], + }, + }, + { + id: '5ca6f09f-2e10-4d0e-8397-467925a28568', + version: 3, + versionModifiedAt: '2024-10-15T10:05:33.573Z', + createdAt: '2024-10-15T04:32:39.923Z', + lastModifiedAt: '2024-10-15T10:05:33.573Z', + lastModifiedBy: '', + createdBy: '', + container: 'sctm-app-methods', + key: 'banktransfer', + value: { + id: 'banktransfer', + name: { + 'en-GB': 'Bank Transfer', + }, + description: { + 'en-GB': '', + }, + imageUrl: 'https://www.mollie.com/external/icons/payment-methods/banktransfer.svg', + status: 'Active', + displayOrder: 0, + pricingConstraints: [ + { + currencyCode: 'EUR', + countryCode: 'DE', + minAmount: 1000, + maxAmount: 300000, + surchargeCost: '2%', + }, + ], + }, + }, + ]); + + (getCartFromPayment as jest.Mock).mockReturnValueOnce({ + id: 'cart-id', + country: 'DE', + } as Cart); + mockResource = { id: 'RANDOMID_12345', paymentMethodInfo: { @@ -281,7 +670,7 @@ describe('Test listPaymentMethodsByPayment', () => { amountPlanned: { type: 'centPrecision', currencyCode: 'EUR', - centAmount: 1000, + centAmount: 200000, fractionDigits: 2, }, custom: { @@ -348,6 +737,110 @@ describe('Test listPaymentMethodsByPayment', () => { }, ]); + (getMethodConfigObjects as jest.Mock).mockReturnValueOnce([ + { + id: 'e561ae8b-5d55-4659-b4a7-3bf13a177eaf', + version: 2, + versionModifiedAt: '2024-10-15T10:04:28.910Z', + createdAt: '2024-10-15T04:32:39.858Z', + lastModifiedAt: '2024-10-15T10:04:28.910Z', + lastModifiedBy: '', + createdBy: '', + container: 'sctm-app-methods', + key: 'creditcard', + value: { + id: 'creditcard', + name: { + 'en-GB': 'creditcard', + }, + description: { + 'en-GB': '', + }, + imageUrl: 'https://www.mollie.com/external/icons/payment-methods/applepay.svg', + status: 'Active', + displayOrder: 0, + pricingConstraints: [ + { + currencyCode: 'EUR', + countryCode: 'DE', + minAmount: 0, + maxAmount: 3000, + surchargeCost: '2%', + }, + ], + }, + }, + { + id: 'a8cfe2c6-c40e-4611-900d-faf3a0fc517c', + version: 1, + versionModifiedAt: '2024-10-15T04:32:39.962Z', + createdAt: '2024-10-15T04:32:39.962Z', + lastModifiedAt: '2024-10-15T04:32:39.962Z', + lastModifiedBy: '', + createdBy: '', + container: 'sctm-app-methods', + key: 'bancontact', + value: { + id: 'bancontact', + name: { + 'en-GB': 'bancontact', + }, + description: { + 'en-GB': '', + }, + imageUrl: 'https://www.mollie.com/external/icons/payment-methods/bancontact.svg', + status: 'Active', + displayOrder: 0, + pricingConstraints: [ + { + currencyCode: 'EUR', + countryCode: 'DE', + minAmount: 1000, + maxAmount: 44000, + surchargeCost: '2%', + }, + ], + }, + }, + { + id: '5ca6f09f-2e10-4d0e-8397-467925a28568', + version: 3, + versionModifiedAt: '2024-10-15T10:05:33.573Z', + createdAt: '2024-10-15T04:32:39.923Z', + lastModifiedAt: '2024-10-15T10:05:33.573Z', + lastModifiedBy: '', + createdBy: '', + container: 'sctm-app-methods', + key: 'banktransfer', + value: { + id: 'banktransfer', + name: { + 'en-GB': 'Bank Transfer', + }, + description: { + 'en-GB': '', + }, + imageUrl: 'https://www.mollie.com/external/icons/payment-methods/banktransfer.svg', + status: 'Active', + displayOrder: 0, + pricingConstraints: [ + { + currencyCode: 'EUR', + countryCode: 'DE', + minAmount: 1000, + maxAmount: 300000, + surchargeCost: '2%', + }, + ], + }, + }, + ]); + + (getCartFromPayment as jest.Mock).mockReturnValueOnce({ + id: 'cart-id', + country: 'DE', + } as Cart); + mockResource = { id: 'RANDOMID_12345', paymentMethodInfo: {