Feature/OpenInvoice uses SRPanel (#2053)

* OpenInvoice comp uses SRPanel

* PersonalDetails & Address do not consider an input element to be in error if empty, unless the whole form is being validated

* IbanInput fields do not consider an element to be in error if empty, unless the whole form is being validated

* layout & countrySpecificLabels are optional props when setting SRMessages

* Adding mockSRContext object (needed for next phase)

* Reordered OpenInvoice comps to match markup in playground. Added switching mechanism to sho/hide them
This commit is contained in:
sponglord
2023-03-21 15:11:19 +01:00
committed by GitHub
parent 01cc85a3df
commit d33226a814
18 changed files with 367 additions and 152 deletions

View File

@@ -375,6 +375,7 @@ const CardInput: FunctionalComponent<CardInputProps> = props => {
setSortedErrorList(currentErrorsSortedByLayout);
switch (srPanelResp?.action) {
// A call to focus the first field in error will always follow the call to validate the whole form
case ERROR_ACTION_FOCUS_FIELD:
if (shouldMoveFocusSR) setFocusOnFirstField(isValidating.current, sfp, srPanelResp?.fieldToFocus);
// Remove 'showValidation' mode - allowing time for collation of all the fields in error whilst it is 'showValidation' mode (some errors come in a second render pass)

View File

@@ -4,6 +4,7 @@ import OpenInvoice from '../../internal/OpenInvoice';
import CoreProvider from '../../../core/Context/CoreProvider';
import { OpenInvoiceProps } from '../../internal/OpenInvoice/types';
import { AddressSpecifications } from '../../internal/Address/types';
import SRPanelProvider from '../../../core/Errors/SRPanelProvider';
export interface OpenInvoiceContainerProps extends Partial<OpenInvoiceProps> {
consentCheckboxLabel?: h.JSX.Element;
@@ -86,14 +87,16 @@ export default class OpenInvoiceContainer extends UIElement<OpenInvoiceContainer
render() {
return (
<CoreProvider i18n={this.props.i18n} loadingContext={this.props.loadingContext}>
<OpenInvoice
setComponentRef={this.setComponentRef}
{...this.props}
{...this.state}
onChange={this.setState}
onSubmit={this.submit}
payButton={this.payButton}
/>
<SRPanelProvider srPanel={this.props.modules.srPanel}>
<OpenInvoice
setComponentRef={this.setComponentRef}
{...this.props}
{...this.state}
onChange={this.setState}
onSubmit={this.submit}
payButton={this.payButton}
/>
</SRPanelProvider>
</CoreProvider>
);
}

View File

@@ -33,7 +33,8 @@ export default function Address(props: AddressProps) {
const { data, errors, valid, isValid, handleChangeFor, triggerValidation } = useForm<AddressData>({
schema: requiredFieldsSchema,
defaultData: props.data,
rules: props.validationRules || getAddressValidationRules(specifications),
// Ensure any passed validation rules are merged with the default ones
rules: { ...getAddressValidationRules(specifications), ...props.validationRules },
formatters: addressFormatters
});
@@ -134,7 +135,7 @@ export default function Address(props: AddressProps) {
return (
<Fragment>
<Fieldset classNameModifiers={[label]} label={label}>
<Fieldset classNameModifiers={[label || 'address']} label={label}>
{addressSchema.map(field => (field instanceof Array ? getWrapper(field) : getComponent(field, {})))}
</Fieldset>
{/* Needed to easily test when showValidation is called */}

View File

@@ -22,10 +22,7 @@ function getErrorMessage(errors: AddressStateError, fieldName: string, i18n: Lan
* - then you should implement <CountryField> or <StateField> directly
*/
function FieldContainer(props: FieldContainerProps) {
const {
i18n,
commonProps: { isCollatingErrors }
} = useCoreContext();
const { i18n } = useCoreContext();
const { classNameModifiers = [], data, errors, valid, fieldName, onInput, onBlur, trimOnBlur, maxlength, disabled } = props;
const value: string = data[fieldName];
@@ -68,7 +65,6 @@ function FieldContainer(props: FieldContainerProps) {
errorMessage={errorMessage}
isValid={valid[fieldName]}
name={fieldName}
isCollatingErrors={isCollatingErrors}
i18n={i18n}
>
{renderFormField('text', {
@@ -77,7 +73,6 @@ function FieldContainer(props: FieldContainerProps) {
value,
onInput,
onBlur,
isCollatingErrors,
maxlength,
trimOnBlur,
disabled

View File

@@ -0,0 +1,16 @@
import Language from '../../../language';
import { ADDRESS_SCHEMA } from './constants';
import { AddressField } from '../../../types';
import { StringObject } from './types';
/**
* Used by the SRPanel sorting function to tell it whether we need to prepend the field type to the SR panel message, and, if so, we retrieve the correct translation for the field type.
* (Whether we need to prepend the field type depends on whether we know that the error message correctly reflects the label of the field. Ultimately all error messages should do this
* and this mapping fn will become redundant)
*/
export const mapFieldKey = (key: string, i18n: Language, countrySpecificLabels: StringObject): string => {
if (ADDRESS_SCHEMA.includes(key as AddressField)) {
return countrySpecificLabels?.[key] ? i18n.get(countrySpecificLabels?.[key]) : i18n.get(key);
}
return null;
};

View File

@@ -11,14 +11,14 @@ import { CompanyDetailsSchema, CompanyDetailsProps } from './types';
import useForm from '../../../utils/useForm';
import { ComponentMethodsRef } from '../../types';
const companyDetailsSchema = ['name', 'registrationNumber'];
export const COMPANY_DETAILS_SCHEMA = ['name', 'registrationNumber'];
export default function CompanyDetails(props: CompanyDetailsProps) {
const { label = '', namePrefix, requiredFields, visibility } = props;
const { i18n } = useCoreContext();
const { handleChangeFor, triggerValidation, data, valid, errors, isValid } = useForm<CompanyDetailsSchema>({
schema: requiredFields,
rules: props.validationRules,
rules: { ...companyDetailsValidationRules, ...props.validationRules },
defaultData: props.data
});
@@ -93,6 +93,6 @@ CompanyDetails.defaultProps = {
data: {},
onChange: () => {},
visibility: 'editable',
requiredFields: companyDetailsSchema,
requiredFields: COMPANY_DETAILS_SCHEMA,
validationRules: companyDetailsValidationRules
};

View File

@@ -1,8 +1,22 @@
export const companyDetailsValidationRules = {
import { ValidatorRules } from '../../../utils/Validator/types';
import { isEmpty } from '../../../utils/validator-utils';
export const companyDetailsValidationRules: ValidatorRules = {
default: {
validate: value => {
return value && value.length > 0;
},
modes: ['blur'],
errorMessage: 'error.va.gen.01' // = "Incomplete field"
},
name: {
validate: value => (isEmpty(value) ? null : true), // valid, if there are chars other than spaces
errorMessage: 'companyDetails.name.invalid',
modes: ['blur']
},
registrationNumber: {
validate: value => (isEmpty(value) ? null : true),
errorMessage: 'companyDetails.registrationNumber.invalid',
modes: ['blur']
}
};

View File

@@ -162,6 +162,9 @@ class IbanInput extends Component<IbanInputProps, IbanInputState> {
if (currentIban.length > 0) {
const validationStatus = checkIbanStatus(currentIban).status;
this.setError('iban', validationStatus !== 'valid' ? ibanErrorObj : null, this.onChange);
} else {
// Empty field is not in error
this.setError('iban', null, this.onChange);
}
};
@@ -197,7 +200,8 @@ class IbanInput extends Component<IbanInputProps, IbanInputState> {
value: data['ownerName'],
'aria-invalid': !!this.state.errors.holder,
'aria-label': i18n.get('sepa.ownerName'),
onInput: e => this.handleHolderInput(e.target.value)
onInput: e => this.handleHolderInput(e.target.value),
onBlur: e => this.handleHolderInput(e.target.value)
})}
</Field>
)}

View File

@@ -1,12 +1,12 @@
import { h } from 'preact';
import { useEffect, useRef, useState } from 'preact/hooks';
import { useEffect, useRef, useState, useMemo } from 'preact/hooks';
import useCoreContext from '../../../core/Context/useCoreContext';
import CompanyDetails from '../CompanyDetails';
import PersonalDetails from '../PersonalDetails';
import Address from '../Address';
import Checkbox from '../FormFields/Checkbox';
import ConsentCheckbox from '../FormFields/ConsentCheckbox';
import { getActiveFieldsData, getInitialActiveFieldsets, fieldsetsSchema } from './utils';
import { getActiveFieldsData, getInitialActiveFieldsets, fieldsetsSchema, mapFieldKey } from './utils';
import {
OpenInvoiceActiveFieldsets,
OpenInvoiceFieldsetsRefs,
@@ -18,6 +18,21 @@ import {
import './OpenInvoice.scss';
import IbanInput from '../IbanInput';
import { ComponentMethodsRef } from '../../types';
import { enhanceErrorObjectKeys } from '../../../core/Errors/utils';
import { GenericError, SetSRMessagesReturnObject } from '../../../core/Errors/types';
import useSRPanelContext from '../../../core/Errors/useSRPanelContext';
import { SetSRMessagesReturnFn } from '../../../core/Errors/SRPanelProvider';
import Specifications from '../Address/Specifications';
import { PERSONAL_DETAILS_SCHEMA } from '../PersonalDetails/PersonalDetails';
import { COMPANY_DETAILS_SCHEMA } from '../CompanyDetails/CompanyDetails';
import { setFocusOnField } from '../../../utils/setFocus';
import { ERROR_ACTION_FOCUS_FIELD } from '../../../core/Errors/constants';
const consentCBErrorObj: GenericError = {
isValid: false,
errorMessage: 'consent.checkbox.invalid',
error: 'consent.checkbox.invalid'
};
export default function OpenInvoice(props: OpenInvoiceProps) {
const { countryCode, visibility } = props;
@@ -30,6 +45,19 @@ export default function OpenInvoice(props: OpenInvoiceProps) {
props.setComponentRef?.(openInvoiceRef.current);
}
const isValidating = useRef(false);
/** SR stuff */
const { setSRMessagesFromObjects, shouldMoveFocusSR } = useSRPanelContext();
// Generate a setSRMessages function - implemented as a partial, since the initial set of arguments don't change.
const setSRMessages: SetSRMessagesReturnFn = setSRMessagesFromObjects?.({
fieldTypeMappingFn: mapFieldKey
});
const specifications = useMemo(() => new Specifications(), []);
/** end SR stuff */
const initialActiveFieldsets: OpenInvoiceActiveFieldsets = getInitialActiveFieldsets(visibility, props.data);
const [activeFieldsets, setActiveFieldsets] = useState<OpenInvoiceActiveFieldsets>(initialActiveFieldsets);
@@ -57,12 +85,13 @@ export default function OpenInvoice(props: OpenInvoiceProps) {
// Expose methods expected by parent
openInvoiceRef.current.showValidation = () => {
isValidating.current = true;
fieldsetsSchema.forEach(fieldset => {
if (fieldsetsRefs[fieldset].current) fieldsetsRefs[fieldset].current.showValidation();
});
setErrors({
...(hasConsentCheckbox && { consentCheckbox: !data.consentCheckbox })
...(hasConsentCheckbox && { consentCheckbox: data.consentCheckbox ? null : consentCBErrorObj })
});
};
@@ -74,6 +103,71 @@ export default function OpenInvoice(props: OpenInvoiceProps) {
const isValid: boolean = fieldsetsAreValid && consentCheckboxValid;
const newData: OpenInvoiceStateData = getActiveFieldsData(activeFieldsets, data);
const DELIVERY_ADDRESS_PREFIX = 'deliveryAddress:';
/** Create messages for SRPanel */
// Extract nested errors from the various child components...
const {
companyDetails: extractedCompanyDetailsErrors,
personalDetails: extractedPersonalDetailsErrors,
bankAccount: extractedBankAccountErrors,
billingAddress: extractedBillingAddressErrors,
deliveryAddress: extractedDeliveryAddressErrors,
...remainingErrors
} = errors;
// (Differentiate between billingAddress and deliveryAddress errors by adding a prefix to the latter)
const enhancedDeliveryAddressErrors = enhanceErrorObjectKeys(extractedDeliveryAddressErrors, DELIVERY_ADDRESS_PREFIX);
// ...and then collate the errors into a new object so that they all sit at top level
const errorsForPanel = {
...(typeof extractedCompanyDetailsErrors === 'object' && extractedCompanyDetailsErrors),
...(typeof extractedPersonalDetailsErrors === 'object' && extractedPersonalDetailsErrors),
...(typeof extractedBankAccountErrors === 'object' && extractedBankAccountErrors),
...(typeof extractedBillingAddressErrors === 'object' && extractedBillingAddressErrors),
...(typeof enhancedDeliveryAddressErrors === 'object' && enhancedDeliveryAddressErrors),
...remainingErrors
};
// Create layout
const companyDetailsLayout: string[] = COMPANY_DETAILS_SCHEMA;
const personalDetailsReqFields: string[] = props.personalDetailsRequiredFields ?? PERSONAL_DETAILS_SCHEMA;
const personalDetailLayout: string[] = PERSONAL_DETAILS_SCHEMA.filter(x => personalDetailsReqFields?.includes(x));
const bankAccountLayout = ['holder', 'iban'];
const billingAddressLayout = specifications.getAddressSchemaForCountryFlat(data.billingAddress?.country);
const deliveryAddressLayout = specifications.getAddressSchemaForCountryFlat(data.deliveryAddress?.country);
// In order to sort the deliveryAddress errors the layout entries need to have the same (prefixed) identifier as the errors themselves
const deliveryAddressLayoutEnhanced = deliveryAddressLayout.map(item => `${DELIVERY_ADDRESS_PREFIX}${item}`);
const fullLayout = companyDetailsLayout.concat(personalDetailLayout, bankAccountLayout, billingAddressLayout, deliveryAddressLayoutEnhanced, [
'consentCheckbox'
]);
// Country specific address labels
const countrySpecificLabels = specifications.getAddressLabelsForCountry(data.billingAddress?.country ?? data.deliveryAddress?.country);
// Set messages: Pass dynamic props (errors, layout etc) to SRPanel via partial
const srPanelResp: SetSRMessagesReturnObject = setSRMessages?.({
errors: errorsForPanel,
isValidating: isValidating.current,
layout: fullLayout,
countrySpecificLabels
});
// A call to focus the first field in error will always follow the call to validate the whole form
if (srPanelResp?.action === ERROR_ACTION_FOCUS_FIELD) {
// Focus first field in error, if required
if (shouldMoveFocusSR) setFocusOnField('.adyen-checkout__open-invoice', srPanelResp.fieldToFocus);
// Remove 'showValidation' mode - allowing time for collation of all the fields in error whilst it is 'showValidation' mode (some errors come in a second render pass)
setTimeout(() => {
isValidating.current = false;
}, 300);
}
props.onChange({ data: newData, errors, valid, isValid });
}, [data, activeFieldsets]);

View File

@@ -3,6 +3,7 @@ import { CompanyDetailsSchema } from '../CompanyDetails/types';
import { AddressSpecifications } from '../Address/types';
import { UIElementProps } from '../../types';
import UIElement from '../../UIElement';
import { GenericError, ValidationRuleErrorObj } from '../../../core/Errors/types';
export interface OpenInvoiceVisibility {
companyDetails?: FieldsetVisibility;
@@ -49,12 +50,12 @@ export interface OpenInvoiceStateData {
}
export interface OpenInvoiceStateError {
consentCheckbox?: boolean;
companyDetails?: boolean;
billingAddress?: boolean;
deliveryAddress?: boolean;
personalDetails?: boolean;
bankAccount?: boolean;
consentCheckbox?: boolean | GenericError;
companyDetails?: boolean | ValidationRuleErrorObj;
billingAddress?: boolean | ValidationRuleErrorObj;
deliveryAddress?: boolean | ValidationRuleErrorObj;
personalDetails?: boolean | ValidationRuleErrorObj;
bankAccount?: boolean | object;
}
export interface OpenInvoiceStateValid {

View File

@@ -1,4 +1,8 @@
import { OpenInvoiceActiveFieldsets, OpenInvoiceStateData, OpenInvoiceVisibility } from './types';
import Language from '../../../language';
import { mapFieldKey as mapFieldKeyPD } from '../PersonalDetails/utils';
import { mapFieldKey as mapFieldKeyAddress } from '../Address/utils';
import { StringObject } from '../Address/types';
export const fieldsetsSchema: Array<keyof OpenInvoiceStateData> = [
'companyDetails',
@@ -29,3 +33,36 @@ export const getInitialActiveFieldsets = (visibility: OpenInvoiceVisibility, dat
acc[fieldset] = isVisible && (!isDeliveryAddress || billingAddressIsHidden || isPrefilled(data[fieldset]));
return acc;
}, {} as OpenInvoiceActiveFieldsets);
/**
* Used by the SRPanel sorting function to tell it whether we need to prepend the field type to the SR panel message, and, if so, we retrieve the correct translation for the field type.
* (Whether we need to prepend the field type depends on whether we know that the error message correctly reflects the label of the field. Ultimately all error messages should do this
* and this mapping fn will become redundant)
*/
export const mapFieldKey = (key: string, i18n: Language, countrySpecificLabels: StringObject): string => {
let refKey = key;
let label;
// Differentiate between address types (billing and delivery)
const splitKey = refKey.split(':');
const hasSplitKey = splitKey.length > 1;
if (hasSplitKey) {
label = splitKey[0];
refKey = splitKey[1];
}
const addressKey = mapFieldKeyAddress(refKey, i18n, countrySpecificLabels);
if (addressKey) return hasSplitKey ? `${i18n.get(label)} ${addressKey}` : addressKey;
// Personal details related
switch (refKey) {
case 'gender':
case 'dateOfBirth':
return mapFieldKeyPD(refKey, i18n);
default:
break;
}
// We know that the translated error messages do contain a reference to the field they refer to, so we won't need to map them
return null;
};

View File

@@ -14,7 +14,7 @@ import useForm from '../../../utils/useForm';
import './PersonalDetails.scss';
import { ComponentMethodsRef } from '../../types';
const personalDetailsSchema = ['firstName', 'lastName', 'gender', 'dateOfBirth', 'shopperEmail', 'telephoneNumber'];
export const PERSONAL_DETAILS_SCHEMA = ['firstName', 'lastName', 'gender', 'dateOfBirth', 'shopperEmail', 'telephoneNumber'];
export default function PersonalDetails(props: PersonalDetailsProps) {
const { label = '', namePrefix, placeholders, requiredFields, visibility } = props;
@@ -197,7 +197,7 @@ PersonalDetails.defaultProps = {
data: {},
onChange: () => {},
placeholders: {},
requiredFields: personalDetailsSchema,
requiredFields: PERSONAL_DETAILS_SCHEMA,
validationRules: personalDetailsValidationRules,
visibility: 'editable'
};

View File

@@ -1,4 +1,5 @@
import { unformatDate } from '../FormFields/InputDate/utils';
import Language from '../../../language';
export const getFormattedData = data => {
const { firstName, lastName, gender, dateOfBirth, shopperEmail, telephoneNumber } = data;
@@ -16,3 +17,19 @@ export const getFormattedData = data => {
...(telephoneNumber && { telephoneNumber })
};
};
/**
* Used by the SRPanel sorting function to tell it whether we need to prepend the field type to the SR panel message, and, if so, we retrieve the correct translation for the field type.
* (Whether we need to prepend the field type depends on whether we know that the error message correctly reflects the label of the field. Ultimately all error messages should do this
* and this mapping fn will become redundant)
*/
export const mapFieldKey = (key: string, i18n: Language): string => {
switch (key) {
case 'gender':
case 'dateOfBirth':
return i18n.get(key);
// We know that the translated error messages do contain a reference to the field they refer to, so we won't need to map them
default:
return null;
}
};

View File

@@ -1,6 +1,7 @@
import { email, telephoneNumber } from '../../../utils/regex';
import { unformatDate } from '../FormFields/InputDate/utils';
import { ValidatorRules } from '../../../utils/Validator/types';
import { isEmpty } from '../../../utils/validator-utils';
const isDateOfBirthValid = value => {
if (!value) return false;
@@ -19,31 +20,28 @@ export const personalDetailsValidationRules: ValidatorRules = {
modes: ['blur']
},
firstName: {
validate: value => {
return value && value.length > 0;
},
validate: value => (isEmpty(value) ? null : true), // valid, if there are chars other than spaces,
errorMessage: 'firstName.invalid',
modes: ['blur']
},
lastName: {
validate: value => {
return value && value.length > 0;
},
validate: value => (isEmpty(value) ? null : true),
errorMessage: 'lastName.invalid',
modes: ['blur']
},
dateOfBirth: {
validate: value => isDateOfBirthValid(value),
validate: value => (isEmpty(value) ? null : isDateOfBirthValid(value)),
errorMessage: 'dateOfBirth.invalid',
modes: ['blur']
},
telephoneNumber: {
validate: value => telephoneNumber.test(value),
validate: value => (isEmpty(value) ? null : telephoneNumber.test(value)),
errorMessage: 'telephoneNumber.invalid',
modes: ['blur']
},
shopperEmail: {
validate: value => email.test(value),
// If it's empty it's not in error, else, is it a valid email?
validate: value => (isEmpty(value) ? null : email.test(value)),
errorMessage: 'shopperEmail.invalid',
modes: ['blur']
}

View File

@@ -10,6 +10,15 @@ export interface ISRPanelContext {
shouldMoveFocusSR: boolean;
}
// Will be needed once PersonalDetails & Address have ability to administer their own SRPanel
// export const mockSRContext: ISRPanelContext = {
// srPanel: null,
// setSRMessagesFromObjects: null,
// setSRMessagesFromStrings: null,
// clearSRPanel: null,
// shouldMoveFocusSR: null
// };
export const SRPanelContext = createContext<ISRPanelContext>({
srPanel: null,
setSRMessagesFromObjects: null,

View File

@@ -11,7 +11,7 @@ type SRPanelProviderProps = {
children: ComponentChildren;
};
export type SetSRMessagesReturnFn = ({ errors, isValidating, layout, countrySpecificLabels }) => SetSRMessagesReturnObject;
export type SetSRMessagesReturnFn = ({ errors, isValidating, layout = null, countrySpecificLabels = null }) => SetSRMessagesReturnObject;
const SRPanelProvider = ({ srPanel, children }: SRPanelProviderProps) => {
const { i18n } = useCoreContext();

View File

@@ -12,6 +12,15 @@
<header></header>
<main>
<form class="merchant-checkout__form" method="post">
<div class="merchant-checkout__payment-method">
<div class="merchant-checkout__payment-method__header">
<h2>RatePay</h2>
</div>
<div class="merchant-checkout__payment-method__details">
<div class="ratepay-field"></div>
</div>
</div>
<div class="merchant-checkout__payment-method">
<div class="merchant-checkout__payment-method__header">
<h2>RatePay Direct Debit</h2>
@@ -48,14 +57,6 @@
</div>
</div>
<div class="merchant-checkout__payment-method">
<div class="merchant-checkout__payment-method__header">
<h2>RatePay</h2>
</div>
<div class="merchant-checkout__payment-method__details">
<div class="ratepay-field"></div>
</div>
</div>
<div class="merchant-checkout__payment-method">
<div class="merchant-checkout__payment-method__header">
<h2>Affirm</h2>

View File

@@ -8,6 +8,16 @@ import '../../style.scss';
window.paymentData = {};
const showComps = {
ratepay: true,
ratepaydd: true,
afterpay: true,
afterpayb2b: true,
facilypay_3x: true,
affirm: true,
atome: true
};
getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsData => {
window.checkout = await AdyenCheckout({
clientKey: process.env.__CLIENT_KEY__,
@@ -21,116 +31,130 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsData => {
amount // Optional. Used to display the amount in the Pay Button.
});
// AFTERPAY
window.afterpay = checkout
.create('afterpay_default', {
countryCode: 'NL', // 'NL' / 'BE'
visibility: {
personalDetails: 'editable', // editable [default] / readOnly / hidden
billingAddress: 'readOnly',
deliveryAddress: 'hidden'
},
data: {
billingAddress: {
city: 'Gravenhage',
country: 'NL',
houseNumberOrName: '1',
postalCode: '2521VA',
street: 'Neherkade'
// RATEPAY
if (showComps.ratepay) {
window.ratepay = checkout
.create('ratepay', {
countryCode: 'DE', // 'DE' / 'AT' / 'CH'
visibility: {
personalDetails: 'editable', // editable [default] / readOnly / hidden
billingAddress: 'editable',
deliveryAddress: 'editable'
}
}
})
.mount('.afterpay-field');
})
.mount('.ratepay-field');
}
// RATEPAY
if (showComps.ratepaydd) {
window.ratepaydd = checkout
.create('ratepay_directdebit', {
//countryCode: 'DE', // 'DE' / 'AT' / 'CH'
visibility: {
personalDetails: 'editable', // editable [default] / readOnly / hidden
billingAddress: 'editable',
deliveryAddress: 'editable'
}
})
.mount('.ratepay-direct-field');
}
// AFTERPAY
if (showComps.afterpay) {
window.afterpay = checkout
.create('afterpay_default', {
countryCode: 'NL', // 'NL' / 'BE'
visibility: {
personalDetails: 'editable', // editable [default] / readOnly / hidden
billingAddress: 'readOnly',
deliveryAddress: 'hidden'
},
data: {
billingAddress: {
city: 'Gravenhage',
country: 'NL',
houseNumberOrName: '1',
postalCode: '2521VA',
street: 'Neherkade'
}
}
})
.mount('.afterpay-field');
}
// AFTERPAY B2B
window.afterpayb2b = checkout
.create('afterpay_b2b', {
countryCode: 'NL', // 'NL' / 'BE'
visibility: {
companyDetails: 'editable' // editable [default] / readOnly / hidden
}
})
.mount('.afterpayb2b-field');
// AFFIRM
window.affirm = checkout
.create('affirm', {
countryCode: 'US', // 'US' / 'CA'
visibility: {
personalDetails: 'editable', // editable [default] / readOnly / hidden
billingAddress: 'editable',
deliveryAddress: 'editable'
},
data: {
personalDetails: {
firstName: 'Jan',
lastName: 'Jansen',
shopperEmail: 'shopper@testemail.com',
telephoneNumber: '+17203977880'
},
billingAddress: {
city: 'Boulder',
country: 'US',
houseNumberOrName: '242',
postalCode: '80302',
stateOrProvince: 'CO',
street: 'Silver Cloud Lane'
if (showComps.afterpayb2b) {
window.afterpayb2b = checkout
.create('afterpay_b2b', {
countryCode: 'NL', // 'NL' / 'BE'
visibility: {
companyDetails: 'editable' // editable [default] / readOnly / hidden
}
}
})
.mount('.affirm-field');
})
.mount('.afterpayb2b-field');
}
// FACILYPAY_3x
window.facilypay_3x = checkout
.create('facilypay_3x', {
countryCode: 'ES', // 'ES' / 'FR'
visibility: {
personalDetails: 'editable', // editable [default] / readOnly / hidden
billingAddress: 'editable',
deliveryAddress: 'editable'
}
})
.mount('.facilypay_3x-field');
if (showComps.facilypay_3x) {
window.facilypay_3x = checkout
.create('facilypay_3x', {
countryCode: 'ES', // 'ES' / 'FR'
visibility: {
personalDetails: 'editable', // editable [default] / readOnly / hidden
billingAddress: 'editable',
deliveryAddress: 'editable'
}
})
.mount('.facilypay_3x-field');
}
// RATEPAY
window.ratepay = checkout
.create('ratepay', {
countryCode: 'DE', // 'DE' / 'AT' / 'CH'
visibility: {
personalDetails: 'editable', // editable [default] / readOnly / hidden
billingAddress: 'editable',
deliveryAddress: 'editable'
}
})
.mount('.ratepay-field');
// RATEPAY
window.ratepaydd = checkout
.create('ratepay_directdebit', {
//countryCode: 'DE', // 'DE' / 'AT' / 'CH'
visibility: {
personalDetails: 'editable', // editable [default] / readOnly / hidden
billingAddress: 'editable',
deliveryAddress: 'editable'
}
})
.mount('.ratepay-direct-field');
// AFFIRM
if (showComps.affirm) {
window.affirm = checkout
.create('affirm', {
countryCode: 'US', // 'US' / 'CA'
visibility: {
personalDetails: 'editable', // editable [default] / readOnly / hidden
billingAddress: 'editable',
deliveryAddress: 'editable'
},
data: {
personalDetails: {
firstName: 'Jan',
lastName: 'Jansen',
shopperEmail: 'shopper@testemail.com',
telephoneNumber: '+17203977880'
},
billingAddress: {
city: 'Boulder',
country: 'US',
houseNumberOrName: '242',
postalCode: '80302',
stateOrProvince: 'CO',
street: 'Silver Cloud Lane'
}
}
})
.mount('.affirm-field');
}
// ATOME
window.atome = checkout
.create('atome', {
countryCode: 'SG',
data: {
personalDetails: {
firstName: 'Robert',
lastName: 'Jahnsen',
telephoneNumber: '80002018'
},
billingAddress: {
postalCode: '111111',
street: 'Silver Cloud Lane'
if (showComps.atome) {
window.atome = checkout
.create('atome', {
countryCode: 'SG',
data: {
personalDetails: {
firstName: 'Robert',
lastName: 'Jahnsen',
telephoneNumber: '80002018'
},
billingAddress: {
postalCode: '111111',
street: 'Silver Cloud Lane'
}
}
}
})
.mount('.atome-field');
})
.mount('.atome-field');
}
});