Merge pull request #980 from sharetribe/add-stripe-business-accounts

Add stripe business accounts
This commit is contained in:
Jenni Nurmi 2019-01-07 14:40:31 +02:00 committed by GitHub
commit 400a0d3df2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 987 additions and 261 deletions

View file

@ -14,6 +14,9 @@ way to update this template, but currently, we follow a pattern:
## Upcoming version 2019-XX-XX
- [add] Support for Stripe company accounts. `PayoutDetailsForm` was separated into smaller
subcomponents. Multiple new translation keys were added and they might not be translated into
French yet. [#980](https://github.com/sharetribe/flex-template-web/pull/980)
- Manage availability of listings. This works for listings that have booking unit type:
'line-item/night', or 'line-item/day'. There's also 'manage availability' link in the
ManageListingCards of "your listings" page.

View file

@ -102,3 +102,14 @@
margin-bottom: 0;
}
}
.modalTitle {
@apply --marketplaceModalTitleStyles;
}
.modalPayoutDetailsWrapper {
@media (--viewportMedium) {
width: 604px;
padding-top: 11px;
}
}

View file

@ -264,7 +264,7 @@ class EditListingWizard extends Component {
onClose={this.handlePayoutModalClose}
onManageDisableScrolling={onManageDisableScrolling}
>
<div className={css.modalHeaderWrapper}>
<div className={css.modalPayoutDetailsWrapper}>
<h1 className={css.modalTitle}>
<FormattedMessage id="EditListingPhotosPanel.payoutModalTitleOneMoreThing" />
<br />
@ -273,14 +273,14 @@ class EditListingWizard extends Component {
<p className={css.modalMessage}>
<FormattedMessage id="EditListingPhotosPanel.payoutModalInfo" />
</p>
<PayoutDetailsForm
className={css.payoutDetails}
inProgress={fetchInProgress}
createStripeAccountError={errors ? errors.createStripeAccountError : null}
onChange={onPayoutDetailsFormChange}
onSubmit={this.handlePayoutSubmit}
/>
</div>
<PayoutDetailsForm
className={css.payoutDetails}
inProgress={fetchInProgress}
createStripeAccountError={errors ? errors.createStripeAccountError : null}
onChange={onPayoutDetailsFormChange}
onSubmit={this.handlePayoutSubmit}
/>
</Modal>
</div>
);

View file

@ -15,6 +15,16 @@
display: inline;
}
/* Highlight the borders if the checkbox is hovered, focused or checked */
&:hover + label .notChecked,
&:hover + label .required,
&:focus + label .notChecked,
&:focus + label .required,
&:checked + label .notChecked,
&:checked + label .required {
stroke: var(--matterColorDark);
}
/* Hightlight the text on checked, hover and focus */
&:focus + label .text,
&:hover + label .text,
@ -26,13 +36,13 @@
.label {
display: flex;
align-items: center;
padding: 0;
padding-top: 6px;
}
.radioButtonWrapper {
/* This should follow line-height */
height: 32px;
margin-top: -1px;
margin-top: -2px;
margin-right: 12px;
align-self: baseline;
@ -49,10 +59,16 @@
.notChecked {
stroke: var(--matterColorAnti);
&:hover {
stroke: pink;
}
}
.required {
stroke: var(--attentionColor);
&:hover {
stroke: pink;
}
}
.text {

View file

@ -0,0 +1,5 @@
@import '../../marketplace.css';
.root {
fill: var(--marketplaceColor);
}

View file

@ -0,0 +1,7 @@
import IconAdd from './IconAdd';
export const Icon = {
component: IconAdd,
props: {},
group: 'icons',
};

View file

@ -0,0 +1,33 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import css from './IconAdd.css';
const IconAdd = props => {
const { className, rootClassName } = props;
const classes = classNames(rootClassName || css.root, className);
return (
<svg className={classes} width="12" height="12" xmlns="http://www.w3.org/2000/svg">
<path
d="M6.89 10.4V6.888h3.509a.889.889 0 1 0 0-1.779H6.89V1.6a.89.89 0 0 0-1.778 0v3.511h-3.51a.888.888 0 1 0 0 1.778h3.51v3.51a.889.889 0 1 0 1.778 0"
fillRule="evenodd"
/>
</svg>
);
};
const { string } = PropTypes;
IconAdd.defaultProps = {
className: null,
rootClassName: null,
};
IconAdd.propTypes = {
className: string,
rootClassName: string,
};
export default IconAdd;

View file

@ -5,3 +5,11 @@ export const Icon = {
props: {},
group: 'icons',
};
export const IconSmall = {
component: IconClose,
props: {
size: 'small',
},
group: 'icons',
};

View file

@ -3,11 +3,23 @@ import PropTypes from 'prop-types';
import classNames from 'classnames';
import css from './IconClose.css';
const SIZE_SMALL = 'small';
const IconClose = props => {
const { className, rootClassName } = props;
const { className, rootClassName, size } = props;
const classes = classNames(rootClassName || css.root, className);
if (size === SIZE_SMALL) {
return (
<svg className={classes} width="9" height="9" xmlns="http://www.w3.org/2000/svg">
<path
d="M2.175 8.396l2.482-2.482 2.482 2.482a.889.889 0 1 0 1.258-1.257L5.914 4.657l2.482-2.483A.89.89 0 0 0 7.139.917L4.657 3.4 2.175.918A.888.888 0 1 0 .917 2.174L3.4 4.657.918 7.139a.889.889 0 1 0 1.257 1.257"
fillRule="evenodd"
/>
</svg>
);
}
return (
<svg
className={classes}

View file

@ -30,6 +30,7 @@ export { default as FieldSelect } from './FieldSelect/FieldSelect';
export { default as FieldTextInput } from './FieldTextInput/FieldTextInput';
export { default as Footer } from './Footer/Footer';
export { default as Form } from './Form/Form';
export { default as IconAdd } from './IconAdd/IconAdd';
export { default as IconArrowHead } from './IconArrowHead/IconArrowHead';
export { default as IconBannedUser } from './IconBannedUser/IconBannedUser';
export { default as IconCheckmark } from './IconCheckmark/IconCheckmark';

View file

@ -62,7 +62,7 @@
padding-top: 40px;
@media (--viewportMedium) {
width: 571px;
width: 604px;
padding-top: 11px;
}
}

View file

@ -389,29 +389,67 @@ export const createStripeAccount = payoutDetails => (dispatch, getState, sdk) =>
dispatch(stripeAccountCreateRequest());
const { accountType, country } = payoutDetails;
let payoutDetailValues;
if (accountType === 'company') {
payoutDetailValues = payoutDetails['company'];
} else if (accountType === 'individual') {
payoutDetailValues = payoutDetails['individual'];
}
const {
firstName,
lastName,
birthDate,
country,
streetAddress,
postalCode,
city,
state,
province,
address,
bankAccountToken,
personalIdNumber,
} = payoutDetails;
companyName,
companyTaxId,
personalAddress,
additionalOwners,
} = payoutDetailValues;
const hasProvince = province && !state;
const hasProvince = address.province && !address.state;
const address = {
city,
line1: streetAddress,
postal_code: postalCode,
state: hasProvince ? province : state,
const addressValue = {
city: address.city,
line1: address.streetAddress,
postal_code: address.postalCode,
state: hasProvince ? address.province : address.state ? address.state : '',
};
let personalAddressValue;
if (personalAddress) {
personalAddressValue = {
city: personalAddress.city,
line1: personalAddress.streetAddress,
postal_code: personalAddress.postalCode,
state: hasProvince
? personalAddress.province
: personalAddress.state
? personalAddress.state
: '',
};
}
const additionalOwnersValue = additionalOwners
? additionalOwners.map(owner => {
return {
first_name: owner.firstName,
last_name: owner.lastName,
dob: owner.birthDate,
address: {
city: owner.city,
line1: owner.streetAddress,
postal_code: owner.postalCode,
state: hasProvince ? owner.province : owner.state ? owner.state : '',
},
};
})
: [];
const idNumber =
country === 'US' ? { ssn_last_4: personalIdNumber } : { personal_id_number: personalIdNumber };
@ -420,9 +458,13 @@ export const createStripeAccount = payoutDetails => (dispatch, getState, sdk) =>
legal_entity: {
first_name: firstName,
last_name: lastName,
address: omitBy(address, isUndefined),
address: omitBy(addressValue, isUndefined),
dob: birthDate,
type: 'individual',
type: accountType,
business_name: companyName,
business_tax_id: companyTaxId,
personal_address: personalAddressValue,
additional_owners: additionalOwnersValue,
...idNumber,
},
tos_shown_and_accepted: true,

View file

@ -20,6 +20,7 @@ import * as FieldReviewRating from './components/FieldReviewRating/FieldReviewRa
import * as FieldSelect from './components/FieldSelect/FieldSelect.example';
import * as FieldTextInput from './components/FieldTextInput/FieldTextInput.example';
import * as Footer from './components/Footer/Footer.example';
import * as IconAdd from './components/IconAdd/IconAdd.example';
import * as IconBannedUser from './components/IconBannedUser/IconBannedUser.example';
import * as IconCheckmark from './components/IconCheckmark/IconCheckmark.example';
import * as IconClose from './components/IconClose/IconClose.example';
@ -118,6 +119,7 @@ export {
FieldSelect,
FieldTextInput,
Footer,
IconAdd,
IconBannedUser,
IconCheckmark,
IconClose,

View file

@ -2,6 +2,7 @@ import React from 'react';
import { bool, object, string } from 'prop-types';
import { FieldSelect, FieldTextInput } from '../../components';
import * as validators from '../../util/validators';
import { intlShape } from 'react-intl';
import { stripeCountryConfigs } from './PayoutDetailsForm';
import css from './PayoutDetailsForm.css';
@ -23,13 +24,24 @@ const CANADIAN_PROVINCES = [
];
const PayoutDetailsAddress = props => {
const { country, intl, disabled, form } = props;
const { className, country, intl, disabled, form, fieldId } = props;
const countryConfig = country ? stripeCountryConfigs(country).addressConfig : null;
const isRequired = (countryConfig, field) => {
return countryConfig[field];
};
const showTitle =
fieldId === 'company.address' ||
fieldId === 'individual' ||
fieldId === 'company.personalAddress';
const addressTitle = intl.formatMessage({
id:
fieldId === 'company.address'
? 'PayoutDetailsForm.companyAddressTitle'
: 'PayoutDetailsForm.streetAddressLabel',
});
const showAddressLine = country && isRequired(countryConfig, 'addressLine');
const streetAddressLabel = intl.formatMessage({
@ -89,11 +101,13 @@ const PayoutDetailsAddress = props => {
);
return (
<div>
<div className={className ? className : css.sectionContainer}>
{showTitle ? <h3 className={css.subTitle}>{addressTitle}</h3> : null}
{showAddressLine ? (
<FieldTextInput
id="streetAddress"
name="streetAddress"
id={`${fieldId}.streetAddress`}
name={`${fieldId}.streetAddress`}
disabled={disabled}
className={css.field}
type="text"
@ -101,14 +115,14 @@ const PayoutDetailsAddress = props => {
label={streetAddressLabel}
placeholder={streetAddressPlaceholder}
validate={streetAddressRequired}
onUnmount={() => form.change('streetAddress', undefined)}
onUnmount={() => form.change(`${fieldId}.streetAddress`, undefined)}
/>
) : null}
<div className={css.formRow}>
{showPostalCode ? (
<FieldTextInput
id="postalCode"
name="postalCode"
id={`${fieldId}.postalCode`}
name={`${fieldId}.postalCode`}
disabled={disabled}
className={css.postalCode}
type="text"
@ -116,13 +130,13 @@ const PayoutDetailsAddress = props => {
label={postalCodeLabel}
placeholder={postalCodePlaceholder}
validate={postalCodeRequired}
onUnmount={() => form.change('postalCode', undefined)}
onUnmount={() => form.change(`${fieldId}.postalCode`, undefined)}
/>
) : null}
{showCity ? (
<FieldTextInput
id="city"
name="city"
id={`${fieldId}.city`}
name={`${fieldId}.city`}
disabled={disabled}
className={css.city}
type="text"
@ -130,14 +144,14 @@ const PayoutDetailsAddress = props => {
label={cityLabel}
placeholder={cityPlaceholder}
validate={cityRequired}
onUnmount={() => form.change('city', undefined)}
onUnmount={() => form.change(`${fieldId}.city`, undefined)}
/>
) : null}
</div>
{showState ? (
<FieldTextInput
id="state"
name="state"
id={`${fieldId}.state`}
name={`${fieldId}.state`}
disabled={disabled}
className={css.state}
type="text"
@ -145,14 +159,14 @@ const PayoutDetailsAddress = props => {
label={stateLabel}
placeholder={statePlaceholder}
validate={stateRequired}
onUnmount={() => form.change('state', undefined)}
onUnmount={() => form.change(`${fieldId}.state`, undefined)}
/>
) : null}
{showProvince ? (
<FieldSelect
id="province"
name="province"
id={`${fieldId}.province`}
name={`${fieldId}.province`}
disabled={disabled}
className={css.selectCountry}
autoComplete="province"
@ -175,12 +189,17 @@ const PayoutDetailsAddress = props => {
PayoutDetailsAddress.defaultProps = {
country: null,
disabled: false,
fieldId: null,
};
PayoutDetailsAddress.propTypes = {
country: string,
disabled: bool,
form: object.isRequired,
fieldId: string,
// from injectIntl
intl: intlShape.isRequired,
};
export default PayoutDetailsAddress;

View file

@ -0,0 +1,52 @@
import React from 'react';
import { bool, string } from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { StripeBankAccountTokenInputField } from '../../components';
import * as validators from '../../util/validators';
import { stripeCountryConfigs } from './PayoutDetailsForm';
import css from './PayoutDetailsForm.css';
const countryCurrency = countryCode => {
const country = stripeCountryConfigs(countryCode);
return country.currency;
};
const PayoutDetailsBankDetails = props => {
const { country, disabled, fieldId } = props;
// StripeBankAccountTokenInputField handles the error messages
// internally, we just have to make sure we require a valid token
// out of the field. Therefore the empty validation message.
const bankAccountRequired = validators.required(' ');
return (
<div className={css.sectionContainer}>
<h3 className={css.subTitle}>
<FormattedMessage id="PayoutDetailsForm.bankDetails" />
</h3>
<StripeBankAccountTokenInputField
className={css.bankDetailsStripeField}
disabled={disabled}
name={`${fieldId}.bankAccountToken`}
formName="PayoutDetailsForm"
country={country}
currency={countryCurrency(country)}
validate={bankAccountRequired}
/>
</div>
);
};
PayoutDetailsBankDetails.defaultProps = {
country: null,
disabled: false,
fieldId: null,
};
PayoutDetailsBankDetails.propTypes = {
country: string,
disabled: bool,
fieldId: string,
};
export default PayoutDetailsBankDetails;

View file

@ -1,6 +1,7 @@
@import '../../marketplace.css';
.root {
margin-top: 48px;
}
.disabled {
@ -38,6 +39,20 @@
margin-bottom: 24px;
}
.radioButtonRow {
display: flex;
justify-content: left;
flex-shrink: 0;
flex-wrap: wrap;
width: 100%;
margin-bottom: 24px;
white-space: nowrap;
}
.radioButtonRow > :first-child {
margin-right: 36px;
}
.field {
width: 100%;
}
@ -64,6 +79,10 @@
width: calc(60% - 9px);
}
.taxId {
margin-top: 24px;
}
.error {
@apply --marketplaceModalErrorStyles;
}
@ -86,3 +105,60 @@
cursor: pointer;
}
}
.bankDetailsStripeField p {
@apply --marketplaceH4FontStyles;
}
.personalAddressContainer {
margin-bottom: 28px;
}
.fieldArrayAdd {
@apply --marketplaceLinkStyles;
@apply --marketplaceSearchFilterSublabelFontStyles;
font-weight: var(--fontWeightMedium);
margin-bottom: 2px;
}
.fieldArrayRemove {
@apply --marketplaceH5FontStyles;
color: var(--matterColorAnti);
float: right;
line-height: 20px;
&:hover {
color: var(--matterColor);
}
}
.closeIcon {
@apply --marketplaceModalCloseIcon;
}
.additionalOwnerWrapper {
margin-bottom: 35px;
@media (--viewportMedium) {
margin-bottom: 56px;
}
}
.additionalOwnerWrapper .sectionContainer {
margin-bottom: 24px;
}
.additionalOwnerLabel {
display: inline-block;
}
.closeIcon {
margin-right: 5px;
}
.addIcon {
margin-right: 7px;
display: inline-block;
height: 18px;
padding-top: 1px;
}

View file

@ -3,25 +3,17 @@ import { bool, object, string } from 'prop-types';
import { compose } from 'redux';
import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
import { Form as FinalForm } from 'react-final-form';
import arrayMutators from 'final-form-arrays';
import classNames from 'classnames';
import config from '../../config';
import {
Button,
ExternalLink,
StripeBankAccountTokenInputField,
FieldSelect,
FieldBirthdayInput,
FieldTextInput,
Form,
} from '../../components';
import * as validators from '../../util/validators';
import { Button, ExternalLink, FieldRadioButton, FieldSelect, Form } from '../../components';
import { isStripeInvalidPostalCode } from '../../util/errors';
import * as validators from '../../util/validators';
import PayoutDetailsAddress from './PayoutDetailsAddress';
import PayoutDetailsFormCompany from './PayoutDetailsFormCompany';
import PayoutDetailsFormIndividual from './PayoutDetailsFormIndividual';
import css from './PayoutDetailsForm.css';
const MIN_STRIPE_ACCOUNT_AGE = 18;
const supportedCountries = config.stripe.supportedCountries.map(c => c.code);
export const stripeCountryConfigs = countryCode => {
@ -33,20 +25,17 @@ export const stripeCountryConfigs = countryCode => {
return country;
};
const countryCurrency = countryCode => {
const country = stripeCountryConfigs(countryCode);
return country.currency;
};
const PayoutDetailsFormComponent = props => (
<FinalForm
{...props}
mutators={{
...arrayMutators,
}}
render={fieldRenderProps => {
const {
className,
createStripeAccountError,
disabled,
form,
handleSubmit,
inProgress,
intl,
@ -56,49 +45,14 @@ const PayoutDetailsFormComponent = props => (
submitButtonText,
values,
} = fieldRenderProps;
const { country } = values;
const firstNameLabel = intl.formatMessage({ id: 'PayoutDetailsForm.firstNameLabel' });
const firstNamePlaceholder = intl.formatMessage({
id: 'PayoutDetailsForm.firstNamePlaceholder',
});
const firstNameRequired = validators.required(
intl.formatMessage({
id: 'PayoutDetailsForm.firstNameRequired',
})
);
const { country, accountType } = values;
const lastNameLabel = intl.formatMessage({ id: 'PayoutDetailsForm.lastNameLabel' });
const lastNamePlaceholder = intl.formatMessage({
id: 'PayoutDetailsForm.lastNamePlaceholder',
const individualAccountLabel = intl.formatMessage({
id: 'PayoutDetailsForm.individualAccount',
});
const lastNameRequired = validators.required(
intl.formatMessage({
id: 'PayoutDetailsForm.lastNameRequired',
})
);
const birthdayLabel = intl.formatMessage({ id: 'PayoutDetailsForm.birthdayLabel' });
const birthdayLabelMonth = intl.formatMessage({
id: 'PayoutDetailsForm.birthdayLabelMonth',
});
const birthdayLabelYear = intl.formatMessage({ id: 'PayoutDetailsForm.birthdayLabelYear' });
const birthdayRequired = validators.required(
intl.formatMessage({
id: 'PayoutDetailsForm.birthdayRequired',
})
);
const birthdayMinAge = validators.ageAtLeast(
intl.formatMessage(
{
id: 'PayoutDetailsForm.birthdayMinAge',
},
{
minAge: MIN_STRIPE_ACCOUNT_AGE,
}
),
MIN_STRIPE_ACCOUNT_AGE
);
const companyAccountLabel = intl.formatMessage({ id: 'PayoutDetailsForm.companyAccount' });
const countryLabel = intl.formatMessage({ id: 'PayoutDetailsForm.countryLabel' });
const countryPlaceholder = intl.formatMessage({
@ -110,59 +64,16 @@ const PayoutDetailsFormComponent = props => (
})
);
// StripeBankAccountTokenInputField handles the error messages
// internally, we just have to make sure we require a valid token
// out of the field. Therefore the empty validation message.
const bankAccountRequired = validators.required(' ');
const showPersonalIdNumber =
(country && stripeCountryConfigs(country).personalIdNumberRequired) ||
(country && stripeCountryConfigs(country).ssnLast4Required);
const personalIdNumberRequired = validators.required(
intl.formatMessage({
id: `PayoutDetailsForm.personalIdNumberRequired`,
})
);
let personalIdNumberLabel = null;
let personalIdNumberPlaceholder = null;
let personalIdNumberValid = personalIdNumberRequired;
if (country === 'US') {
personalIdNumberLabel = intl.formatMessage({
id: `PayoutDetailsForm.personalIdNumberLabel.US`,
});
personalIdNumberPlaceholder = intl.formatMessage({
id: `PayoutDetailsForm.personalIdNumberPlaceholder.US`,
});
const validSSN = validators.validSsnLast4(
intl.formatMessage({
id: `PayoutDetailsForm.personalIdNumberValid`,
})
);
personalIdNumberValid = validators.composeValidators(personalIdNumberRequired, validSSN);
} else if (country === 'HK') {
personalIdNumberLabel = intl.formatMessage({
id: `PayoutDetailsForm.personalIdNumberLabel.HK`,
});
personalIdNumberPlaceholder = intl.formatMessage({
id: `PayoutDetailsForm.personalIdNumberPlaceholder.HK`,
});
const validHKID = validators.validHKID(
intl.formatMessage({
id: `PayoutDetailsForm.personalIdNumberValid`,
})
);
personalIdNumberValid = validators.composeValidators(personalIdNumberRequired, validHKID);
}
const classes = classNames(css.root, className, {
[css.disabled]: disabled,
});
const submitInProgress = inProgress;
const submitDisabled = pristine || invalid || disabled || submitInProgress;
const showAsRequired = pristine;
const showIndividual = country && accountType && accountType === 'individual';
const showCompany = country && accountType && accountType === 'company';
let error = null;
@ -185,130 +96,87 @@ const PayoutDetailsFormComponent = props => (
<FormattedMessage id="PayoutDetailsForm.stripeConnectedAccountTermsLink" />
</ExternalLink>
);
return (
<Form className={classes} onSubmit={handleSubmit}>
<div className={css.sectionContainer}>
<h3 className={css.subTitle}>
<FormattedMessage id="PayoutDetailsForm.personalDetailsTitle" />
<FormattedMessage id="PayoutDetailsForm.accountTypeTitle" />
</h3>
<div className={css.formRow}>
<FieldTextInput
id="fname"
name="fname"
disabled={disabled}
className={css.firstName}
type="text"
autoComplete="given-name"
label={firstNameLabel}
placeholder={firstNamePlaceholder}
validate={firstNameRequired}
<div className={css.radioButtonRow}>
<FieldRadioButton
id="individual"
name="accountType"
label={individualAccountLabel}
value="individual"
showAsRequired={showAsRequired}
/>
<FieldTextInput
id="lname"
name="lname"
disabled={disabled}
className={css.lastName}
type="text"
autoComplete="family-name"
label={lastNameLabel}
placeholder={lastNamePlaceholder}
validate={lastNameRequired}
<FieldRadioButton
id="company"
name="accountType"
label={companyAccountLabel}
value="company"
showAsRequired={showAsRequired}
/>
</div>
<FieldBirthdayInput
id="birthDate"
name="birthDate"
disabled={disabled}
className={css.field}
label={birthdayLabel}
labelForMonth={birthdayLabelMonth}
labelForYear={birthdayLabelYear}
format={null}
valueFromForm={values.birthDate}
validate={validators.composeValidators(birthdayRequired, birthdayMinAge)}
/>
</div>
<div className={css.sectionContainer}>
<h3 className={css.subTitle}>
<FormattedMessage id="PayoutDetailsForm.addressTitle" />
</h3>
<FieldSelect
id="country"
name="country"
disabled={disabled}
className={css.selectCountry}
autoComplete="country"
label={countryLabel}
validate={countryRequired}
>
<option disabled value="">
{countryPlaceholder}
</option>
{supportedCountries.map(c => (
<option key={c} value={c}>
{intl.formatMessage({ id: `PayoutDetailsForm.countryNames.${c}` })}
</option>
))}
</FieldSelect>
{accountType ? (
<React.Fragment>
<div className={css.sectionContainer}>
<h3 className={css.subTitle}>Country</h3>
<FieldSelect
id="country"
name="country"
disabled={disabled}
className={css.selectCountry}
autoComplete="country"
label={countryLabel}
validate={countryRequired}
>
<option disabled value="">
{countryPlaceholder}
</option>
{supportedCountries.map(c => (
<option key={c} value={c}>
{intl.formatMessage({ id: `PayoutDetailsForm.countryNames.${c}` })}
</option>
))}
</FieldSelect>
</div>
<PayoutDetailsAddress country={country} intl={intl} disabled={disabled} form={form} />
</div>
{country ? (
<div className={css.sectionContainer}>
<h3 className={css.subTitle}>
<FormattedMessage id="PayoutDetailsForm.bankDetails" />
</h3>
<StripeBankAccountTokenInputField
disabled={disabled}
name="bankAccountToken"
formName="PayoutDetailsForm"
country={country}
currency={countryCurrency(country)}
validate={bankAccountRequired}
/>
</div>
{showIndividual ? (
<PayoutDetailsFormIndividual
fieldRenderProps={fieldRenderProps}
country={country}
/>
) : showCompany ? (
<PayoutDetailsFormCompany fieldRenderProps={fieldRenderProps} country={country} />
) : null}
{error}
<p className={css.termsText}>
<FormattedMessage
id="PayoutDetailsForm.stripeToSText"
values={{ stripeConnectedAccountTermsLink }}
/>
</p>
<Button
className={css.submitButton}
type="submit"
inProgress={submitInProgress}
disabled={submitDisabled}
ready={ready}
>
{submitButtonText ? (
submitButtonText
) : (
<FormattedMessage id="PayoutDetailsForm.submitButtonText" />
)}
</Button>
</React.Fragment>
) : null}
{showPersonalIdNumber ? (
<div className={css.sectionContainer}>
<h3 className={css.subTitle}>
<FormattedMessage id="PayoutDetailsForm.personalIdNumberTitle" />
</h3>
<FieldTextInput
id="personalIdNumber"
name="personalIdNumber"
disabled={disabled}
className={css.personalIdNumber}
type="text"
label={personalIdNumberLabel}
placeholder={personalIdNumberPlaceholder}
validate={personalIdNumberValid}
/>
</div>
) : null}
{error}
<p className={css.termsText}>
<FormattedMessage
id="PayoutDetailsForm.stripeToSText"
values={{ stripeConnectedAccountTermsLink }}
/>
</p>
<Button
className={css.submitButton}
type="submit"
inProgress={submitInProgress}
disabled={submitDisabled}
ready={ready}
>
{submitButtonText ? (
submitButtonText
) : (
<FormattedMessage id="PayoutDetailsForm.submitButtonText" />
)}
</Button>
</Form>
);
}}

View file

@ -0,0 +1,211 @@
import React from 'react';
import { bool, string } from 'prop-types';
import { compose } from 'redux';
import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
import { FieldArray } from 'react-final-form-arrays';
import { FieldTextInput, IconAdd, IconClose, InlineTextButton } from '../../components';
import * as validators from '../../util/validators';
import PayoutDetailsAddress from './PayoutDetailsAddress';
import PayoutDetailsBankDetails from './PayoutDetailsBankDetails';
import PayoutDetailsPersonalDetails from './PayoutDetailsPersonalDetails';
import { stripeCountryConfigs } from './PayoutDetailsForm';
import css from './PayoutDetailsForm.css';
// In EU, there can be a maximum of 4 additional owners
const MAX_NUMBER_OF_ADDITIONAL_OWNERS = 4;
const PayoutDetailsFormCompanyComponent = ({ fieldRenderProps }) => {
const {
id,
disabled,
form,
intl,
values,
form: {
mutators: { push },
},
} = fieldRenderProps;
const { country } = values;
const companyNameLabel = intl.formatMessage({ id: 'PayoutDetailsForm.companyNameLabel' });
const companyNamePlaceholder = intl.formatMessage({
id: 'PayoutDetailsForm.companyNamePlaceholder',
});
const companyNameRequired = validators.required(
intl.formatMessage({
id: 'PayoutDetailsForm.companyNameRequired',
})
);
const companyTaxIdLabel = intl.formatMessage({
id: `PayoutDetailsForm.companyTaxIdLabel.${country}`,
});
const companyTaxIdPlaceholder = intl.formatMessage(
{
id: 'PayoutDetailsForm.companyTaxIdPlaceholder',
},
{
idName: companyTaxIdLabel,
}
);
const companyTaxIdRequired = validators.required(
intl.formatMessage(
{
id: 'PayoutDetailsForm.companyTaxIdRequired',
},
{
idName: companyTaxIdLabel,
}
)
);
const showPersonalAddressField =
country &&
stripeCountryConfigs(country).companyConfig &&
stripeCountryConfigs(country).companyConfig.personalAddress;
const showAdditionalOwnersField =
country &&
stripeCountryConfigs(country).companyConfig &&
stripeCountryConfigs(country).companyConfig.additionalOwners;
const hasAdditionalOwners = values.company && values.company.additionalOwners;
const hasMaxNumberOfAdditionalOwners =
hasAdditionalOwners &&
values.company.additionalOwners.length >= MAX_NUMBER_OF_ADDITIONAL_OWNERS;
return (
<React.Fragment>
{country ? (
<React.Fragment>
<div className={css.sectionContainer}>
<h3 className={css.subTitle}>
<FormattedMessage id="PayoutDetailsForm.companyDetailsTitle" />
</h3>
<FieldTextInput
id="company.companyName"
name="company.companyName"
disabled={disabled}
type="text"
autoComplete="company-name"
label={companyNameLabel}
placeholder={companyNamePlaceholder}
validate={companyNameRequired}
/>
<FieldTextInput
id="company.companyTaxId"
name="company.companyTaxId"
className={css.taxId}
disabled={disabled}
type="text"
autoComplete="company-tax-id"
label={companyTaxIdLabel}
placeholder={companyTaxIdPlaceholder}
validate={companyTaxIdRequired}
/>
</div>
<PayoutDetailsAddress
country={country}
intl={intl}
disabled={disabled}
form={form}
fieldId="company.address"
/>
<PayoutDetailsBankDetails country={country} disabled={disabled} fieldId="company" />
<PayoutDetailsPersonalDetails
intl={intl}
disabled={disabled}
values={values}
country={country}
fieldId="company"
/>
{showPersonalAddressField ? (
<PayoutDetailsAddress
className={css.personalAddressContainer}
country={country}
intl={intl}
disabled={disabled}
form={form}
fieldId="company.personalAddress"
/>
) : null}
{showAdditionalOwnersField ? (
<div className={css.additionalOwnerWrapper}>
<FieldArray id={id} name={'company.additionalOwners'}>
{({ fields }) =>
fields.map((name, index) => (
<div className={css.additionalOwnerWrapper} key={name}>
<div
className={css.fieldArrayRemove}
onClick={() => fields.remove(index)}
style={{ cursor: 'pointer' }}
>
<span className={css.additionalOwnerLabel}>
<IconClose rootClassName={css.closeIcon} size="small" />
<FormattedMessage id="PayoutDetailsForm.additionalOwnerRemove" />
</span>
</div>
<PayoutDetailsPersonalDetails
intl={intl}
disabled={disabled}
values={values}
country={country}
fieldId={`company.additionalOwners.${index}`}
/>
{showPersonalAddressField ? (
<PayoutDetailsAddress
className={css.personalAddressContainer}
country={country}
intl={intl}
disabled={disabled}
form={form}
fieldId={`company.additionalOwners.${index}`}
/>
) : null}
</div>
))
}
</FieldArray>
{!hasAdditionalOwners || !hasMaxNumberOfAdditionalOwners ? (
<InlineTextButton
type="button"
className={css.fieldArrayAdd}
onClick={() => push('company.additionalOwners', undefined)}
>
<span className={css.additionalOwnerLabel}>
<IconAdd rootClassName={css.addIcon} />
<FormattedMessage id="PayoutDetailsForm.additionalOwnerLabel" />
</span>
</InlineTextButton>
) : null}
</div>
) : null}
</React.Fragment>
) : null}
</React.Fragment>
);
};
PayoutDetailsFormCompanyComponent.defaultProps = {
id: null,
disabled: false,
};
PayoutDetailsFormCompanyComponent.propTypes = {
id: string,
disabled: bool,
// from injectIntl
intl: intlShape.isRequired,
};
const PayoutDetailsFormCompany = compose(injectIntl)(PayoutDetailsFormCompanyComponent);
export default PayoutDetailsFormCompany;

View file

@ -0,0 +1,48 @@
import React from 'react';
import { bool } from 'prop-types';
import { compose } from 'redux';
import { injectIntl, intlShape } from 'react-intl';
import PayoutDetailsAddress from './PayoutDetailsAddress';
import PayoutDetailsBankDetails from './PayoutDetailsBankDetails';
import PayoutDetailsPersonalDetails from './PayoutDetailsPersonalDetails';
const PayoutDetailsFormIndividualComponent = ({ fieldRenderProps }) => {
const { disabled, form, intl, values } = fieldRenderProps;
const { country } = values;
return (
<React.Fragment>
<PayoutDetailsPersonalDetails
intl={intl}
disabled={disabled}
values={values}
country={country}
fieldId="individual"
/>
<PayoutDetailsAddress
country={country}
intl={intl}
disabled={disabled}
form={form}
fieldId="individual.address"
/>
<PayoutDetailsBankDetails country={country} disabled={disabled} fieldId="individual" />
</React.Fragment>
);
};
PayoutDetailsFormIndividualComponent.defaultProps = {
disabled: false,
};
PayoutDetailsFormIndividualComponent.propTypes = {
disabled: bool,
// from injectIntl
intl: intlShape.isRequired,
};
const PayoutDetailsFormIndividual = compose(injectIntl)(PayoutDetailsFormIndividualComponent);
export default PayoutDetailsFormIndividual;

View file

@ -0,0 +1,177 @@
import React from 'react';
import { bool, string } from 'prop-types';
import { FieldBirthdayInput, FieldTextInput } from '../../components';
import * as validators from '../../util/validators';
import { intlShape } from 'react-intl';
import { stripeCountryConfigs } from './PayoutDetailsForm';
import css from './PayoutDetailsForm.css';
const MIN_STRIPE_ACCOUNT_AGE = 18;
const PayoutDetailsPersonalDetails = props => {
const { intl, disabled, values, country, fieldId } = props;
const personalDetailsTitle = intl.formatMessage({
id:
fieldId === 'company' || fieldId === 'individual'
? 'PayoutDetailsForm.personalDetailsTitle'
: 'PayoutDetailsForm.personalDetailsAdditionalOwnerTitle',
});
const firstNameLabel = intl.formatMessage({ id: 'PayoutDetailsForm.firstNameLabel' });
const firstNamePlaceholder = intl.formatMessage({
id: 'PayoutDetailsForm.firstNamePlaceholder',
});
const firstNameRequired = validators.required(
intl.formatMessage({
id: 'PayoutDetailsForm.firstNameRequired',
})
);
const lastNameLabel = intl.formatMessage({ id: 'PayoutDetailsForm.lastNameLabel' });
const lastNamePlaceholder = intl.formatMessage({
id: 'PayoutDetailsForm.lastNamePlaceholder',
});
const lastNameRequired = validators.required(
intl.formatMessage({
id: 'PayoutDetailsForm.lastNameRequired',
})
);
const birthdayLabel = intl.formatMessage({ id: 'PayoutDetailsForm.birthdayLabel' });
const birthdayLabelMonth = intl.formatMessage({
id: 'PayoutDetailsForm.birthdayLabelMonth',
});
const birthdayLabelYear = intl.formatMessage({ id: 'PayoutDetailsForm.birthdayLabelYear' });
const birthdayRequired = validators.required(
intl.formatMessage({
id: 'PayoutDetailsForm.birthdayRequired',
})
);
const birthdayMinAge = validators.ageAtLeast(
intl.formatMessage(
{
id: 'PayoutDetailsForm.birthdayMinAge',
},
{
minAge: MIN_STRIPE_ACCOUNT_AGE,
}
),
MIN_STRIPE_ACCOUNT_AGE
);
const showPersonalIdNumber =
(country && stripeCountryConfigs(country).personalIdNumberRequired) ||
(country && stripeCountryConfigs(country).ssnLast4Required);
const personalIdNumberRequired = validators.required(
intl.formatMessage({
id: `PayoutDetailsForm.personalIdNumberRequired`,
})
);
let personalIdNumberLabel = null;
let personalIdNumberPlaceholder = null;
let personalIdNumberValid = personalIdNumberRequired;
if (country === 'US') {
personalIdNumberLabel = intl.formatMessage({
id: `PayoutDetailsForm.personalIdNumberLabel.US`,
});
personalIdNumberPlaceholder = intl.formatMessage({
id: `PayoutDetailsForm.personalIdNumberPlaceholder.US`,
});
const validSSN = validators.validSsnLast4(
intl.formatMessage({
id: `PayoutDetailsForm.personalIdNumberValid`,
})
);
personalIdNumberValid = validators.composeValidators(personalIdNumberRequired, validSSN);
} else if (country === 'HK') {
personalIdNumberLabel = intl.formatMessage({
id: `PayoutDetailsForm.personalIdNumberLabel.HK`,
});
personalIdNumberPlaceholder = intl.formatMessage({
id: `PayoutDetailsForm.personalIdNumberPlaceholder.HK`,
});
const validHKID = validators.validHKID(
intl.formatMessage({
id: `PayoutDetailsForm.personalIdNumberValid`,
})
);
personalIdNumberValid = validators.composeValidators(personalIdNumberRequired, validHKID);
}
return (
<div className={css.sectionContainer}>
<h3 className={css.subTitle}>{personalDetailsTitle}</h3>
<div className={css.formRow}>
<FieldTextInput
id={`${fieldId}.firstName`}
name={`${fieldId}.firstName`}
disabled={disabled}
className={css.firstName}
type="text"
autoComplete="given-name"
label={firstNameLabel}
placeholder={firstNamePlaceholder}
validate={firstNameRequired}
/>
<FieldTextInput
id={`${fieldId}.lastName`}
name={`${fieldId}.lastName`}
disabled={disabled}
className={css.lastName}
type="text"
autoComplete="family-name"
label={lastNameLabel}
placeholder={lastNamePlaceholder}
validate={lastNameRequired}
/>
</div>
<div className={css.formRow}>
<FieldBirthdayInput
id={`${fieldId}.birthDate`}
name={`${fieldId}.birthDate`}
disabled={disabled}
className={css.field}
label={birthdayLabel}
labelForMonth={birthdayLabelMonth}
labelForYear={birthdayLabelYear}
format={null}
valueFromForm={values.birthDate}
validate={validators.composeValidators(birthdayRequired, birthdayMinAge)}
/>
</div>
{showPersonalIdNumber ? (
<FieldTextInput
id={`${fieldId}.personalIdNumber`}
name={`${fieldId}.personalIdNumber`}
disabled={disabled}
className={css.personalIdNumber}
type="text"
label={personalIdNumberLabel}
placeholder={personalIdNumberPlaceholder}
validate={personalIdNumberValid}
/>
) : null}
</div>
);
};
PayoutDetailsPersonalDetails.defaultProps = {
country: null,
disabled: false,
fieldId: null,
};
PayoutDetailsPersonalDetails.propTypes = {
country: string,
disabled: bool,
fieldId: string,
intl: intlShape.isRequired,
};
export default PayoutDetailsPersonalDetails;

View file

@ -38,6 +38,10 @@ export const stripeSupportedCountries = [
accountConfig: {
iban: true,
},
companyConfig: {
personalAddress: true,
additionalOwners: true,
},
},
{
// Belgium
@ -51,6 +55,10 @@ export const stripeSupportedCountries = [
accountConfig: {
iban: true,
},
companyConfig: {
personalAddress: true,
additionalOwners: true,
},
},
{
// Canada
@ -80,6 +88,10 @@ export const stripeSupportedCountries = [
accountConfig: {
iban: true,
},
companyConfig: {
personalAddress: true,
additionalOwners: true,
},
},
{
// Finland
@ -93,6 +105,10 @@ export const stripeSupportedCountries = [
accountConfig: {
iban: true,
},
companyConfig: {
personalAddress: true,
additionalOwners: true,
},
},
{
// France
@ -106,6 +122,10 @@ export const stripeSupportedCountries = [
accountConfig: {
iban: true,
},
companyConfig: {
personalAddress: true,
additionalOwners: true,
},
},
{
// Germany
@ -119,6 +139,10 @@ export const stripeSupportedCountries = [
accountConfig: {
iban: true,
},
companyConfig: {
personalAddress: true,
additionalOwners: true,
},
},
{
// Hong Kong
@ -134,6 +158,9 @@ export const stripeSupportedCountries = [
accountNumber: true,
},
personalIdNumberRequired: true,
companyConfig: {
personalAddress: true,
},
},
{
// Ireland
@ -147,6 +174,10 @@ export const stripeSupportedCountries = [
accountConfig: {
iban: true,
},
companyConfig: {
personalAddress: true,
additionalOwners: true,
},
},
{
// Italy
@ -160,6 +191,10 @@ export const stripeSupportedCountries = [
accountConfig: {
iban: true,
},
companyConfig: {
personalAddress: true,
additionalOwners: true,
},
},
{
// Luxembourg
@ -173,6 +208,10 @@ export const stripeSupportedCountries = [
accountConfig: {
iban: true,
},
companyConfig: {
personalAddress: true,
additionalOwners: true,
},
},
{
// Netherlands
@ -186,6 +225,10 @@ export const stripeSupportedCountries = [
accountConfig: {
iban: true,
},
companyConfig: {
personalAddress: true,
additionalOwners: true,
},
},
{
// New Zealand
@ -212,6 +255,10 @@ export const stripeSupportedCountries = [
accountConfig: {
iban: true,
},
companyConfig: {
personalAddress: true,
additionalOwners: true,
},
},
{
// Portugal
@ -225,6 +272,10 @@ export const stripeSupportedCountries = [
accountConfig: {
iban: true,
},
companyConfig: {
personalAddress: true,
additionalOwners: true,
},
},
{
// Spain
@ -238,6 +289,10 @@ export const stripeSupportedCountries = [
accountConfig: {
iban: true,
},
companyConfig: {
personalAddress: true,
additionalOwners: true,
},
},
{
// Sweden
@ -251,6 +306,10 @@ export const stripeSupportedCountries = [
accountConfig: {
iban: true,
},
companyConfig: {
personalAddress: true,
additionalOwners: true,
},
},
{
// Switzerland
@ -264,6 +323,10 @@ export const stripeSupportedCountries = [
accountConfig: {
iban: true,
},
companyConfig: {
personalAddress: true,
additionalOwners: true,
},
},
{
// United Kingdom
@ -278,6 +341,10 @@ export const stripeSupportedCountries = [
sortCode: true,
accountNumber: true,
},
companyConfig: {
personalAddress: true,
additionalOwners: true,
},
},
{
// United States

View file

@ -452,6 +452,9 @@
"PasswordResetPage.recoveryLinkText": "password recovery page",
"PasswordResetPage.resetFailed": "Reset failed. Please try again.",
"PasswordResetPage.title": "Reset password",
"PayoutDetailsForm.accountTypeTitle": "Account type",
"PayoutDetailsForm.additionalOwnerLabel": "Add additional owner",
"PayoutDetailsForm.additionalOwnerRemove": "Remove additional owner",
"PayoutDetailsForm.addressTitle": "Address",
"PayoutDetailsForm.bankDetails": "Bank details",
"PayoutDetailsForm.birthdayDatePlaceholder": "dd",
@ -481,6 +484,35 @@
"PayoutDetailsForm.cityLabel": "City",
"PayoutDetailsForm.cityPlaceholder": "Helsinki",
"PayoutDetailsForm.cityRequired": "This field is required",
"PayoutDetailsForm.companyAccount": "I represent a company",
"PayoutDetailsForm.companyAddressTitle": "Company address",
"PayoutDetailsForm.companyDetailsTitle": "Company details",
"PayoutDetailsForm.companyNameLabel": "Company name",
"PayoutDetailsForm.companyNamePlaceholder": "Enter company name...",
"PayoutDetailsForm.companyNameRequired": "Company name is required",
"PayoutDetailsForm.companyTaxIdLabel.AT": "Firmenbuchnummer (FN)",
"PayoutDetailsForm.companyTaxIdLabel.AU": "Company ACN/ABN - TFN",
"PayoutDetailsForm.companyTaxIdLabel.BE": "TVA/BTW/CBE",
"PayoutDetailsForm.companyTaxIdLabel.CA": "Business Number (Tax ID)",
"PayoutDetailsForm.companyTaxIdLabel.CH": "VAT number UID/MWST/TVA/IVA",
"PayoutDetailsForm.companyTaxIdLabel.DE": "Handelsregisternummer (HRB) ",
"PayoutDetailsForm.companyTaxIdLabel.DK": "Momsregistreringsnummer (CVR)",
"PayoutDetailsForm.companyTaxIdLabel.ES": "Número de Identificación Fiscal (NIF)",
"PayoutDetailsForm.companyTaxIdLabel.FI": "Y-tunnus",
"PayoutDetailsForm.companyTaxIdLabel.FR": "Numéro SIREN",
"PayoutDetailsForm.companyTaxIdLabel.GB": "Companies House Registration Number (CRN)",
"PayoutDetailsForm.companyTaxIdLabel.HK": "Registration Number",
"PayoutDetailsForm.companyTaxIdLabel.IE": "Company Number",
"PayoutDetailsForm.companyTaxIdLabel.IT": "Numero RI/ REA",
"PayoutDetailsForm.companyTaxIdLabel.LU": "Company/RCS number",
"PayoutDetailsForm.companyTaxIdLabel.NL": "KVK number",
"PayoutDetailsForm.companyTaxIdLabel.NO": "Organisasjonsnummer (Orgnr)",
"PayoutDetailsForm.companyTaxIdLabel.NZ": "NZBN",
"PayoutDetailsForm.companyTaxIdLabel.PT": "N.º Contribuinte",
"PayoutDetailsForm.companyTaxIdLabel.SE": "Organisationsnummer",
"PayoutDetailsForm.companyTaxIdLabel.US": "Tax ID",
"PayoutDetailsForm.companyTaxIdPlaceholder": "Enter {idName}...",
"PayoutDetailsForm.companyTaxIdRequired": "{idName} is required",
"PayoutDetailsForm.countryLabel": "Country",
"PayoutDetailsForm.countryNames.AT": "Austria",
"PayoutDetailsForm.countryNames.AU": "Australia",
@ -510,10 +542,12 @@
"PayoutDetailsForm.firstNameLabel": "First name",
"PayoutDetailsForm.firstNamePlaceholder": "John",
"PayoutDetailsForm.firstNameRequired": "This field is required",
"PayoutDetailsForm.individualAccount": "I'm an individual",
"PayoutDetailsForm.information": "Since this was your first listing, we need to know bit more about you in order to send you money. We only ask these once.",
"PayoutDetailsForm.lastNameLabel": "Last name",
"PayoutDetailsForm.lastNamePlaceholder": "Doe",
"PayoutDetailsForm.lastNameRequired": "This field is required",
"PayoutDetailsForm.personalDetailsAdditionalOwnerTitle": "Additional owner details",
"PayoutDetailsForm.personalDetailsTitle": "Personal details",
"PayoutDetailsForm.personalIdNumberTitle": "Personal id number",
"PayoutDetailsForm.personalIdNumberLabel.HK": "Hong Kong Identity Card Number (HKID)",

View file

@ -452,6 +452,9 @@
"PasswordResetPage.recoveryLinkText": "la page de réinitialisation du mot de passe",
"PasswordResetPage.resetFailed": "La réinitialisation a échoué.",
"PasswordResetPage.title": "Réinitialiser le mot de passe",
"PayoutDetailsForm.accountTypeTitle": "Account type",
"PayoutDetailsForm.additionalOwnerLabel": "+ Add additional owner",
"PayoutDetailsForm.additionalOwnerRemove": "Remove additional owner",
"PayoutDetailsForm.addressTitle": "Adresse",
"PayoutDetailsForm.bankDetails": "Détail du compte bancaire",
"PayoutDetailsForm.birthdayDatePlaceholder": "jj",
@ -481,6 +484,35 @@
"PayoutDetailsForm.cityLabel": "Ville",
"PayoutDetailsForm.cityPlaceholder": "Helsinki",
"PayoutDetailsForm.cityRequired": "Ce champ est requis.",
"PayoutDetailsForm.companyAccount": "I represent a company",
"PayoutDetailsForm.companyAddressTitle": "Company address",
"PayoutDetailsForm.companyDetailsTitle": "Company details",
"PayoutDetailsForm.companyNameLabel": "Company name",
"PayoutDetailsForm.companyNamePlaceholder": "Enter company name...",
"PayoutDetailsForm.companyNameRequired": "Company name is required",
"PayoutDetailsForm.companyTaxIdLabel.AT": "Firmenbuchnummer (FN)",
"PayoutDetailsForm.companyTaxIdLabel.AU": "Company ACN/ABN - TFN",
"PayoutDetailsForm.companyTaxIdLabel.BE": "TVA/BTW/CBE",
"PayoutDetailsForm.companyTaxIdLabel.CA": "Business Number (Tax ID)",
"PayoutDetailsForm.companyTaxIdLabel.CH": "VAT number UID/MWST/TVA/IVA",
"PayoutDetailsForm.companyTaxIdLabel.DE": "Handelsregisternummer (HRB) ",
"PayoutDetailsForm.companyTaxIdLabel.DK": "Momsregistreringsnummer (CVR)",
"PayoutDetailsForm.companyTaxIdLabel.ES": "Número de Identificación Fiscal (NIF)",
"PayoutDetailsForm.companyTaxIdLabel.FI": "Y-tunnus",
"PayoutDetailsForm.companyTaxIdLabel.FR": "Numéro SIREN",
"PayoutDetailsForm.companyTaxIdLabel.GB": "Companies House Registration Number (CRN)",
"PayoutDetailsForm.companyTaxIdLabel.HK": "Registration Number",
"PayoutDetailsForm.companyTaxIdLabel.IE": "Company Number",
"PayoutDetailsForm.companyTaxIdLabel.IT": "Numero RI/ REA",
"PayoutDetailsForm.companyTaxIdLabel.LU": "Company/RCS number",
"PayoutDetailsForm.companyTaxIdLabel.NL": "KVK number",
"PayoutDetailsForm.companyTaxIdLabel.NO": "Organisasjonsnummer (Orgnr)",
"PayoutDetailsForm.companyTaxIdLabel.NZ": "NZBN",
"PayoutDetailsForm.companyTaxIdLabel.PT": "N.º Contribuinte",
"PayoutDetailsForm.companyTaxIdLabel.SE": "Organisationsnummer",
"PayoutDetailsForm.companyTaxIdLabel.US": "Tax ID",
"PayoutDetailsForm.companyTaxIdPlaceholder": "Enter {idName}...",
"PayoutDetailsForm.companyTaxIdRequired": "{idName} is required",
"PayoutDetailsForm.countryLabel": "Pays",
"PayoutDetailsForm.countryNames.AT": "Autriche",
"PayoutDetailsForm.countryNames.AU": "Australie",
@ -510,10 +542,12 @@
"PayoutDetailsForm.firstNameLabel": "Prénom",
"PayoutDetailsForm.firstNamePlaceholder": "Prénom",
"PayoutDetailsForm.firstNameRequired": "Ce champ est requis.",
"PayoutDetailsForm.individualAccount": "I'm an individual",
"PayoutDetailsForm.information": "Puisque c'est votre première annonce, nous devons en savoir un peu plus pour pouvoir vous transférer vos gains. Nous ne vous les demanderons qu'une seule fois.",
"PayoutDetailsForm.lastNameLabel": "Nom",
"PayoutDetailsForm.lastNamePlaceholder": "Nom",
"PayoutDetailsForm.lastNameRequired": "Ce champ est requis.",
"PayoutDetailsForm.personalDetailsAdditionalOwnerTitle": "Additional owner details",
"PayoutDetailsForm.personalDetailsTitle": "Détails",
"PayoutDetailsForm.personalIdNumberLabel.HK": "Hong Kong Identity Card Number (HKID)",
"PayoutDetailsForm.personalIdNumberLabel.US": "Quatre derniers chiffres du numéro de sécurité sociale (SSN)",