mirror of
https://github.com/valitydev/checkout.git
synced 2024-11-06 02:25:18 +00:00
IMP-154: Remove deprecated logic (#282)
This commit is contained in:
parent
b1c4e29b4c
commit
80a3c8a611
@ -1,5 +1,4 @@
|
||||
import delay from 'checkout/utils/delay';
|
||||
import guid from 'checkout/utils/guid';
|
||||
import { delay, guid } from '../../common/utils';
|
||||
|
||||
export type FetchCapiParams = {
|
||||
endpoint: string;
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { getNocacheValue } from 'checkout/utils';
|
||||
|
||||
import { AppConfig } from './app-config';
|
||||
import { fetchCapi } from './fetch-capi';
|
||||
import { getNocacheValue } from '../../common/utils';
|
||||
|
||||
export const getAppConfig = (): Promise<AppConfig> =>
|
||||
fetchCapi({ endpoint: `./appConfig.json?nocache=${getNocacheValue()}` });
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { getNocacheValue } from 'checkout/utils';
|
||||
|
||||
import { fetchCapi } from './fetch-capi';
|
||||
import { getNocacheValue } from '../../common/utils';
|
||||
|
||||
export interface Env {
|
||||
version: string;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Locale } from 'checkout/locale';
|
||||
import { getNocacheValue } from 'checkout/utils';
|
||||
|
||||
import { fetchCapi } from './fetch-capi';
|
||||
import { getNocacheValue } from '../../common/utils';
|
||||
|
||||
export const getLocale = (locale: string): Promise<Locale> =>
|
||||
fetchCapi({
|
||||
|
@ -14,4 +14,3 @@ export * from './create-payment';
|
||||
export * from './get-service-provider-by-id';
|
||||
export * from './shorten-url';
|
||||
export * from './get-env';
|
||||
export * from './p2p';
|
||||
|
@ -1,32 +0,0 @@
|
||||
import guid from 'checkout/utils/guid';
|
||||
|
||||
export type CompleteInfo = {
|
||||
invoiceId: string;
|
||||
paymentId: string;
|
||||
payerTransactionId?: string;
|
||||
};
|
||||
|
||||
export const complete = async (capiEndpoint: string, accessToken: string, info: CompleteInfo): Promise<void> => {
|
||||
try {
|
||||
const response = await fetch(`${capiEndpoint}/p2p/payments/complete`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=utf-8',
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
'X-Request-ID': guid(),
|
||||
},
|
||||
body: JSON.stringify(info),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API error: ${response.status} Endpoint: ${capiEndpoint}/p2p/payments/complete`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
// Handle network errors or other unexpected issues
|
||||
throw new Error(`Network or unexpected error during the API request to ${capiEndpoint}: ${error.message}`);
|
||||
}
|
||||
// Fallback for non-Error exceptions
|
||||
throw new Error(`An unexpected error occurred during the API request to ${capiEndpoint}.`);
|
||||
}
|
||||
};
|
@ -1,14 +0,0 @@
|
||||
import { Destination } from './model';
|
||||
import { fetchCapi } from '../fetch-capi';
|
||||
|
||||
export const getDestinations = (
|
||||
capiEndpoint: string,
|
||||
accessToken: string,
|
||||
invoiceID: string,
|
||||
paymentID: string,
|
||||
gatewayID: string,
|
||||
): Promise<Destination[]> =>
|
||||
fetchCapi({
|
||||
endpoint: `${capiEndpoint}/p2p/payments/destinations?invoiceId=${invoiceID}&paymentId=${paymentID}&gatewayId=${gatewayID}`,
|
||||
accessToken,
|
||||
});
|
@ -1,13 +0,0 @@
|
||||
import { Gateway } from './model';
|
||||
import { fetchCapi } from '../fetch-capi';
|
||||
|
||||
export const getGateways = (
|
||||
capiEndpoint: string,
|
||||
accessToken: string,
|
||||
invoiceID: string,
|
||||
paymentID: string,
|
||||
): Promise<Gateway[]> =>
|
||||
fetchCapi({
|
||||
endpoint: `${capiEndpoint}/p2p/payments/gateways?invoiceId=${invoiceID}&paymentId=${paymentID}`,
|
||||
accessToken,
|
||||
});
|
@ -1,4 +0,0 @@
|
||||
export * from './get-destinations';
|
||||
export * from './get-gateways';
|
||||
export * from './complete';
|
||||
export * from './model';
|
@ -1,19 +0,0 @@
|
||||
export type Gateway = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type DestinationBankCard = {
|
||||
destinationType: 'BankCard';
|
||||
pan: string;
|
||||
bankName?: string;
|
||||
};
|
||||
|
||||
export type DestinationSBP = {
|
||||
destinationType: 'DestinationSBP';
|
||||
phoneNumber: string;
|
||||
bankName?: string;
|
||||
recipientName?: string;
|
||||
};
|
||||
|
||||
export type Destination = DestinationBankCard | DestinationSBP;
|
@ -1,21 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { device } from 'checkout/utils/device';
|
||||
|
||||
export const AppWrapper = styled.div`
|
||||
position: relative;
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
|
||||
@media ${device.desktop} {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: auto;
|
||||
padding: 45px 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`;
|
@ -1,51 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
|
||||
import { useInitApp, useTheme } from 'checkout/hooks';
|
||||
import { InitParams } from 'checkout/initialize';
|
||||
|
||||
import { AppWrapper } from './app-wrapper';
|
||||
import { GlobalStyle } from './global-style';
|
||||
import { InitialContext } from './initial-context';
|
||||
import { LayoutLoader } from './layout-loader';
|
||||
import ModalContainer from './modal-container/modal-container';
|
||||
import { ModalError } from './modal-error';
|
||||
import { Overlay } from './overlay';
|
||||
import { ResultContext } from './result-context';
|
||||
|
||||
export type AppProps = {
|
||||
initParams: InitParams;
|
||||
onComplete: () => void;
|
||||
};
|
||||
|
||||
export function App({ initParams, onComplete }: AppProps) {
|
||||
const theme = useTheme(initParams);
|
||||
const { state, init } = useInitApp();
|
||||
const [isComplete, setIsComplete] = useState(null);
|
||||
|
||||
useEffect(() => init(initParams), [initParams]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isComplete) {
|
||||
onComplete();
|
||||
}
|
||||
}, [isComplete]);
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<GlobalStyle theme={theme} />
|
||||
<AppWrapper>
|
||||
<Overlay />
|
||||
{state.status === 'PRISTINE' && <LayoutLoader />}
|
||||
{state.status === 'SUCCESS' && (
|
||||
<InitialContext.Provider value={state.data}>
|
||||
<ResultContext.Provider value={{ setIsComplete }}>
|
||||
<ModalContainer />
|
||||
</ResultContext.Provider>
|
||||
</InitialContext.Provider>
|
||||
)}
|
||||
{state.status === 'FAILURE' && <ModalError error={state.error} />}
|
||||
</AppWrapper>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
import { createGlobalStyle } from 'styled-components';
|
||||
|
||||
import { device } from 'checkout/utils/device';
|
||||
|
||||
export const GlobalStyle = createGlobalStyle`
|
||||
body,
|
||||
html,
|
||||
#app {
|
||||
margin: 0;
|
||||
position: relative;
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
min-width: 320px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
&._loading {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media ${device.desktop} {
|
||||
height: 100%;
|
||||
min-width: 680px;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
font-family: ${({ theme }) => theme.font.family};
|
||||
}
|
||||
`;
|
@ -1 +0,0 @@
|
||||
export * from './app';
|
@ -1,5 +0,0 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
import { InitialData } from 'checkout/hooks';
|
||||
|
||||
export const InitialContext = createContext<InitialData>(null);
|
@ -1,42 +0,0 @@
|
||||
import styled, { keyframes } from 'styled-components';
|
||||
|
||||
import { device } from 'checkout/utils/device';
|
||||
|
||||
import { Loader } from '../ui/loader';
|
||||
|
||||
const growth = keyframes`
|
||||
from {
|
||||
transform: scale(0);
|
||||
}
|
||||
to {
|
||||
transform: scale(1);
|
||||
}
|
||||
`;
|
||||
|
||||
const LayoutLoaderWrapper = styled.div`
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
@media ${device.desktop} {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: translate(0, 0);
|
||||
animation: ${growth} 0.5s;
|
||||
}
|
||||
`;
|
||||
|
||||
export const LayoutLoader = () => (
|
||||
<LayoutLoaderWrapper>
|
||||
<Loader />
|
||||
</LayoutLoaderWrapper>
|
||||
);
|
@ -1 +0,0 @@
|
||||
export * from './modal-container';
|
@ -1,151 +0,0 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { lazy, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { InvoiceChangeType } from 'checkout/backend';
|
||||
import { ErrorBoundaryFallback } from 'checkout/components/ui';
|
||||
import { ModalName, ModalState, ResultFormInfo, ResultType } from 'checkout/hooks';
|
||||
import { PayableInvoiceData, useInvoiceEvents, useModal } from 'checkout/hooks';
|
||||
import isNil from 'checkout/utils/is-nil';
|
||||
|
||||
import { ModalContext } from './modal-context';
|
||||
import { PayableInvoiceContext } from './payable-invoice-context';
|
||||
import { useInteractionModel } from './use-interaction-model';
|
||||
import { InitialContext } from '../initial-context';
|
||||
|
||||
const Modal = lazy(() => import('./modal/modal'));
|
||||
|
||||
const UserInteractionModal = lazy(() => import('./user-interaction-modal/user-interaction-modal'));
|
||||
|
||||
const Container = styled.div`
|
||||
height: 100%;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const Modals = ({ modalState }: { modalState: ModalState[] }) => {
|
||||
const activeModalName = useMemo(() => modalState.find((modal) => modal.active).name, [modalState]);
|
||||
return (
|
||||
<ErrorBoundary fallback={<ErrorBoundaryFallback />}>
|
||||
{activeModalName === ModalName.modalForms && <Modal />}
|
||||
{activeModalName === ModalName.modalInteraction && <UserInteractionModal />}
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
const ModalContainer = () => {
|
||||
const {
|
||||
appConfig: { capiEndpoint },
|
||||
initConfig,
|
||||
model: { serviceProviders, invoice, invoiceAccessToken },
|
||||
availablePaymentMethods,
|
||||
} = useContext(InitialContext);
|
||||
const {
|
||||
modalState,
|
||||
toInitialState,
|
||||
goToFormInfo,
|
||||
prepareToPay,
|
||||
prepareToRetry,
|
||||
forgetPaymentAttempt,
|
||||
setViewInfoError,
|
||||
toInteractionState,
|
||||
} = useModal({
|
||||
integrationType: initConfig.integrationType,
|
||||
availablePaymentMethods,
|
||||
serviceProviders,
|
||||
});
|
||||
const [payableInvoiceData, setPayableInvoiceData] = useState<PayableInvoiceData>(null);
|
||||
const { eventsState, startPolling, searchEventsChange } = useInvoiceEvents(capiEndpoint, payableInvoiceData);
|
||||
const { interactionModel, setPaymentInteraction, setPaymentStarted } = useInteractionModel();
|
||||
|
||||
useEffect(() => {
|
||||
if (initConfig.integrationType === 'invoice') {
|
||||
setPayableInvoiceData({
|
||||
invoice: {
|
||||
id: invoice.id,
|
||||
dueDate: invoice.dueDate,
|
||||
externalID: invoice.externalID,
|
||||
},
|
||||
invoiceAccessToken,
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isNil(payableInvoiceData)) return;
|
||||
if (eventsState.status === 'PRISTINE') {
|
||||
startPolling();
|
||||
}
|
||||
if (eventsState.status === 'POLLING_SUCCESS') {
|
||||
const change = eventsState.payload;
|
||||
switch (change.changeType) {
|
||||
case InvoiceChangeType.InvoiceCreated:
|
||||
if (initConfig.integrationType === 'invoice') {
|
||||
toInitialState();
|
||||
}
|
||||
if (initConfig.integrationType === 'invoiceTemplate') {
|
||||
prepareToPay();
|
||||
}
|
||||
break;
|
||||
case InvoiceChangeType.PaymentInteractionRequested:
|
||||
if (initConfig.skipUserInteraction) {
|
||||
goToFormInfo(new ResultFormInfo(ResultType.hookTimeout));
|
||||
break;
|
||||
}
|
||||
setPaymentInteraction(change);
|
||||
searchEventsChange('PaymentStarted');
|
||||
break;
|
||||
case InvoiceChangeType.InvoiceStatusChanged:
|
||||
case InvoiceChangeType.PaymentStatusChanged:
|
||||
goToFormInfo(
|
||||
new ResultFormInfo(ResultType.hookProcessed, {
|
||||
change,
|
||||
}),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (eventsState.status === 'POLLING_TIMEOUT') {
|
||||
goToFormInfo(new ResultFormInfo(ResultType.hookTimeout));
|
||||
}
|
||||
if (eventsState.status === 'EVENT_CHANGE_FOUND') {
|
||||
setPaymentStarted(eventsState.payload);
|
||||
}
|
||||
if (eventsState.status === 'FAILURE') {
|
||||
goToFormInfo(
|
||||
new ResultFormInfo(ResultType.hookError, {
|
||||
error: eventsState.error,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}, [payableInvoiceData, eventsState]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isNil(interactionModel)) return;
|
||||
toInteractionState(interactionModel);
|
||||
}, [interactionModel]);
|
||||
|
||||
return (
|
||||
<motion.div animate={{ opacity: 1 }} initial={{ opacity: 0 }} transition={{ duration: 1 }}>
|
||||
<Container>
|
||||
<ModalContext.Provider
|
||||
value={{
|
||||
modalState,
|
||||
goToFormInfo,
|
||||
prepareToPay,
|
||||
prepareToRetry,
|
||||
forgetPaymentAttempt,
|
||||
setViewInfoError,
|
||||
}}
|
||||
>
|
||||
<PayableInvoiceContext.Provider value={{ payableInvoiceData, setPayableInvoiceData }}>
|
||||
{/* eslint-disable-next-line react/jsx-max-depth */}
|
||||
<Modals modalState={modalState}></Modals>
|
||||
</PayableInvoiceContext.Provider>
|
||||
</ModalContext.Provider>
|
||||
</Container>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModalContainer;
|
@ -1,13 +0,0 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
import { FormInfo, ModalState } from 'checkout/hooks';
|
||||
import { Direction } from 'checkout/hooks/use-modal';
|
||||
|
||||
export const ModalContext = createContext<{
|
||||
modalState: ModalState[];
|
||||
goToFormInfo: (formInfo: FormInfo, direction?: Direction) => void;
|
||||
prepareToPay: () => void;
|
||||
prepareToRetry: (resetFormData: boolean) => void;
|
||||
forgetPaymentAttempt: () => void;
|
||||
setViewInfoError: (hasError: boolean) => void;
|
||||
}>(null);
|
@ -1,26 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { useContext } from 'react';
|
||||
import { ReactSVG } from 'react-svg';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { InitialContext } from '../../initial-context';
|
||||
|
||||
const FooterWrapper = styled.footer`
|
||||
padding: 16px 4px;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
`;
|
||||
|
||||
export const Footer: React.FC = () => {
|
||||
const { initConfig, appConfig } = useContext(InitialContext);
|
||||
const initConfigBrandless = initConfig.brandless;
|
||||
const appConfigBrandless = appConfig.brandless;
|
||||
if (appConfigBrandless && initConfigBrandless) {
|
||||
return <></>;
|
||||
}
|
||||
return (
|
||||
<FooterWrapper>
|
||||
<ReactSVG src="/assets/logo.svg" />
|
||||
</FooterWrapper>
|
||||
);
|
||||
};
|
@ -1,22 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { device } from 'checkout/utils/device';
|
||||
|
||||
export const FormBlock = styled.div`
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: ${({ theme }) => theme.form.background};
|
||||
|
||||
@media ${device.desktop} {
|
||||
height: auto;
|
||||
min-height: auto;
|
||||
width: 680px;
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
flex-direction: row;
|
||||
padding: 30px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`;
|
@ -1,88 +0,0 @@
|
||||
import { useContext, useEffect, useMemo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Gateway } from 'checkout/backend';
|
||||
import { ApiExtensionFormInfo } from 'checkout/hooks';
|
||||
import isNil from 'checkout/utils/is-nil';
|
||||
|
||||
import { CompletePayment } from './complete-payment';
|
||||
import { Destinations } from './destinations';
|
||||
import { GatewaySelector } from './gateway-selector';
|
||||
import { InitialContext } from '../../../../initial-context';
|
||||
import { ModalContext } from '../../../modal-context';
|
||||
import { PayableInvoiceContext } from '../../../payable-invoice-context';
|
||||
import { FormLoader } from '../form-loader';
|
||||
import { useActiveModalForm } from '../use-active-modal-form';
|
||||
|
||||
const FormContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
min-height: 500px;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
const SelectorContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
`;
|
||||
|
||||
const ApiExtensionForm = ({ onMount }: { onMount: () => void }) => {
|
||||
const {
|
||||
payableInvoiceData: { invoiceAccessToken, invoice },
|
||||
} = useContext(PayableInvoiceContext);
|
||||
const { appConfig, locale } = useContext(InitialContext);
|
||||
const { modalState } = useContext(ModalContext);
|
||||
const { paymentID } = useActiveModalForm<ApiExtensionFormInfo>(modalState);
|
||||
|
||||
const [gateway, setGateway] = useState<Gateway | null>(null);
|
||||
const [destinationStatus, setDestinationStatus] = useState<string | null>(null);
|
||||
const [completeStatus, setCompleteStatus] = useState<string | null>(null);
|
||||
const isLoader = useMemo(() => completeStatus === 'SUCCESS' || completeStatus === 'LOADING', [completeStatus]);
|
||||
|
||||
useEffect(() => {
|
||||
onMount();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormContainer>
|
||||
<SelectorContainer>
|
||||
<GatewaySelector
|
||||
capiEndpoint={appConfig.capiEndpoint}
|
||||
invoiceAccessToken={invoiceAccessToken}
|
||||
invoiceID={invoice.id}
|
||||
locale={locale}
|
||||
paymentID={paymentID}
|
||||
onSelect={setGateway}
|
||||
></GatewaySelector>
|
||||
{!isNil(gateway) && (
|
||||
<Destinations
|
||||
capiEndpoint={appConfig.capiEndpoint}
|
||||
gatewayID={gateway?.id}
|
||||
getDestinationsStatusChanged={setDestinationStatus}
|
||||
invoiceAccessToken={invoiceAccessToken}
|
||||
invoiceID={invoice.id}
|
||||
locale={locale}
|
||||
paymentID={paymentID}
|
||||
></Destinations>
|
||||
)}
|
||||
</SelectorContainer>
|
||||
{!isNil(gateway) && destinationStatus === 'SUCCESS' && (
|
||||
<CompletePayment
|
||||
capiEndpoint={appConfig.capiEndpoint}
|
||||
invoiceAccessToken={invoiceAccessToken}
|
||||
invoiceID={invoice.id}
|
||||
locale={locale}
|
||||
paymentID={paymentID}
|
||||
onCompleteStatusChanged={setCompleteStatus}
|
||||
/>
|
||||
)}
|
||||
</FormContainer>
|
||||
{isLoader && <FormLoader />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ApiExtensionForm;
|
@ -1,58 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
`;
|
||||
|
||||
export const Row = styled.div<{ $gap?: number }>`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: ${({ $gap }) => `${$gap}px` || 0};
|
||||
`;
|
||||
|
||||
export const Label = styled.p`
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 16px;
|
||||
margin: 0;
|
||||
`;
|
||||
|
||||
export const Value = styled.p`
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 16px;
|
||||
margin: 0;
|
||||
text-align: end;
|
||||
`;
|
||||
|
||||
export const Info = styled.p`
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
margin: 0;
|
||||
`;
|
||||
|
||||
export const Alert = styled.div`
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
background-color: ${({ theme }) => theme.alert.background};
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0 0 0 16px;
|
||||
|
||||
li {
|
||||
line-height: 18px;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
line-height: 18px;
|
||||
}
|
||||
`;
|
@ -1,53 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Button } from 'checkout/components/ui';
|
||||
import { useComplete } from 'checkout/hooks/p2p';
|
||||
import { Locale } from 'checkout/locale';
|
||||
import isNil from 'checkout/utils/is-nil';
|
||||
|
||||
import { Info } from './common-components';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
`;
|
||||
|
||||
export type CompletePaymentProps = {
|
||||
locale: Locale;
|
||||
capiEndpoint: string;
|
||||
invoiceAccessToken: string;
|
||||
invoiceID: string;
|
||||
paymentID: string;
|
||||
onCompleteStatusChanged?: (status: string) => void;
|
||||
};
|
||||
|
||||
export const CompletePayment = ({
|
||||
locale,
|
||||
capiEndpoint,
|
||||
invoiceAccessToken,
|
||||
invoiceID,
|
||||
paymentID,
|
||||
onCompleteStatusChanged,
|
||||
}: CompletePaymentProps) => {
|
||||
const {
|
||||
state: { status },
|
||||
complete,
|
||||
} = useComplete(capiEndpoint, invoiceAccessToken, invoiceID, paymentID);
|
||||
|
||||
useEffect(() => {
|
||||
if (isNil(onCompleteStatusChanged)) return;
|
||||
onCompleteStatusChanged(status);
|
||||
}, [status]);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Info>{locale['form.p2p.complete.info']}</Info>
|
||||
<Button color="primary" onClick={complete}>
|
||||
{locale['form.p2p.complete.button']}
|
||||
</Button>
|
||||
{status === 'FAILURE' && <div>{locale['form.p2p.error']}</div>}
|
||||
</Container>
|
||||
);
|
||||
};
|
@ -1,48 +0,0 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Container = styled.div`
|
||||
cursor: pointer;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const copy = (copyValue: string) => {
|
||||
const tempInput = document.createElement('input');
|
||||
tempInput.setAttribute('value', copyValue);
|
||||
tempInput.style.position = 'absolute';
|
||||
tempInput.style.left = '-1000px';
|
||||
document.body.appendChild(tempInput);
|
||||
tempInput.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(tempInput);
|
||||
};
|
||||
|
||||
export type CopyToClipboardProps = {
|
||||
copyValue: string;
|
||||
};
|
||||
|
||||
export const CopyToClipboard = ({ copyValue }: CopyToClipboardProps) => {
|
||||
const handleClick = () => {
|
||||
copy(copyValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<Container onClick={handleClick}>
|
||||
<motion.svg
|
||||
fill="currentColor"
|
||||
height="16"
|
||||
transition={{ duration: 0.3 }}
|
||||
viewBox="0 0 16 16"
|
||||
whileTap={{ translateY: 3 }}
|
||||
width="16"
|
||||
>
|
||||
<path d="M4 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 5a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1h1v1a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h1v1z" />
|
||||
</motion.svg>
|
||||
</Container>
|
||||
);
|
||||
};
|
@ -1,28 +0,0 @@
|
||||
import { DestinationBankCard } from 'checkout/backend/p2p';
|
||||
import { Locale } from 'checkout/locale';
|
||||
|
||||
import { CopyToClipboard } from './copy-to-clipboard';
|
||||
import { Container, Label, Row, Value } from '../common-components';
|
||||
|
||||
export type DestinationInfoBankCardInfo = {
|
||||
locale: Locale;
|
||||
destination: DestinationBankCard;
|
||||
};
|
||||
|
||||
export const DestinationInfoBankCard = ({ locale, destination }: DestinationInfoBankCardInfo) => (
|
||||
<Container>
|
||||
<Row>
|
||||
<Label>{locale['form.p2p.destination.bank.card.pan']}</Label>
|
||||
<Row $gap={8}>
|
||||
<Value>{destination.pan}</Value>
|
||||
<CopyToClipboard copyValue={destination.pan} />
|
||||
</Row>
|
||||
</Row>
|
||||
{destination?.bankName && (
|
||||
<Row>
|
||||
<Label>{locale['form.p2p.destination.bank.name']}</Label>
|
||||
<Value>{destination.bankName}</Value>
|
||||
</Row>
|
||||
)}
|
||||
</Container>
|
||||
);
|
@ -1,34 +0,0 @@
|
||||
import { DestinationSBP } from 'checkout/backend';
|
||||
import { Locale } from 'checkout/locale';
|
||||
|
||||
import { CopyToClipboard } from './copy-to-clipboard';
|
||||
import { Container, Label, Row, Value } from '../common-components';
|
||||
|
||||
export type DestinationInfoSpbProps = {
|
||||
locale: Locale;
|
||||
destination: DestinationSBP;
|
||||
};
|
||||
|
||||
export const DestinationInfoSpb = ({ locale, destination }: DestinationInfoSpbProps) => (
|
||||
<Container>
|
||||
<Row>
|
||||
<Label>{locale['form.p2p.destination.spb.phone']}</Label>
|
||||
<Row $gap={8}>
|
||||
<Value>{destination.phoneNumber}</Value>
|
||||
<CopyToClipboard copyValue={destination.phoneNumber} />
|
||||
</Row>
|
||||
</Row>
|
||||
{destination?.bankName && (
|
||||
<Row>
|
||||
<Label>{locale['form.p2p.destination.spb.bank.name']}</Label>
|
||||
<Value>{destination.bankName}</Value>
|
||||
</Row>
|
||||
)}
|
||||
{destination?.recipientName && (
|
||||
<Row>
|
||||
<Label>{locale['form.p2p.destination.spb.recipient']}</Label>
|
||||
<Value>{destination.recipientName}</Value>
|
||||
</Row>
|
||||
)}
|
||||
</Container>
|
||||
);
|
@ -1,44 +0,0 @@
|
||||
import { useContext, useMemo } from 'react';
|
||||
|
||||
import { Destination } from 'checkout/backend';
|
||||
import { Locale } from 'checkout/locale';
|
||||
import { formatAmount } from 'checkout/utils';
|
||||
|
||||
import { DestinationInfoBankCard } from './destination-info-bank-card';
|
||||
import { DestinationInfoSpb } from './destination-info-spb';
|
||||
import { InitialContext } from '../../../../../initial-context';
|
||||
import { Info, Container, Row, Label, Value, Alert } from '../common-components';
|
||||
|
||||
type DestinationInfoProps = {
|
||||
locale: Locale;
|
||||
destination: Destination;
|
||||
};
|
||||
|
||||
export const DestinationInfo = ({ locale, destination }: DestinationInfoProps) => {
|
||||
const { amountInfo } = useContext(InitialContext);
|
||||
const formattedAmount = useMemo(() => formatAmount(amountInfo), [amountInfo]);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Alert>
|
||||
<ul>
|
||||
{locale['form.p2p.alert.li'].map((value, key) => (
|
||||
<li key={key}>{value}</li>
|
||||
))}
|
||||
</ul>
|
||||
<p>{locale['form.p2p.alert.p']}</p>
|
||||
</Alert>
|
||||
<Info>{locale['form.p2p.destination.info']}</Info>
|
||||
<Row>
|
||||
<Label>{locale['form.p2p.destination.amount']}</Label>
|
||||
<Value>{formattedAmount}</Value>
|
||||
</Row>
|
||||
{destination.destinationType === 'BankCard' && (
|
||||
<DestinationInfoBankCard destination={destination} locale={locale} />
|
||||
)}
|
||||
{destination.destinationType === 'DestinationSBP' && (
|
||||
<DestinationInfoSpb destination={destination} locale={locale} />
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
};
|
@ -1 +0,0 @@
|
||||
export * from './destination-info';
|
@ -1,49 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { useDestinations } from 'checkout/hooks';
|
||||
import { Locale } from 'checkout/locale';
|
||||
import isNil from 'checkout/utils/is-nil';
|
||||
|
||||
import { DestinationInfo } from './destination-info';
|
||||
|
||||
export type DestinationsProps = {
|
||||
locale: Locale;
|
||||
capiEndpoint: string;
|
||||
invoiceAccessToken: string;
|
||||
invoiceID: string;
|
||||
paymentID: string;
|
||||
gatewayID: string;
|
||||
getDestinationsStatusChanged?: (status: string) => void;
|
||||
};
|
||||
|
||||
export const Destinations = ({
|
||||
locale,
|
||||
capiEndpoint,
|
||||
invoiceAccessToken,
|
||||
invoiceID,
|
||||
paymentID,
|
||||
gatewayID,
|
||||
getDestinationsStatusChanged,
|
||||
}: DestinationsProps) => {
|
||||
const { state, getDestinations } = useDestinations(capiEndpoint, invoiceAccessToken, invoiceID, paymentID);
|
||||
|
||||
useEffect(() => {
|
||||
getDestinations(gatewayID);
|
||||
}, [gatewayID]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isNil(getDestinationsStatusChanged)) return;
|
||||
getDestinationsStatusChanged(state.status);
|
||||
}, [state]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{state.status === 'LOADING' && <div>{locale['form.p2p.loading']}</div>}
|
||||
{state.status === 'FAILURE' && <div>{locale['form.p2p.error']}</div>}
|
||||
{state.status === 'SUCCESS' &&
|
||||
state.data.map((destination, i) => (
|
||||
<DestinationInfo key={i} destination={destination} locale={locale} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,62 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { Gateway } from 'checkout/backend';
|
||||
import { Select } from 'checkout/components/ui';
|
||||
import { useGateways } from 'checkout/hooks/p2p';
|
||||
import { Locale } from 'checkout/locale';
|
||||
|
||||
export type GatewaySelectorProps = {
|
||||
locale: Locale;
|
||||
capiEndpoint: string;
|
||||
invoiceAccessToken: string;
|
||||
invoiceID: string;
|
||||
paymentID: string;
|
||||
onSelect: (gateway: Gateway | null) => void;
|
||||
};
|
||||
|
||||
export const GatewaySelector = ({
|
||||
capiEndpoint,
|
||||
invoiceAccessToken,
|
||||
invoiceID,
|
||||
paymentID,
|
||||
locale,
|
||||
onSelect,
|
||||
}: GatewaySelectorProps) => {
|
||||
const { state, getGateways } = useGateways(capiEndpoint, invoiceAccessToken, invoiceID, paymentID);
|
||||
const [isGatewaySelected, setIsGatewaySelected] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
getGateways();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (state.status !== 'SUCCESS') return;
|
||||
state.data.length === 1 && onSelect(state.data[0]);
|
||||
setIsGatewaySelected(true);
|
||||
}, [state]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{state.status === 'PRISTINE' && <div>{locale['form.p2p.loading']}</div>}
|
||||
{state.status === 'FAILURE' && <div>{locale['form.p2p.error']}</div>}
|
||||
{state.status === 'SUCCESS' && !isGatewaySelected && (
|
||||
<Select
|
||||
dirty={false}
|
||||
error={false}
|
||||
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const found = state.data.find((gateway) => gateway.id === e.target.value);
|
||||
onSelect(found);
|
||||
setIsGatewaySelected(true);
|
||||
}}
|
||||
>
|
||||
<option value="">{locale['form.p2p.select.destination']}</option>
|
||||
{state.data.map((gateway, i) => (
|
||||
<option key={i} value={gateway.id}>
|
||||
{gateway.name}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,7 +0,0 @@
|
||||
export type CardFormInputs = {
|
||||
cardNumber: string;
|
||||
expireDate: string;
|
||||
secureCode: string;
|
||||
cardHolder: string;
|
||||
amount: string;
|
||||
};
|
@ -1,120 +0,0 @@
|
||||
import { useContext, useEffect } from 'react';
|
||||
import { useForm, SubmitHandler } from 'react-hook-form';
|
||||
|
||||
import { ResultFormInfo, ResultType } from 'checkout/hooks';
|
||||
import { PaymentMethodName, useCreatePayment } from 'checkout/hooks';
|
||||
import { isEmptyObject } from 'checkout/utils/is-empty-object';
|
||||
|
||||
import { CardFormInputs } from './card-form-inputs';
|
||||
import { CardHolder, CardNumber, ExpireDate, SecureCode } from './fields';
|
||||
import { isSecureCodeAvailable } from './is-secure-code-available';
|
||||
import { InitialContext } from '../../../../initial-context';
|
||||
import { ModalContext } from '../../../modal-context';
|
||||
import { Amount } from '../common-fields';
|
||||
import { toAmountConfig, toCardHolderConfig } from '../fields-config';
|
||||
import { FormGroup } from '../form-group';
|
||||
import { Header } from '../header';
|
||||
import { PayButton } from '../pay-button';
|
||||
|
||||
const CardForm = ({ onMount }: { onMount: () => void }) => {
|
||||
const {
|
||||
locale,
|
||||
initConfig,
|
||||
model: { invoiceTemplate },
|
||||
} = useContext(InitialContext);
|
||||
const { setViewInfoError, goToFormInfo, prepareToPay } = useContext(ModalContext);
|
||||
const { createPaymentState, setFormData } = useCreatePayment();
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
watch,
|
||||
formState: { errors, dirtyFields, isSubmitted },
|
||||
} = useForm<CardFormInputs>({ mode: 'onChange' });
|
||||
const cardHolder = toCardHolderConfig(initConfig.requireCardHolder);
|
||||
const amount = toAmountConfig(initConfig, invoiceTemplate);
|
||||
|
||||
useEffect(() => {
|
||||
onMount();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isSubmitted && !isEmptyObject(errors)) {
|
||||
setViewInfoError(true);
|
||||
}
|
||||
}, [isSubmitted, errors]);
|
||||
|
||||
useEffect(() => {
|
||||
if (createPaymentState.status === 'FAILURE') {
|
||||
goToFormInfo(
|
||||
new ResultFormInfo(ResultType.hookError, {
|
||||
error: createPaymentState.error,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}, [createPaymentState]);
|
||||
|
||||
const isSecureCode = isSecureCodeAvailable(watch('cardNumber'));
|
||||
|
||||
const onSubmit: SubmitHandler<CardFormInputs> = (values) => {
|
||||
prepareToPay();
|
||||
setFormData({ method: PaymentMethodName.BankCard, values });
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Header title={locale['form.header.pay.card.label']} />
|
||||
<FormGroup>
|
||||
<CardNumber
|
||||
fieldError={errors.cardNumber}
|
||||
isDirty={dirtyFields.cardNumber}
|
||||
locale={locale}
|
||||
register={register}
|
||||
watch={watch}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup $gap={10}>
|
||||
<ExpireDate
|
||||
fieldError={errors.expireDate}
|
||||
isDirty={dirtyFields.expireDate}
|
||||
locale={locale}
|
||||
register={register}
|
||||
/>
|
||||
{isSecureCode && (
|
||||
<SecureCode
|
||||
cardNumber={watch('cardNumber')}
|
||||
fieldError={errors.secureCode}
|
||||
isDirty={dirtyFields.secureCode}
|
||||
locale={locale}
|
||||
obscureCardCvv={initConfig?.obscureCardCvv}
|
||||
register={register}
|
||||
/>
|
||||
)}
|
||||
</FormGroup>
|
||||
{cardHolder.visible && (
|
||||
<FormGroup>
|
||||
<CardHolder
|
||||
fieldError={errors.cardHolder}
|
||||
isDirty={dirtyFields.cardHolder}
|
||||
locale={locale}
|
||||
register={register}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
{amount.visible && (
|
||||
<FormGroup>
|
||||
<Amount
|
||||
cost={amount.cost}
|
||||
fieldError={errors.amount}
|
||||
isDirty={dirtyFields.amount}
|
||||
locale={locale}
|
||||
localeCode={initConfig.locale}
|
||||
register={register}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
<PayButton />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardForm;
|
@ -1,35 +0,0 @@
|
||||
import { FieldError, UseFormRegister } from 'react-hook-form';
|
||||
|
||||
import { Input } from 'checkout/components';
|
||||
import { Locale } from 'checkout/locale';
|
||||
import isNil from 'checkout/utils/is-nil';
|
||||
|
||||
import { formatCardHolder } from './format-card-holder';
|
||||
import { validateCardHolder } from './validate-card-holder';
|
||||
import { ReactComponent as UserIcon } from '../../../../../../../ui/icon/user.svg';
|
||||
import { CardFormInputs } from '../../card-form-inputs';
|
||||
|
||||
export type CardHolderProps = {
|
||||
register: UseFormRegister<CardFormInputs>;
|
||||
locale: Locale;
|
||||
fieldError: FieldError;
|
||||
isDirty: boolean;
|
||||
};
|
||||
|
||||
export const CardHolder = ({ register, locale, fieldError, isDirty }: CardHolderProps) => (
|
||||
<Input
|
||||
{...register('cardHolder', {
|
||||
required: true,
|
||||
validate: (value) => !validateCardHolder(value) || 'Card holder is invalid',
|
||||
})}
|
||||
autoComplete="cc-name"
|
||||
dirty={isDirty}
|
||||
error={!isNil(fieldError)}
|
||||
icon={<UserIcon />}
|
||||
id="card-holder-input"
|
||||
mark={true}
|
||||
placeholder={locale['form.input.cardholder.placeholder']}
|
||||
spellCheck={false}
|
||||
onInput={formatCardHolder}
|
||||
/>
|
||||
);
|
@ -1,10 +0,0 @@
|
||||
import { FormEvent } from 'react';
|
||||
|
||||
import { safeVal } from 'checkout/utils';
|
||||
|
||||
export const formatCardHolder = (e: FormEvent<HTMLInputElement>) => {
|
||||
const target = e.currentTarget;
|
||||
let value = target.value;
|
||||
value = value.toUpperCase();
|
||||
return safeVal(value, target);
|
||||
};
|
@ -1 +0,0 @@
|
||||
export * from './card-holder';
|
@ -1,7 +0,0 @@
|
||||
import { isContainCardNumber } from 'checkout/utils';
|
||||
|
||||
const CARD_HOLDER_REGEXP = /^[a-zA-Z0-9 .,'/-]+$/;
|
||||
|
||||
export function validateCardHolder(value: string): boolean {
|
||||
return !value || !value.trim() || !CARD_HOLDER_REGEXP.test(value) || isContainCardNumber(value);
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
import { FieldError, UseFormRegister, UseFormWatch } from 'react-hook-form';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Input } from 'checkout/components';
|
||||
import { CardTypeIcon } from 'checkout/components/ui/card-type-icon';
|
||||
import { Locale } from 'checkout/locale';
|
||||
import isNil from 'checkout/utils/is-nil';
|
||||
|
||||
import { formatCardNumber } from './format-card-number';
|
||||
import { validateCardNumber } from './validate-card-number';
|
||||
import { ReactComponent as CardIcon } from '../../../../../../../ui/icon/card.svg';
|
||||
import { CardFormInputs } from '../../card-form-inputs';
|
||||
|
||||
const InputContainer = styled.div`
|
||||
width: 100%;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const CardNumberInput = styled(Input)`
|
||||
input {
|
||||
padding-right: 50px !important;
|
||||
}
|
||||
`;
|
||||
|
||||
export type CardNumberProps = {
|
||||
register: UseFormRegister<CardFormInputs>;
|
||||
watch: UseFormWatch<CardFormInputs>;
|
||||
locale: Locale;
|
||||
fieldError: FieldError;
|
||||
isDirty: boolean;
|
||||
};
|
||||
|
||||
export const CardNumber = ({ register, locale, fieldError, isDirty, watch }: CardNumberProps) => (
|
||||
<InputContainer>
|
||||
<CardNumberInput
|
||||
{...register('cardNumber', {
|
||||
required: true,
|
||||
validate: (value) => !validateCardNumber(value) || 'Card number is invalid',
|
||||
})}
|
||||
autoComplete="cc-number"
|
||||
dirty={isDirty}
|
||||
error={!isNil(fieldError)}
|
||||
icon={<CardIcon />}
|
||||
id="card-number-input"
|
||||
mark={true}
|
||||
placeholder={locale['form.input.card.placeholder']}
|
||||
type="tel"
|
||||
onInput={formatCardNumber}
|
||||
/>
|
||||
<CardTypeIcon cardNumber={watch('cardNumber')} />
|
||||
</InputContainer>
|
||||
);
|
@ -1,27 +0,0 @@
|
||||
import { number } from 'card-validator';
|
||||
import { FormEvent } from 'react';
|
||||
|
||||
import { replaceFullWidthChars, safeVal } from 'checkout/utils';
|
||||
|
||||
function format(num: string): string {
|
||||
num = num.replace(/\D/g, '');
|
||||
const { card } = number(num);
|
||||
if (!card) {
|
||||
return num;
|
||||
}
|
||||
const upperLength = card.lengths[card.lengths.length - 1];
|
||||
num = num.slice(0, upperLength);
|
||||
const nums = num.split('');
|
||||
for (const gap of card.gaps.reverse()) {
|
||||
nums.splice(gap, 0, ' ');
|
||||
}
|
||||
return nums.join('').trim();
|
||||
}
|
||||
|
||||
export function formatCardNumber(e: FormEvent<HTMLInputElement>): number {
|
||||
const target = e.currentTarget;
|
||||
let value = target.value;
|
||||
value = replaceFullWidthChars(value);
|
||||
value = format(value);
|
||||
return safeVal(value, target);
|
||||
}
|
@ -1 +0,0 @@
|
||||
export * from './card-number';
|
@ -1,5 +0,0 @@
|
||||
import * as cardValidator from 'card-validator';
|
||||
|
||||
export function validateCardNumber(value: string): boolean {
|
||||
return !cardValidator.number(value).isValid;
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
import { FieldError, UseFormRegister } from 'react-hook-form';
|
||||
|
||||
import { Input } from 'checkout/components';
|
||||
import { Locale } from 'checkout/locale';
|
||||
import isNil from 'checkout/utils/is-nil';
|
||||
|
||||
import { formatExpiry } from './format-expiry';
|
||||
import { validateExpireDate } from './validate-expire-date';
|
||||
import { ReactComponent as CalendarIcon } from '../../../../../../../ui/icon/calendar.svg';
|
||||
import { CardFormInputs } from '../../card-form-inputs';
|
||||
|
||||
export type ExpireDateProps = {
|
||||
register: UseFormRegister<CardFormInputs>;
|
||||
locale: Locale;
|
||||
fieldError: FieldError;
|
||||
isDirty: boolean;
|
||||
};
|
||||
|
||||
export const ExpireDate = ({ register, locale, fieldError, isDirty }: ExpireDateProps) => (
|
||||
<Input
|
||||
{...register('expireDate', {
|
||||
required: true,
|
||||
validate: (value) => !validateExpireDate(value) || 'Exp date is invalid',
|
||||
})}
|
||||
autoComplete="cc-exp"
|
||||
dirty={isDirty}
|
||||
error={!isNil(fieldError)}
|
||||
icon={<CalendarIcon />}
|
||||
id="expire-date-input"
|
||||
mark={true}
|
||||
placeholder={locale['form.input.expiry.placeholder']}
|
||||
type="tel"
|
||||
onInput={formatExpiry}
|
||||
/>
|
||||
);
|
@ -1,33 +0,0 @@
|
||||
import { FormEvent } from 'react';
|
||||
|
||||
import { replaceFullWidthChars, safeVal } from 'checkout/utils';
|
||||
|
||||
function format(expiry: string): string {
|
||||
const parts = expiry.match(/^\D*(\d{1,2})(\D+)?(\d{1,4})?/);
|
||||
if (!parts) {
|
||||
return '';
|
||||
}
|
||||
let mon = parts[1] || '';
|
||||
let sep = parts[2] || '';
|
||||
const year = parts[3] || '';
|
||||
if (year.length > 0) {
|
||||
sep = ' / ';
|
||||
} else if (sep === ' /') {
|
||||
mon = mon.substring(0, 1);
|
||||
sep = '';
|
||||
} else if (mon.length === 2 || sep.length > 0) {
|
||||
sep = ' / ';
|
||||
} else if (mon.length === 1 && mon !== '0' && mon !== '1') {
|
||||
mon = '0' + mon;
|
||||
sep = ' / ';
|
||||
}
|
||||
return mon + sep + year;
|
||||
}
|
||||
|
||||
export function formatExpiry(e: FormEvent<HTMLInputElement>): number {
|
||||
const target = e.currentTarget;
|
||||
let value = target.value;
|
||||
value = replaceFullWidthChars(value);
|
||||
value = format(value);
|
||||
return safeVal(value, target);
|
||||
}
|
@ -1 +0,0 @@
|
||||
export * from './expire-date';
|
@ -1,23 +0,0 @@
|
||||
export interface ExpiryDate {
|
||||
month: number;
|
||||
year: number;
|
||||
}
|
||||
|
||||
export function cardExpiryVal(value: string): ExpiryDate {
|
||||
let month;
|
||||
let prefix;
|
||||
let year;
|
||||
let ref;
|
||||
(ref = value.split(/[\s/]+/, 2)), (month = ref[0]), (year = ref[1]);
|
||||
if ((year != null ? year.length : void 0) === 2 && /^\d+$/.test(year)) {
|
||||
prefix = new Date().getFullYear();
|
||||
prefix = prefix.toString().slice(0, 2);
|
||||
year = prefix + year;
|
||||
}
|
||||
month = parseInt(month, 10);
|
||||
year = parseInt(year, 10);
|
||||
return {
|
||||
month,
|
||||
year,
|
||||
};
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import { cardExpiryVal } from './card-expiry-val';
|
||||
import { validateCardExpiry } from './validate-card-expiry';
|
||||
|
||||
export function validateExpireDate(value: any): boolean {
|
||||
if (!value) {
|
||||
return true;
|
||||
}
|
||||
const formatVal = cardExpiryVal(value);
|
||||
return !validateCardExpiry(formatVal);
|
||||
}
|
@ -1 +0,0 @@
|
||||
export * from './expire-date';
|
@ -1,37 +0,0 @@
|
||||
import { ExpiryDate } from './card-expiry-val';
|
||||
|
||||
export function validateCardExpiry({ month, year }: ExpiryDate): boolean {
|
||||
if (!(month && year)) {
|
||||
return false;
|
||||
}
|
||||
const newMonth = month + '';
|
||||
let newYear = year + '';
|
||||
if (!/^\d+$/.test(newMonth)) {
|
||||
return false;
|
||||
}
|
||||
if (!/^\d+$/.test(newYear)) {
|
||||
return false;
|
||||
}
|
||||
if (!(1 <= month && month <= 12)) {
|
||||
return false;
|
||||
}
|
||||
if (newYear.length === 2) {
|
||||
if (year < 70) {
|
||||
newYear = '20' + year;
|
||||
} else {
|
||||
newYear = '19' + year;
|
||||
}
|
||||
}
|
||||
if (newYear.length !== 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Bring back the validation for the date when the world goes okay.
|
||||
|
||||
// const expiry = new Date(year, month);
|
||||
// const currentTime = new Date();
|
||||
// expiry.setMonth(expiry.getMonth() - 1);
|
||||
// expiry.setMonth(expiry.getMonth() + 1, 1);
|
||||
// return expiry > currentTime;
|
||||
return true;
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
export * from './card-holder';
|
||||
export * from './card-number';
|
||||
export * from './expire-date';
|
||||
export * from './secure-code';
|
@ -1,11 +0,0 @@
|
||||
import { number } from 'card-validator';
|
||||
|
||||
import { replaceFullWidthChars } from 'checkout/utils';
|
||||
|
||||
export function formatCVC(value: string, cardNumber: string): string {
|
||||
value = replaceFullWidthChars(value);
|
||||
const { card } = number(cardNumber);
|
||||
const size = card?.code?.size || 4;
|
||||
value = value.replace(/\D/g, '').slice(0, size);
|
||||
return value;
|
||||
}
|
@ -1 +0,0 @@
|
||||
export * from './secure-code';
|
@ -1,49 +0,0 @@
|
||||
import { number } from 'card-validator';
|
||||
import { FieldError, UseFormRegister } from 'react-hook-form';
|
||||
|
||||
import { Input } from 'checkout/components';
|
||||
import { Locale } from 'checkout/locale';
|
||||
import { safeVal } from 'checkout/utils';
|
||||
import isNil from 'checkout/utils/is-nil';
|
||||
|
||||
import { formatCVC } from './format-cvc';
|
||||
import { validateSecureCode } from './validate-secure-code';
|
||||
import { ReactComponent as LockIcon } from '../../../../../../../ui/icon/lock.svg';
|
||||
import { CardFormInputs } from '../../card-form-inputs';
|
||||
|
||||
export interface SecureCodeProps {
|
||||
register: UseFormRegister<CardFormInputs>;
|
||||
locale: Locale;
|
||||
obscureCardCvv: boolean;
|
||||
cardNumber: string;
|
||||
fieldError: FieldError;
|
||||
isDirty: boolean;
|
||||
}
|
||||
|
||||
const getPlaceholder = (cardNumber: string | null, locale: Locale) => {
|
||||
const name = number(cardNumber)?.card?.code.name;
|
||||
return name || locale['form.input.secure.placeholder'];
|
||||
};
|
||||
|
||||
export const SecureCode = ({ cardNumber, locale, obscureCardCvv, register, fieldError, isDirty }: SecureCodeProps) => (
|
||||
<Input
|
||||
{...register('secureCode', {
|
||||
required: true,
|
||||
validate: (value) => !validateSecureCode(value, { cardNumber }) || 'Secure code is invalid',
|
||||
})}
|
||||
autoComplete="cc-csc"
|
||||
dirty={isDirty}
|
||||
error={!isNil(fieldError)}
|
||||
icon={<LockIcon />}
|
||||
id="secure-code-input"
|
||||
mark={true}
|
||||
placeholder={getPlaceholder(cardNumber, locale)}
|
||||
type={obscureCardCvv ? 'password' : 'tel'}
|
||||
onInput={(e) => {
|
||||
const target = e.currentTarget;
|
||||
const value = target.value;
|
||||
const formatted = formatCVC(value, cardNumber);
|
||||
return safeVal(formatted, target);
|
||||
}}
|
||||
/>
|
||||
);
|
@ -1,6 +0,0 @@
|
||||
import { cvv, number } from 'card-validator';
|
||||
|
||||
export function validateSecureCode(value: string, { cardNumber }: { cardNumber: string }): boolean {
|
||||
const { card } = number(cardNumber);
|
||||
return !(card ? cvv(value, card.code.size) : cvv(value)).isValid;
|
||||
}
|
@ -1 +0,0 @@
|
||||
export * from './card-form';
|
@ -1,32 +0,0 @@
|
||||
import { number } from 'card-validator';
|
||||
|
||||
import { isSecureCodeAvailable } from './is-secure-code-available';
|
||||
|
||||
jest.mock('card-validator');
|
||||
|
||||
describe('isSecureCodeAvailable', () => {
|
||||
it('returns false for Uzcard', () => {
|
||||
(number as jest.Mock).mockReturnValue({ card: { type: 'uzcard' } });
|
||||
expect(isSecureCodeAvailable('some-uzcard-number')).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false for Humo card', () => {
|
||||
(number as jest.Mock).mockReturnValue({ card: { type: 'humo' } });
|
||||
expect(isSecureCodeAvailable('some-humo-number')).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true for other card types', () => {
|
||||
(number as jest.Mock).mockReturnValue({ card: { type: 'visa' } });
|
||||
expect(isSecureCodeAvailable('some-visa-number')).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true for invalid card number', () => {
|
||||
(number as jest.Mock).mockReturnValue(null);
|
||||
expect(isSecureCodeAvailable('invalid-number')).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true when card number is null or undefined', () => {
|
||||
expect(isSecureCodeAvailable(null)).toBe(true);
|
||||
expect(isSecureCodeAvailable(undefined)).toBe(true);
|
||||
});
|
||||
});
|
@ -1,6 +0,0 @@
|
||||
import { number } from 'card-validator';
|
||||
|
||||
export const isSecureCodeAvailable = (cardNumber: string): boolean => {
|
||||
const cardType = number(cardNumber)?.card?.type;
|
||||
return !['uzcard', 'humo'].includes(cardType);
|
||||
};
|
@ -1,37 +0,0 @@
|
||||
import { FieldError, UseFormRegister } from 'react-hook-form';
|
||||
|
||||
import { InvoiceTemplateLineCostRange, InvoiceTemplateLineCostUnlim } from 'checkout/backend';
|
||||
import { Input } from 'checkout/components';
|
||||
import { Locale } from 'checkout/locale';
|
||||
import isNil from 'checkout/utils/is-nil';
|
||||
|
||||
import { formatAmount } from './format-amount';
|
||||
import { getPlaceholder } from './get-placeholder';
|
||||
import { validateAmount } from './validate-amount';
|
||||
import { ReactComponent as AmountIcon } from '../../../../../../ui/icon/amount.svg';
|
||||
|
||||
export type AmountProps = {
|
||||
register: UseFormRegister<any>;
|
||||
cost: InvoiceTemplateLineCostRange | InvoiceTemplateLineCostUnlim;
|
||||
locale: Locale;
|
||||
localeCode: string;
|
||||
fieldError: FieldError;
|
||||
isDirty: boolean;
|
||||
};
|
||||
|
||||
export const Amount = ({ register, locale, fieldError, cost, localeCode, isDirty }: AmountProps) => (
|
||||
<Input
|
||||
{...register('amount', {
|
||||
required: true,
|
||||
validate: (value) => !validateAmount(value, cost) || 'Amount is invalid',
|
||||
})}
|
||||
dirty={isDirty}
|
||||
error={!isNil(fieldError)}
|
||||
icon={<AmountIcon />}
|
||||
id="amount-input"
|
||||
mark={true}
|
||||
placeholder={getPlaceholder(cost, locale['form.input.amount.placeholder'], localeCode)}
|
||||
type="tel"
|
||||
onInput={formatAmount}
|
||||
/>
|
||||
);
|
@ -1,52 +0,0 @@
|
||||
import { FormEvent } from 'react';
|
||||
|
||||
import { replaceFullWidthChars, safeVal } from 'checkout/utils';
|
||||
|
||||
const createNumArr = (num: string): string[] => {
|
||||
let numTempArr;
|
||||
if (/^\d+(\.\d+)?$/.test(num)) {
|
||||
numTempArr = num.split('.');
|
||||
} else {
|
||||
numTempArr = num.split(',');
|
||||
}
|
||||
return numTempArr;
|
||||
};
|
||||
|
||||
const getNumType = (num: string): string => (/^\d+(\.\d+)?$/.test(num) ? '.' : ',');
|
||||
|
||||
const format = (num: string): string => {
|
||||
let result = num.replace(/\s/g, '');
|
||||
const formatReg = /\B(?=(\d{3})+(?!\d))/g;
|
||||
if (/^\d+([.,])?$/.test(result)) {
|
||||
result = result.replace(formatReg, ' ');
|
||||
const lastChar = result.charAt(result.length - 1);
|
||||
const isLastCharDot = lastChar === '.';
|
||||
const isLastCharComma = lastChar === ',';
|
||||
const isLastCharSpace = lastChar === ' ';
|
||||
result = isLastCharDot || isLastCharComma || isLastCharSpace ? result + ' ' : result;
|
||||
} else if (/^\d+([.,]\d+)?$/.test(result)) {
|
||||
const numTempArr = createNumArr(result);
|
||||
numTempArr[1] = numTempArr[1].slice(0, 2);
|
||||
result = numTempArr.join(getNumType(result) + ' ').replace(formatReg, ' ');
|
||||
} else if (result.length > 1) {
|
||||
result = result.slice(0, -1).replace(formatReg, ' ');
|
||||
result = createNumArr(result)
|
||||
.join(getNumType(result) + ' ')
|
||||
.replace(formatReg, ' ');
|
||||
} else {
|
||||
result = '';
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
export function formatAmount(e: FormEvent<HTMLInputElement>): number {
|
||||
const target = e.currentTarget;
|
||||
let value = target.value;
|
||||
const nativeEvent = e.nativeEvent as any;
|
||||
value = replaceFullWidthChars(value);
|
||||
if (nativeEvent.inputType === 'deleteContentBackward') {
|
||||
return safeVal(value, target);
|
||||
} else {
|
||||
return safeVal(format(value), target);
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
import { InvoiceTemplateLineCostRange, InvoiceTemplateLineCostUnlim } from 'checkout/backend';
|
||||
import { formatAmount } from 'checkout/utils';
|
||||
|
||||
const toUnlimPlaceholder = (localeString: string, currency: string): string => `${localeString} ${currency}`;
|
||||
|
||||
const toRangePlaceholder = (cost: InvoiceTemplateLineCostRange, locale: string): string => {
|
||||
const range = cost.range;
|
||||
const lower = formatAmount({
|
||||
minorValue: range.lowerBound,
|
||||
currencyCode: cost.currency,
|
||||
status: 'final',
|
||||
locale,
|
||||
});
|
||||
const upper = formatAmount({
|
||||
minorValue: range.upperBound,
|
||||
currencyCode: cost.currency,
|
||||
status: 'final',
|
||||
locale,
|
||||
});
|
||||
return `${lower} - ${upper}`;
|
||||
};
|
||||
|
||||
export const getPlaceholder = (
|
||||
cost: InvoiceTemplateLineCostRange | InvoiceTemplateLineCostUnlim,
|
||||
localeString: string,
|
||||
localeCode: string,
|
||||
): string => {
|
||||
if (!cost) {
|
||||
return;
|
||||
}
|
||||
switch (cost.costType) {
|
||||
case 'InvoiceTemplateLineCostUnlim':
|
||||
return toUnlimPlaceholder(localeString, 'RUB'); // TODO unlim cost type does't support currency
|
||||
case 'InvoiceTemplateLineCostRange':
|
||||
return toRangePlaceholder(cost as InvoiceTemplateLineCostRange, localeCode);
|
||||
}
|
||||
};
|
@ -1 +0,0 @@
|
||||
export * from './amount';
|
@ -1,30 +0,0 @@
|
||||
import { CostType, InvoiceTemplateLineCostRange, InvoiceTemplateLineCostUnlim } from 'checkout/backend';
|
||||
import isNumber from 'checkout/utils/is-number';
|
||||
import toNumber from 'checkout/utils/to-number';
|
||||
|
||||
function validate(amount: number, min?: number, max?: number): boolean {
|
||||
if (!amount || !isNumber(amount) || amount <= 0) {
|
||||
return true;
|
||||
}
|
||||
if (min && max) {
|
||||
return !(amount >= min && amount <= max);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export const validateAmount = (
|
||||
value: string,
|
||||
cost: InvoiceTemplateLineCostRange | InvoiceTemplateLineCostUnlim,
|
||||
): boolean => {
|
||||
if (value) {
|
||||
value = value.replace(/\s/g, '').replace(/,/g, '.');
|
||||
}
|
||||
const binded = validate.bind(null, toNumber(value) * 100);
|
||||
switch (cost.costType) {
|
||||
case CostType.InvoiceTemplateLineCostRange:
|
||||
const range = (cost as InvoiceTemplateLineCostRange).range;
|
||||
return binded(range.lowerBound, range.upperBound);
|
||||
case CostType.InvoiceTemplateLineCostUnlim:
|
||||
return binded();
|
||||
}
|
||||
};
|
@ -1,30 +0,0 @@
|
||||
import { FieldError, FieldErrorsImpl, Merge, UseFormRegister } from 'react-hook-form';
|
||||
|
||||
import { Input } from 'checkout/components';
|
||||
import { Locale } from 'checkout/locale';
|
||||
import { formatEmail, validateEmail } from 'checkout/utils';
|
||||
import isNil from 'checkout/utils/is-nil';
|
||||
|
||||
export type EmailProps = {
|
||||
register: UseFormRegister<any>;
|
||||
locale: Locale;
|
||||
fieldError: FieldError | Merge<FieldError, FieldErrorsImpl<any>>;
|
||||
isDirty: boolean;
|
||||
};
|
||||
|
||||
export const Email = ({ register, locale, fieldError, isDirty }: EmailProps) => (
|
||||
<Input
|
||||
{...register('email', {
|
||||
required: true,
|
||||
validate: (value) => !validateEmail(value) || 'Email is invalid',
|
||||
})}
|
||||
autoComplete="email"
|
||||
dirty={isDirty}
|
||||
error={!isNil(fieldError)}
|
||||
id="email-input"
|
||||
mark={true}
|
||||
placeholder={locale['form.input.email.placeholder']}
|
||||
type="email"
|
||||
onInput={formatEmail}
|
||||
/>
|
||||
);
|
@ -1 +0,0 @@
|
||||
export * from './email';
|
@ -1,3 +0,0 @@
|
||||
export * from './email';
|
||||
export * from './amount';
|
||||
export * from './phone';
|
@ -1 +0,0 @@
|
||||
export * from './phone';
|
@ -1,31 +0,0 @@
|
||||
import { FieldError, FieldErrorsImpl, Merge, UseFormRegister } from 'react-hook-form';
|
||||
|
||||
import { Input } from 'checkout/components';
|
||||
import { Locale } from 'checkout/locale';
|
||||
import { formatPhoneNumber, validatePhone } from 'checkout/utils';
|
||||
import isNil from 'checkout/utils/is-nil';
|
||||
|
||||
export interface PhoneProps {
|
||||
register: UseFormRegister<any>;
|
||||
locale: Locale;
|
||||
fieldError: FieldError | Merge<FieldError, FieldErrorsImpl<any>>;
|
||||
isDirty: boolean;
|
||||
}
|
||||
|
||||
export const Phone = ({ register, locale, fieldError, isDirty }: PhoneProps) => (
|
||||
<Input
|
||||
{...register('phoneNumber', {
|
||||
required: true,
|
||||
validate: (value) => !validatePhone(value) || 'Phone number is invalid',
|
||||
})}
|
||||
autoComplete="tel"
|
||||
dirty={isDirty}
|
||||
error={!isNil(fieldError)}
|
||||
id="phone-input"
|
||||
mark={true}
|
||||
placeholder={locale['form.input.phone.placeholder']}
|
||||
type="tel"
|
||||
onFocus={formatPhoneNumber}
|
||||
onInput={formatPhoneNumber}
|
||||
/>
|
||||
);
|
@ -1,8 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Divider = styled.div`
|
||||
height: 1px;
|
||||
background-color: ${({ theme }) => theme.divider};
|
||||
margin: 30px auto;
|
||||
width: 40%;
|
||||
`;
|
@ -1,24 +0,0 @@
|
||||
import { InvoiceTemplateLineCostRange, InvoiceTemplateLineCostUnlim } from 'checkout/backend';
|
||||
|
||||
export interface ItemConfig {
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
export interface AmountConfig extends ItemConfig {
|
||||
cost?: InvoiceTemplateLineCostRange | InvoiceTemplateLineCostUnlim;
|
||||
}
|
||||
|
||||
export interface EmailConfig extends ItemConfig {
|
||||
value?: string;
|
||||
}
|
||||
|
||||
export interface PhoneNumberConfig extends ItemConfig {
|
||||
value?: string;
|
||||
}
|
||||
|
||||
export interface FieldsConfig {
|
||||
amount: AmountConfig;
|
||||
email: EmailConfig;
|
||||
cardHolder: ItemConfig;
|
||||
phoneNumber: PhoneNumberConfig;
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
export * from './fields-config';
|
||||
export * from './to-fields-config';
|
@ -1,59 +0,0 @@
|
||||
import {
|
||||
InvoiceTemplate,
|
||||
InvoiceTemplateLineCostRange,
|
||||
InvoiceTemplateLineCostUnlim,
|
||||
InvoiceTemplateSingleLine,
|
||||
} from 'checkout/backend';
|
||||
import { InitConfig } from 'checkout/config';
|
||||
|
||||
import { AmountConfig, EmailConfig, FieldsConfig, PhoneNumberConfig } from './fields-config';
|
||||
|
||||
const toSingleLineAmountConfig = (c: InvoiceTemplateSingleLine): AmountConfig => {
|
||||
const result = { visible: false } as AmountConfig;
|
||||
switch (c.price.costType) {
|
||||
case 'InvoiceTemplateLineCostUnlim':
|
||||
result.visible = true;
|
||||
result.cost = c.price as InvoiceTemplateLineCostUnlim;
|
||||
break;
|
||||
case 'InvoiceTemplateLineCostRange':
|
||||
result.visible = true;
|
||||
result.cost = c.price as InvoiceTemplateLineCostRange;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const toTemplateAmountConfig = (c: InitConfig, t: InvoiceTemplate): AmountConfig => {
|
||||
switch (t.details.templateType) {
|
||||
case 'InvoiceTemplateSingleLine':
|
||||
return c.amount ? { visible: false } : toSingleLineAmountConfig(t.details as InvoiceTemplateSingleLine);
|
||||
}
|
||||
return { visible: false };
|
||||
};
|
||||
|
||||
export const toAmountConfig = (c: InitConfig, template: InvoiceTemplate): AmountConfig => {
|
||||
switch (c.integrationType) {
|
||||
case 'invoiceTemplate':
|
||||
return toTemplateAmountConfig(c, template);
|
||||
}
|
||||
return { visible: false };
|
||||
};
|
||||
|
||||
export const toPhoneNumberConfig = (phoneNumber: string): PhoneNumberConfig => {
|
||||
return phoneNumber ? { visible: false, value: phoneNumber } : { visible: true };
|
||||
};
|
||||
|
||||
export const toEmailConfig = (email: string): EmailConfig => {
|
||||
return email ? { visible: false, value: email } : { visible: true };
|
||||
};
|
||||
|
||||
export const toCardHolderConfig = (requireCardHolder: boolean | null) => ({
|
||||
visible: requireCardHolder === null ? true : requireCardHolder,
|
||||
});
|
||||
|
||||
export const toFieldsConfig = (c: InitConfig, t: InvoiceTemplate): FieldsConfig => ({
|
||||
amount: toAmountConfig(c, t),
|
||||
email: toEmailConfig(c.email),
|
||||
cardHolder: toCardHolderConfig(c.requireCardHolder),
|
||||
phoneNumber: toPhoneNumberConfig(c.phoneNumber),
|
||||
});
|
@ -1,143 +0,0 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import { lazy, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { ErrorBoundaryFallback } from 'checkout/components/ui';
|
||||
import { FormName, ModalForms, ModalName, SlideDirection } from 'checkout/hooks';
|
||||
import { findNamed } from 'checkout/utils';
|
||||
import { device } from 'checkout/utils/device';
|
||||
|
||||
import { FormLoader } from './form-loader';
|
||||
import NoAvailablePaymentMethodForm from './no-available-payment-method-form/no-available-payment-method-form';
|
||||
import RedirectForm from './redirect-form/redirect-form';
|
||||
import ResultForm from './result-form/result-form';
|
||||
import { ModalContext } from '../../modal-context';
|
||||
|
||||
const Container = styled.div`
|
||||
padding: 16px;
|
||||
|
||||
@media ${device.desktop} {
|
||||
width: 360px;
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const Form = styled.div<{ height?: number }>`
|
||||
position: relative;
|
||||
background: #fff;
|
||||
border-radius: 16px;
|
||||
border: 1px solid ${({ theme }) => theme.form.border};
|
||||
padding: 16px;
|
||||
@media ${device.desktop} {
|
||||
padding: 24px;
|
||||
}
|
||||
overflow: hidden;
|
||||
transition: height 0.3s;
|
||||
height: ${({ height }) => (height ? `${height}px` : 'auto')};
|
||||
`;
|
||||
|
||||
const PaymentMethods = lazy(() => import('./payment-methods/payment-methods'));
|
||||
|
||||
const CardForm = lazy(() => import('./card-form/card-form'));
|
||||
|
||||
const WalletForm = lazy(() => import('./wallet-form/wallet-form'));
|
||||
|
||||
const WalletProviders = lazy(() => import('./wallet-providers/wallet-providers'));
|
||||
|
||||
const PaymentTerminalForm = lazy(() => import('./payment-terminal-form/payment-terminal-form'));
|
||||
|
||||
const PaymentTerminalSelectorForm = lazy(
|
||||
() => import('./payment-terminal-selector-form/payment-terminal-selector-form'),
|
||||
);
|
||||
|
||||
const QrCodeInteractionForm = lazy(() => import('./qr-code-interaction-form/qr-code-interaction-form'));
|
||||
|
||||
const ApiExtensionForm = lazy(() => import('./api-extension-form/api-extension-form'));
|
||||
|
||||
const renderForm = (name: FormName, onMount: () => void) => {
|
||||
switch (name) {
|
||||
case FormName.paymentMethods:
|
||||
return <PaymentMethods onMount={onMount} />;
|
||||
case FormName.cardForm:
|
||||
return <CardForm onMount={onMount} />;
|
||||
case FormName.walletForm:
|
||||
return <WalletForm onMount={onMount} />;
|
||||
case FormName.walletProviders:
|
||||
return <WalletProviders onMount={onMount} />;
|
||||
case FormName.resultForm:
|
||||
return <ResultForm onMount={onMount} />;
|
||||
case FormName.noAvailablePaymentMethodForm:
|
||||
return <NoAvailablePaymentMethodForm onMount={onMount} />;
|
||||
case FormName.redirectForm:
|
||||
return <RedirectForm onMount={onMount} />;
|
||||
case FormName.paymentTerminalForm:
|
||||
return <PaymentTerminalForm onMount={onMount} />;
|
||||
case FormName.paymentTerminalSelector:
|
||||
return <PaymentTerminalSelectorForm onMount={onMount} />;
|
||||
case FormName.qrCodeInteractionForm:
|
||||
return <QrCodeInteractionForm onMount={onMount} />;
|
||||
case FormName.apiExtensionForm:
|
||||
return <ApiExtensionForm onMount={onMount} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const toInitialPos = (slideDirection: SlideDirection): number => {
|
||||
switch (slideDirection) {
|
||||
case SlideDirection.left:
|
||||
return -300;
|
||||
case SlideDirection.right:
|
||||
return 300;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
const DEFAULT_HEIGHT_PX = 300;
|
||||
|
||||
export const FormContainer = () => {
|
||||
const contentElement = useRef(null);
|
||||
const [height, setHeight] = useState(0);
|
||||
const { modalState } = useContext(ModalContext);
|
||||
|
||||
const {
|
||||
formName,
|
||||
viewInfo: { slideDirection, inProcess },
|
||||
} = useMemo(() => {
|
||||
const found = findNamed(modalState, ModalName.modalForms) as ModalForms;
|
||||
return {
|
||||
formName: found.formsInfo.find((item) => item.active)?.name,
|
||||
viewInfo: found.viewInfo,
|
||||
};
|
||||
}, [modalState]);
|
||||
|
||||
const onMount = useCallback(() => {
|
||||
const elHight = contentElement.current?.clientHeight || 0;
|
||||
if (elHight !== height) {
|
||||
setHeight(elHight);
|
||||
}
|
||||
}, [contentElement, height, setHeight]);
|
||||
|
||||
useEffect(() => {
|
||||
setHeight(contentElement.current?.clientHeight || DEFAULT_HEIGHT_PX);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Form height={height}>
|
||||
<motion.div
|
||||
key={formName}
|
||||
ref={contentElement}
|
||||
animate={{ x: 0 }}
|
||||
initial={{ x: toInitialPos(slideDirection) }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<ErrorBoundary fallback={<ErrorBoundaryFallback />}>{renderForm(formName, onMount)}</ErrorBoundary>
|
||||
{inProcess && <FormLoader />}
|
||||
</motion.div>
|
||||
</Form>
|
||||
</Container>
|
||||
);
|
||||
};
|
@ -1,12 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const FormGroup = styled.div<{
|
||||
direction?: 'column' | 'row';
|
||||
$gap?: number;
|
||||
}>`
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
flex-direction: ${({ direction }) => direction || 'row'};
|
||||
margin-bottom: 10px;
|
||||
gap: ${({ $gap }) => `${$gap}px` || 0};
|
||||
`;
|
@ -1,33 +0,0 @@
|
||||
import { motion } from 'framer-motion';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Loader } from 'checkout/components';
|
||||
|
||||
const fadeIn = {
|
||||
hidden: { opacity: 0 },
|
||||
show: { opacity: 1, transition: { duration: 0.5 } },
|
||||
exit: { opacity: 0, transition: { duration: 0.5 } },
|
||||
};
|
||||
|
||||
const LoaderWrapper = styled.div`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
`;
|
||||
|
||||
export const FormLoader = () => (
|
||||
<motion.div animate="show" exit="exit" initial="hidden" variants={fadeIn}>
|
||||
<LoaderWrapper key="form-loader" id="form-loader">
|
||||
<Loader />
|
||||
</LoaderWrapper>
|
||||
</motion.div>
|
||||
);
|
@ -1 +0,0 @@
|
||||
export * from './form-loader';
|
@ -1,23 +0,0 @@
|
||||
import {
|
||||
KnownProviderCategories,
|
||||
PaymentMethod,
|
||||
PaymentMethodName,
|
||||
PaymentTerminalPaymentMethod,
|
||||
} from 'checkout/hooks';
|
||||
import isNil from 'checkout/utils/is-nil';
|
||||
|
||||
export const getAvailableTerminalPaymentMethod = (
|
||||
availablePaymentMethods: PaymentMethod[],
|
||||
category: KnownProviderCategories,
|
||||
): PaymentTerminalPaymentMethod | null => {
|
||||
if (isNil(category)) {
|
||||
return null;
|
||||
}
|
||||
const found = availablePaymentMethods.find((m) => {
|
||||
if (m.name !== PaymentMethodName.PaymentTerminal) {
|
||||
return false;
|
||||
}
|
||||
return (m as PaymentTerminalPaymentMethod).category === category;
|
||||
});
|
||||
return found ? (found as PaymentTerminalPaymentMethod) : null;
|
||||
};
|
@ -1,11 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const HeaderWrapper = styled.div`
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
height: 20px;
|
||||
`;
|
@ -1,34 +0,0 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { ChevronButton } from 'checkout/components';
|
||||
import { FormInfo, ModalForms, ModalName, ModalState } from 'checkout/hooks';
|
||||
import { Direction } from 'checkout/hooks';
|
||||
import { findInfoWithPrevious, findNamed } from 'checkout/utils';
|
||||
|
||||
import { ModalContext } from '../../../modal-context';
|
||||
import { HeaderWrapper } from '../header-wrapper';
|
||||
import { Title } from '../title';
|
||||
|
||||
const getDestination = (modals: ModalState[]): FormInfo => {
|
||||
const modalForms = findNamed(modals, ModalName.modalForms) as ModalForms;
|
||||
const withPrevious = findInfoWithPrevious(modals);
|
||||
return withPrevious ? (findNamed(modalForms.formsInfo, withPrevious.previous) as FormInfo) : null;
|
||||
};
|
||||
|
||||
export const Header = ({ title }: { title: string }) => {
|
||||
const { modalState, goToFormInfo } = useContext(ModalContext);
|
||||
const destination = getDestination(modalState);
|
||||
|
||||
return (
|
||||
<HeaderWrapper>
|
||||
{destination && (
|
||||
<ChevronButton
|
||||
id="desktop-back-btn"
|
||||
type="left"
|
||||
onClick={() => goToFormInfo(destination, Direction.back)}
|
||||
/>
|
||||
)}
|
||||
<Title>{title}</Title>
|
||||
</HeaderWrapper>
|
||||
);
|
||||
};
|
@ -1 +0,0 @@
|
||||
export * from './header';
|
@ -1,2 +0,0 @@
|
||||
export * from './form-container';
|
||||
export * from './common-fields';
|
@ -1 +0,0 @@
|
||||
export * from './no-available-payment-method-form';
|
@ -1,25 +0,0 @@
|
||||
import { useContext, useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { InitialContext } from '../../../../initial-context';
|
||||
import { Text } from '../text';
|
||||
|
||||
const Container = styled.div`
|
||||
padding: 80px 0;
|
||||
`;
|
||||
|
||||
const NoAvailablePaymentMethodForm = ({ onMount }: { onMount: () => void }) => {
|
||||
const { locale } = useContext(InitialContext);
|
||||
|
||||
useEffect(() => {
|
||||
onMount();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Text centered={true}>{locale['info.modal.no.available.payment.method']}</Text>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default NoAvailablePaymentMethodForm;
|
@ -1 +0,0 @@
|
||||
export * from './pay-button';
|
@ -1,29 +0,0 @@
|
||||
import { useContext } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Button } from 'checkout/components';
|
||||
import { AmountInfo } from 'checkout/hooks';
|
||||
import { Locale } from 'checkout/locale';
|
||||
import { formatAmount } from 'checkout/utils';
|
||||
|
||||
import { InitialContext } from '../../../../initial-context';
|
||||
|
||||
const PayButtonWrapper = styled(Button)`
|
||||
margin-top: 20px;
|
||||
`;
|
||||
|
||||
const toLabel = (locale: Locale, amountInfo: AmountInfo): string => {
|
||||
const amount = formatAmount(amountInfo);
|
||||
const amountLabel = amount ? ` ${amount}` : '';
|
||||
return `${locale['form.button.pay.label']}${amountLabel}`;
|
||||
};
|
||||
|
||||
export const PayButton = () => {
|
||||
const { locale, amountInfo } = useContext(InitialContext);
|
||||
const label = toLabel(locale, amountInfo);
|
||||
return (
|
||||
<PayButtonWrapper color="primary" id="pay-btn" type="submit">
|
||||
{label}
|
||||
</PayButtonWrapper>
|
||||
);
|
||||
};
|
@ -1 +0,0 @@
|
||||
export * from './payment-methods';
|
@ -1,24 +0,0 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { PaymentMethodIcon, PaymentMethodTitle } from 'checkout/components/ui';
|
||||
import { CardFormInfo, FormName } from 'checkout/hooks';
|
||||
|
||||
import { Method } from './method';
|
||||
import { InitialContext } from '../../../../../initial-context';
|
||||
import { ModalContext } from '../../../../modal-context';
|
||||
|
||||
export const BankCard = () => {
|
||||
const { locale } = useContext(InitialContext);
|
||||
const { goToFormInfo } = useContext(ModalContext);
|
||||
|
||||
const onClick = () => {
|
||||
goToFormInfo(new CardFormInfo(FormName.paymentMethods));
|
||||
};
|
||||
|
||||
return (
|
||||
<Method id="bank-card-payment-method" onClick={onClick}>
|
||||
<PaymentMethodIcon name="bank-card" />
|
||||
<PaymentMethodTitle>{locale['form.payment.method.name.card.label']}</PaymentMethodTitle>
|
||||
</Method>
|
||||
);
|
||||
};
|
@ -1,11 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Description = styled.p`
|
||||
font-weight: 400;
|
||||
font-size: 13px;
|
||||
color: ${({ theme }) => theme.font.primaryColor};
|
||||
letter-spacing: 0.1px;
|
||||
line-height: 17px;
|
||||
padding: 4px 0 0;
|
||||
margin: 0;
|
||||
`;
|
@ -1 +0,0 @@
|
||||
export * from './methods-list';
|
@ -1,17 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Method = styled.li`
|
||||
border-radius: 8px;
|
||||
border: 1px solid ${({ theme }) => theme.paymentMethodItem.border};
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
border-color: ${({ theme }) => theme.paymentMethodItem.hover};
|
||||
}
|
||||
`;
|
@ -1,18 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { PaymentMethod } from 'checkout/hooks';
|
||||
|
||||
import { Methods } from './methods';
|
||||
|
||||
const List = styled.ul`
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
min-height: 266px;
|
||||
`;
|
||||
|
||||
export const MethodsList = ({ methods }: { methods: PaymentMethod[] }) => (
|
||||
<List>
|
||||
<Methods methods={methods} />
|
||||
</List>
|
||||
);
|
@ -1,38 +0,0 @@
|
||||
import {
|
||||
DigitalWalletPaymentMethod,
|
||||
PaymentMethod,
|
||||
PaymentMethodName,
|
||||
PaymentTerminalPaymentMethod,
|
||||
} from 'checkout/hooks';
|
||||
import { assertUnreachable } from 'checkout/utils';
|
||||
|
||||
import { BankCard } from './bank-card';
|
||||
import { PaymentTerminalMethodItems } from './payment-terminal-method-items';
|
||||
import { Wallets } from './wallets';
|
||||
import { WalletProviderPaymentMethodItem } from '../../wallet-provider-payment-method-item';
|
||||
|
||||
const Method = ({ method }: { method: PaymentMethod }) => {
|
||||
switch (method.name) {
|
||||
case PaymentMethodName.BankCard:
|
||||
return <BankCard />;
|
||||
case PaymentMethodName.PaymentTerminal:
|
||||
return <PaymentTerminalMethodItems method={method as PaymentTerminalPaymentMethod} />;
|
||||
case PaymentMethodName.DigitalWallet:
|
||||
const { serviceProviders } = method as DigitalWalletPaymentMethod;
|
||||
if (serviceProviders.length === 1) {
|
||||
return <WalletProviderPaymentMethodItem serviceProvider={serviceProviders[0]} />;
|
||||
}
|
||||
return <Wallets />;
|
||||
default:
|
||||
assertUnreachable(method.name);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const Methods = ({ methods }: { methods: PaymentMethod[] }) => (
|
||||
<>
|
||||
{methods.map((method, index) => (
|
||||
<Method key={index} method={method} />
|
||||
))}
|
||||
</>
|
||||
);
|
@ -1,13 +0,0 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { KnownProviderCategories } from 'checkout/hooks';
|
||||
|
||||
export const CategoryContent: React.FC<{
|
||||
category: KnownProviderCategories;
|
||||
}> = ({ category }) => {
|
||||
switch (category) {
|
||||
case 'netbanking':
|
||||
return <img height="68px" src="/assets/inb-logo.jpg" width="106px"></img>;
|
||||
}
|
||||
return <div>{category}</div>;
|
||||
};
|
@ -1,18 +0,0 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { PaymentTerminalPaymentMethod } from 'checkout/hooks';
|
||||
|
||||
import { CategoryContent } from './category-content';
|
||||
import { MetadataContent } from './metadata-content';
|
||||
|
||||
export const Content: React.FC<{
|
||||
method: PaymentTerminalPaymentMethod;
|
||||
localeCode: string;
|
||||
}> = ({ method, localeCode }) => {
|
||||
if (method.serviceProviders.length === 1) {
|
||||
return <MetadataContent localeCode={localeCode} serviceProvider={method.serviceProviders[0]} />;
|
||||
}
|
||||
if (method.serviceProviders.length > 1) {
|
||||
return <CategoryContent category={method.category} />;
|
||||
}
|
||||
};
|
@ -1 +0,0 @@
|
||||
export * from './content';
|
@ -1,17 +0,0 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { ServiceProvider } from 'checkout/backend';
|
||||
import { getMetadata, MetadataLogo, MetadataTitle } from 'checkout/components';
|
||||
|
||||
export const MetadataContent: React.FC<{
|
||||
serviceProvider: ServiceProvider;
|
||||
localeCode: string;
|
||||
}> = ({ serviceProvider, localeCode }) => {
|
||||
const { logo, title } = getMetadata(serviceProvider);
|
||||
return (
|
||||
<>
|
||||
{title && <MetadataTitle localeCode={localeCode} metadata={title} />}
|
||||
{logo && <MetadataLogo metadata={logo} />}
|
||||
</>
|
||||
);
|
||||
};
|
@ -1 +0,0 @@
|
||||
export * from './payment-terminal-method-items';
|
@ -1,83 +0,0 @@
|
||||
import { useContext, useEffect } from 'react';
|
||||
|
||||
import { PaymentMethodName, ServiceProvider, ServiceProviderContactInfo } from 'checkout/backend';
|
||||
import { getMetadata, PaymentMethodItemContainer } from 'checkout/components/ui';
|
||||
import {
|
||||
FormName,
|
||||
PaymentTerminalFormInfo,
|
||||
PaymentTerminalSelectorFormInfo,
|
||||
ResultFormInfo,
|
||||
ResultType,
|
||||
} from 'checkout/hooks';
|
||||
import { PaymentTerminalPaymentMethod, useCreatePayment, PaymentTerminalFormValues } from 'checkout/hooks';
|
||||
import isNil from 'checkout/utils/is-nil';
|
||||
|
||||
import { Content } from './content';
|
||||
import { InitialContext } from '../../../../../../initial-context';
|
||||
import { ModalContext } from '../../../../../modal-context';
|
||||
|
||||
export interface PaymentTerminalMethodItemProps {
|
||||
method: PaymentTerminalPaymentMethod;
|
||||
}
|
||||
|
||||
const isRequiredEmail = (contactInfo: ServiceProviderContactInfo, emailPrefilled: boolean): boolean =>
|
||||
!isNil(contactInfo) && contactInfo.email === true && !emailPrefilled;
|
||||
|
||||
const isRequiredPhoneNumber = (contactInfo: ServiceProviderContactInfo, phoneNumberPrefilled: boolean): boolean =>
|
||||
!isNil(contactInfo) && contactInfo.phoneNumber === true && !phoneNumberPrefilled;
|
||||
|
||||
const isRequiredPaymentTerminalForm = (
|
||||
serviceProvider: ServiceProvider,
|
||||
emailPrefilled: boolean,
|
||||
phoneNumberPrefilled: boolean,
|
||||
): boolean => {
|
||||
const { form, contactInfo } = getMetadata(serviceProvider);
|
||||
return (
|
||||
!isNil(form) ||
|
||||
isRequiredEmail(contactInfo, emailPrefilled) ||
|
||||
isRequiredPhoneNumber(contactInfo, phoneNumberPrefilled)
|
||||
);
|
||||
};
|
||||
|
||||
export const PaymentTerminalMethodItem = ({ method }: PaymentTerminalMethodItemProps) => {
|
||||
const { initConfig } = useContext(InitialContext);
|
||||
const { goToFormInfo, prepareToPay } = useContext(ModalContext);
|
||||
|
||||
const emailPrefilled = !!initConfig.email;
|
||||
const phoneNumberPrefilled = !!initConfig.phoneNumber;
|
||||
|
||||
const { createPaymentState, setFormData } = useCreatePayment();
|
||||
|
||||
const onClick = () => {
|
||||
if (method.serviceProviders.length === 1) {
|
||||
const serviceProvider = method.serviceProviders[0];
|
||||
if (isRequiredPaymentTerminalForm(serviceProvider, emailPrefilled, phoneNumberPrefilled)) {
|
||||
goToFormInfo(new PaymentTerminalFormInfo(serviceProvider.id, FormName.paymentMethods));
|
||||
} else {
|
||||
prepareToPay();
|
||||
setFormData({
|
||||
method: PaymentMethodName.PaymentTerminal,
|
||||
values: {
|
||||
provider: serviceProvider.id,
|
||||
} as PaymentTerminalFormValues,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (method.serviceProviders.length > 1) {
|
||||
goToFormInfo(new PaymentTerminalSelectorFormInfo(method.category, FormName.paymentMethods));
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (createPaymentState.status === 'FAILURE') {
|
||||
const error = createPaymentState.error;
|
||||
goToFormInfo(new ResultFormInfo(ResultType.hookError, { error }));
|
||||
}
|
||||
}, [createPaymentState]);
|
||||
|
||||
return (
|
||||
<PaymentMethodItemContainer id={`${Math.floor(Math.random() * 100)}-payment-method-item`} onClick={onClick}>
|
||||
<Content localeCode={initConfig.locale} method={method} />
|
||||
</PaymentMethodItemContainer>
|
||||
);
|
||||
};
|
@ -1,23 +0,0 @@
|
||||
import { KnownProviderCategories, PaymentTerminalPaymentMethod } from 'checkout/hooks';
|
||||
import { assertUnreachable } from 'checkout/utils';
|
||||
|
||||
import { PaymentTerminalMethodItem } from './payment-terminal-method-item';
|
||||
|
||||
export interface PaymentTerminalMethodItemsProps {
|
||||
method: PaymentTerminalPaymentMethod;
|
||||
}
|
||||
|
||||
export const PaymentTerminalMethodItems = ({ method }: PaymentTerminalMethodItemsProps) => {
|
||||
switch (method.category) {
|
||||
case KnownProviderCategories.UPI:
|
||||
case KnownProviderCategories.PIX:
|
||||
case KnownProviderCategories.PaymentTerminal:
|
||||
case KnownProviderCategories.DigitalWallet:
|
||||
case KnownProviderCategories.NetBanking:
|
||||
case KnownProviderCategories.OnlineBanking:
|
||||
return <PaymentTerminalMethodItem method={method} />;
|
||||
default:
|
||||
assertUnreachable(method.category);
|
||||
return null;
|
||||
}
|
||||
};
|
@ -1,3 +0,0 @@
|
||||
import { FormInfo } from 'checkout/hooks';
|
||||
|
||||
export type SetFormInfoAction = (formInfo: FormInfo) => any;
|
@ -1,7 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Text = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
`;
|
@ -1,27 +0,0 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { PaymentMethodIcon, PaymentMethodTitle } from 'checkout/components/ui';
|
||||
import { FormName, WalletProvidersFormInfo } from 'checkout/hooks';
|
||||
|
||||
import { Method } from './method';
|
||||
import { Text } from './text';
|
||||
import { InitialContext } from '../../../../../initial-context';
|
||||
import { ModalContext } from '../../../../modal-context';
|
||||
|
||||
export const Wallets = () => {
|
||||
const { locale } = useContext(InitialContext);
|
||||
const { goToFormInfo } = useContext(ModalContext);
|
||||
|
||||
const onClick = () => {
|
||||
goToFormInfo(new WalletProvidersFormInfo(FormName.paymentMethods));
|
||||
};
|
||||
|
||||
return (
|
||||
<Method id="wallets-payment-method" onClick={onClick}>
|
||||
<PaymentMethodIcon name="wallets" />
|
||||
<Text>
|
||||
<PaymentMethodTitle>{locale['form.payment.method.name.wallet.label']}</PaymentMethodTitle>
|
||||
</Text>
|
||||
</Method>
|
||||
);
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user