mirror of
https://github.com/valitydev/checkout.git
synced 2024-11-06 02:25:18 +00:00
Add ApiError to fetch api. Add retry predicate to withRetry (#287)
This commit is contained in:
parent
f687f4d55d
commit
e9faabd525
@ -107,7 +107,7 @@ const provideInvoiceUnpaid = async (model: PaymentModelInvoice): Promise<Payment
|
||||
}
|
||||
}
|
||||
} catch (exception) {
|
||||
console.error('provideInvoiceUnpaid error:', extractError(exception));
|
||||
console.error(`provideInvoiceUnpaid error: ${extractError(exception)}`);
|
||||
return [
|
||||
{
|
||||
name: 'paymentProcessFailed',
|
||||
|
@ -1,37 +1,48 @@
|
||||
import { extractError } from './extractError';
|
||||
import { ApiError } from './fetchApi';
|
||||
|
||||
describe('extractError', () => {
|
||||
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: {}');
|
||||
});
|
||||
});
|
||||
|
@ -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.';
|
||||
};
|
||||
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,11 @@
|
||||
import { guid } from './guid';
|
||||
|
||||
export type ApiError = {
|
||||
status: number;
|
||||
endpoint: string;
|
||||
details: Record<string, any>;
|
||||
};
|
||||
|
||||
export async function fetchApi(
|
||||
endpoint: string,
|
||||
accessToken: string,
|
||||
@ -7,7 +13,8 @@ export async function fetchApi(
|
||||
path: string,
|
||||
body?: any,
|
||||
): Promise<Response> {
|
||||
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<string, any> = {};
|
||||
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;
|
||||
|
@ -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
|
||||
});
|
||||
});
|
||||
|
@ -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 =
|
||||
<T>(asyncFn: (...args: any[]) => Promise<T>, maxRetries = 3, retryDelay = 3000) =>
|
||||
<T>(
|
||||
asyncFn: (...args: any[]) => Promise<T>,
|
||||
maxRetries = 3,
|
||||
retryDelay = 3000,
|
||||
shouldRetryPredicate: ShouldRetryPredicate = defaultShouldRetryPredicate,
|
||||
) =>
|
||||
async (...args: any[]): Promise<T> => {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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: [
|
||||
|
@ -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`
|
||||
|
@ -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`
|
||||
|
@ -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 = {
|
||||
|
@ -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 = {
|
||||
|
@ -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 = {
|
||||
|
@ -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 = {
|
||||
|
@ -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;
|
||||
|
@ -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 = {
|
||||
|
@ -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' };
|
||||
|
||||
|
@ -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,8 +52,11 @@ export const useDestinations = (
|
||||
dispatch({ type: 'FETCH_SUCCESS', payload: destinations });
|
||||
} catch (error) {
|
||||
dispatch({ type: 'FETCH_FAILURE' });
|
||||
// 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]);
|
||||
|
||||
return { state, getDestinations };
|
||||
|
@ -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[] };
|
||||
|
||||
|
@ -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`
|
||||
|
@ -16,7 +16,10 @@
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"checkout/*": ["./src/common/*"]
|
||||
}
|
||||
},
|
||||
"include": ["./src/**/*", "./types/**/*"]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user