Skip to content

Commit

Permalink
MOL-587: Fix based on reviews
Browse files Browse the repository at this point in the history
  • Loading branch information
NghiaDTr committed Dec 11, 2024
1 parent 14ae610 commit 2faf080
Show file tree
Hide file tree
Showing 4 changed files with 243 additions and 7 deletions.
18 changes: 13 additions & 5 deletions processor/src/service/payment.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import {
convertCentToEUR,
parseStringToJsonObject,
roundSurchargeAmountToCent,
sortTransactionsByLatestCreationTime,
} from '../utils/app.utils';
import ApplePaySession from '@mollie/api-client/dist/types/src/data/applePaySession/ApplePaySession';
import { getMethodConfigObjects, getSingleMethodConfigObject } from '../commercetools/customObjects.commercetools';
Expand Down Expand Up @@ -536,9 +537,9 @@ export const handleCreateRefund = async (ctPayment: Payment): Promise<Controller
} else {
logger.debug('SCTM - handleCreateRefund - creating a refund for the latest success charge transaction');

const reversedTransactions = Object.assign([], ctPayment.transactions).reverse() as Transaction[];
const latestTransactions = sortTransactionsByLatestCreationTime(ctPayment.transactions);

successChargeTransaction = reversedTransactions.find(
successChargeTransaction = latestTransactions.find(
(transaction) =>
transaction.type === CTTransactionType.Charge && transaction.state === CTTransactionState.Success,
);
Expand Down Expand Up @@ -605,20 +606,27 @@ export const handlePaymentCancelRefund = async (ctPayment: Payment): Promise<Con
pendingRefundTransaction?.custom?.fields[CustomFields.transactionRefundForMolliePayment],
) as Transaction;
}

if (!successChargeTransaction) {
throw new CustomError(
400,
'SCTM - handlePaymentCancelRefund - Cannot find the valid Success Charge transaction.',
);
}
}

