mirror of
https://github.com/valitydev/checkout.git
synced 2024-11-06 02:25:18 +00:00
TD-378: Add payment terminal selector form (#135)
This commit is contained in:
parent
b64e662f71
commit
016a897ede
@ -27,7 +27,7 @@ export * from './payment-error';
|
||||
export * from './invoice-status';
|
||||
export * from './samsungpay';
|
||||
export * from './mobile-commerce';
|
||||
export * from './online-banking';
|
||||
export * from './service-provider';
|
||||
export * from './shortened-url-params';
|
||||
export * from './shortened-url';
|
||||
export * from './service-provider-metadata';
|
||||
|
@ -8,8 +8,8 @@ export interface MetadataTextLocalization {
|
||||
}
|
||||
|
||||
export interface ServiceProviderMetadataField {
|
||||
name: string;
|
||||
type: JSX.IntrinsicElements['input']['type'];
|
||||
name: string;
|
||||
required: boolean;
|
||||
pattern?: string;
|
||||
localization?: MetadataTextLocalization;
|
@ -25,6 +25,7 @@ import { RedirectForm } from './redirect-form';
|
||||
import { PaymentTerminalBankCardForm } from './payment-terminal-bank-card-form';
|
||||
import { PaymentTerminalForm } from './payment-terminal-form';
|
||||
import { QrCodeInteractionForm } from './qr-code-interaction-form';
|
||||
import { PaymentTerminalSelectorForm } from './payment-terminal-selector-form';
|
||||
|
||||
const Container = styled.div`
|
||||
padding: 0 5px;
|
||||
@ -215,6 +216,8 @@ class FormContainerDef extends React.Component<FormContainerProps, { height: num
|
||||
return <PaymentTerminalBankCardForm key={name} />;
|
||||
case FormName.paymentTerminalForm:
|
||||
return <PaymentTerminalForm key={name} />;
|
||||
case FormName.paymentTerminalSelector:
|
||||
return <PaymentTerminalSelectorForm key={name} />;
|
||||
case FormName.qrCodeInteractionForm:
|
||||
return <QrCodeInteractionForm key={name} />;
|
||||
default:
|
||||
|
@ -0,0 +1,11 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { KnownProviderCategories } from 'checkout/state';
|
||||
|
||||
export const CategoryContent: React.FC<{ category: KnownProviderCategories }> = ({ category }) => {
|
||||
switch (category) {
|
||||
case 'netbanking':
|
||||
return <img src="/assets/inb-logo.jpg" height="68px" width="106px"></img>;
|
||||
}
|
||||
return <div>{category}</div>;
|
||||
};
|
@ -0,0 +1,17 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { PaymentTerminalPaymentMethod } from 'checkout/state';
|
||||
import { MetadataContent } from './metadata-content';
|
||||
import { CategoryContent } from './category-content';
|
||||
|
||||
export const Content: React.FC<{ method: PaymentTerminalPaymentMethod; localeCode: string }> = ({
|
||||
method,
|
||||
localeCode
|
||||
}) => {
|
||||
if (method.serviceProviders.length === 1) {
|
||||
return <MetadataContent serviceProvider={method.serviceProviders[0]} localeCode={localeCode} />;
|
||||
}
|
||||
if (method.serviceProviders.length > 1) {
|
||||
return <CategoryContent category={method.category} />;
|
||||
}
|
||||
};
|
@ -0,0 +1 @@
|
||||
export * from './content';
|
@ -0,0 +1,17 @@
|
||||
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 metadata={title} localeCode={localeCode} />}
|
||||
{logo && <MetadataLogo metadata={logo} />}
|
||||
</>
|
||||
);
|
||||
};
|
@ -5,12 +5,14 @@ import {
|
||||
FormName,
|
||||
KnownProviderCategories,
|
||||
PaymentTerminalFormInfo,
|
||||
PaymentTerminalPaymentMethod
|
||||
PaymentTerminalPaymentMethod,
|
||||
PaymentTerminalSelectorFormInfo
|
||||
} from 'checkout/state';
|
||||
import { getMetadata, MetadataLogo, MetadataTitle, PaymentMethodItemContainer } from 'checkout/components/ui';
|
||||
import { getMetadata, PaymentMethodItemContainer } from 'checkout/components/ui';
|
||||
import { PayAction, SetFormInfoAction } from './types';
|
||||
import { payWithPaymentTerminal } from './pay-with-payment-terminal';
|
||||
import { ServiceProvider, ServiceProviderContactInfo } from 'checkout/backend';
|
||||
import { Content } from './content';
|
||||
|
||||
export interface PaymentTerminalMethodItemProps {
|
||||
method: PaymentTerminalPaymentMethod;
|
||||
@ -21,8 +23,11 @@ export interface PaymentTerminalMethodItemProps {
|
||||
phoneNumberPrefilled: boolean;
|
||||
}
|
||||
|
||||
const toPaymentTerminal = (category: KnownProviderCategories, setFormInfo: SetFormInfoAction) =>
|
||||
setFormInfo(new PaymentTerminalFormInfo(category, FormName.paymentMethods));
|
||||
const toPaymentTerminalSelector = (category: KnownProviderCategories, setFormInfo: SetFormInfoAction) =>
|
||||
setFormInfo(new PaymentTerminalSelectorFormInfo(category, FormName.paymentMethods));
|
||||
|
||||
const toPaymentTerminal = (serviceProviderID: string, setFormInfo: SetFormInfoAction) =>
|
||||
setFormInfo(new PaymentTerminalFormInfo(serviceProviderID, FormName.paymentMethods));
|
||||
|
||||
const isRequiredEmail = (contactInfo: ServiceProviderContactInfo, emailPrefilled: boolean): boolean =>
|
||||
!isNil(contactInfo) && contactInfo.email === true && !emailPrefilled;
|
||||
@ -44,15 +49,21 @@ const isRequiredPaymentTerminalForm = (
|
||||
};
|
||||
|
||||
const provideMethod = (
|
||||
serviceProvider: ServiceProvider,
|
||||
method: PaymentTerminalPaymentMethod,
|
||||
pay: PayAction,
|
||||
setFormInfo: SetFormInfoAction,
|
||||
emailPrefilled: boolean,
|
||||
phoneNumberPrefilled: boolean
|
||||
) => {
|
||||
return isRequiredPaymentTerminalForm(serviceProvider, emailPrefilled, phoneNumberPrefilled)
|
||||
? toPaymentTerminal(serviceProvider.category as KnownProviderCategories, setFormInfo)
|
||||
: payWithPaymentTerminal(serviceProvider.id, pay);
|
||||
if (method.serviceProviders.length === 1) {
|
||||
const serviceProvider = method.serviceProviders[0];
|
||||
return isRequiredPaymentTerminalForm(serviceProvider, emailPrefilled, phoneNumberPrefilled)
|
||||
? toPaymentTerminal(serviceProvider.id, setFormInfo)
|
||||
: payWithPaymentTerminal(serviceProvider.id, pay);
|
||||
}
|
||||
if (method.serviceProviders.length > 1) {
|
||||
return toPaymentTerminalSelector(method.category, setFormInfo);
|
||||
}
|
||||
};
|
||||
|
||||
export const PaymentTerminalMethodItem: React.FC<PaymentTerminalMethodItemProps> = ({
|
||||
@ -62,15 +73,10 @@ export const PaymentTerminalMethodItem: React.FC<PaymentTerminalMethodItemProps>
|
||||
localeCode,
|
||||
emailPrefilled,
|
||||
phoneNumberPrefilled
|
||||
}) => {
|
||||
const serviceProvider = method.serviceProviders[0];
|
||||
const { logo, title } = getMetadata(serviceProvider);
|
||||
return (
|
||||
<PaymentMethodItemContainer
|
||||
id={`${serviceProvider.id}-payment-method-item`}
|
||||
onClick={() => provideMethod(serviceProvider, pay, setFormInfo, emailPrefilled, phoneNumberPrefilled)}>
|
||||
{title && <MetadataTitle metadata={title} localeCode={localeCode} />}
|
||||
{logo && <MetadataLogo metadata={logo} />}
|
||||
</PaymentMethodItemContainer>
|
||||
);
|
||||
};
|
||||
}) => (
|
||||
<PaymentMethodItemContainer
|
||||
id={`${Math.floor(Math.random() * 100)}-payment-method-item`}
|
||||
onClick={() => provideMethod(method, pay, setFormInfo, emailPrefilled, phoneNumberPrefilled)}>
|
||||
<Content method={method} localeCode={localeCode} />
|
||||
</PaymentMethodItemContainer>
|
||||
);
|
||||
|
@ -31,8 +31,8 @@ export const PaymentTerminalMethodItems: React.FC<PaymentTerminalMethodItemsProp
|
||||
case KnownProviderCategories.PIX:
|
||||
case KnownProviderCategories.PaymentTerminal:
|
||||
case KnownProviderCategories.DigitalWallet:
|
||||
case KnownProviderCategories.OnlineBanking:
|
||||
case KnownProviderCategories.NetBanking:
|
||||
case KnownProviderCategories.OnlineBanking:
|
||||
return (
|
||||
<PaymentTerminalMethodItem
|
||||
method={method}
|
||||
|
@ -18,9 +18,9 @@ import { PayButton } from '../pay-button';
|
||||
import { FormGroup } from '../form-group';
|
||||
import {
|
||||
getActiveModalFormSelector,
|
||||
getAvailableTerminalPaymentMethodSelector,
|
||||
getInitConfigSelector,
|
||||
getModelSelector
|
||||
getModelSelector,
|
||||
getServiceProviderSelector
|
||||
} from 'checkout/selectors';
|
||||
import { getMetadata, MetadataField, MetadataLogo } from 'checkout/components/ui';
|
||||
import { toAmountConfig, toEmailConfig, toPhoneNumberConfig } from '../fields-config';
|
||||
@ -38,17 +38,15 @@ const Container = styled.div`
|
||||
`;
|
||||
|
||||
const PaymentTerminalFormRef: React.FC<InjectedFormProps> = ({ submitFailed, initialize, handleSubmit }) => {
|
||||
const { providerID, paymentStatus } = useAppSelector<PaymentTerminalFormInfo>(getActiveModalFormSelector);
|
||||
const serviceProvider = useAppSelector(getServiceProviderSelector(providerID));
|
||||
const { form, contactInfo, logo } = getMetadata(serviceProvider);
|
||||
const initConfig = useAppSelector(getInitConfigSelector);
|
||||
const model = useAppSelector(getModelSelector);
|
||||
const { category } = useAppSelector<PaymentTerminalFormInfo>(getActiveModalFormSelector);
|
||||
const paymentMethod = useAppSelector(getAvailableTerminalPaymentMethodSelector(category));
|
||||
const serviceProvider = paymentMethod?.serviceProviders[0];
|
||||
const formValues = useAppSelector((s) => get(s.form, 'paymentTerminalForm.values'));
|
||||
const { form, contactInfo, logo } = getMetadata(serviceProvider);
|
||||
const { paymentStatus } = useAppSelector<PaymentTerminalFormInfo>(getActiveModalFormSelector);
|
||||
const amount = toAmountConfig(initConfig, model.invoiceTemplate);
|
||||
const email = toEmailConfig(initConfig.email);
|
||||
const phoneNumber = toPhoneNumberConfig(initConfig.phoneNumber);
|
||||
const formValues = useAppSelector((s) => get(s.form, 'paymentTerminalForm.values'));
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -0,0 +1 @@
|
||||
export * from './payment-terminal-selector-form';
|
@ -0,0 +1,53 @@
|
||||
import * as React from 'react';
|
||||
import styled from 'checkout/styled-components';
|
||||
import { useAppDispatch, useAppSelector } from 'checkout/configure-store';
|
||||
|
||||
import { Header } from '../header';
|
||||
import {
|
||||
FormName,
|
||||
KnownProviderCategories,
|
||||
PaymentTerminalFormInfo,
|
||||
PaymentTerminalSelectorFormInfo
|
||||
} from 'checkout/state';
|
||||
import {
|
||||
getActiveModalFormSelector,
|
||||
getAvailableTerminalPaymentMethodSelector,
|
||||
getLocaleSelector
|
||||
} from 'checkout/selectors';
|
||||
import { ServiceProviderPane } from './service-provider-pane';
|
||||
import { goToFormInfo } from 'checkout/actions';
|
||||
import { Locale } from 'checkout/locale';
|
||||
|
||||
const Container = styled.div`
|
||||
min-height: 300px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const Grid = styled.div`
|
||||
display: grid;
|
||||
grid-gap: 16px;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
`;
|
||||
|
||||
const navigate = (providerID: string) =>
|
||||
goToFormInfo(new PaymentTerminalFormInfo(providerID, FormName.paymentTerminalSelector));
|
||||
|
||||
const toHeader = (locale: Locale, category: KnownProviderCategories) => locale[`form.header.${category}.label`];
|
||||
|
||||
export const PaymentTerminalSelectorForm: React.FC = () => {
|
||||
const locale = useAppSelector(getLocaleSelector);
|
||||
const { category } = useAppSelector<PaymentTerminalSelectorFormInfo>(getActiveModalFormSelector);
|
||||
const paymentMethod = useAppSelector(getAvailableTerminalPaymentMethodSelector(category));
|
||||
const dispatch = useAppDispatch();
|
||||
return (
|
||||
<Container>
|
||||
<Header title={toHeader(locale, category)} />
|
||||
<Grid>
|
||||
{paymentMethod?.serviceProviders.map((p, i) => (
|
||||
<ServiceProviderPane key={i} serviceProvider={p} onClick={(id) => dispatch(navigate(id))} />
|
||||
))}
|
||||
</Grid>
|
||||
</Container>
|
||||
);
|
||||
};
|
@ -0,0 +1,55 @@
|
||||
import * as React from 'react';
|
||||
import styled from 'checkout/styled-components';
|
||||
|
||||
import { ServiceProvider, ServiceProviderIconMetadata } from 'checkout/backend';
|
||||
import { getMetadata, MetadataLogo } from 'checkout/components';
|
||||
|
||||
const PaneContainer = styled.div`
|
||||
cursor: pointer;
|
||||
|
||||
height: 64px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid ${({ theme }) => theme.color.neutral[0.2]};
|
||||
|
||||
:hover {
|
||||
border-color: ${({ theme }) => theme.color.primary[1]};
|
||||
}
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const PaneLabel = styled.p`
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
margin: 0;
|
||||
`;
|
||||
|
||||
const PaneLogoContainer = styled.div`
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const PaneLogo: React.FC<{ logo: ServiceProviderIconMetadata }> = ({ logo }) => (
|
||||
<PaneLogoContainer>
|
||||
<MetadataLogo metadata={logo} />
|
||||
</PaneLogoContainer>
|
||||
);
|
||||
|
||||
export const ServiceProviderPane: React.FC<{ serviceProvider: ServiceProvider; onClick: (id: string) => void }> = ({
|
||||
serviceProvider,
|
||||
onClick
|
||||
}) => {
|
||||
const { logo } = getMetadata(serviceProvider);
|
||||
return (
|
||||
<PaneContainer onClick={() => onClick(serviceProvider.id)}>
|
||||
{logo && <PaneLogo logo={logo} />}
|
||||
<PaneLabel>{serviceProvider.brandName}</PaneLabel>
|
||||
</PaneContainer>
|
||||
);
|
||||
};
|
@ -12,12 +12,13 @@ import {
|
||||
PaymentTerminalPaymentMethod,
|
||||
KnownProviderCategories,
|
||||
PaymentTerminalBankCardFormInfo,
|
||||
PaymentTerminalFormInfo
|
||||
PaymentTerminalFormInfo,
|
||||
PaymentTerminalSelectorFormInfo
|
||||
} from 'checkout/state';
|
||||
import { BankCardTokenProvider } from 'checkout/backend/model';
|
||||
import { assertUnreachable } from 'checkout/utils';
|
||||
|
||||
const toPaymentTerminalForms = ({ category }: PaymentTerminalPaymentMethod) => {
|
||||
const toPaymentTerminalForms = ({ category, serviceProviders }: PaymentTerminalPaymentMethod) => {
|
||||
switch (category) {
|
||||
case KnownProviderCategories.BankCard:
|
||||
return new PaymentTerminalBankCardFormInfo();
|
||||
@ -27,7 +28,10 @@ const toPaymentTerminalForms = ({ category }: PaymentTerminalPaymentMethod) => {
|
||||
case KnownProviderCategories.PIX:
|
||||
case KnownProviderCategories.PaymentTerminal:
|
||||
case KnownProviderCategories.OnlineBanking:
|
||||
return new PaymentTerminalFormInfo(category);
|
||||
if (serviceProviders.length === 1) {
|
||||
return new PaymentTerminalFormInfo(serviceProviders[0].id);
|
||||
}
|
||||
return new PaymentTerminalSelectorFormInfo(category);
|
||||
default:
|
||||
assertUnreachable(category);
|
||||
return null;
|
||||
|
@ -1,3 +1,5 @@
|
||||
import isNil from 'lodash-es/isNil';
|
||||
|
||||
import {
|
||||
PaymentMethodName,
|
||||
State,
|
||||
@ -9,6 +11,9 @@ import {
|
||||
export const getAvailableTerminalPaymentMethodSelector = (category: KnownProviderCategories) => (
|
||||
state: State
|
||||
): PaymentTerminalPaymentMethod | null => {
|
||||
if (isNil(category)) {
|
||||
return null;
|
||||
}
|
||||
const found = state.availablePaymentMethods.find((m) => {
|
||||
if (m.name !== PaymentMethodName.PaymentTerminal) {
|
||||
return false;
|
||||
|
@ -15,6 +15,7 @@ export enum FormName {
|
||||
redirectForm = 'redirectForm',
|
||||
paymentTerminalBankCard = 'paymentTerminalBankCard',
|
||||
paymentTerminalForm = 'paymentTerminalForm',
|
||||
paymentTerminalSelector = 'paymentTerminalSelector',
|
||||
qrCodeInteractionForm = 'qrCodeInteractionForm'
|
||||
}
|
||||
|
||||
|
@ -15,3 +15,4 @@ export * from './redirect-form-info';
|
||||
export * from './payment-terminal-bank-card-form-info';
|
||||
export * from './payment-terminal-form-info';
|
||||
export * from './qr-code-interaction-form-info';
|
||||
export * from './payment-terminal-selector-form-info';
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { KnownProviderCategories } from 'checkout/state/payment-method';
|
||||
import { FormInfo, FormName } from './form-info';
|
||||
import { PaymentStatus } from './payment-status';
|
||||
|
||||
@ -7,7 +6,7 @@ export class PaymentTerminalFormInfo extends FormInfo {
|
||||
active = true;
|
||||
paymentStatus = PaymentStatus.pristine;
|
||||
|
||||
constructor(public category: KnownProviderCategories, previous?: FormName) {
|
||||
constructor(public providerID: string, previous?: FormName) {
|
||||
super(previous);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
import { KnownProviderCategories } from 'checkout/state';
|
||||
import { FormInfo, FormName } from './form-info';
|
||||
|
||||
export class PaymentTerminalSelectorFormInfo extends FormInfo {
|
||||
name = FormName.paymentTerminalSelector;
|
||||
active = true;
|
||||
|
||||
constructor(public category: KnownProviderCategories, previous?: FormName) {
|
||||
super(previous);
|
||||
}
|
||||
}
|
BIN
src/assets/inb-logo.jpg
Normal file
BIN
src/assets/inb-logo.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
@ -7,7 +7,7 @@
|
||||
"info.modal.no.available.payment.method": "No available payment methods",
|
||||
"footer.pay.label": "Secure payment with",
|
||||
"form.header.payment.methods.label": "Please choose a payment method",
|
||||
"form.header.payment.method.groups.label": "Please choose a payment method",
|
||||
"form.header.netbanking.label": "Please choose a bank",
|
||||
"form.header.pay.card.label": "Pay with a card",
|
||||
"form.header.pay.mobile.commerce.label": "Pay with mobile phone account",
|
||||
"form.header.pay.upi.label": "Unified payment interface",
|
||||
|
@ -7,7 +7,7 @@
|
||||
"info.modal.no.available.payment.method": "利用できる支払方法はありません",
|
||||
"footer.pay.label": "Secure payment with",
|
||||
"form.header.payment.methods.label": "お支払い方法を選択してください",
|
||||
"form.header.payment.method.groups.label": "お支払い方法を選択してください",
|
||||
"form.header.netbanking.label": "Please choose a bank",
|
||||
"form.header.pay.card.label": "カードで支払う",
|
||||
"form.header.pay.mobile.commerce.label": "携帯電話アカウントで支払います。",
|
||||
"form.header.pay.upi.label": "Unified payment interface",
|
||||
|
@ -7,7 +7,7 @@
|
||||
"info.modal.no.available.payment.method": "Sem opções de pagamento disponível",
|
||||
"footer.pay.label": "Secure payment with",
|
||||
"form.header.payment.methods.label": "Por favor escolha seu método de pagamento",
|
||||
"form.header.payment.method.groups.label": "Por favor escolha seu método de pagamento",
|
||||
"form.header.netbanking.label": "Please choose a bank",
|
||||
"form.header.pay.card.label": "Pay with a card",
|
||||
"form.header.pay.mobile.commerce.label": "Pay with mobile phone account",
|
||||
"form.header.pay.upi.label": "Unified payment interface",
|
||||
|
@ -7,7 +7,7 @@
|
||||
"info.modal.no.available.payment.method": "Отсутствуют доступные методы оплаты",
|
||||
"footer.pay.label": "Безопасная оплата с",
|
||||
"form.header.payment.methods.label": "Выберите способ оплаты",
|
||||
"form.header.payment.method.groups.label": "Выберите способ оплаты",
|
||||
"form.header.netbanking.label": "Выберите банк",
|
||||
"form.header.pay.card.label": "Оплата банковской картой",
|
||||
"form.header.pay.mobile.commerce.label": "Оплата со счета телефона",
|
||||
"form.header.pay.upi.label": "Unified payment interface",
|
||||
|
Loading…
Reference in New Issue
Block a user