Skip to content

Commit

Permalink
Check if OnRamp is available before presenting card payment options
Browse files Browse the repository at this point in the history
  • Loading branch information
luads committed Nov 28, 2024
1 parent 9aa3529 commit bab4551
Show file tree
Hide file tree
Showing 10 changed files with 144 additions and 28 deletions.
39 changes: 39 additions & 0 deletions packages/checkout/sdk/src/availability/availability.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,43 @@ describe('availabilityService', () => {
);
});
});

describe('checkOnRampAvailability', () => {
it('should return true when status is 2xx', async () => {
const mockResponse = {};
mockedAxios.post.mockResolvedValueOnce(mockResponse);
const response = await availabilityService(true, false).checkOnRampAvailability();

expect(mockedAxios.post).toHaveBeenCalledTimes(1);
expect(response).toEqual(true);
});

it('should return false when status is 403', async () => {
const mockResponse = {
status: 403,
};
mockedAxios.post.mockRejectedValueOnce({ response: mockResponse });
const response = await availabilityService(true, false).checkOnRampAvailability();

expect(mockedAxios.post).toHaveBeenCalledTimes(1);
expect(response).toEqual(false);
});

it('should throw error when status is neither 204 or 403', async () => {
const mockResponse = {
status: 500,
statusText: 'error message',
};
mockedAxios.post.mockRejectedValueOnce({ response: mockResponse });

await expect(availabilityService(true, false).checkOnRampAvailability())
.rejects
.toThrow(
new CheckoutError(
'Error fetching from api: 500 error message',
CheckoutErrorType.API_ERROR,
),
);
});
});
});
12 changes: 10 additions & 2 deletions packages/checkout/sdk/src/availability/availability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ENV_DEVELOPMENT, IMMUTABLE_API_BASE_URL } from '../env';

export type AvailabilityService = {
checkDexAvailability: () => Promise<boolean>
checkOnRampAvailability: () => Promise<boolean>
};

export const availabilityService = (
Expand All @@ -18,11 +19,11 @@ export const availabilityService = (
return IMMUTABLE_API_BASE_URL[Environment.SANDBOX];
};

const checkDexAvailability = async (): Promise<boolean> => {
const checkAvailability = async (endpoint: string): Promise<boolean> => {
let response;

try {
response = await axios.post(`${postEndpoint()}/v1/availability/checkout/swap`);
response = await axios.post(endpoint);
} catch (err: any) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
Expand All @@ -41,7 +42,14 @@ export const availabilityService = (
return true;
};

// eslint-disable-next-line max-len
const checkDexAvailability = async (): Promise<boolean> => checkAvailability(`${postEndpoint()}/v1/availability/checkout/swap`);

// eslint-disable-next-line max-len
const checkOnRampAvailability = async (): Promise<boolean> => checkAvailability(`${postEndpoint()}/v1/availability/checkout/onramp`);

return {
checkDexAvailability,
checkOnRampAvailability,
};
};
8 changes: 8 additions & 0 deletions packages/checkout/sdk/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,14 @@ export class Checkout {
return this.availability.checkDexAvailability();
}

/**
* Fetches OnRamp widget availability.
* @returns {Promise<boolean>} - A promise that resolves to a boolean.
*/
public async isOnRampAvailable(): Promise<boolean> {
return this.availability.checkOnRampAvailability();
}

/**
* Fetches a quote and then performs the approval and swap transaction.
* @param {SwapParams} params - The parameters for the swap.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ type GeoblockLoaderParams = {
widget: React.ReactNode;
serviceUnavailableView: React.ReactNode;
checkout: Checkout,
checkAvailability: () => Promise<boolean>,
};

export function GeoblockLoader({
widget,
serviceUnavailableView,
checkout,
checkAvailability,
}: GeoblockLoaderParams) {
const { t } = useTranslation();
const { showLoader, hideLoader, isLoading } = useHandover();
Expand All @@ -27,7 +29,7 @@ export function GeoblockLoader({
try {
showLoader({ text: t('views.LOADING_VIEW.text') });
setRequested(true);
setAvailable(await checkout.isSwapAvailable());
setAvailable(await checkAvailability());
hideLoader();
} catch {
hideLoader();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@ export default function AddTokensWidget({
isSwapAvailable: await checkout.isSwapAvailable(),
},
});

addTokensDispatch({
payload: {
type: AddTokensActions.SET_IS_ONRAMP_AVAILABLE,
isOnRampAvailable: await checkout.isOnRampAvailable(),
},
});
})();
}, [checkout]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface AddTokensState {
selectedToken: TokenInfo | undefined;
selectedAmount: string;
isSwapAvailable: boolean | undefined;
isOnRampAvailable: boolean | undefined;
}

export const initialAddTokensState: AddTokensState = {
Expand All @@ -30,6 +31,7 @@ export const initialAddTokensState: AddTokensState = {
selectedToken: undefined,
selectedAmount: '',
isSwapAvailable: undefined,
isOnRampAvailable: undefined,
};

export interface AddTokensContextState {
Expand All @@ -52,7 +54,8 @@ type ActionPayload =
| SetSelectedRouteData
| SetSelectedToken
| SetSelectedAmount
| SetIsSwapAvailable;
| SetIsSwapAvailable
| SetIsOnRampAvailable;

export enum AddTokensActions {
SET_ID = 'SET_ID',
Expand All @@ -66,6 +69,7 @@ export enum AddTokensActions {
SET_SELECTED_TOKEN = 'SET_SELECTED_TOKEN',
SET_SELECTED_AMOUNT = 'SET_SELECTED_AMOUNT',
SET_IS_SWAP_AVAILABLE = 'SET_IS_SWAP_AVAILABLE',
SET_IS_ONRAMP_AVAILABLE = 'SET_IS_ONRAMP_AVAILABLE',
}

export interface SetId {
Expand Down Expand Up @@ -122,6 +126,10 @@ export interface SetIsSwapAvailable {
type: AddTokensActions.SET_IS_SWAP_AVAILABLE;
isSwapAvailable: boolean;
}
export interface SetIsOnRampAvailable {
type: AddTokensActions.SET_IS_ONRAMP_AVAILABLE;
isOnRampAvailable: boolean;
}

// eslint-disable-next-line @typescript-eslint/naming-convention
export const AddTokensContext = createContext<AddTokensContextState>({
Expand Down Expand Up @@ -193,6 +201,11 @@ export const addTokensReducer: Reducer<AddTokensState, AddTokensAction> = (
...state,
isSwapAvailable: action.payload.isSwapAvailable,
};
case AddTokensActions.SET_IS_ONRAMP_AVAILABLE:
return {
...state,
isOnRampAvailable: action.payload.isOnRampAvailable,
};
default:
return state;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export function AddTokens({
selectedRouteData,
selectedToken,
isSwapAvailable,
isOnRampAvailable,
} = addTokensState;

const { viewDispatch } = useContext(ViewContext);
Expand Down Expand Up @@ -488,14 +489,14 @@ export function AddTokens({
};

const shouldShowOnRampOption = useMemo(() => {
if (showOnrampOption && selectedToken) {
if (showOnrampOption && isOnRampAvailable && selectedToken) {
const isAllowedToken = onRampAllowedTokens.find(
(token) => token.address?.toLowerCase() === selectedToken.address?.toLowerCase(),
);
return !!isAllowedToken;
}
return false;
}, [selectedToken, onRampAllowedTokens, showOnrampOption]);
}, [selectedToken, onRampAllowedTokens, showOnrampOption, isOnRampAvailable]);

const showInitialEmptyState = !selectedToken;

Expand Down Expand Up @@ -619,7 +620,7 @@ export function AddTokens({
/>

<HeroFormControl.Caption>
{`${t('views.ADD_TOKENS.fees.fiatPricePrefix')}
{`${t('views.ADD_TOKENS.fees.fiatPricePrefix')}
$${getFormattedAmounts(selectedAmountUsd)}`}
</HeroFormControl.Caption>
</HeroFormControl>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import { HandoverProvider } from '../../context/handover-context/HandoverProvide
import { sendOnRampWidgetCloseEvent } from './OnRampWidgetEvents';
import i18n from '../../i18n';
import { orchestrationEvents } from '../../lib/orchestrationEvents';
import { GeoblockLoader } from '../../components/Geoblock/GeoblockLoader';
import { ServiceUnavailableToRegionErrorView } from '../../views/error/ServiceUnavailableToRegionErrorView';
import { ServiceType } from '../../views/error/serviceTypes';

const OnRampWidget = React.lazy(() => import('./OnRampWidget'));

Expand Down Expand Up @@ -90,22 +93,34 @@ export class OnRamp extends Base<WidgetType.ONRAMP> {
<CustomAnalyticsProvider checkout={this.checkout}>
<ThemeProvider id="onramp-container" config={this.strongConfig()}>
<HandoverProvider>
<ConnectLoader
widgetConfig={this.strongConfig()}
params={connectLoaderParams}
closeEvent={() => sendOnRampWidgetCloseEvent(window)}
goBackEvent={() => this.goBackEvent(window)}
showBackButton={this.parameters.showBackButton}
>
<Suspense fallback={<LoadingView loadingText={t('views.ONRAMP.initialLoadingText')} />}>
<OnRampWidget
tokenAddress={this.parameters.tokenAddress}
amount={this.parameters.amount}
config={this.strongConfig()}
<GeoblockLoader
checkout={this.checkout}
checkAvailability={() => this.checkout.isOnRampAvailable()}
widget={(
<ConnectLoader
widgetConfig={this.strongConfig()}
params={connectLoaderParams}
closeEvent={() => sendOnRampWidgetCloseEvent(window)}
goBackEvent={() => this.goBackEvent(window)}
showBackButton={this.parameters.showBackButton}
>
<Suspense fallback={<LoadingView loadingText={t('views.ONRAMP.initialLoadingText')} />}>
<OnRampWidget
tokenAddress={this.parameters.tokenAddress}
amount={this.parameters.amount}
config={this.strongConfig()}
showBackButton={this.parameters.showBackButton}
/>
</Suspense>
</ConnectLoader>
)}
serviceUnavailableView={(
<ServiceUnavailableToRegionErrorView
service={ServiceType.GENERIC}
onCloseClick={() => sendOnRampWidgetCloseEvent(window)}
/>
</Suspense>
</ConnectLoader>
)}
/>
</HandoverProvider>
</ThemeProvider>
</CustomAnalyticsProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export function SaleContextProvider(props: {
preferredCurrency,
customOrderData,
waitFulfillmentSettlements,
hideExcludedPaymentTypes,
hideExcludedPaymentTypes: defaultHideExcludedPaymentTypes,
},
} = props;

Expand Down Expand Up @@ -202,6 +202,9 @@ export function SaleContextProvider(props: {
const [disabledPaymentTypes, setDisabledPaymentTypes] = useState<
SalePaymentTypes[]
>([]);
const [hideExcludedPaymentTypes, setHideExcludedPaymentTypes] = useState<
boolean
>(defaultHideExcludedPaymentTypes);

const [invalidParameters, setInvalidParameters] = useState<boolean>(false);

Expand Down Expand Up @@ -233,6 +236,30 @@ export function SaleContextProvider(props: {
[],
);

useEffect(() => {
if (!checkout) {
return;
}

(async () => {
const isOnRampAvailable = await checkout.isOnRampAvailable();

if (!isOnRampAvailable) {
setDisabledPaymentTypes([
...new Set([...excludePaymentTypes, SalePaymentTypes.DEBIT, SalePaymentTypes.CREDIT]),
]);

setHideExcludedPaymentTypes(true);

return;
}

if (excludePaymentTypes?.length > 0) {
setDisabledPaymentTypes(excludePaymentTypes);
}
})();
}, [checkout, excludePaymentTypes]);

useEffect(() => {
const getUserInfo = async () => {
const signer = provider?.getSigner();
Expand Down Expand Up @@ -370,11 +397,6 @@ export function SaleContextProvider(props: {
}
}, [items, collectionName, environmentId]);

useEffect(() => {
if (excludePaymentTypes?.length <= 0) return;
setDisabledPaymentTypes(excludePaymentTypes);
}, [excludePaymentTypes]);

const values = useMemo(
() => ({
config,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { HandoverProvider } from '../../context/handover-context/HandoverProvide
import { topUpBridgeOption, topUpOnRampOption } from './helpers';
import { sendSwapWidgetCloseEvent } from './SwapWidgetEvents';
import i18n from '../../i18n';
import { GeoblockLoader } from './GeoblockLoader';
import { GeoblockLoader } from '../../components/Geoblock/GeoblockLoader';

const SwapWidget = React.lazy(() => import('./SwapWidget'));

Expand Down Expand Up @@ -123,6 +123,7 @@ export class Swap extends Base<WidgetType.SWAP> {
<HandoverProvider>
<GeoblockLoader
checkout={this.checkout}
checkAvailability={() => this.checkout.isSwapAvailable()}
widget={
(
<ConnectLoader
Expand Down

0 comments on commit bab4551

Please sign in to comment.