/**
* @deprecated v1.2 - Will be remove in the next version
*/
if (!pendingRefundTransaction || !successChargeTransaction) {
const reversedTransactions = Object.assign([], ctPayment.transactions).reverse() as Transaction[];
const latestTransactions = sortTransactionsByLatestCreationTime(ctPayment.transactions);

pendingRefundTransaction = reversedTransactions.find(
pendingRefundTransaction = latestTransactions.find(
(transaction) =>
transaction.type === CTTransactionType.Refund && transaction.state === CTTransactionState.Pending,
);

successChargeTransaction = reversedTransactions.find(
successChargeTransaction = latestTransactions.find(
(transaction) =>
transaction.type === CTTransactionType.Charge && transaction.state === CTTransactionState.Success,
);
Expand Down
21 changes: 20 additions & 1 deletion processor/src/utils/app.utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SurchargeCost } from './../types/commercetools.types';
import { Payment } from '@commercetools/platform-sdk';
import { Payment, Transaction } from '@commercetools/platform-sdk';
import CustomError from '../errors/custom.error';
import { logger } from './logger.utils';
/**
Expand Down Expand Up @@ -101,3 +101,22 @@ export const calculateTotalSurchargeAmount = (ctPayment: Payment, surcharges?: S
export const roundSurchargeAmountToCent = (surchargeAmountInEur: number, fractionDigits: number): number => {
return Math.round(surchargeAmountInEur * Math.pow(10, fractionDigits));
};

export const sortTransactionsByLatestCreationTime = (transactions: Transaction[]): Transaction[] => {
const clonedTransactions = Object.assign([], transactions);

return clonedTransactions.sort((a: Transaction, b: Transaction) => {
const timeA = a.timestamp as string;
const timeB = b.timestamp as string;

if (timeA < timeB) {
return 1;
}

if (timeA > timeB) {
return -1;
}

return 0;
});
};
148 changes: 148 additions & 0 deletions processor/tests/service/payment.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1724,6 +1724,7 @@ describe('Test handleCreateRefund', () => {
transactions: [
{
id: uuid,
timestamp: '2024-06-24T08:28:43.474Z',
type: 'Charge',
interactionId: 'tr_123123',
amount: {
Expand All @@ -1736,6 +1737,7 @@ describe('Test handleCreateRefund', () => {
},
{
id: 'test-123',
timestamp: '2024-06-24T08:30:43.474Z',
type: 'Charge',
interactionId: targetedMolliePaymentId,
amount: {
Expand Down Expand Up @@ -2335,6 +2337,152 @@ describe('Test handlePaymentCancelRefund', () => {
paymentId: CTPaymentMocked.transactions[1].interactionId,
});
});

it('should throw error if valid Success Charge transaction was not found (interactionId is defined in the Initial CancelAuthorization transaction)', async () => {
const CTPaymentMocked: 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-07806b101992',
type: 'Charge',
interactionId: 'tr_test123123',
amount: {
type: 'centPrecision',
currencyCode: 'EUR',
centAmount: 1000,
fractionDigits: 2,
},
state: 'Success',
},
{
id: '5c8b0375-305a-4f19-ae8e-07806b101999',
type: 'Charge',
interactionId: 'tr_dummy',
amount: {
type: 'centPrecision',
currencyCode: 'EUR',
centAmount: 1000,
fractionDigits: 2,
},
state: 'Success',
},
{
id: '5c8b0375-305a-4f19-ae8e-07806b102011',
type: 'Refund',
interactionId: 're_TEST',
amount: {
type: 'centPrecision',
currencyCode: 'EUR',
centAmount: 1000,
fractionDigits: 2,
},
state: 'Pending',
},
{
id: '5c8b0375-305a-4f19-ae8e-07806b102000',
type: 'Refund',
interactionId: 're_4qqhO89gsT',
amount: {
type: 'centPrecision',
currencyCode: 'EUR',
centAmount: 1000,
fractionDigits: 2,
},
state: 'Pending',
custom: {
type: {
typeId: 'type',
id: 'custom-type',
},
fields: {
[CustomFieldName.transactionRefundForMolliePayment]: 'tr_123123',
},
},
},
{
id: '5c8b0375-305a-4f19-ae8e-07806b102000',
type: 'CancelAuthorization',
interactionId: 're_4qqhO89gsT',
amount: {
type: 'centPrecision',
currencyCode: 'EUR',
centAmount: 1000,
fractionDigits: 2,
},
state: 'Initial',
custom: {
type: {
typeId: 'type',
id: 'sctm_payment_cancel_reason',
},
fields: {
reasonText: 'dummy reason',
},
},
},
],
interfaceInteractions: [],
paymentMethodInfo: {
method: 'creditcard',
},
};

const mollieRefund: Refund = {
resource: 'refund',
id: CTPaymentMocked.transactions[3].interactionId,
description: 'Order',
amount: {
currency: 'EUR',
value: '5.95',
},
status: 'pending',
metadata: '{"bookkeeping_id":12345}',
paymentId: 'tr_7UhSN1zuXS',
createdAt: '2023-03-14T17:09:02.0Z',
_links: {
self: {
href: '...',
type: 'application/hal+json',
},
payment: {
href: 'https://api.mollie.com/v2/payments/tr_7UhSN1zuXS',
type: 'application/hal+json',
},
documentation: {
href: '...',
type: 'text/html',
},
},
} as Refund;

(getPaymentRefund as jest.Mock).mockReturnValueOnce(mollieRefund);

(cancelPaymentRefund as jest.Mock).mockReturnValueOnce(true);

(getPaymentCancelActions as jest.Mock).mockReturnValueOnce([]);

try {
await handlePaymentCancelRefund(CTPaymentMocked);
} catch (error: any) {
expect(getPaymentRefund).toBeCalledTimes(0);
expect(cancelPaymentRefund).toBeCalledTimes(0);

expect(error).toBeInstanceOf(CustomError);
expect((error as CustomError).message).toBe(
'SCTM - handlePaymentCancelRefund - Cannot find the valid Success Charge transaction.',
);
}
});
});

describe('Test handlePaymentWebhook', () => {
Expand Down
63 changes: 62 additions & 1 deletion processor/tests/utils/app.utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import {
parseStringToJsonObject,
removeEmptyProperties,
roundSurchargeAmountToCent,
sortTransactionsByLatestCreationTime,
validateEmail,
} from '../../src/utils/app.utils';
import { logger } from '../../src/utils/logger.utils';
import CustomError from '../../src/errors/custom.error';
import { Payment } from '@commercetools/platform-sdk';
import { Payment, Transaction } from '@commercetools/platform-sdk';
import { SurchargeCost } from '../../src/types/commercetools.types';

describe('Test createDateNowString', () => {
Expand Down Expand Up @@ -145,3 +146,63 @@ describe('Test roundSurchargeAmountToCent', () => {
expect(roundSurchargeAmountToCent(surchargeAmountInEur, fractionDigits)).toBe(30100);
});
});

describe('Test sortTransactionsByLatestCreationTime', () => {
it('should return the correct order', () => {
const data = [
{
id: '39c1eae1-e9b4-45f0-ac18-7d83ec429cc8',
timestamp: '2024-06-24T08:28:43.474Z',
type: 'Authorization',
amount: {
type: 'centPrecision',
currencyCode: 'GBP',
centAmount: 61879,
fractionDigits: 2,
},
interactionId: '12789fae-d6d6-4b66-9739-3a420dbda2a8',
state: 'Failure',
},
{
id: '39c1eae1-e9b4-45f0-ac18-7d83ec429cde',
timestamp: '2024-06-24T08:29:43.474Z',
type: 'Authorization',
amount: {
type: 'centPrecision',
currencyCode: 'GBP',
centAmount: 61879,
fractionDigits: 2,
},
interactionId: '12789fae-d6d6-4b66-9739-3a420dbda2a8',
state: 'Failure',
},
{
id: '39c1eae1-e9b4-45f0-ac18-7d83ec429cd9',
timestamp: '2024-06-24T08:30:43.474Z',
type: 'Authorization',
amount: {
type: 'centPrecision',
currencyCode: 'GBP',
centAmount: 61879,
fractionDigits: 2,
},
interactionId: '12789fae-d6d6-4b66-9739-3a420dbda2a8',
state: 'Failure',
},
{
id: '39c1eae1-e9b4-45f0-ac18-7d83ec429111',
type: 'Authorization',
amount: {
type: 'centPrecision',
currencyCode: 'GBP',
centAmount: 61879,
fractionDigits: 2,
},
interactionId: '12789fae-d6d6-4b66-9739-3a420dbda2a8',
state: 'Failure',
},
] as Transaction[];

expect(sortTransactionsByLatestCreationTime(data)).toStrictEqual([data[2], data[1], data[0], data[3]]);
});
});

0 comments on commit 2faf080

Please sign in to comment.