From e9faabd52521559faf98df8f035a4f3d745ad8cf Mon Sep 17 00:00:00 2001 From: Ildar Galeev Date: Fri, 15 Mar 2024 16:07:06 +0700 Subject: [PATCH] Add ApiError to fetch api. Add retry predicate to withRetry (#287) --- .../paymentCondition/initPaymentCondition.ts | 2 +- src/common/utils/extractError.test.ts | 53 +++++++++++-------- src/common/utils/extractError.ts | 19 +++++-- src/common/utils/fetchApi.test.ts | 32 ++++++----- src/common/utils/fetchApi.ts | 22 +++++--- src/common/utils/withRetry.test.ts | 53 ++++++++----------- src/common/utils/withRetry.ts | 27 +++++----- .../GlobalContainer/usePaymentCondition.ts | 4 +- .../ApiExtensionView/ApiExtensionView.tsx | 9 ++-- .../ApiExtensionView/CompletePayment.tsx | 5 +- .../DestinationInfo/DestinationInfo.tsx | 7 +-- .../DestinationInfoBankAccount.tsx | 5 +- .../DestinationInfoBankCard.tsx | 5 +- .../DestinationInfo/DestinationInfoSpb.tsx | 5 +- .../ApiExtensionView/Destinations.tsx | 5 +- .../ApiExtensionView/GatewaySelector.tsx | 5 +- .../ApiExtensionView/useComplete.ts | 4 +- .../ApiExtensionView/useDestinations.ts | 9 ++-- .../ApiExtensionView/useGateways.ts | 4 +- .../PaymentResultView/PaymentResultView.tsx | 9 ++-- tsconfig.json | 5 +- 21 files changed, 167 insertions(+), 122 deletions(-) diff --git a/src/common/paymentCondition/initPaymentCondition.ts b/src/common/paymentCondition/initPaymentCondition.ts index ecd1e639..3a264370 100644 --- a/src/common/paymentCondition/initPaymentCondition.ts +++ b/src/common/paymentCondition/initPaymentCondition.ts @@ -107,7 +107,7 @@ const provideInvoiceUnpaid = async (model: PaymentModelInvoice): Promise { - it('should return the correct message for standard Error instances', () => { - const error = new Error('Test error message'); - expect(extractError(error)).toBe('Error: Test error message'); + it('should extract message from Error instance', () => { + const testError = new Error('Test error message'); + expect(extractError(testError)).toEqual('Error: Test error message'); }); - it('should return the message from a custom error object with details', () => { - const error = { - details: { - message: 'Custom error message', - }, + it('should handle ApiError object with a message in details', () => { + const testApiError: ApiError = { + status: 404, + endpoint: 'https://api.example.com/data', + details: { message: 'Resource not found' }, }; - expect(extractError(error)).toBe('Custom error message'); + expect(extractError(testApiError)).toEqual('Error 404 at https://api.example.com/data: Resource not found'); }); - it('should return a default message for non-Error objects without a message', () => { - const error = { someProperty: 'someValue' }; - expect(extractError(error)).toBe('An unexpected error occurred.'); + it('should stringify details if message is not a string in ApiError', () => { + const testApiError: ApiError = { + status: 500, + endpoint: 'https://api.example.com/data', + details: { error: 'Internal Server Error', code: 500 }, + }; + expect(extractError(testApiError)).toEqual( + `Error 500 at https://api.example.com/data: {"error":"Internal Server Error","code":500}`, + ); }); - it('should handle null errors gracefully', () => { - const error = null; - expect(extractError(error)).toBe('An unexpected error occurred.'); + it('should return a default message for non-object errors', () => { + expect(extractError('String error')).toEqual('An unexpected error occurred.'); + expect(extractError(1234)).toEqual('An unexpected error occurred.'); + expect(extractError(null)).toEqual('An unexpected error occurred.'); }); - it('should handle undefined errors gracefully', () => { - const error = undefined; - expect(extractError(error)).toBe('An unexpected error occurred.'); + it('should return a default message for empty objects', () => { + expect(extractError({})).toEqual('An unexpected error occurred.'); }); - it('should return a default message for errors without a recognizable structure', () => { - const error = 'Some string error'; - expect(extractError(error)).toBe('An unexpected error occurred.'); + it('should handle ApiError without details gracefully', () => { + const testApiError: ApiError = { + status: 403, + endpoint: 'https://api.example.com/data', + details: {}, + }; + expect(extractError(testApiError)).toEqual('Error 403 at https://api.example.com/data: {}'); }); }); diff --git a/src/common/utils/extractError.ts b/src/common/utils/extractError.ts index d48e0cab..0bdd3304 100644 --- a/src/common/utils/extractError.ts +++ b/src/common/utils/extractError.ts @@ -1,18 +1,27 @@ +import { ApiError } from './fetchApi'; + /** * Extracts a human-readable error message. * - * @param {Error | { details?: { message?: string } } | unknown} error - The error to extract the message from. + * @param {Error | ApiError | unknown} error - The error to extract the message from. * @returns {string} The extracted error message. */ -export const extractError = (error: Error | { details?: { message?: string } } | unknown): string => { +export const extractError = (error: Error | ApiError | unknown): string => { + // Handling standard Error instances if (error instanceof Error) { return `${error.name}: ${error.message}`; } + + // Handling ApiError objects if (typeof error === 'object' && error !== null) { - const message = (error as { details?: { message?: string } }).details?.message; - if (typeof message === 'string') { - return message; + const apiError = error as ApiError; + if ('status' in apiError && 'endpoint' in apiError && 'details' in apiError) { + // Attempting to extract a meaningful message from the details object + const detailsMessage = apiError.details.message || JSON.stringify(apiError.details); + return `Error ${apiError.status} at ${apiError.endpoint}: ${detailsMessage}`; } } + + // Default message for when the error cannot be identified return 'An unexpected error occurred.'; }; diff --git a/src/common/utils/fetchApi.test.ts b/src/common/utils/fetchApi.test.ts index 07f425bf..7a7027af 100644 --- a/src/common/utils/fetchApi.test.ts +++ b/src/common/utils/fetchApi.test.ts @@ -1,12 +1,15 @@ import { fetchApi } from './fetchApi'; +// TypeScript type assertion for mocking +declare let global: { fetch: jest.Mock }; + beforeEach(() => { global.fetch = jest.fn(); }); describe('fetchApi', () => { it('handles successful responses correctly', async () => { - (global.fetch as jest.Mock).mockResolvedValueOnce({ + global.fetch.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({ data: 'success' }), }); @@ -16,30 +19,33 @@ describe('fetchApi', () => { expect(data).toEqual({ data: 'success' }); }); - it('throws a generic error for non-JSON error responses', async () => { - (global.fetch as jest.Mock).mockResolvedValueOnce({ + it('throws an ApiError object for non-JSON error responses', async () => { + global.fetch.mockResolvedValueOnce({ ok: false, status: 500, - // Simulating a failure in response.json() method json: () => Promise.reject(new Error('Failed to parse JSON')), }); - await expect(fetchApi('https://api.example.com', 'token123', 'POST', 'path', {})).rejects.toThrow( - `Status: 500 Endpoint: https://api.example.com/path`, - ); + await expect(fetchApi('https://api.example.com', 'token123', 'POST', 'path', {})).rejects.toEqual({ + status: 500, + endpoint: 'https://api.example.com/path', + details: {}, // Assuming failure to parse JSON results in an empty details object + }); }); - it('throws an error with JSON error details for 400 responses', async () => { + it('throws an ApiError object with JSON error details for 400 responses', async () => { const errorDetails = { error: 'Bad Request', message: 'Invalid parameters' }; - (global.fetch as jest.Mock).mockResolvedValueOnce({ + global.fetch.mockResolvedValueOnce({ ok: false, status: 400, - json: () => Promise.resolve({ error: 'Bad Request', message: 'Invalid parameters' }), + json: () => Promise.resolve(errorDetails), }); - await expect(fetchApi('https://api.example.com', 'token123', 'POST', 'path', {})).rejects.toThrow( - `Status: 400 Endpoint: https://api.example.com/path ${JSON.stringify(errorDetails)}`, - ); + await expect(fetchApi('https://api.example.com', 'token123', 'POST', 'path', {})).rejects.toEqual({ + status: 400, + endpoint: 'https://api.example.com/path', + details: errorDetails, + }); }); }); diff --git a/src/common/utils/fetchApi.ts b/src/common/utils/fetchApi.ts index 36f6d8c4..939275a7 100644 --- a/src/common/utils/fetchApi.ts +++ b/src/common/utils/fetchApi.ts @@ -1,5 +1,11 @@ import { guid } from './guid'; +export type ApiError = { + status: number; + endpoint: string; + details: Record; +}; + export async function fetchApi( endpoint: string, accessToken: string, @@ -7,7 +13,8 @@ export async function fetchApi( path: string, body?: any, ): Promise { - const response = await fetch(`${endpoint}/${path}`, { + const fullEndpoint = `${endpoint}/${path}`; + const response = await fetch(fullEndpoint, { method, headers: { 'Content-Type': 'application/json;charset=utf-8', @@ -18,14 +25,17 @@ export async function fetchApi( }); if (!response.ok) { - let errorDetails = `Status: ${response.status} Endpoint: ${endpoint}/${path}`; + let errorDetails: Record = {}; try { - const errorBody = await response.json(); - errorDetails += ` ${JSON.stringify(errorBody)}`; + errorDetails = await response.json(); } catch (ex) { - // Ignore error + // If the response is not in JSON format or cannot be parsed, errorDetails will remain an empty object } - throw new Error(errorDetails); + throw { + status: response.status, + endpoint: fullEndpoint, + details: errorDetails, + } as ApiError; } return response; diff --git a/src/common/utils/withRetry.test.ts b/src/common/utils/withRetry.test.ts index 9770fee4..7abb411c 100644 --- a/src/common/utils/withRetry.test.ts +++ b/src/common/utils/withRetry.test.ts @@ -1,42 +1,33 @@ import { withRetry } from './withRetry'; -// Mock async function that simulates varying behavior -const createMockAsyncFunction = (shouldSucceedAfterAttempts: number, result: string, error: Error): jest.Mock => { - let attempts = 0; - return jest.fn(() => { - attempts++; - return new Promise((resolve, reject) => { - if (attempts >= shouldSucceedAfterAttempts) { - resolve(result); - } else { - reject(error); - } - }); - }); -}; +// Mock the delay function to avoid actual waiting time during tests +jest.mock('./delay', () => ({ + delay: jest.fn(() => Promise.resolve()), +})); describe('withRetry', () => { - test('should succeed on first attempt', async () => { - const mockFn = createMockAsyncFunction(1, 'success', new Error('fail')); - const retriedFn = withRetry(mockFn, 3, 10); + it('retries until success for recoverable errors', async () => { + const mockRecoverableErrorFn = jest + .fn() + .mockRejectedValueOnce(new Error('Fail')) // Fail the first time + .mockResolvedValueOnce('Recovered'); // Succeed the second time + const retriedFn = withRetry(mockRecoverableErrorFn, 2, 100); - await expect(retriedFn()).resolves.toBe('success'); - expect(mockFn).toHaveBeenCalledTimes(1); + await expect(retriedFn()).resolves.toEqual('Recovered'); + expect(mockRecoverableErrorFn).toHaveBeenCalledTimes(2); // The function should be retried once }); - test('should fail after all retries', async () => { - const mockFn = createMockAsyncFunction(5, 'success', new Error('fail')); // Succeeds after 5 attempts, but we only retry 3 times - const retriedFn = withRetry(mockFn, 3, 10); + it('throws immediately on last attempt without further delay', async () => { + const mockFailFn = jest.fn().mockRejectedValue(new Error('Mock error')); + const retriedFn = withRetry(mockFailFn, 3, 1000); // Allow for retries - await expect(retriedFn()).rejects.toThrow('fail'); - expect(mockFn).toHaveBeenCalledTimes(3); - }); + const startTime = Date.now(); + await expect(retriedFn()).rejects.toThrow('Mock error'); + const endTime = Date.now(); - test('should succeed after 2 retries', async () => { - const mockFn = createMockAsyncFunction(3, 'success', new Error('fail')); // Succeeds on the third attempt - const retriedFn = withRetry(mockFn, 3, 10); - - await expect(retriedFn()).resolves.toBe('success'); - expect(mockFn).toHaveBeenCalledTimes(3); + // Adjusting expectation due to potential slight delays in promise rejection handling + // Ensure that the total execution time is significantly less than the cumulative delay that would have occurred + expect(endTime - startTime).toBeLessThan(1000); // Significantly less than if it had waited after the last retry + expect(mockFailFn).toHaveBeenCalledTimes(3); // Attempted thrice }); }); diff --git a/src/common/utils/withRetry.ts b/src/common/utils/withRetry.ts index 1b4f2002..b6256356 100644 --- a/src/common/utils/withRetry.ts +++ b/src/common/utils/withRetry.ts @@ -1,15 +1,18 @@ import { delay } from './delay'; -/** - * Creates a retry wrapper for any async function, allowing retries on failure. - * - * @param {Function} asyncFn - The asynchronous function to wrap with retry logic. - * @param {number} maxRetries - The maximum number of retries. - * @param {number} retryDelay - The initial delay between retries in milliseconds. - * @returns {Function} - A new function that wraps the original async function with retry logic. - */ +// Defining a type for the predicate function +type ShouldRetryPredicate = (error: any) => boolean; + +// Default predicate function that allows retry only if the error is an instance of Error +const defaultShouldRetryPredicate: ShouldRetryPredicate = (error: any) => error instanceof Error; + export const withRetry = - (asyncFn: (...args: any[]) => Promise, maxRetries = 3, retryDelay = 3000) => + ( + asyncFn: (...args: any[]) => Promise, + maxRetries = 3, + retryDelay = 3000, + shouldRetryPredicate: ShouldRetryPredicate = defaultShouldRetryPredicate, + ) => async (...args: any[]): Promise => { let lastError: any; for (let attempt = 0; attempt < maxRetries; attempt++) { @@ -17,12 +20,12 @@ export const withRetry = return await asyncFn(...args); } catch (error) { lastError = error; - // If this is the last attempt, throw the error immediately - if (attempt === maxRetries - 1) { + // If this is the last attempt or the predicate returns false, throw the error immediately + if (attempt === maxRetries - 1 || !shouldRetryPredicate(error)) { throw lastError; } await delay(retryDelay); - // Increase retryDelay for exponential backoff + // Optional: Increase retryDelay for exponential backoff retryDelay *= 2; } } diff --git a/src/components/GlobalContainer/usePaymentCondition.ts b/src/components/GlobalContainer/usePaymentCondition.ts index 546df55d..cca3c75b 100644 --- a/src/components/GlobalContainer/usePaymentCondition.ts +++ b/src/components/GlobalContainer/usePaymentCondition.ts @@ -42,7 +42,7 @@ export const usePaymentCondition = (model: PaymentModel, initConditions: Payment apiMethodCall: 1000, }); } catch (exception) { - console.error(`startPayment error: ${extractError(exception)}`, exception); + console.error(`startPayment error: ${extractError(exception)}`); dispatch({ type: 'COMBINE_CONDITIONS', payload: [ @@ -65,7 +65,7 @@ export const usePaymentCondition = (model: PaymentModel, initConditions: Payment apiMethodCall: 3000, }); } catch (exception) { - console.error(`startWaitingPaymentResult error: ${extractError(exception)}`, exception); + console.error(`startWaitingPaymentResult error: ${extractError(exception)}`); dispatch({ type: 'COMBINE_CONDITIONS', payload: [ diff --git a/src/components/ViewContainer/ApiExtensionView/ApiExtensionView.tsx b/src/components/ViewContainer/ApiExtensionView/ApiExtensionView.tsx index caa54eed..1068e04b 100644 --- a/src/components/ViewContainer/ApiExtensionView/ApiExtensionView.tsx +++ b/src/components/ViewContainer/ApiExtensionView/ApiExtensionView.tsx @@ -1,13 +1,14 @@ import { useContext, useEffect, useMemo, useState } from 'react'; import styled from 'styled-components'; +import { Gateway } from 'checkout/backend/p2p'; +import { LocaleContext, PaymentConditionsContext, PaymentContext, PaymentModelContext } from 'checkout/contexts'; +import { InvoiceDetermined, PaymentStarted } from 'checkout/paymentCondition'; +import { isNil } from 'checkout/utils'; + import { CompletePayment } from './CompletePayment'; import { Destinations } from './Destinations'; import { GatewaySelector } from './GatewaySelector'; -import { Gateway } from '../../../common/backend/p2p'; -import { LocaleContext, PaymentConditionsContext, PaymentContext, PaymentModelContext } from '../../../common/contexts'; -import { InvoiceDetermined, PaymentStarted } from '../../../common/paymentCondition'; -import { isNil } from '../../../common/utils'; import { FormLoader } from '../../../components/legacy'; const FormContainer = styled.div` diff --git a/src/components/ViewContainer/ApiExtensionView/CompletePayment.tsx b/src/components/ViewContainer/ApiExtensionView/CompletePayment.tsx index 179159fd..73eea6d2 100644 --- a/src/components/ViewContainer/ApiExtensionView/CompletePayment.tsx +++ b/src/components/ViewContainer/ApiExtensionView/CompletePayment.tsx @@ -1,10 +1,11 @@ import { useEffect } from 'react'; import styled from 'styled-components'; +import { Locale } from 'checkout/contexts'; +import { isNil } from 'checkout/utils'; + import { Info } from './commonComponents'; import { useComplete } from './useComplete'; -import { Locale } from '../../../common/contexts'; -import { isNil } from '../../../common/utils'; import { Button } from '../../../components/legacy'; const Container = styled.div` diff --git a/src/components/ViewContainer/ApiExtensionView/DestinationInfo/DestinationInfo.tsx b/src/components/ViewContainer/ApiExtensionView/DestinationInfo/DestinationInfo.tsx index 0984ba3a..5c15e4fd 100644 --- a/src/components/ViewContainer/ApiExtensionView/DestinationInfo/DestinationInfo.tsx +++ b/src/components/ViewContainer/ApiExtensionView/DestinationInfo/DestinationInfo.tsx @@ -1,11 +1,12 @@ import { useContext } from 'react'; +import { Destination } from 'checkout/backend/p2p'; +import { Locale } from 'checkout/contexts'; +import { ViewModelContext } from 'checkout/contexts'; + import { DestinationInfoBankAccount } from './DestinationInfoBankAccount'; import { DestinationInfoBankCard } from './DestinationInfoBankCard'; import { DestinationInfoSpb } from './DestinationInfoSpb'; -import { Destination } from '../../../../common/backend/p2p'; -import { Locale } from '../../../../common/contexts'; -import { ViewModelContext } from '../../../../common/contexts'; import { Info, Container, Row, Label, Value, Alert } from '../commonComponents'; type DestinationInfoProps = { diff --git a/src/components/ViewContainer/ApiExtensionView/DestinationInfo/DestinationInfoBankAccount.tsx b/src/components/ViewContainer/ApiExtensionView/DestinationInfo/DestinationInfoBankAccount.tsx index e4b9a372..a750346e 100644 --- a/src/components/ViewContainer/ApiExtensionView/DestinationInfo/DestinationInfoBankAccount.tsx +++ b/src/components/ViewContainer/ApiExtensionView/DestinationInfo/DestinationInfoBankAccount.tsx @@ -1,6 +1,7 @@ +import { DestinationBankAccount } from 'checkout/backend/p2p'; +import { Locale } from 'checkout/contexts'; + import { CopyToClipboard } from './CopyToClipboard'; -import { DestinationBankAccount } from '../../../../common/backend/p2p'; -import { Locale } from '../../../../common/contexts'; import { Container, Label, Row, Value } from '../commonComponents'; export type DestinationInfoBankCardInfo = { diff --git a/src/components/ViewContainer/ApiExtensionView/DestinationInfo/DestinationInfoBankCard.tsx b/src/components/ViewContainer/ApiExtensionView/DestinationInfo/DestinationInfoBankCard.tsx index 5710fbee..5b07c3bf 100644 --- a/src/components/ViewContainer/ApiExtensionView/DestinationInfo/DestinationInfoBankCard.tsx +++ b/src/components/ViewContainer/ApiExtensionView/DestinationInfo/DestinationInfoBankCard.tsx @@ -1,6 +1,7 @@ +import { DestinationBankCard } from 'checkout/backend/p2p'; +import { Locale } from 'checkout/contexts'; + import { CopyToClipboard } from './CopyToClipboard'; -import { DestinationBankCard } from '../../../../common/backend/p2p'; -import { Locale } from '../../../../common/contexts'; import { Container, Label, Row, Value } from '../commonComponents'; export type DestinationInfoBankCardInfo = { diff --git a/src/components/ViewContainer/ApiExtensionView/DestinationInfo/DestinationInfoSpb.tsx b/src/components/ViewContainer/ApiExtensionView/DestinationInfo/DestinationInfoSpb.tsx index b51a2fdc..bb6c1cba 100644 --- a/src/components/ViewContainer/ApiExtensionView/DestinationInfo/DestinationInfoSpb.tsx +++ b/src/components/ViewContainer/ApiExtensionView/DestinationInfo/DestinationInfoSpb.tsx @@ -1,6 +1,7 @@ +import { DestinationSBP } from 'checkout/backend/p2p'; +import { Locale } from 'checkout/contexts'; + import { CopyToClipboard } from './CopyToClipboard'; -import { DestinationSBP } from '../../../../common/backend/p2p'; -import { Locale } from '../../../../common/contexts'; import { Container, Label, Row, Value } from '../commonComponents'; export type DestinationInfoSpbProps = { diff --git a/src/components/ViewContainer/ApiExtensionView/Destinations.tsx b/src/components/ViewContainer/ApiExtensionView/Destinations.tsx index 0547062e..959f2886 100644 --- a/src/components/ViewContainer/ApiExtensionView/Destinations.tsx +++ b/src/components/ViewContainer/ApiExtensionView/Destinations.tsx @@ -1,9 +1,10 @@ import { useEffect } from 'react'; +import { Locale } from 'checkout/contexts'; +import { isNil } from 'checkout/utils'; + import { DestinationInfo } from './DestinationInfo'; import { useDestinations } from './useDestinations'; -import { Locale } from '../../../common/contexts'; -import { isNil } from '../../../common/utils'; export type DestinationsProps = { locale: Locale; diff --git a/src/components/ViewContainer/ApiExtensionView/GatewaySelector.tsx b/src/components/ViewContainer/ApiExtensionView/GatewaySelector.tsx index 766b6854..189dd271 100644 --- a/src/components/ViewContainer/ApiExtensionView/GatewaySelector.tsx +++ b/src/components/ViewContainer/ApiExtensionView/GatewaySelector.tsx @@ -1,8 +1,9 @@ import { useEffect } from 'react'; +import { Gateway } from 'checkout/backend/p2p'; +import { Locale } from 'checkout/contexts'; + import { useGateways } from './useGateways'; -import { Gateway } from '../../../common/backend/p2p'; -import { Locale } from '../../../common/contexts'; import { Select } from '../../../components/legacy'; export type GatewaySelectorProps = { diff --git a/src/components/ViewContainer/ApiExtensionView/useComplete.ts b/src/components/ViewContainer/ApiExtensionView/useComplete.ts index e255ba99..65636963 100644 --- a/src/components/ViewContainer/ApiExtensionView/useComplete.ts +++ b/src/components/ViewContainer/ApiExtensionView/useComplete.ts @@ -1,7 +1,7 @@ import { useCallback, useReducer } from 'react'; -import { complete as completeApi } from '../../../common/backend/p2p'; -import { extractError, withRetry } from '../../../common/utils'; +import { complete as completeApi } from 'checkout/backend/p2p'; +import { extractError, withRetry } from 'checkout/utils'; type State = { status: 'PRISTINE' | 'LOADING' | 'SUCCESS' | 'FAILURE' }; diff --git a/src/components/ViewContainer/ApiExtensionView/useDestinations.ts b/src/components/ViewContainer/ApiExtensionView/useDestinations.ts index 22feaffa..1866a8ae 100644 --- a/src/components/ViewContainer/ApiExtensionView/useDestinations.ts +++ b/src/components/ViewContainer/ApiExtensionView/useDestinations.ts @@ -1,7 +1,7 @@ import { useCallback, useReducer, useRef } from 'react'; -import { Destination, getDestinations as getApiDestinations } from '../../../common/backend/p2p'; -import { extractError, withRetry } from '../../../common/utils'; +import { Destination, getDestinations as getApiDestinations } from 'checkout/backend/p2p'; +import { extractError, withRetry } from 'checkout/utils'; type State = { status: 'PRISTINE' | 'LOADING' | 'FAILURE' } | { status: 'SUCCESS'; data: Destination[] }; @@ -52,7 +52,10 @@ export const useDestinations = ( dispatch({ type: 'FETCH_SUCCESS', payload: destinations }); } catch (error) { dispatch({ type: 'FETCH_FAILURE' }); - console.error(`Failed to fetch destinations. ${extractError(error)}`); + // Api returns 500 error when there are no no requisites available. + if ('status' in error && error.status !== 500) { + console.error(`Failed to fetch destinations. ${extractError(error)}`); + } } }, [capiEndpoint, accessToken, invoiceID, paymentID, gatewayID]); diff --git a/src/components/ViewContainer/ApiExtensionView/useGateways.ts b/src/components/ViewContainer/ApiExtensionView/useGateways.ts index 523ef556..7b63372c 100644 --- a/src/components/ViewContainer/ApiExtensionView/useGateways.ts +++ b/src/components/ViewContainer/ApiExtensionView/useGateways.ts @@ -1,7 +1,7 @@ import { useCallback, useReducer } from 'react'; -import { Gateway, getGateways as getApiGateways } from '../../../common/backend/p2p'; -import { extractError, withRetry } from '../../../common/utils'; +import { Gateway, getGateways as getApiGateways } from 'checkout/backend/p2p'; +import { extractError, withRetry } from 'checkout/utils'; type State = { status: 'PRISTINE' | 'LOADING' | 'FAILURE' } | { status: 'SUCCESS'; data: Gateway[] }; diff --git a/src/components/ViewContainer/PaymentResultView/PaymentResultView.tsx b/src/components/ViewContainer/PaymentResultView/PaymentResultView.tsx index 426e5cb0..53773f29 100644 --- a/src/components/ViewContainer/PaymentResultView/PaymentResultView.tsx +++ b/src/components/ViewContainer/PaymentResultView/PaymentResultView.tsx @@ -1,15 +1,16 @@ import { useContext, useEffect } from 'react'; import styled from 'styled-components'; -import { IconName } from './types'; -import { getResultInfo } from './utils'; import { CompletePaymentContext, LocaleContext, PaymentConditionsContext, PaymentModelContext, -} from '../../../common/contexts'; -import { isNil, last } from '../../../common/utils'; +} from 'checkout/contexts'; +import { isNil, last } from 'checkout/utils'; + +import { IconName } from './types'; +import { getResultInfo } from './utils'; import { Button, ErrorIcon, Link, SuccessIcon, WarningIcon } from '../../../components/legacy'; const Wrapper = styled.div` diff --git a/tsconfig.json b/tsconfig.json index 0180d4db..75f5a0d8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,7 +16,10 @@ "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, - "baseUrl": "." + "baseUrl": ".", + "paths": { + "checkout/*": ["./src/common/*"] + } }, "include": ["./src/**/*", "./types/**/*"] }