1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-01-17 06:52:27 +01:00

consolidated the payzen process with types

This commit is contained in:
Sylvain 2021-04-13 11:30:30 +02:00
parent dca2651fb3
commit c0afe9419e
3 changed files with 234 additions and 10 deletions

View File

@ -7,6 +7,7 @@ import SettingAPI from '../../../api/setting';
import { SettingName } from '../../../models/setting';
import PayzenAPI from '../../../api/payzen';
import { Loader } from '../../base/loader';
import { KryptonClient, KryptonError, ProcessPaymentAnswer } from '../../../models/payzen';
interface PayzenFormProps {
onSubmit: () => void,
@ -27,8 +28,8 @@ interface PayzenFormProps {
export const PayzenForm: React.FC<PayzenFormProps> = ({ onSubmit, onSuccess, onError, children, className, paymentSchedule = false, cartItems, customer, operator, formId }) => {
const { t } = useTranslation('shared');
const PayZenKR = useRef(null);
const [loading, setLoading] = useState(true);
const PayZenKR = useRef<KryptonClient>(null);
const [loadingClass, setLoadingClass] = useState<'hidden' | 'loader' | 'loader-overlay'>('loader');
useEffect(() => {
const api = new SettingAPI();
@ -43,6 +44,7 @@ export const PayzenForm: React.FC<PayzenFormProps> = ({ onSubmit, onSuccess, onE
.then(({ KR }) => KR.addForm("#payzenPaymentForm"))
.then(({ KR, result }) => KR.showForm(result.formId))
.then(({ KR }) => KR.onFormReady(handleFormReady))
.then(({ KR }) => KR.onFormCreated(handleFormCreated))
.then(({ KR }) => PayZenKR.current = KR);
}).catch(error => onError(error));
});
@ -51,23 +53,36 @@ export const PayzenForm: React.FC<PayzenFormProps> = ({ onSubmit, onSuccess, onE
/**
* Callback triggered on PayZen successful payments
*/
const onPaid = (event) => {
const onPaid = (event: ProcessPaymentAnswer): boolean => {
if (event.clientAnswer.orderStatus === 'PAID') {
PayZenKR.current.removeForms();
onSuccess(event.clientAnswer);
} else {
onError(event.clientAnswer);
const transaction = event.clientAnswer.transactions[0];
const error = `${transaction?.errorMessage}. ${transaction?.detailedErrorMessage || ''}`;
onError(error || event.clientAnswer.orderStatus);
}
return true;
};
/**
* Callback triggered when the PayZen form was entirely loaded and displayed
*/
const handleFormReady = () => {
setLoading(false);
setLoadingClass('hidden');
};
/**
* Callback triggered when the PayZen form has started to show up but is not entirely loaded
*/
const handleFormCreated = () => {
setLoadingClass('loader-overlay');
}
/**
* Callback triggered when the PayZen payment was refused
*/
const handleError = (answer) => {
const handleError = (answer: KryptonError) => {
const message = `${answer.errorMessage}. ${answer.detailedErrorMessage ? answer.detailedErrorMessage : ''}`;
onError(message);
}
@ -82,8 +97,8 @@ export const PayzenForm: React.FC<PayzenFormProps> = ({ onSubmit, onSuccess, onE
try {
const { result } = await PayZenKR.current.validateForm();
if (result === null) {
PayZenKR.current.onSubmit(onPaid);
PayZenKR.current.onError(handleError);
await PayZenKR.current.onSubmit(onPaid);
await PayZenKR.current.onError(handleError);
await PayZenKR.current.submit();
}
} catch (err) {
@ -94,7 +109,7 @@ export const PayzenForm: React.FC<PayzenFormProps> = ({ onSubmit, onSuccess, onE
const Loader: FunctionComponent = () => {
return (
<div className="fa-3x loader">
<div className={`fa-3x ${loadingClass}`}>
<i className="fas fa-circle-notch fa-spin" />
</div>
);
@ -102,7 +117,7 @@ export const PayzenForm: React.FC<PayzenFormProps> = ({ onSubmit, onSuccess, onE
return (
<form onSubmit={handleSubmit} id={formId} className={className ? className : ''}>
{loading && <Loader />}
<Loader />
<div className="container">
<div id="payzenPaymentForm" />
</div>

View File

@ -6,3 +6,203 @@ export interface CreatePaymentResponse {
formToken: string
orderId: string
}
export interface OrderDetails {
mode?: 'TEST' | 'PRODUCTION',
orderCurrency?: string,
orderEffectiveAmount?: number,
orderId?: string,
orderTotalAmount?: number,
_type: 'V4/OrderDetails'
}
export interface Customer {
email?: string,
reference?: string,
billingDetails?: {
address?: string,
address2?: string,
category?: 'PRIVATE' | 'COMPANY',
cellPhoneNumber?: string,
city?: string
country?: string,
district?: string,
firstName?: string,
identityCode?: string,
language?: 'DE' | 'EN' | 'ZH' | 'ES' | 'FR' | 'IT' | 'JP' | 'NL' | 'PL' | 'PT' | 'RU',
lastName?: string,
phoneNumber?: string,
state?: string,
streetNumber?: string,
title?: string,
zipCode?: string,
_type: 'V4/Customer/BillingDetails'
},
shippingDetails: {
address?: string,
address2?: string,
category?: 'PRIVATE' | 'COMPANY',
city?: string
country?: string,
deliveryCompanyName?: string,
district?: string,
firstName?: string,
identityCode?: string,
lastName?: string,
legalName?: string,
phoneNumber?: string,
shippingMethod?: 'RECLAIM_IN_SHOP' | 'RELAY_POINT' | 'RECLAIM_IN_STATION' | 'PACKAGE_DELIVERY_COMPANY' | 'ETICKET',
shippingSpeed?: 'STANDARD' | 'EXPRESS' | 'PRIORITY',
state?: string,
streetNumber?: string,
zipCode?: string,
_type: 'V4/Customer/ShippingDetails'
},
shoppingCart: {
insuranceAmount?: number,
shippingAmount?: number,
taxAmount?: number
cartItemInfo: Array<{
productAmount?: string,
productLabel?: string
productQty?: number,
productRef?: string,
productType?: 'FOOD_AND_GROCERY' | 'AUTOMOTIVE' | 'ENTERTAINMENT' | 'HOME_AND_GARDEN' | 'HOME_APPLIANCE' | 'AUCTION_AND_GROUP_BUYING' | 'FLOWERS_AND_GIFTS' | 'COMPUTER_AND_SOFTWARE' | 'HEALTH_AND_BEAUTY' | 'SERVICE_FOR_INDIVIDUAL' | 'SERVICE_FOR_BUSINESS' | 'SPORTS' | 'CLOTHING_AND_ACCESSORIES' | 'TRAVEL' | 'HOME_AUDIO_PHOTO_VIDEO' | 'TELEPHONY',
productVat?: number,
}>,
_type: 'V4/Customer/ShoppingCart'
}
_type: 'V4/Customer/Customer'
}
export interface PaymentTransaction {
amount?: number,
creationDate?: string,
currency?: string,
detailedErrorCode? : string,
detailedErrorMessage?: string,
detailedStatus?: 'ACCEPTED' | 'AUTHORISED' | 'AUTHORISED_TO_VALIDATE' | 'CANCELLED' | 'CAPTURED' | 'EXPIRED' | 'PARTIALLY_AUTHORISED' | 'REFUSED' | 'UNDER_VERIFICATION' | 'WAITING_AUTHORISATION' | 'WAITING_AUTHORISATION_TO_VALIDATE' | 'ERROR',
effectiveStrongAuthentication?: 'ENABLED' | 'DISABLED' ,
errorCode?: string,
errorMessage?: string,
metadata?: any,
operationType?: 'DEBIT' | 'CREDIT' | 'VERIFICATION',
orderDetails?: OrderDetails,
paymentMethodToken?: string,
paymentMethodType?: 'CARD',
shopId?: string,
status?: 'PAID' | 'UNPAID' | 'RUNNING' | 'PARTIALLY_PAID',
transactionDetails?: {
creationContext?: 'CHARGE' | 'REFUND',
effectiveAmount?: number,
effectiveCurrency?: string,
liabilityShift?: 'YES' | 'NO',
mid?: string,
parentTransactionUuid?: string,
sequenceNumber?: string,
cardDetails?: any,
fraudManagement?: any,
taxAmount?: number,
taxRate?: number,
preTaxAmount?: number,
externalTransactionId?: number,
dcc?: any,
nsu?: string,
tid?: string,
acquirerNetwork?: string,
taxRefundAmount?: number,
occurrenceType?: string
},
uuid?: string,
_type: 'V4/PaymentTransaction'
}
export interface Payment {
customer: Customer,
orderCycle: 'OPEN' | 'CLOSED',
orderDetails: OrderDetails,
orderStatus: 'PAID' | 'UNPAID' | 'RUNNING' | 'PARTIALLY_PAID',
serverDate: string,
shopId: string,
transactions: Array<PaymentTransaction>,
_type: 'V4/Payment'
}
export interface ProcessPaymentAnswer {
clientAnswer: Payment,
hash: string,
hashAlgorithm: string,
hashKey: string,
rawClientAnswer: string
_type: 'V4/Charge/ProcessPaymentAnswer'
}
export interface KryptonError {
children: Array<KryptonError>,
detailedErrorCode: string,
detailedErrorMessage: string,
errorCode: string,
errorMessage: string,
field: any,
formId: string,
metadata: {
answer: ProcessPaymentAnswer,
formToken: string
},
_errorKey: string,
_type: 'krypton/error'
}
export interface KryptonFocus {
field: string,
formId: string,
_type: 'krypton/focus'
}
export interface KryptonConfig {
'kr-public-key': string,
'kr-language': string,
'kr-post-url-success': string,
'kr-get-url-success': string,
'kr-post-url-refused': string,
'kr-get-url-refused': string,
'kr-clear-on-error': boolean,
'kr-hide-debug-toolbar': boolean,
'kr-spa-mode': boolean
}
type DefaultCallback = () => void
type BrandChangedCallback = (event: {KR: KryptonClient, cardInfo: {brand: string}}) => void
type ErrorCallback = (event: KryptonError) => void
type FocusCallback = (event: KryptonFocus) => void
type InstallmentChangedCallback = (event: {KR: KryptonClient, installmentInfo: {brand: string, hasInterests: boolean, installmentCount: number, totalAmount: number}}) => void
type SubmitCallback = (event: ProcessPaymentAnswer) => boolean
type ClickCallback = (event: any) => boolean
export interface KryptonClient {
addForm: (selector: string) => Promise<{KR: KryptonClient, result: {formId: string}}>,
showForm: (formId: string) => Promise<{KR: KryptonClient}>,
hideForm: (formId: string) => Promise<{KR: KryptonClient}>,
removeForms: () => Promise<{KR: KryptonClient}>,
attachForm: (selector: string) => Promise<{KR: KryptonClient}>,
onBrandChanged: (callback: BrandChangedCallback) => Promise<{KR: KryptonClient}>,
onError: (callback: ErrorCallback) => Promise<{KR: KryptonClient}>,
onFocus: (callback: FocusCallback) => Promise<{KR: KryptonClient}>,
onInstallmentChanged: (callback: InstallmentChangedCallback) => Promise<{KR: KryptonClient}>
onFormReady: (callback: DefaultCallback) => Promise<{KR: KryptonClient}>,
onFormCreated: (callback: DefaultCallback) => Promise<{KR: KryptonClient}>,
onSubmit: (callback: SubmitCallback) => Promise<{KR: KryptonClient}>,
button: {
onClick: (callback: ClickCallback | Promise<boolean>) => Promise<{KR: KryptonClient}>
},
openPopin: () => Promise<{KR: KryptonClient}>,
closePopin: () => Promise<{KR: KryptonClient}>,
fields: {
focus: (selector: string) => Promise<{KR: KryptonClient}>,
},
setFormConfig: (config: KryptonConfig) => Promise<{KR: KryptonClient}>,
setShopName: (name: string) => Promise<{KR: KryptonClient}>,
setFormToken: (formToken: string) => Promise<{KR: KryptonClient}>,
validateForm: () => Promise<{KR: KryptonClient, result?: KryptonError}>,
submit: () => Promise<{KR: KryptonClient}>,
}

View File

@ -1,8 +1,17 @@
.payzen-modal {
.payzen-form {
.hidden {
display: none;
}
.loader {
text-align: center;
}
.loader-overlay {
position: absolute;
top: 65px;
left: 190px;
z-index: 1;
}
.container {
display: flex;
justify-content: center;