APM-13 Payment provider terminal metadata refactoring (#54)

This commit is contained in:
Ildar Galeev 2022-03-03 20:11:02 +03:00 committed by GitHub
parent 0a50b32331
commit 34483bc588
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 117 additions and 146 deletions

View File

@ -1,3 +1,5 @@
export const METADATA_NAMESPACE = 'dev.vality.checkout';
export interface ServiceProviderMetadataField {
name: string;
type: JSX.IntrinsicElements['input']['type'];
@ -6,5 +8,7 @@ export interface ServiceProviderMetadataField {
}
export interface ServiceProviderMetadata {
form?: ServiceProviderMetadataField[];
METADATA_NAMESPACE: {
form?: ServiceProviderMetadataField[];
};
}

View File

@ -4,5 +4,5 @@ export class ServiceProvider {
id: string;
brandName: string;
category: string;
metadata: ServiceProviderMetadata;
metadata?: ServiceProviderMetadata;
}

View File

@ -2,14 +2,12 @@ import * as React from 'react';
import { connect } from 'react-redux';
import { ReactSVG } from 'react-svg';
import { State } from 'checkout/state';
import { PaymentMethodName, State } from 'checkout/state';
import { Locale } from 'checkout/locale';
import { Config } from 'checkout/config';
import SecureIcon from './secure-icon.svg';
import VisaIcon from './visa-icon.svg';
import McIcon from './mc-icon.svg';
import PciDssIcon from './pci-dss-icon.svg';
import MirAcceptIcon from './mir-accept.svg';
import { device } from 'checkout/utils/device';
import styled, { css } from 'checkout/styled-components';
@ -92,11 +90,6 @@ const StyledPciDssIcon = styled(PciDssIcon)`
${fillIconWhite}
`;
const StyledMirAcceptIcon = styled(MirAcceptIcon)`
${iconGap}
${fixPosition}
`;
const LogoWrapper = styled.div`
padding-left: 6px;
fill: #fff;
@ -104,19 +97,21 @@ const LogoWrapper = styled.div`
export interface FooterProps {
locale: Locale;
config: Config;
brandless: boolean;
isPaymentMethodBankCard: boolean;
className?: string;
}
const mapStateToProps = (state: State) => ({
locale: state.config.locale,
config: state.config
brandless: state.config.appConfig.brandless,
isPaymentMethodBankCard: !!state.availablePaymentMethods.find((m) => m.name === PaymentMethodName.BankCard)
});
const FooterDef: React.FC<FooterProps> = (props) => (
<FooterWrapper className={props.className}>
<SafePaymentContainer>
{!props.config.appConfig.brandless && (
{!props.brandless && (
<SafePayment>
<StyledSecureIcon />
<Label>{props.locale['footer.pay.label']}</Label>
@ -130,12 +125,13 @@ const FooterDef: React.FC<FooterProps> = (props) => (
</LogoWrapper>
</SafePayment>
)}
<SafeLogos>
<StyledVisaIcon />
<StyledMcIcon />
<StyledMirAcceptIcon />
<StyledPciDssIcon />
</SafeLogos>
{props.isPaymentMethodBankCard && (
<SafeLogos>
<StyledVisaIcon />
<StyledMcIcon />
<StyledPciDssIcon />
</SafeLogos>
)}
</SafePaymentContainer>
</FooterWrapper>
);

View File

@ -1,10 +0,0 @@
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12.4161 0.625962C12.1642 0.458013 11.836 0.458013 11.584 0.625962L0.334033 8.12596C0.0590402 8.30929 -0.0635344 8.65102 0.0322393 8.96734C0.128013 9.28366 0.419558 9.5 0.750058 9.5H3.56256V20H2.25006C1.83584 20 1.50006 20.3358 1.50006 20.75C1.50006 21.1642 1.83584 21.5 2.25006 21.5H21.7501C22.1643 21.5 22.5001 21.1642 22.5001 20.75C22.5001 20.3358 22.1643 20 21.7501 20H20.4376V9.5H23.2501C23.5806 9.5 23.8721 9.28366 23.9679 8.96734C24.0637 8.65102 23.9411 8.30929 23.6661 8.12596L12.4161 0.625962ZM18.5626 9.5V20H16.6876V9.5H18.5626ZM14.8126 9.5V20H12.9376V9.5H14.8126ZM11.0626 9.5V20H9.18756V9.5H11.0626ZM7.31256 9.5V20H5.43756V9.5H7.31256ZM12 6.5C11.1716 6.5 10.5 5.82843 10.5 5C10.5 4.17157 11.1716 3.5 12 3.5C12.8284 3.5 13.5 4.17157 13.5 5C13.5 5.82843 12.8284 6.5 12 6.5Z"
fill="gray"
/>
<path
d="M0.750058 23C0.335845 23 5.83126e-05 23.3358 5.83126e-05 23.75C5.83126e-05 24.1642 0.335845 24.5 0.750058 24.5H23.2501C23.6643 24.5 24.0001 24.1642 24.0001 23.75C24.0001 23.3358 23.6643 23 23.2501 23H0.750058Z"
fill="gray"
/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,4 +1,7 @@
import * as React from 'react';
import { Validator } from 'redux-form/lib/Field';
import { useEffect, useMemo } from 'react';
import { Field, InjectedFormProps, reduxForm, WrappedFieldProps } from 'redux-form';
import { Header } from '../header';
import { useAppDispatch, useAppSelector } from 'checkout/configure-store';
@ -11,33 +14,18 @@ import {
} from 'checkout/state';
import { FormGroup } from '../form-group';
import { Input } from 'checkout/components';
import styled from 'checkout/styled-components';
import { ReactSVG } from 'react-svg';
import { Amount } from '../common-fields';
import { toFieldsConfig } from '../fields-config';
import { Field, InjectedFormProps, reduxForm, WrappedFieldProps } from 'redux-form';
import { Locale } from 'checkout/locale';
import { Validator } from 'redux-form/lib/Field';
import { useEffect, useMemo } from 'react';
import { PayButton } from 'checkout/components/app/modal-container/modal/form-container/pay-button';
import { PayButton } from '../pay-button';
import { pay, setViewInfoError } from 'checkout/actions';
import { ServiceProviderMetadata, ServiceProviderMetadataField } from 'checkout/backend';
import { METADATA_NAMESPACE, ServiceProviderMetadataField } from 'checkout/backend';
import { isError } from 'checkout/utils';
import { LOGO_BY_SERVICE_PROVIDER_ID } from 'checkout/constants';
const BankLogoWrapper = styled.div`
margin: auto;
`;
const StyledLogo = styled(ReactSVG)`
svg {
width: auto;
height: 48px;
margin-bottom: 20px;
}
`;
import { ProviderLogo } from './provider-logo';
const createValidator = (field: ServiceProviderMetadataField): Validator => (value) =>
(!!field.required && !value) || (!!field.pattern && !new RegExp(field.pattern).test(value));
const WrappedInput: React.FC<WrappedFieldProps & { locale: Locale; field: ServiceProviderMetadataField }> = ({
locale,
field,
@ -56,6 +44,7 @@ const WrappedInput: React.FC<WrappedFieldProps & { locale: Locale; field: Servic
/>
);
};
const FormField: React.FC<{ field: ServiceProviderMetadataField }> = ({ field }) => {
const validate = useMemo(() => createValidator(field), [field]);
const locale = useAppSelector((s) => s.config.locale);
@ -69,13 +58,14 @@ const OnlineBankingAccountFormRef: React.FC<InjectedFormProps> = (props) => {
const formInfo = useAppSelector(getCurrentModalFormSelector) as OnlineBankingAccountFormInfo;
const { amount } = useAppSelector((s) => toFieldsConfig(s.config.initConfig, s.model.invoiceTemplate));
const { serviceProvider } = formInfo;
const metadata: ServiceProviderMetadata = serviceProvider?.metadata;
const metadata = serviceProvider?.metadata;
const formMetadata = metadata && metadata[METADATA_NAMESPACE]?.form;
const dispatch = useAppDispatch();
const logo = LOGO_BY_SERVICE_PROVIDER_ID[serviceProvider?.id];
useEffect(() => {
dispatch(setViewInfoError(false));
}, []);
useEffect(() => {
if (props.submitFailed) {
dispatch(setViewInfoError(true));
@ -93,32 +83,21 @@ const OnlineBankingAccountFormRef: React.FC<InjectedFormProps> = (props) => {
};
return (
!!metadata && (
<form onSubmit={props.handleSubmit(submit)}>
<Header title={serviceProvider.brandName} />
{!!logo && (
<BankLogoWrapper>
<StyledLogo src={logo.src} />
</BankLogoWrapper>
)}
{metadata.form?.map((field) => (
<FormGroup key={field.name}>
<FormField field={field} />
</FormGroup>
))}
{/*{email.visible && (*/}
{/* <FormGroup>*/}
{/* <Email />*/}
{/* </FormGroup>*/}
{/*)}*/}
{amount.visible && (
<FormGroup>
<Amount cost={amount.cost} />
</FormGroup>
)}
<PayButton />
</form>
)
<form onSubmit={props.handleSubmit(submit)}>
<Header title={serviceProvider?.brandName} />
<ProviderLogo />
{formMetadata?.map((field) => (
<FormGroup key={field.name}>
<FormField field={field} />
</FormGroup>
))}
{amount.visible && (
<FormGroup>
<Amount cost={amount.cost} />
</FormGroup>
)}
<PayButton />
</form>
);
};

View File

@ -0,0 +1,26 @@
import * as React from 'react';
import styled from 'checkout/styled-components';
import { Bank } from 'checkout/components';
const LogoContainer = styled.div`
height: 48px;
margin-bottom: 20px;
display: flex;
align-items: center;
justify-content: center;
`;
const BankLogo = styled(Bank)`
width: 48px;
height: 48px;
path {
fill: ${({ theme }) => theme.color.secondary[0.9]};
}
`;
export const ProviderLogo: React.FC = () => (
<LogoContainer>
<BankLogo />
</LogoContainer>
);

View File

@ -0,0 +1,28 @@
import * as React from 'react';
import { useContext } from 'react';
import { ThemeContext } from 'styled-components';
import { Bank, RoundIcon } from 'checkout/components';
import { Theme } from 'checkout/themes';
import styled from 'checkout/styled-components';
const DefaultLogo = styled(Bank)`
width: 24px;
height: 24px;
path {
fill: #fff;
}
`;
const getRoundIconColor = (theme: Theme, index?: number): string => {
const colors = theme.color.iconBackgrounds;
return index ? colors[index % colors.length] : colors[0];
};
export const BankLogo: React.FC<{ index?: number }> = ({ index }) => {
const theme = useContext<Theme>(ThemeContext);
return (
<RoundIcon color={getRoundIconColor(theme, index)}>
<DefaultLogo />
</RoundIcon>
);
};

View File

@ -1,37 +0,0 @@
import * as React from 'react';
import BankIcon from './bank.svg';
import { RoundIcon } from 'checkout/components';
import { useContext } from 'react';
import { ThemeContext } from 'styled-components';
import { Theme } from 'checkout/themes';
import { ReactSVG } from 'react-svg';
import styled from 'checkout/styled-components';
const StyledLogoWrapper = styled(RoundIcon)`
& > *,
svg {
width: 24px;
height: 24px;
}
`;
export const BankLogo: React.FC<{ index?: number; src?: string; color?: string }> = ({
children,
index,
src,
color
}) => {
const theme = useContext<Theme>(ThemeContext);
return (
<StyledLogoWrapper
color={
color ||
(index
? theme.color.iconBackgrounds[index % theme.color.iconBackgrounds.length]
: theme.color.iconBackgrounds[0])
}>
{src ? <ReactSVG src={src} /> : children || <BankIcon />}
</StyledLogoWrapper>
);
};

View File

@ -1,10 +0,0 @@
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12.4161 0.625962C12.1642 0.458013 11.836 0.458013 11.584 0.625962L0.334033 8.12596C0.0590402 8.30929 -0.0635344 8.65102 0.0322393 8.96734C0.128013 9.28366 0.419558 9.5 0.750058 9.5H3.56256V20H2.25006C1.83584 20 1.50006 20.3358 1.50006 20.75C1.50006 21.1642 1.83584 21.5 2.25006 21.5H21.7501C22.1643 21.5 22.5001 21.1642 22.5001 20.75C22.5001 20.3358 22.1643 20 21.7501 20H20.4376V9.5H23.2501C23.5806 9.5 23.8721 9.28366 23.9679 8.96734C24.0637 8.65102 23.9411 8.30929 23.6661 8.12596L12.4161 0.625962ZM18.5626 9.5V20H16.6876V9.5H18.5626ZM14.8126 9.5V20H12.9376V9.5H14.8126ZM11.0626 9.5V20H9.18756V9.5H11.0626ZM7.31256 9.5V20H5.43756V9.5H7.31256ZM12 6.5C11.1716 6.5 10.5 5.82843 10.5 5C10.5 4.17157 11.1716 3.5 12 3.5C12.8284 3.5 13.5 4.17157 13.5 5C13.5 5.82843 12.8284 6.5 12 6.5Z"
fill="white"
/>
<path
d="M0.750058 23C0.335845 23 5.83126e-05 23.3358 5.83126e-05 23.75C5.83126e-05 24.1642 0.335845 24.5 0.750058 24.5H23.2501C23.6643 24.5 24.0001 24.1642 24.0001 23.75C24.0001 23.3358 23.6643 23 23.2501 23H0.750058Z"
fill="white"
/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -3,9 +3,8 @@ import { setViewInfoHeight } from 'checkout/actions';
import { FilteredList } from 'checkout/components';
import styled from 'checkout/styled-components';
import { useAppDispatch, useAppSelector } from 'checkout/configure-store';
import { BankLogo } from '../bank-logo';
import { BankLogo } from './bank-logo';
import { ServiceProvider } from 'checkout/backend';
import { LOGO_BY_SERVICE_PROVIDER_ID } from 'checkout/constants';
const StyledFilteredList: typeof FilteredList = styled(FilteredList)`
max-height: 615px;
@ -14,19 +13,15 @@ const StyledFilteredList: typeof FilteredList = styled(FilteredList)`
export const BanksList: React.FC<{ items: ServiceProvider[]; select?: (item: ServiceProvider) => void }> = (props) => {
const locale = useAppSelector((s) => s.config.locale);
const dispatch = useAppDispatch();
return (
<StyledFilteredList
placeholder={locale['form.onlineBanking.search']}
values={props.items}
filter={(search, { brandName }) => brandName.toLocaleLowerCase().includes(search.toLowerCase().trim())}
item={({ id, brandName }, { idx }) => {
const logo = LOGO_BY_SERVICE_PROVIDER_ID[id];
return {
title: brandName,
icon: <BankLogo index={idx} src={logo?.src} color={logo?.backgroundColor} />
};
}}
item={({ brandName }, { idx }) => ({
title: brandName,
icon: <BankLogo index={idx} />
})}
search={() => dispatch(setViewInfoHeight(null))}
select={(item) => props.select(item)}
/>

View File

@ -0,0 +1,8 @@
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M20.6935 0.209937C20.2736 -0.069979 19.7266 -0.069979 19.3067 0.209937L0.556722 12.7099C0.0984004 13.0155 -0.105891 13.585 0.0537321 14.1122C0.213355 14.6394 0.699263 15 1.2501 15H5.9376V32.5H3.7501C3.05974 32.5 2.5001 33.0596 2.5001 33.75C2.5001 34.4404 3.05974 35 3.7501 35H36.2501C36.9405 35 37.5001 34.4404 37.5001 33.75C37.5001 33.0596 36.9405 32.5 36.2501 32.5H34.0626V15H38.7501C39.3009 15 39.7868 14.6394 39.9465 14.1122C40.1061 13.585 39.9018 13.0155 39.4435 12.7099L20.6935 0.209937ZM30.9376 15V32.5H27.8126V15H30.9376ZM24.6876 15V32.5H21.5626V15H24.6876ZM18.4376 15V32.5H15.3126V15H18.4376ZM12.1876 15V32.5H9.0626V15H12.1876ZM20 10C18.6193 10 17.5 8.88071 17.5 7.5C17.5 6.11929 18.6193 5 20 5C21.3807 5 22.5 6.11929 22.5 7.5C22.5 8.88071 21.3807 10 20 10Z"
/>
<path
d="M1.2501 37.5C0.559741 37.5 9.71877e-05 38.0596 9.71877e-05 38.75C9.71877e-05 39.4404 0.559741 40 1.2501 40H38.7501C39.4405 40 40.0001 39.4404 40.0001 38.75C40.0001 38.0596 39.4405 37.5 38.7501 37.5H1.2501Z"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -10,4 +10,5 @@ export { default as Letter } from './letter.svg';
export { default as Lock } from './lock.svg';
export { default as Phone } from './phone.svg';
export { default as User } from './user.svg';
export { default as Bank } from './bank.svg';
export * from './card';

View File

@ -1 +0,0 @@
export * from './logo-by-service-provider-id';

View File

@ -1,6 +0,0 @@
export interface Logo {
src: string;
backgroundColor?: string;
}
export const LOGO_BY_SERVICE_PROVIDER_ID: { [name in string]: Logo } = {};

View File

@ -1,10 +1,11 @@
import { ServiceProviderMetadata } from 'checkout/backend';
import { METADATA_NAMESPACE, ServiceProviderMetadata } from 'checkout/backend';
import { isNil } from 'lodash-es';
import { assertServiceFormField } from './assert-service-form-field';
import { logPrefix } from 'checkout/log-messages';
export function assertMetadata(name: string, metadata: ServiceProviderMetadata) {
if (!isNil(metadata?.form)) {
export function assertMetadata(name: string, serviceProviderMetadata: ServiceProviderMetadata) {
const metadata = serviceProviderMetadata && serviceProviderMetadata[METADATA_NAMESPACE];
if (!isNil(metadata) && !isNil(metadata?.form)) {
const errors = metadata.form.map((field) => assertServiceFormField(field)).filter((err) => !!err);
if (errors.length) {
console.error(`${logPrefix} ${name} form has errors: ${errors.join('; ')}`);

View File

@ -2,6 +2,5 @@
<path
d="M28.216 11.8L17.308 37H11.548L0.676 11.8H6.976L14.644 29.8L22.42 11.8H28.216ZM36.2946 17.344C39.2946 17.344 41.5986 18.064 43.2066 19.504C44.8146 20.92 45.6186 23.068 45.6186 25.948V37H40.3626V34.588C39.3066 36.388 37.3386 37.288 34.4586 37.288C32.9706 37.288 31.6746 37.036 30.5706 36.532C29.4906 36.028 28.6626 35.332 28.0866 34.444C27.5106 33.556 27.2226 32.548 27.2226 31.42C27.2226 29.62 27.8946 28.204 29.2386 27.172C30.6066 26.14 32.7066 25.624 35.5386 25.624H40.0026C40.0026 24.4 39.6306 23.464 38.8866 22.816C38.1426 22.144 37.0266 21.808 35.5386 21.808C34.5066 21.808 33.4866 21.976 32.4786 22.312C31.4946 22.624 30.6546 23.056 29.9586 23.608L27.9426 19.684C28.9986 18.94 30.2586 18.364 31.7226 17.956C33.2106 17.548 34.7346 17.344 36.2946 17.344ZM35.8626 33.508C36.8226 33.508 37.6746 33.292 38.4186 32.86C39.1626 32.404 39.6906 31.744 40.0026 30.88V28.9H36.1506C33.8466 28.9 32.6946 29.656 32.6946 31.168C32.6946 31.888 32.9706 32.464 33.5226 32.896C34.0986 33.304 34.8786 33.508 35.8626 33.508ZM50.7014 10.288H56.3174V37H50.7014V10.288ZM61.5295 17.632H67.1455V37H61.5295V17.632ZM64.3375 14.932C63.3055 14.932 62.4655 14.632 61.8175 14.032C61.1695 13.432 60.8455 12.688 60.8455 11.8C60.8455 10.912 61.1695 10.168 61.8175 9.568C62.4655 8.968 63.3055 8.668 64.3375 8.668C65.3695 8.668 66.2095 8.956 66.8575 9.532C67.5055 10.108 67.8295 10.828 67.8295 11.692C67.8295 12.628 67.5055 13.408 66.8575 14.032C66.2095 14.632 65.3695 14.932 64.3375 14.932ZM84.8496 36.064C84.2976 36.472 83.6136 36.784 82.7976 37C82.0056 37.192 81.1656 37.288 80.2776 37.288C77.9736 37.288 76.1856 36.7 74.9136 35.524C73.6656 34.348 73.0416 32.62 73.0416 30.34V22.384H70.0536V18.064H73.0416V13.348H78.6576V18.064H83.4816V22.384H78.6576V30.268C78.6576 31.084 78.8616 31.72 79.2696 32.176C79.7016 32.608 80.3016 32.824 81.0696 32.824C81.9576 32.824 82.7136 32.584 83.3376 32.104L84.8496 36.064ZM106.875 17.632L98.1266 38.188C97.2386 40.42 96.1346 41.992 94.8146 42.904C93.5186 43.816 91.9466 44.272 90.0986 44.272C89.0906 44.272 88.0946 44.116 87.1106 43.804C86.1266 43.492 85.3226 43.06 84.6986 42.508L86.7506 38.512C87.1826 38.896 87.6746 39.196 88.2266 39.412C88.8026 39.628 89.3666 39.736 89.9186 39.736C90.6866 39.736 91.3106 39.544 91.7906 39.16C92.2706 38.8 92.7026 38.188 93.0866 37.324L93.1586 37.144L84.7706 17.632H90.5666L96.0026 30.772L101.475 17.632H106.875Z"
fill="white"
fill-opacity="0.84"
/>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB