mirror of
https://github.com/valitydev/checkout.git
synced 2024-11-06 02:25:18 +00:00
Add failure state to create payment hook (#191)
This commit is contained in:
parent
98404b1f15
commit
50ad71282d
@ -9,12 +9,9 @@ function getFingerprintFromComponents(components: Fingerprint2.Component[]) {
|
||||
}
|
||||
|
||||
const getClientInfoUrl = (): { url: string } | undefined => {
|
||||
const url = (document.referrer || '').slice(
|
||||
0,
|
||||
// URL max length (API constraint)
|
||||
599
|
||||
);
|
||||
return url ? { url } : undefined;
|
||||
if (document.referrer === '') return;
|
||||
const url = new URL(document.referrer);
|
||||
return { url: url.origin };
|
||||
};
|
||||
|
||||
export const createPaymentResource = (
|
||||
|
@ -4,8 +4,8 @@ import { InjectedFormProps, reduxForm } from 'redux-form';
|
||||
|
||||
import { FormGroup } from '../form-group';
|
||||
import { CardHolder, CardNumber, ExpireDate, SecureCode } from './fields';
|
||||
import { CardFormInfo, CardFormValues, FormName, PaymentStatus } from 'checkout/state';
|
||||
import { pay, prepareToPay, setViewInfoError } from 'checkout/actions';
|
||||
import { CardFormInfo, CardFormValues, FormName, PaymentStatus, ResultFormInfo, ResultType } from 'checkout/state';
|
||||
import { goToFormInfo, pay, prepareToPay, setViewInfoError } from 'checkout/actions';
|
||||
import { PayButton } from '../pay-button';
|
||||
import { Header } from '../header/header';
|
||||
import { toAmountConfig, toCardHolderConfig } from '../fields-config';
|
||||
@ -14,7 +14,6 @@ import { useAppDispatch, useAppSelector } from 'checkout/configure-store';
|
||||
import { getActiveModalFormSelector } from 'checkout/selectors';
|
||||
import { InitialContext } from '../../../../initial-context';
|
||||
import { PaymentMethodName, useCreatePayment } from 'checkout/hooks';
|
||||
import isNil from 'checkout/utils/is-nil';
|
||||
|
||||
const CardFormDef = ({ submitFailed, initialize, handleSubmit }: InjectedFormProps) => {
|
||||
const {
|
||||
@ -22,7 +21,7 @@ const CardFormDef = ({ submitFailed, initialize, handleSubmit }: InjectedFormPro
|
||||
initConfig,
|
||||
model: { invoiceTemplate }
|
||||
} = useContext(InitialContext);
|
||||
const { paymentPayload, setFormData } = useCreatePayment();
|
||||
const { createPaymentState, setFormData } = useCreatePayment();
|
||||
const { paymentStatus } = useAppSelector<CardFormInfo>(getActiveModalFormSelector);
|
||||
const cardHolder = toCardHolderConfig(initConfig.requireCardHolder);
|
||||
const amount = toAmountConfig(initConfig, invoiceTemplate);
|
||||
@ -47,10 +46,13 @@ const CardFormDef = ({ submitFailed, initialize, handleSubmit }: InjectedFormPro
|
||||
if (submitFailed) {
|
||||
dispatch(setViewInfoError(true));
|
||||
}
|
||||
if (!isNil(paymentPayload)) {
|
||||
dispatch(pay(paymentPayload));
|
||||
if (createPaymentState.status === 'SUCCESS') {
|
||||
dispatch(pay(createPaymentState.data));
|
||||
}
|
||||
}, [submitFailed, paymentPayload]);
|
||||
if (createPaymentState.status === 'FAILURE') {
|
||||
dispatch(goToFormInfo(new ResultFormInfo(ResultType.hookError, createPaymentState.error)));
|
||||
}
|
||||
}, [submitFailed, createPaymentState]);
|
||||
|
||||
const submit = (values: CardFormValues) => {
|
||||
dispatch(prepareToPay());
|
||||
|
@ -6,7 +6,9 @@ import {
|
||||
FormName,
|
||||
PaymentTerminalFormInfo,
|
||||
PaymentTerminalFormValues,
|
||||
PaymentTerminalSelectorFormInfo
|
||||
PaymentTerminalSelectorFormInfo,
|
||||
ResultFormInfo,
|
||||
ResultType
|
||||
} from 'checkout/state';
|
||||
import { getMetadata, PaymentMethodItemContainer } from 'checkout/components/ui';
|
||||
import { PaymentMethodName, ServiceProvider, ServiceProviderContactInfo } from 'checkout/backend';
|
||||
@ -45,7 +47,7 @@ export const PaymentTerminalMethodItem = ({ method }: PaymentTerminalMethodItemP
|
||||
const emailPrefilled = !!initConfig.email;
|
||||
const phoneNumberPrefilled = !!initConfig.phoneNumber;
|
||||
|
||||
const { paymentPayload, setFormData } = useCreatePayment();
|
||||
const { createPaymentState, setFormData } = useCreatePayment();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const onClick = () => {
|
||||
@ -69,10 +71,13 @@ export const PaymentTerminalMethodItem = ({ method }: PaymentTerminalMethodItemP
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isNil(paymentPayload)) {
|
||||
dispatch(pay(paymentPayload));
|
||||
if (createPaymentState.status === 'SUCCESS') {
|
||||
dispatch(pay(createPaymentState.data));
|
||||
}
|
||||
}, [paymentPayload]);
|
||||
if (createPaymentState.status === 'FAILURE') {
|
||||
dispatch(goToFormInfo(new ResultFormInfo(ResultType.hookError, createPaymentState.error)));
|
||||
}
|
||||
}, [createPaymentState]);
|
||||
|
||||
return (
|
||||
<PaymentMethodItemContainer id={`${Math.floor(Math.random() * 100)}-payment-method-item`} onClick={onClick}>
|
||||
|
@ -2,11 +2,11 @@ import * as React from 'react';
|
||||
import { useContext, useEffect } from 'react';
|
||||
import { InjectedFormProps, reduxForm } from 'redux-form';
|
||||
|
||||
import { FormName, PaymentTerminalFormValues } from 'checkout/state';
|
||||
import { FormName, PaymentTerminalFormValues, ResultFormInfo, ResultType } from 'checkout/state';
|
||||
import { Header } from '../header';
|
||||
import { useAppDispatch } from 'checkout/configure-store';
|
||||
import { ProviderSelectorField } from './provider-selector';
|
||||
import { pay, prepareToPay, setViewInfoError } from 'checkout/actions';
|
||||
import { goToFormInfo, pay, prepareToPay, setViewInfoError } from 'checkout/actions';
|
||||
import { PayButton } from '../pay-button';
|
||||
import { PaymentMethodName } from 'checkout/backend';
|
||||
import styled from 'checkout/styled-components';
|
||||
@ -14,7 +14,6 @@ import { toEmailConfig, toPhoneNumberConfig } from '../fields-config';
|
||||
import { FormGroup } from '../form-group';
|
||||
import { Email, Phone } from '../common-fields';
|
||||
import { getMetadata } from 'checkout/components';
|
||||
import isNil from 'checkout/utils/is-nil';
|
||||
|
||||
import { InitialContext } from '../../../../initial-context';
|
||||
import { getAvailableTerminalPaymentMethod } from '../get-available-terminal-payment-method';
|
||||
@ -29,7 +28,7 @@ const ProviderSelectorDescription = styled.p`
|
||||
|
||||
export const PaymentTerminalBankCardFormDef: React.FC<InjectedFormProps> = ({ submitFailed, handleSubmit }) => {
|
||||
const { locale, initConfig, availablePaymentMethods } = useContext(InitialContext);
|
||||
const { paymentPayload, setFormData } = useCreatePayment();
|
||||
const { createPaymentState, setFormData } = useCreatePayment();
|
||||
const paymentMethod = getAvailableTerminalPaymentMethod(availablePaymentMethods, KnownProviderCategories.BankCard);
|
||||
const serviceProviders = paymentMethod?.serviceProviders;
|
||||
const email = toEmailConfig(initConfig.email);
|
||||
@ -45,10 +44,13 @@ export const PaymentTerminalBankCardFormDef: React.FC<InjectedFormProps> = ({ su
|
||||
if (submitFailed) {
|
||||
dispatch(setViewInfoError(true));
|
||||
}
|
||||
if (!isNil(paymentPayload)) {
|
||||
dispatch(pay(paymentPayload));
|
||||
if (createPaymentState.status === 'SUCCESS') {
|
||||
dispatch(pay(createPaymentState.data));
|
||||
}
|
||||
}, [submitFailed, paymentPayload]);
|
||||
if (createPaymentState.status === 'FAILURE') {
|
||||
dispatch(goToFormInfo(new ResultFormInfo(ResultType.hookError, createPaymentState.error)));
|
||||
}
|
||||
}, [submitFailed, createPaymentState]);
|
||||
|
||||
const submit = (values: PaymentTerminalFormValues) => {
|
||||
dispatch(prepareToPay());
|
||||
|
@ -5,8 +5,15 @@ import get from 'lodash-es/get';
|
||||
import styled from 'checkout/styled-components';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from 'checkout/configure-store';
|
||||
import { pay, prepareToPay, setViewInfoError } from 'checkout/actions';
|
||||
import { FormName, PaymentStatus, PaymentTerminalFormValues, PaymentTerminalFormInfo } from 'checkout/state';
|
||||
import { goToFormInfo, pay, prepareToPay, setViewInfoError } from 'checkout/actions';
|
||||
import {
|
||||
FormName,
|
||||
PaymentStatus,
|
||||
PaymentTerminalFormValues,
|
||||
PaymentTerminalFormInfo,
|
||||
ResultFormInfo,
|
||||
ResultType
|
||||
} from 'checkout/state';
|
||||
import { Header } from '../header';
|
||||
import { PayButton } from '../pay-button';
|
||||
import { FormGroup } from '../form-group';
|
||||
@ -26,7 +33,6 @@ import {
|
||||
} from './init-config-payment';
|
||||
import { MetadataSelect } from './metadata-select';
|
||||
import { PaymentMethodName, useCreatePayment } from 'checkout/hooks';
|
||||
import isNil from 'checkout/utils/is-nil';
|
||||
import { InitialContext } from '../../../../initial-context';
|
||||
|
||||
const Container = styled.div`
|
||||
@ -42,7 +48,7 @@ const PaymentTerminalFormRef: React.FC<InjectedFormProps> = ({ submitFailed, ini
|
||||
initConfig,
|
||||
model: { serviceProviders, invoiceTemplate }
|
||||
} = useContext(InitialContext);
|
||||
const { paymentPayload, setFormData } = useCreatePayment();
|
||||
const { createPaymentState, setFormData } = useCreatePayment();
|
||||
const { providerID, paymentStatus } = useAppSelector<PaymentTerminalFormInfo>(getActiveModalFormSelector);
|
||||
const serviceProvider = serviceProviders.find((value) => value.id === providerID);
|
||||
const { form, contactInfo, logo, paymentSessionInfo, prefilledMetadataValues } = getMetadata(serviceProvider);
|
||||
@ -88,10 +94,13 @@ const PaymentTerminalFormRef: React.FC<InjectedFormProps> = ({ submitFailed, ini
|
||||
if (submitFailed) {
|
||||
dispatch(setViewInfoError(true));
|
||||
}
|
||||
if (!isNil(paymentPayload)) {
|
||||
dispatch(pay(paymentPayload));
|
||||
if (createPaymentState.status === 'SUCCESS') {
|
||||
dispatch(pay(createPaymentState.data));
|
||||
}
|
||||
}, [submitFailed, paymentPayload]);
|
||||
if (createPaymentState.status === 'FAILURE') {
|
||||
dispatch(goToFormInfo(new ResultFormInfo(ResultType.hookError, createPaymentState.error)));
|
||||
}
|
||||
}, [submitFailed, createPaymentState]);
|
||||
|
||||
const submit = (values?: Partial<PaymentTerminalFormValues>) => {
|
||||
const payload = {
|
||||
|
@ -4,6 +4,7 @@ import { ResultFormContent } from './result-form-content';
|
||||
import { getFailedDescription } from './get-failed-description';
|
||||
import { getLastChange } from 'checkout/utils';
|
||||
import { ResultFormType } from './result-form-content';
|
||||
import isObject from 'checkout/utils/is-object';
|
||||
|
||||
export const refunded = (l: Locale): ResultFormContent => ({
|
||||
hasActions: false,
|
||||
@ -34,6 +35,23 @@ export const failed = (l: Locale, e: PaymentError | LogicError): ResultFormConte
|
||||
type: ResultFormType.ERROR
|
||||
});
|
||||
|
||||
const getErrorDescription = (error: unknown): string => {
|
||||
if (error instanceof Error) {
|
||||
return `${error.name}: ${error.message}`;
|
||||
} else if (isObject(error)) {
|
||||
return JSON.stringify(error);
|
||||
}
|
||||
return 'Unknown error';
|
||||
};
|
||||
|
||||
export const failedHook = (l: Locale, error: unknown): ResultFormContent => ({
|
||||
hasActions: true,
|
||||
hasDone: false,
|
||||
header: l['form.header.final.failed.label'],
|
||||
description: getErrorDescription(error),
|
||||
type: ResultFormType.ERROR
|
||||
});
|
||||
|
||||
const processed = (l: Locale): ResultFormContent => ({
|
||||
hasActions: false,
|
||||
hasDone: true,
|
||||
|
@ -5,6 +5,7 @@ import { FormName, ModalForms, ModalName, ResultFormInfo, ResultState, ResultTyp
|
||||
import { setResult } from 'checkout/actions';
|
||||
import { findNamed } from 'checkout/utils';
|
||||
import { makeContentError, makeContentInvoice } from './make-content';
|
||||
import { failedHook } from './make-content/make-from-payment-change';
|
||||
import { ActionBlock } from './action-block';
|
||||
import { ResultIcon } from './result-icons';
|
||||
import styled, { css } from 'checkout/styled-components';
|
||||
@ -78,6 +79,8 @@ export const ResultForm = () => {
|
||||
switch (resultFormInfo.resultType) {
|
||||
case ResultType.error:
|
||||
return makeContentError(locale, error);
|
||||
case ResultType.hookError:
|
||||
return failedHook(locale, resultFormInfo.hookError);
|
||||
case ResultType.processed:
|
||||
return makeContentInvoice(locale, events.events, events.status, error);
|
||||
}
|
||||
|
@ -4,19 +4,18 @@ import { InjectedFormProps, reduxForm } from 'redux-form';
|
||||
import get from 'lodash-es/get';
|
||||
|
||||
import { FormGroup } from '../form-group';
|
||||
import { FormName, PaymentStatus, WalletFormInfo, WalletFormValues } from 'checkout/state';
|
||||
import { FormName, PaymentStatus, ResultFormInfo, ResultType, WalletFormInfo, WalletFormValues } from 'checkout/state';
|
||||
import { PayButton } from '../pay-button';
|
||||
import { Header } from '../header';
|
||||
import { Amount } from '../common-fields';
|
||||
import { toFieldsConfig } from '../fields-config';
|
||||
import { pay, prepareToPay, setViewInfoError } from 'checkout/actions';
|
||||
import { goToFormInfo, pay, prepareToPay, setViewInfoError } from 'checkout/actions';
|
||||
import { SignUp } from './sign-up';
|
||||
import { getActiveModalFormSelector } from 'checkout/selectors';
|
||||
import { useAppDispatch, useAppSelector } from 'checkout/configure-store';
|
||||
import { getMetadata, MetadataField, MetadataLogo, obscurePassword, sortByIndex } from 'checkout/components/ui';
|
||||
import { LogoContainer } from './logo-container';
|
||||
import { PaymentMethodName, useCreatePayment } from 'checkout/hooks';
|
||||
import isNil from 'checkout/utils/is-nil';
|
||||
|
||||
import { InitialContext } from '../../../../initial-context';
|
||||
|
||||
@ -26,7 +25,7 @@ const WalletFormDef = ({ submitFailed, initialize, handleSubmit }: InjectedFormP
|
||||
initConfig,
|
||||
model: { invoiceTemplate }
|
||||
} = useContext(InitialContext);
|
||||
const { paymentPayload, setFormData } = useCreatePayment();
|
||||
const { createPaymentState, setFormData } = useCreatePayment();
|
||||
const { activeProvider, paymentStatus } = useAppSelector<WalletFormInfo>(getActiveModalFormSelector);
|
||||
const dispatch = useAppDispatch();
|
||||
const formValues = useAppSelector((s) => get(s.form, 'walletForm.values'));
|
||||
@ -60,10 +59,13 @@ const WalletFormDef = ({ submitFailed, initialize, handleSubmit }: InjectedFormP
|
||||
if (submitFailed) {
|
||||
dispatch(setViewInfoError(true));
|
||||
}
|
||||
if (!isNil(paymentPayload)) {
|
||||
dispatch(pay(paymentPayload));
|
||||
if (createPaymentState.status === 'SUCCESS') {
|
||||
dispatch(pay(createPaymentState.data));
|
||||
}
|
||||
}, [submitFailed, paymentPayload]);
|
||||
if (createPaymentState.status === 'FAILURE') {
|
||||
dispatch(goToFormInfo(new ResultFormInfo(ResultType.hookError, createPaymentState.error)));
|
||||
}
|
||||
}, [submitFailed, createPaymentState]);
|
||||
|
||||
return (
|
||||
<form id="wallet-form" onSubmit={handleSubmit(submit)}>
|
||||
|
@ -1,7 +1,14 @@
|
||||
import * as React from 'react';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { FormInfo, FormName, PaymentTerminalFormValues, WalletFormInfo } from 'checkout/state';
|
||||
import {
|
||||
FormInfo,
|
||||
FormName,
|
||||
PaymentTerminalFormValues,
|
||||
ResultFormInfo,
|
||||
ResultType,
|
||||
WalletFormInfo
|
||||
} from 'checkout/state';
|
||||
import { getMetadata, MetadataLogo, PaymentMethodItemContainer } from 'checkout/components/ui';
|
||||
import { PaymentMethodName, ServiceProvider } from 'checkout/backend';
|
||||
import { PaymentRequestedPayload, goToFormInfo, pay, prepareToPay } from 'checkout/actions';
|
||||
@ -19,7 +26,7 @@ export interface WalletProviderPaymentMethodItemProps {
|
||||
export const WalletProviderPaymentMethodItem = ({ serviceProvider }: WalletProviderPaymentMethodItemProps) => {
|
||||
const { logo, form } = getMetadata(serviceProvider);
|
||||
|
||||
const { paymentPayload, setFormData } = useCreatePayment();
|
||||
const { createPaymentState, setFormData } = useCreatePayment();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const onClick = () => {
|
||||
@ -37,10 +44,13 @@ export const WalletProviderPaymentMethodItem = ({ serviceProvider }: WalletProvi
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isNil(paymentPayload)) {
|
||||
dispatch(pay(paymentPayload));
|
||||
if (createPaymentState.status === 'SUCCESS') {
|
||||
dispatch(pay(createPaymentState.data));
|
||||
}
|
||||
}, [paymentPayload]);
|
||||
if (createPaymentState.status === 'FAILURE') {
|
||||
dispatch(goToFormInfo(new ResultFormInfo(ResultType.hookError, createPaymentState.error)));
|
||||
}
|
||||
}, [createPaymentState]);
|
||||
|
||||
return (
|
||||
<PaymentMethodItemContainer onClick={onClick}>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useContext, useState, useCallback } from 'react';
|
||||
import { useContext, useCallback, useReducer } from 'react';
|
||||
|
||||
import isNil from 'checkout/utils/is-nil';
|
||||
import { PaymentRequestedPayload } from 'checkout/actions';
|
||||
@ -8,6 +8,32 @@ import { FormData } from './create-payment';
|
||||
import { InitialContext } from '../components/app/initial-context';
|
||||
import { PayableInvoiceContext } from '../components/app/modal-container/payable-invoice-context';
|
||||
|
||||
type State =
|
||||
| { status: 'PRISTINE' }
|
||||
| { status: 'SUCCESS'; data: PaymentRequestedPayload }
|
||||
| { status: 'FAILURE'; error: unknown };
|
||||
|
||||
type Action =
|
||||
| { type: 'CREATE_PAYMENT_SUCCESS'; payload: PaymentRequestedPayload }
|
||||
| { type: 'CREATE_PAYMENT_FAILURE'; error: unknown };
|
||||
|
||||
const dataReducer = (state: State, action: Action): State => {
|
||||
switch (action.type) {
|
||||
case 'CREATE_PAYMENT_SUCCESS':
|
||||
return {
|
||||
...state,
|
||||
status: 'SUCCESS',
|
||||
data: action.payload
|
||||
};
|
||||
case 'CREATE_PAYMENT_FAILURE':
|
||||
return {
|
||||
...state,
|
||||
status: 'FAILURE',
|
||||
error: action.error
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const useCreatePayment = () => {
|
||||
const {
|
||||
initConfig,
|
||||
@ -18,11 +44,14 @@ export const useCreatePayment = () => {
|
||||
} = useContext(InitialContext);
|
||||
const { payableInvoiceData, setPayableInvoiceData } = useContext(PayableInvoiceContext);
|
||||
|
||||
const [paymentPayload, setPaymentPayload] = useState<PaymentRequestedPayload>(null);
|
||||
const [createPaymentState, dispatch] = useReducer(dataReducer, {
|
||||
status: 'PRISTINE'
|
||||
});
|
||||
|
||||
const setFormData = useCallback(
|
||||
(formData: FormData) => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
let data = payableInvoiceData;
|
||||
if (isNil(data)) {
|
||||
data = await createInvoiceWithTemplate({
|
||||
@ -51,18 +80,22 @@ export const useCreatePayment = () => {
|
||||
formData,
|
||||
payableInvoice: data
|
||||
});
|
||||
|
||||
setPaymentPayload({
|
||||
const payload = {
|
||||
capiEndpoint: appConfig.capiEndpoint,
|
||||
invoiceID: data.invoice.id,
|
||||
invoiceAccessToken: data.invoiceAccessToken,
|
||||
serviceProviders
|
||||
});
|
||||
};
|
||||
dispatch({ type: 'CREATE_PAYMENT_SUCCESS', payload });
|
||||
} catch (error) {
|
||||
dispatch({ type: 'CREATE_PAYMENT_FAILURE', error });
|
||||
console.error('Create payment failure', error);
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
},
|
||||
[payableInvoiceData]
|
||||
);
|
||||
|
||||
return { paymentPayload, setFormData };
|
||||
return { createPaymentState, setFormData };
|
||||
};
|
||||
|
@ -2,16 +2,19 @@ import { FormInfo, FormName } from '../form-info';
|
||||
|
||||
export enum ResultType {
|
||||
error = 'error',
|
||||
processed = 'processed'
|
||||
processed = 'processed',
|
||||
hookError = 'hookError'
|
||||
}
|
||||
|
||||
export class ResultFormInfo extends FormInfo {
|
||||
resultType: ResultType;
|
||||
hookError?: unknown;
|
||||
|
||||
constructor(resultType: ResultType) {
|
||||
constructor(resultType: ResultType, hookError?: unknown) {
|
||||
super();
|
||||
this.name = FormName.resultForm;
|
||||
this.resultType = resultType;
|
||||
this.active = true;
|
||||
this.hookError = hookError;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user