Add support for Ratepay Direct Debit (#1552)

* wip: ratepay

* clean up ratepay direct debit

* fix unit tests for IbanInput

* small test code review changes

* fixed types

* fix scss removed

* skip unit test
This commit is contained in:
António Ferreira
2022-04-11 16:12:51 +02:00
committed by GitHub
parent 116dffdcf0
commit 6b48c0f040
48 changed files with 288 additions and 131 deletions

1
.gitignore vendored
View File

@@ -4,6 +4,7 @@ coverage
*.log*
.env
.env*
stats.json
stats.html
.DS_Store

View File

@@ -0,0 +1,41 @@
import { ClientFunction, Selector } from 'testcafe';
import { OPENINVOICES_URL } from '../../pages';
fixture`Testing RatePay (OpenInvoices)`.page(`${OPENINVOICES_URL}?countryCode=NL`);
const getComponentData = ClientFunction(() => {
return window.afterpay.data;
});
const mockAddressGermany = {
city: 'City',
country: 'DE',
houseNumberOrName: '123',
postalCode: '12345',
stateOrProvince: 'N/A',
street: 'Street'
};
test.skip('should make an RatePay Direct Debit payment', async t => {
const checkboxLabelGender = Selector('.ratepay-direct-field .adyen-checkout__field--gender .adyen-checkout__radio_group__label');
const payButton = Selector('.ratepay-direct-field adyen-checkout__button--pay');
// Opens dropdown
await t
.typeText('.ratepay-direct-field .adyen-checkout__input--firstName', 'First')
.typeText('.ratepay-direct-field .adyen-checkout__input--lastName', 'Last')
// Click checkbox (in reality click its label - for some reason clicking the actual checkboxes takes ages)
.click(checkboxLabelGender.nth(0))
.typeText('.ratepay-direct-field .adyen-checkout__input--dateOfBirth', '01011990', { caretPos: 1 })
.typeText('.ratepay-direct-field .adyen-checkout__input--shopperEmail', 'test@test.com')
.typeText('.ratepay-direct-field .adyen-checkout__input--telephoneNumber', '612345678')
.typeText('.ratepay-direct-field .adyen-checkout__iban-input__owner-name', 'A. Schneider')
.typeText('.ratepay-direct-field .adyen-checkout__iban-input__iban-number', 'DE87123456781234567890')
.typeText('.ratepay-direct-field .adyen-checkout__input--street', mockAddressGermany.street)
.typeText('.ratepay-direct-field .adyen-checkout__input--houseNumberOrName', mockAddressGermany.houseNumberOrName)
.typeText('.ratepay-direct-field .adyen-checkout__input--city', mockAddressGermany.city)
.typeText('.ratepay-direct-field .adyen-checkout__input--postalCode', mockAddressGermany.postalCode)
// Can't use the checkboxLabelGender trick to speed up the click 'cos this label contains a link - so use a Selector with a timeout
.click(payButton)
.wait(5000);
});

View File

@@ -2,11 +2,12 @@ import { h } from 'preact';
import OpenInvoiceContainer from '../helpers/OpenInvoiceContainer';
import ConsentCheckboxLabel from './components/ConsentCheckboxLabel';
import { AFTERPAY_B2B_CONSENT_URL, ALLOWED_COUNTRIES } from './config';
import {OpenInvoiceContainerProps} from "../helpers/OpenInvoiceContainer/OpenInvoiceContainer";
export default class AfterPayB2B extends OpenInvoiceContainer {
public static type = 'afterpay_b2b';
protected static defaultProps = {
protected static defaultProps: OpenInvoiceContainerProps = {
onChange: () => {},
data: { companyDetails: {}, personalDetails: {}, billingAddress: {}, deliveryAddress: {} },
visibility: {

View File

@@ -0,0 +1,13 @@
import OpenInvoiceContainer from '../helpers/OpenInvoiceContainer';
import { ALLOWED_COUNTRIES } from './config';
export default class RatePayDirectDebit extends OpenInvoiceContainer {
public static type = 'ratepay_directdebit';
formatProps(props) {
return {
...super.formatProps({ ...props, ...{ visibility: { bankAccount: 'editable' } } }),
allowedCountries: props.countryCode ? [props.countryCode] : ALLOWED_COUNTRIES
};
}
}

View File

@@ -4,8 +4,8 @@ describe('Sepa', () => {
const mockStateChange = sepa => {
sepa.setState({
data: {
'sepa.ownerName': 'A. Klaassen',
'sepa.ibanNumber': 'NL13TEST0123456789'
'ownerName': 'A. Klaassen',
'ibanNumber': 'NL13TEST0123456789'
},
isValid: true
});
@@ -15,8 +15,8 @@ describe('Sepa', () => {
const mockInvalidStateChange = sepa => {
sepa.setState({
data: {
'sepa.ownerName': 'A. Klaassen',
'sepa.ibanNumber': 'NOTANIBAN'
'ownerName': 'A. Klaassen',
'ibanNumber': 'NOTANIBAN'
},
isValid: false
});

View File

@@ -1,6 +1,6 @@
import { h } from 'preact';
import UIElement from '../UIElement';
import IbanInput from './components/IbanInput';
import IbanInput from '../internal/IbanInput';
import CoreProvider from '../../core/Context/CoreProvider';
import { SepaElementData } from './types';
@@ -27,8 +27,8 @@ class SepaElement extends UIElement {
return {
paymentMethod: {
type: SepaElement.type,
iban: this.state.data['sepa.ibanNumber'],
ownerName: this.state.data['sepa.ownerName']
iban: this.state.data['ibanNumber'],
ownerName: this.state.data['ownerName']
}
};
}

View File

@@ -1,4 +0,0 @@
.adyen-checkout__iban-input__number {
text-transform: uppercase;
padding: 5px 36px 5px 8px;
}

View File

@@ -2,27 +2,25 @@ import { h } from 'preact';
import UIElement from '../../UIElement';
import OpenInvoice from '../../internal/OpenInvoice';
import CoreProvider from '../../../core/Context/CoreProvider';
import { UIElementProps } from '../../types';
import { OpenInvoiceProps } from '../../internal/OpenInvoice/types';
import { AddressSpecifications } from '../../internal/Address/types';
interface OpenInvoiceElementProps extends UIElementProps {
export interface OpenInvoiceContainerProps extends Partial<OpenInvoiceProps>{
consentCheckboxLabel?: h.JSX.Element;
billingAddressRequiredFields?: string[];
billingAddressSpecification?: AddressSpecifications;
// TODO: add other props for OpenInvoiceElement
[key: string]: any;
}
export default class OpenInvoiceContainer extends UIElement<OpenInvoiceElementProps> {
protected static defaultProps = {
export default class OpenInvoiceContainer extends UIElement<OpenInvoiceContainerProps> {
protected static defaultProps: OpenInvoiceContainerProps = {
onChange: () => {},
data: { companyDetails: {}, personalDetails: {}, billingAddress: {}, deliveryAddress: {} },
data: { companyDetails: {}, personalDetails: {}, billingAddress: {}, deliveryAddress: {}, bankAccount: {} },
visibility: {
companyDetails: 'hidden',
personalDetails: 'editable',
billingAddress: 'editable',
deliveryAddress: 'editable'
deliveryAddress: 'editable',
bankAccount: 'hidden'
}
};
@@ -65,7 +63,7 @@ export default class OpenInvoiceContainer extends UIElement<OpenInvoiceElementPr
*/
formatData() {
const { data = {} } = this.state;
const { companyDetails = {}, personalDetails = {}, billingAddress, deliveryAddress } = data;
const { companyDetails = {}, personalDetails = {}, billingAddress, deliveryAddress, bankAccount } = data;
return {
paymentMethod: {
@@ -73,6 +71,13 @@ export default class OpenInvoiceContainer extends UIElement<OpenInvoiceElementPr
},
...personalDetails,
...companyDetails,
...(bankAccount && {
bankAccount: {
iban: bankAccount.ibanNumber,
ownerName: bankAccount.ownerName,
countryCode: bankAccount.countryCode
}
}),
...(billingAddress && { billingAddress }),
...((deliveryAddress || billingAddress) && { deliveryAddress: deliveryAddress || billingAddress })
};

View File

@@ -49,6 +49,7 @@ import Klarna from './Klarna';
import Twint from './Twint';
import MealVoucherFR from './MealVoucherFR';
import OnlineBankingINElement from './OnlineBankingIN';
import RatePayDirectDebit from "./RatePay/RatePayDirectDebit";
/**
* Maps each component with a Component element.
@@ -137,6 +138,7 @@ const componentsMap = {
pix: Pix,
qiwiwallet: QiwiWallet,
ratepay: RatePay,
ratepay_directdebit: RatePayDirectDebit,
redirect: Redirect,
securedfields: SecuredFields,
sepadirectdebit: Sepa,

View File

@@ -9,8 +9,8 @@ const createWrapper = (props?) => mount(<IbanInput i18n={i18n} {...props} />);
describe('IbanInput', () => {
test('Renders two fields', () => {
const wrapper = createWrapper();
expect(wrapper.find('input[name="sepa.ownerName"]')).toHaveLength(1);
expect(wrapper.find('input[name="sepa.ibanNumber"]')).toHaveLength(1);
expect(wrapper.find('input[name="ownerName"]')).toHaveLength(1);
expect(wrapper.find('input[name="ibanNumber"]')).toHaveLength(1);
});
describe('Validation Errors', () => {
@@ -19,12 +19,12 @@ describe('IbanInput', () => {
wrapper.instance().setError('iban', true);
wrapper.update();
expect(wrapper.find('.adyen-checkout__field--error')).toHaveLength(1);
expect(wrapper.find('input[name="sepa.ibanNumber"]').prop('aria-invalid')).toBe(true);
expect(wrapper.find('input[name="ibanNumber"]').prop('aria-invalid')).toBe(true);
wrapper.instance().setError('iban', false);
wrapper.update();
expect(wrapper.find('.adyen-checkout__field--error')).toHaveLength(0);
expect(wrapper.find('input[name="sepa.ibanNumber"]').prop('aria-invalid')).toBe(false);
expect(wrapper.find('input[name="ibanNumber"]').prop('aria-invalid')).toBe(false);
});
test('Set holderName errors', () => {
@@ -55,42 +55,42 @@ describe('IbanInput', () => {
describe('Placeholders', () => {
test('Set iban placeholder', () => {
const wrapper = createWrapper({ placeholders: { ibanNumber: 'test' } });
expect(wrapper.find('input[name="sepa.ibanNumber"]').prop('placeholder')).toBe('test');
expect(wrapper.find('input[name="ibanNumber"]').prop('placeholder')).toBe('test');
});
test('Set holderName placeholder', () => {
const wrapper = createWrapper({ placeholders: { ownerName: 'test' } });
expect(wrapper.find('input[name="sepa.ownerName"]').prop('placeholder')).toBe('test');
expect(wrapper.find('input[name="ownerName"]').prop('placeholder')).toBe('test');
});
});
describe('Send values from outside', () => {
test('Set ibanNumber', () => {
const wrapper = createWrapper({ data: { 'sepa.ibanNumber': 'NL13TEST0123456789' } });
const wrapper = createWrapper({ data: { 'ibanNumber': 'NL13TEST0123456789' } });
setTimeout(() => {
expect(wrapper.find('input[name="sepa.ibanNumber"]').text()).toBe('NL13 TEST 0123 4567 89');
expect(wrapper.find('input[name="ibanNumber"]').text()).toBe('NL13 TEST 0123 4567 89');
});
});
test('Set ibanNumber formatted', () => {
const wrapper = createWrapper({ data: { 'sepa.ibanNumber': 'NL13 TEST 0123 4567 89' } });
const wrapper = createWrapper({ data: { 'ibanNumber': 'NL13 TEST 0123 4567 89' } });
setTimeout(() => {
expect(wrapper.find('input[name="sepa.ibanNumber"]').text()).toBe('NL13 TEST 0123 4567 89');
expect(wrapper.find('input[name="ibanNumber"]').text()).toBe('NL13 TEST 0123 4567 89');
});
});
test('Set ownerName', () => {
const wrapper = createWrapper({ data: { 'sepa.ownerName': 'Hello World' } });
const wrapper = createWrapper({ data: { 'ownerName': 'Hello World' } });
setTimeout(() => {
expect(wrapper.find('input[name="sepa.ownerName"]').text()).toBe('Hello World');
expect(wrapper.find('input[name="ownerName"]').text()).toBe('Hello World');
});
});
test('Set ibanNumber and ownerName', () => {
const wrapper = createWrapper({ data: { 'sepa.ibanNumber': 'NL13TEST0123456789', 'sepa.ownerName': 'Hello World' } });
const wrapper = createWrapper({ data: { 'ibanNumber': 'NL13TEST0123456789', 'ownerName': 'Hello World' } });
setTimeout(() => {
expect(wrapper.find('input[name="sepa.ibanNumber"]').text()).toBe('NL13 TEST 0123 4567 89');
expect(wrapper.find('input[name="sepa.ownerName"]').text()).toBe('Hello World');
expect(wrapper.find('input[name="ibanNumber"]').text()).toBe('NL13 TEST 0123 4567 89');
expect(wrapper.find('input[name="ownerName"]').text()).toBe('Hello World');
});
});
});

View File

@@ -1,18 +1,26 @@
import { Component, h, RefObject } from 'preact';
import useCoreContext from '../../../../core/Context/useCoreContext';
import { renderFormField } from '../../../internal/FormFields';
import Field from '../../../internal/FormFields/Field';
import useCoreContext from '../../../core/Context/useCoreContext';
import { renderFormField } from '../FormFields';
import Field from '../FormFields/Field';
import { checkIbanStatus, isValidHolder } from './validate';
import { electronicFormat, formatIban, getIbanPlaceHolder, getNextCursorPosition } from './utils';
import './IbanInput.scss';
import { electronicFormat, formatIban, getCountryCode, getIbanPlaceHolder, getNextCursorPosition } from './utils';
import Fieldset from '../FormFields/Fieldset';
interface IbanInputProps {
holderName?: string;
holderName?: boolean;
placeholders?: any;
countryCode?: string;
showPayButton?: any;
payButton?: any;
onChange: (data) => void;
label: string;
data: IbanData;
}
interface IbanData {
ownerName?: string;
ibanNumber?: string;
countryCode?: string;
}
interface IbanInputState {
@@ -23,6 +31,7 @@ interface IbanInputState {
isValid: boolean;
cursor: number;
}
class IbanInput extends Component<IbanInputProps, IbanInputState> {
private ibanNumber: RefObject<any>;
@@ -32,8 +41,9 @@ class IbanInput extends Component<IbanInputProps, IbanInputState> {
this.state = {
status: 'ready',
data: {
'sepa.ownerName': props?.data?.ownerName || '',
'sepa.ibanNumber': props?.data?.ibanNumber || ''
ownerName: props?.data?.ownerName || '',
ibanNumber: props?.data?.ibanNumber || '',
countryCode: props?.data?.countryCode || ''
},
isValid: false,
cursor: 0,
@@ -41,14 +51,14 @@ class IbanInput extends Component<IbanInputProps, IbanInputState> {
valid: {}
};
if (this.state.data['sepa.ibanNumber']) {
const electronicFormatIban = electronicFormat(this.state.data['sepa.ibanNumber']); // example: NL13TEST0123456789
this.state.data['sepa.ibanNumber'] = formatIban(electronicFormatIban); // example: NL13 TEST 0123 4567 89
if (this.state.data['ibanNumber']) {
const electronicFormatIban = electronicFormat(this.state.data['ibanNumber']); // example: NL13TEST0123456789
this.state.data['ibanNumber'] = formatIban(electronicFormatIban); // example: NL13 TEST 0123 4567 89
}
if (this.state.data['sepa.ibanNumber'] || this.state.data['sepa.ownerName']) {
const holderNameValid = this.props.holderName ? isValidHolder(this.state.data['sepa.ownerName']) : '';
const ibanValid = this.state.data['sepa.ibanNumber'] ? checkIbanStatus(this.state.data['sepa.ibanNumber']).status === 'valid' : '';
if (this.state.data['ibanNumber'] || this.state.data['ownerName']) {
const holderNameValid = this.props.holderName ? isValidHolder(this.state.data['ownerName']) : '';
const ibanValid = this.state.data['ibanNumber'] ? checkIbanStatus(this.state.data['ibanNumber']).status === 'valid' : '';
const isValid = ibanValid && holderNameValid;
const data = { data: this.state.data, isValid };
@@ -60,7 +70,8 @@ class IbanInput extends Component<IbanInputProps, IbanInputState> {
onChange: () => {},
countryCode: null,
holderName: true,
placeholders: {}
placeholders: {},
label: null
};
setStatus(status) {
@@ -68,8 +79,8 @@ class IbanInput extends Component<IbanInputProps, IbanInputState> {
}
onChange() {
const holderNameValid = this.props.holderName ? isValidHolder(this.state.data['sepa.ownerName']) : '';
const ibanValid = checkIbanStatus(this.state.data['sepa.ibanNumber']).status === 'valid';
const holderNameValid = this.props.holderName ? isValidHolder(this.state.data['ownerName']) : true;
const ibanValid = checkIbanStatus(this.state.data['ibanNumber']).status === 'valid';
const isValid = ibanValid && holderNameValid;
const data = { data: this.state.data, isValid };
@@ -90,9 +101,9 @@ class IbanInput extends Component<IbanInputProps, IbanInputState> {
public handleHolderInput = holder => {
this.setState(
prevState => ({ data: { ...prevState.data, 'sepa.ownerName': holder } }),
prevState => ({ data: { ...prevState.data, ownerName: holder } }),
() => {
this.setError('holder', !isValidHolder(this.state.data['sepa.ownerName']));
this.setError('holder', !isValidHolder(this.state.data['ownerName']));
this.onChange(); // propagate state
}
);
@@ -104,15 +115,20 @@ class IbanInput extends Component<IbanInputProps, IbanInputState> {
const iban = formatIban(electronicFormatIban); // example: NL13 TEST 0123 4567 89
const validationStatus = checkIbanStatus(iban).status;
const countryCode = getCountryCode(electronicFormatIban);
// calculate cursor's new position
const cursor = e.target.selectionStart;
const previousIban = this.state.data['sepa.ibanNumber'];
const previousIban = this.state.data['ibanNumber'];
const newCursorPosition = getNextCursorPosition(cursor, iban, previousIban);
this.setState(
prevState => ({
data: { ...prevState.data, 'sepa.ibanNumber': iban },
errors: { ...prevState.errors, iban: validationStatus === 'invalid' ? 'sepaDirectDebit.ibanField.invalid' : null },
data: { ...prevState.data, ibanNumber: iban, countryCode: countryCode },
errors: {
...prevState.errors,
iban: validationStatus === 'invalid' ? 'sepaDirectDebit.ibanField.invalid' : null
},
valid: { ...prevState.valid, iban: validationStatus === 'valid' }
}),
() => {
@@ -132,8 +148,8 @@ class IbanInput extends Component<IbanInputProps, IbanInputState> {
};
showValidation() {
const validationStatus = checkIbanStatus(this.state.data['sepa.ibanNumber']).status;
const holderStatus = isValidHolder(this.state.data['sepa.ownerName']);
const validationStatus = checkIbanStatus(this.state.data['ibanNumber']).status;
const holderStatus = isValidHolder(this.state.data['ownerName']);
this.setError('iban', validationStatus !== 'valid' ? 'sepaDirectDebit.ibanField.invalid' : null);
this.setError('holder', !holderStatus ? true : null);
}
@@ -141,22 +157,22 @@ class IbanInput extends Component<IbanInputProps, IbanInputState> {
render({ placeholders, countryCode }: IbanInputProps, { data, errors, valid }) {
const { i18n } = useCoreContext();
return (
<div className="adyen-checkout__iban-input">
<Fieldset classNameModifiers={['iban-input']} label={this.props.label}>
{this.props.holderName && (
<Field
className={'adyen-checkout__field--owner-name'}
label={i18n.get('sepa.ownerName')}
filled={data['sepa.ownerName'] && data['sepa.ownerName'].length}
label={i18n.get('ownerName')}
filled={data['ownerName'] && data['ownerName'].length}
errorMessage={errors.holder ? i18n.get('creditCard.holderName.invalid') : false}
dir={'ltr'}
>
{renderFormField('text', {
name: 'sepa.ownerName',
name: 'ownerName',
className: 'adyen-checkout__iban-input__owner-name',
placeholder: 'ownerName' in placeholders ? placeholders.ownerName : i18n.get('sepaDirectDebit.nameField.placeholder'),
value: data['sepa.ownerName'],
value: data['ownerName'],
'aria-invalid': !!this.state.errors.holder,
'aria-label': i18n.get('sepa.ownerName'),
'aria-label': i18n.get('ownerName'),
onInput: e => this.handleHolderInput(e.target.value)
})}
</Field>
@@ -164,9 +180,9 @@ class IbanInput extends Component<IbanInputProps, IbanInputState> {
<Field
className={'adyen-checkout__field--iban-number'}
label={i18n.get('sepa.ibanNumber')}
label={i18n.get('ibanNumber')}
errorMessage={errors.iban ? i18n.get(errors.iban) : false}
filled={data['sepa.ibanNumber'] && data['sepa.ibanNumber'].length}
filled={data['ibanNumber'] && data['ibanNumber'].length}
isValid={valid.iban}
onBlur={this.handleIbanBlur}
dir={'ltr'}
@@ -175,21 +191,21 @@ class IbanInput extends Component<IbanInputProps, IbanInputState> {
ref: ref => {
this.ibanNumber = ref;
},
name: 'sepa.ibanNumber',
name: 'ibanNumber',
className: 'adyen-checkout__iban-input__iban-number',
classNameModifiers: ['large'],
placeholder: 'ibanNumber' in placeholders ? placeholders.ibanNumber : getIbanPlaceHolder(countryCode),
value: data['sepa.ibanNumber'],
value: data['ibanNumber'],
onInput: this.handleIbanInput,
'aria-invalid': !!this.state.errors.iban,
'aria-label': i18n.get('sepa.ibanNumber'),
'aria-label': i18n.get('ibanNumber'),
autocorrect: 'off',
spellcheck: false
})}
</Field>
{this.props.showPayButton && this.props.payButton({ status: this.state.status })}
</div>
</Fieldset>
);
}
}

View File

@@ -66,12 +66,13 @@ export const formatIban = iban =>
.replace(/(.{4})(?!$)/g, '$1 ')
.trim();
export type ElectronicFormat = string;
/**
* Returns any non alphanumeric characters and uppercases them
*
* @internal
*/
export const electronicFormat = iban => {
export const electronicFormat = (iban: string): ElectronicFormat => {
const NON_ALPHANUM = /[^a-zA-Z0-9]/g;
return iban.replace(NON_ALPHANUM, '').toUpperCase();
};
@@ -183,3 +184,9 @@ export const getNextCursorPosition = (cursor, iban, previousIban) => {
return cursor;
};
/**
* @param electronicFormatIban -
* @returns countryCode string
*/
export const getCountryCode = (electronicFormatIban: ElectronicFormat) => electronicFormatIban.slice(0, 2);

View File

@@ -1,4 +1,11 @@
import { iso13616Prepare, iso7064Mod97_10, electronicFormat, regex, getIbanCountrySpecification } from './utils';
import {
iso13616Prepare,
iso7064Mod97_10,
electronicFormat,
regex,
getIbanCountrySpecification,
getCountryCode
} from './utils';
/**
* Contains a validation status
@@ -46,7 +53,7 @@ export const checkIbanStatus = iban => {
return new ValidationStatus('no-validate', 'TOO_SHORT'); // A
}
const countryCode = electronicFormatIban.slice(0, 2);
const countryCode = getCountryCode(electronicFormatIban);
const countrySpecification = getIbanCountrySpecification(countryCode);
if (!countrySpecification) {

View File

@@ -16,6 +16,7 @@ import {
OpenInvoiceStateValid
} from './types';
import './OpenInvoice.scss';
import IbanInput from '../IbanInput';
export default function OpenInvoice(props: OpenInvoiceProps) {
const { countryCode, visibility } = props;
@@ -58,7 +59,10 @@ export default function OpenInvoice(props: OpenInvoiceProps) {
};
const handleSeparateDeliveryAddress = () => {
setActiveFieldsets(prevActiveFields => ({ ...prevActiveFields, deliveryAddress: !activeFieldsets.deliveryAddress }));
setActiveFieldsets(prevActiveFields => ({
...prevActiveFields,
deliveryAddress: !activeFieldsets.deliveryAddress
}));
};
const handleConsentCheckbox = e => {
@@ -101,6 +105,16 @@ export default function OpenInvoice(props: OpenInvoiceProps) {
/>
)}
{activeFieldsets.bankAccount && (
<IbanInput
holderName={true}
label="bankAccount"
data={data.bankAccount}
onChange={handleFieldset('bankAccount')}
ref={fieldsetsRefs.bankAccount}
/>
)}
{activeFieldsets.billingAddress && (
<Address
allowedCountries={props.allowedCountries}

View File

@@ -1,15 +1,24 @@
import { AddressData, FieldsetVisibility, PersonalDetailsSchema } from '../../../types';
import { CompanyDetailsSchema } from '../CompanyDetails/types';
import { AddressSpecifications } from '../Address/types';
import {UIElementProps} from "../../types";
import UIElement from "../../UIElement";
export interface OpenInvoiceVisibility {
companyDetails?: FieldsetVisibility;
personalDetails?: FieldsetVisibility;
billingAddress?: FieldsetVisibility;
deliveryAddress?: FieldsetVisibility;
bankAccount?: FieldsetVisibility;
}
export interface OpenInvoiceProps {
export interface BankDetailsSchema {
countryCode?: string,
ibanNumber?: any,
ownerName?: string
}
export interface OpenInvoiceProps extends UIElementProps{
allowedCountries?: string[];
consentCheckboxLabel: any;
countryCode?: string;
@@ -18,8 +27,9 @@ export interface OpenInvoiceProps {
personalDetails?: PersonalDetailsSchema;
billingAddress?: AddressData;
deliveryAddress?: AddressData;
bankAccount?: BankDetailsSchema
};
onChange: Function;
onChange: (state: any, element?: UIElement) => void;
payButton: any;
showPayButton?: boolean;
visibility?: OpenInvoiceVisibility;
@@ -33,6 +43,7 @@ export interface OpenInvoiceStateData {
personalDetails?: PersonalDetailsSchema;
billingAddress?: AddressData;
deliveryAddress?: AddressData;
bankAccount?: BankDetailsSchema
consentCheckbox?: boolean;
}
@@ -42,6 +53,7 @@ export interface OpenInvoiceStateError {
billingAddress?: boolean;
deliveryAddress?: boolean;
personalDetails?: boolean;
bankAccount?: boolean;
}
export interface OpenInvoiceStateValid {
@@ -50,6 +62,7 @@ export interface OpenInvoiceStateValid {
billingAddress?: boolean;
deliveryAddress?: boolean;
personalDetails?: boolean;
bankAccount?: boolean;
}
export interface OpenInvoiceActiveFieldsets {
@@ -57,6 +70,7 @@ export interface OpenInvoiceActiveFieldsets {
personalDetails: boolean;
billingAddress: boolean;
deliveryAddress: boolean;
bankAccount: boolean;
}
export interface OpenInvoiceFieldsetsRefs {
@@ -64,4 +78,5 @@ export interface OpenInvoiceFieldsetsRefs {
personalDetails?;
billingAddress?;
deliveryAddress?;
bankAccount?;
}

View File

@@ -1,6 +1,12 @@
import { OpenInvoiceActiveFieldsets, OpenInvoiceStateData, OpenInvoiceVisibility } from './types';
export const fieldsetsSchema: Array<keyof OpenInvoiceStateData> = ['companyDetails', 'personalDetails', 'billingAddress', 'deliveryAddress'];
export const fieldsetsSchema: Array<keyof OpenInvoiceStateData> = [
'companyDetails',
'personalDetails',
'billingAddress',
'deliveryAddress',
'bankAccount'
];
const isPrefilled = (fieldsetData: object = {}): boolean => Object.keys(fieldsetData).length > 1;

View File

@@ -27,8 +27,8 @@
"installments.revolving": "الدفع الدوري",
"sepaDirectDebit.ibanField.invalid": "رقم حساب غير صحيح",
"sepaDirectDebit.nameField.placeholder": "جميل سعيد",
"sepa.ownerName": "صاحب الحساب",
"sepa.ibanNumber": "رقم الحساب (IBAN)",
"ownerName": "صاحب الحساب",
"ibanNumber": "رقم الحساب (IBAN)",
"error.title": "خطأ",
"error.subtitle.redirect": "فشل إعادة التوجيه",
"error.subtitle.payment": "فشل الدفع",

View File

@@ -27,8 +27,8 @@
"installments.revolving": "Opakující se platba",
"sepaDirectDebit.ibanField.invalid": "Neplatné číslo účtu",
"sepaDirectDebit.nameField.placeholder": "Jan Novák",
"sepa.ownerName": "Jméno držitele účtu",
"sepa.ibanNumber": "Číslo účtu (IBAN)",
"ownerName": "Jméno držitele účtu",
"ibanNumber": "Číslo účtu (IBAN)",
"error.title": "Chyba",
"error.subtitle.redirect": "Přesměrování selhalo",
"error.subtitle.payment": "Platba selhala",

View File

@@ -27,8 +27,8 @@
"installments.revolving": "Løbende betaling",
"sepaDirectDebit.ibanField.invalid": "Ugyldigt kontonummer",
"sepaDirectDebit.nameField.placeholder": "J. Smith",
"sepa.ownerName": "Kontohavernavn",
"sepa.ibanNumber": "Kontonummer (IBAN)",
"ownerName": "Kontohavernavn",
"ibanNumber": "Kontonummer (IBAN)",
"error.title": "Fejl",
"error.subtitle.redirect": "Omdirigering fejlede",
"error.subtitle.payment": "Betaling fejlede",

View File

@@ -27,8 +27,8 @@
"installments.revolving": "Ratenzahlung",
"sepaDirectDebit.ibanField.invalid": "Ungültige Kontonummer",
"sepaDirectDebit.nameField.placeholder": "A. Müller",
"sepa.ownerName": "Name des Kontoinhabers",
"sepa.ibanNumber": "Kontonummer (IBAN)",
"ownerName": "Name des Kontoinhabers",
"ibanNumber": "Kontonummer (IBAN)",
"error.title": "Fehler",
"error.subtitle.redirect": "Weiterleitung fehlgeschlagen",
"error.subtitle.payment": "Zahlung fehlgeschlagen",

View File

@@ -27,8 +27,8 @@
"installments.revolving": "Ανακυκλούμενη πληρωμή",
"sepaDirectDebit.ibanField.invalid": "Μη έγκυρος αριθμός λογαριασμού",
"sepaDirectDebit.nameField.placeholder": "Γ. Παπαδάκης",
"sepa.ownerName": "Όνομα κατόχου",
"sepa.ibanNumber": "Αριθμός λογαριασμού (IBAN)",
"ownerName": "Όνομα κατόχου",
"ibanNumber": "Αριθμός λογαριασμού (IBAN)",
"error.title": "Σφάλμα",
"error.subtitle.redirect": "Η ανακατεύθυνση απέτυχε",
"error.subtitle.payment": "Η πληρωμή απέτυχε",

View File

@@ -27,8 +27,8 @@
"installments.revolving": "Revolving payment",
"sepaDirectDebit.ibanField.invalid": "Invalid account number",
"sepaDirectDebit.nameField.placeholder": "J. Smith",
"sepa.ownerName": "Holder Name",
"sepa.ibanNumber": "Account Number (IBAN)",
"ownerName": "Holder Name",
"ibanNumber": "Account Number (IBAN)",
"error.title": "Error",
"error.subtitle.redirect": "Redirect failed",
"error.subtitle.payment": "Payment failed",
@@ -214,5 +214,8 @@
"pix.instructions": "Open the app with the PIX registered key, choose Pay with PIX and scan the QR Code or copy and paste the code",
"twint.saved": "saved",
"orPayWith": "or pay with",
"invalidFormatExpects": "Invalid format. Expected format: %{format}"
"invalidFormatExpects": "Invalid format. Expected format: %{format}",
"ownerName": "Holder Name",
"ibanNumber": "Account Number (IBAN)",
"bankAccount": "Bank Details"
}

View File

@@ -25,8 +25,8 @@
"installments.revolving": "Pago rotativo",
"sepaDirectDebit.ibanField.invalid": "Número de cuenta no válido",
"sepaDirectDebit.nameField.placeholder": "J. Smith",
"sepa.ownerName": "Nombre del titular de cuenta",
"sepa.ibanNumber": "Número de cuenta (IBAN)",
"ownerName": "Nombre del titular de cuenta",
"ibanNumber": "Número de cuenta (IBAN)",
"error.title": "Error",
"error.subtitle.redirect": "Redirección fallida",
"error.subtitle.payment": "Pago fallido",

View File

@@ -27,8 +27,8 @@
"installments.revolving": "Toistuva maksu",
"sepaDirectDebit.ibanField.invalid": "Väärä tilin numero",
"sepaDirectDebit.nameField.placeholder": "J. Smith",
"sepa.ownerName": "Haltijan nimi",
"sepa.ibanNumber": "Tilinumero (IBAN)",
"ownerName": "Haltijan nimi",
"ibanNumber": "Tilinumero (IBAN)",
"error.title": "Virhe",
"error.subtitle.redirect": "Uuteen kohteeseen siirto epäonnistui",
"error.subtitle.payment": "Maksu epännistui",

View File

@@ -27,8 +27,8 @@
"installments.revolving": "Paiement en plusieurs fois",
"sepaDirectDebit.ibanField.invalid": "Numéro de compte non valide",
"sepaDirectDebit.nameField.placeholder": "N. Bernard",
"sepa.ownerName": "Au nom de",
"sepa.ibanNumber": "Numéro de compte (IBAN)",
"ownerName": "Au nom de",
"ibanNumber": "Numéro de compte (IBAN)",
"error.title": "Erreur",
"error.subtitle.redirect": "Échec de la redirection",
"error.subtitle.payment": "Échec du paiement",

View File

@@ -27,8 +27,8 @@
"installments.revolving": "Obnovljivo plaćanje",
"sepaDirectDebit.ibanField.invalid": "Nevažeći broj računa",
"sepaDirectDebit.nameField.placeholder": "J. Smith",
"sepa.ownerName": "Ime vlasnika",
"sepa.ibanNumber": "Broj računa (IBAN)",
"ownerName": "Ime vlasnika",
"ibanNumber": "Broj računa (IBAN)",
"error.title": "Greška",
"error.subtitle.redirect": "Preusmjeravanje nije uspjelo",
"error.subtitle.payment": "Plaćanje nije uspjelo",

View File

@@ -27,8 +27,8 @@
"installments.revolving": "Többösszegű fizetés",
"sepaDirectDebit.ibanField.invalid": "Érvénytelen számlaszám",
"sepaDirectDebit.nameField.placeholder": "Gipsz Jakab",
"sepa.ownerName": "Számlatulajdonos neve",
"sepa.ibanNumber": "Számlaszám (IBAN)",
"ownerName": "Számlatulajdonos neve",
"ibanNumber": "Számlaszám (IBAN)",
"error.title": "Hiba",
"error.subtitle.redirect": "Sikertelen átirányítás",
"error.subtitle.payment": "Sikertelen fizetés",

View File

@@ -25,8 +25,8 @@
"installments.revolving": "Pagamento ricorrente",
"sepaDirectDebit.ibanField.invalid": "Numero di conto non valido",
"sepaDirectDebit.nameField.placeholder": "A. Bianchi",
"sepa.ownerName": "Nome Intestatario Conto",
"sepa.ibanNumber": "Numero di conto (IBAN)",
"ownerName": "Nome Intestatario Conto",
"ibanNumber": "Numero di conto (IBAN)",
"error.title": "Errore",
"error.subtitle.redirect": "Reindirizzamento non riuscito",
"error.subtitle.payment": "Pagamento non riuscito",

View File

@@ -27,8 +27,8 @@
"installments.revolving": "リボ払い",
"sepaDirectDebit.ibanField.invalid": "口座番号の入力間違い",
"sepaDirectDebit.nameField.placeholder": "J. Smith",
"sepa.ownerName": "名義",
"sepa.ibanNumber": "口座番号 (IBAN)",
"ownerName": "名義",
"ibanNumber": "口座番号 (IBAN)",
"error.title": "エラー",
"error.subtitle.redirect": "画面の切り替え失敗にしました",
"error.subtitle.payment": "支払できませんでした",

View File

@@ -27,8 +27,8 @@
"installments.revolving": "리볼빙 결제",
"sepaDirectDebit.ibanField.invalid": "유효하지 않은 계좌 번호",
"sepaDirectDebit.nameField.placeholder": "J. Smith",
"sepa.ownerName": "소유자 이름",
"sepa.ibanNumber": "계좌 번호(IBAN)",
"ownerName": "소유자 이름",
"ibanNumber": "계좌 번호(IBAN)",
"error.title": "오류",
"error.subtitle.redirect": "리디렉션 실패",
"error.subtitle.payment": "결제 실패",

View File

@@ -27,8 +27,8 @@
"installments.revolving": "Terugkerende betaling",
"sepaDirectDebit.ibanField.invalid": "Ongeldig rekeningnummer",
"sepaDirectDebit.nameField.placeholder": "J. Janssen",
"sepa.ownerName": "Ten name van",
"sepa.ibanNumber": "Rekeningnummer (IBAN)",
"ownerName": "Ten name van",
"ibanNumber": "Rekeningnummer (IBAN)",
"error.title": "Fout",
"error.subtitle.redirect": "Doorsturen niet gelukt",
"error.subtitle.payment": "Betaling is niet geslaagd",

View File

@@ -27,8 +27,8 @@
"installments.revolving": "Gjentakende betaling",
"sepaDirectDebit.ibanField.invalid": "Ugyldig kontonummer",
"sepaDirectDebit.nameField.placeholder": "O. Nordmann",
"sepa.ownerName": "Kortholders navn",
"sepa.ibanNumber": "Kontonummer (IBAN)",
"ownerName": "Kortholders navn",
"ibanNumber": "Kontonummer (IBAN)",
"error.title": "Feil",
"error.subtitle.redirect": "Videresending feilet",
"error.subtitle.payment": "Betaling feilet",

View File

@@ -27,8 +27,8 @@
"installments.revolving": "Płatność odnawialna",
"sepaDirectDebit.ibanField.invalid": "Nieprawidłowy numer rachunku",
"sepaDirectDebit.nameField.placeholder": "J. Kowalski",
"sepa.ownerName": "Imię i nazwisko posiadacza karty",
"sepa.ibanNumber": "Numer rachunku (IBAN)",
"ownerName": "Imię i nazwisko posiadacza karty",
"ibanNumber": "Numer rachunku (IBAN)",
"error.title": "Błąd",
"error.subtitle.redirect": "Przekierowanie nie powiodło się",
"error.subtitle.payment": "Płatność nie powiodła się",

View File

@@ -25,8 +25,8 @@
"installments.revolving": "Pagamento rotativo",
"sepaDirectDebit.ibanField.invalid": "Número de conta inválido",
"sepaDirectDebit.nameField.placeholder": "J. Silva",
"sepa.ownerName": "Nome do titular da conta bancária",
"sepa.ibanNumber": "Número de conta (NIB)",
"ownerName": "Nome do titular da conta bancária",
"ibanNumber": "Número de conta (NIB)",
"error.title": "Erro",
"error.subtitle.redirect": "Falha no redirecionamento",
"error.subtitle.payment": "Falha no pagamento",

View File

@@ -27,8 +27,8 @@
"installments.revolving": "Plată recurentă",
"sepaDirectDebit.ibanField.invalid": "Numărul de cont este incorect",
"sepaDirectDebit.nameField.placeholder": "J. Smith",
"sepa.ownerName": "Nume posesor",
"sepa.ibanNumber": "Număr cont (IBAN)",
"ownerName": "Nume posesor",
"ibanNumber": "Număr cont (IBAN)",
"error.title": "Eroare",
"error.subtitle.redirect": "Redirecționare eșuată",
"error.subtitle.payment": "Plată eșuată",

View File

@@ -27,8 +27,8 @@
"installments.revolving": "Повторяющаяся оплата",
"sepaDirectDebit.ibanField.invalid": "Недействительный номер счета",
"sepaDirectDebit.nameField.placeholder": "И. Петров",
"sepa.ownerName": "Имя владельца",
"sepa.ibanNumber": "Номер счета (IBAN)",
"ownerName": "Имя владельца",
"ibanNumber": "Номер счета (IBAN)",
"error.title": "Ошибка",
"error.subtitle.redirect": "Сбой перенаправления",
"error.subtitle.payment": "Сбой оплаты",

View File

@@ -27,8 +27,8 @@
"installments.revolving": "Revolvingová platba",
"sepaDirectDebit.ibanField.invalid": "Neplatné číslo účtu",
"sepaDirectDebit.nameField.placeholder": "J. Novák",
"sepa.ownerName": "Meno držiteľa",
"sepa.ibanNumber": "Číslo účtu (IBAN)",
"ownerName": "Meno držiteľa",
"ibanNumber": "Číslo účtu (IBAN)",
"error.title": "Chyba",
"error.subtitle.redirect": "Nepodarilo sa presmerovať",
"error.subtitle.payment": "Platba zlyhala",

View File

@@ -27,8 +27,8 @@
"installments.revolving": "Revolving plačilo",
"sepaDirectDebit.ibanField.invalid": "Neveljavna številka računa",
"sepaDirectDebit.nameField.placeholder": "J. Novak",
"sepa.ownerName": "Ime imetnika",
"sepa.ibanNumber": "Številka računa (IBAN)",
"ownerName": "Ime imetnika",
"ibanNumber": "Številka računa (IBAN)",
"error.title": "Napaka",
"error.subtitle.redirect": "Preusmeritev ni uspela",
"error.subtitle.payment": "Plačilo ni uspelo",

View File

@@ -27,8 +27,8 @@
"installments.revolving": "Uppdelad betalning",
"sepaDirectDebit.ibanField.invalid": "Ogiltigt kontonummer",
"sepaDirectDebit.nameField.placeholder": "A. Andersson",
"sepa.ownerName": "Känt av kontoinnehavaren",
"sepa.ibanNumber": "Kontonummer (IBAN)",
"ownerName": "Känt av kontoinnehavaren",
"ibanNumber": "Kontonummer (IBAN)",
"error.title": "Fel",
"error.subtitle.redirect": "Omdirigering misslyckades",
"error.subtitle.payment": "Betalning misslyckades",

View File

@@ -27,8 +27,8 @@
"installments.revolving": "循环支付",
"sepaDirectDebit.ibanField.invalid": "无效的账号",
"sepaDirectDebit.nameField.placeholder": "J. Smith",
"sepa.ownerName": "持卡人姓名",
"sepa.ibanNumber": "账号 (IBAN)",
"ownerName": "持卡人姓名",
"ibanNumber": "账号 (IBAN)",
"error.title": "错误",
"error.subtitle.redirect": "重定向失败",
"error.subtitle.payment": "支付失败",

View File

@@ -27,8 +27,8 @@
"installments.revolving": "延期付款",
"sepaDirectDebit.ibanField.invalid": "帳戶號碼無效",
"sepaDirectDebit.nameField.placeholder": "J. Smith",
"sepa.ownerName": "持有人名稱",
"sepa.ibanNumber": "帳戶號碼 (IBAN)",
"ownerName": "持有人名稱",
"ibanNumber": "帳戶號碼 (IBAN)",
"error.title": "錯誤",
"error.subtitle.redirect": "無法重新導向",
"error.subtitle.payment": "付款失敗",

View File

@@ -31,6 +31,15 @@ export async function initSession() {
paymentMethodsConfiguration: {
paywithgoogle: {
buttonType: 'plain'
},
card: {
hasHolderName: true,
holderNameRequired: true,
holderName: 'J. Smith',
positionHolderNameOnTop: true,
// billingAddress config:
billingAddressRequired: true
}
}
});

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 Direct Debit</h2>
</div>
<div class="merchant-checkout__payment-method__details">
<div class="ratepay-direct-field"></div>
</div>
</div>
<div class="merchant-checkout__payment-method">
<div class="merchant-checkout__payment-method__header">
<h2>AfterPay</h2>

View File

@@ -104,6 +104,18 @@ getPaymentMethods({ amount, shopperLocale }).then(async paymentMethodsData => {
})
.mount('.ratepay-field');
// RATEPAY
window.ratepay = checkout
.create('ratepay_directdebit', {
//countryCode: 'DE', // 'DE' / 'AT' / 'CH'
visibility: {
personalDetails: 'editable', // editable [default] / readOnly / hidden
billingAddress: 'editable',
deliveryAddress: 'editable'
}
})
.mount('.ratepay-direct-field');
// ATOME
window.atome = checkout
.create('atome', {