diff --git a/CHANGELOG.md b/CHANGELOG.md
index e9545d3011..0833972aeb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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.
diff --git a/src/components/EditListingWizard/EditListingWizard.css b/src/components/EditListingWizard/EditListingWizard.css
index 4bdde1708e..85415243bc 100644
--- a/src/components/EditListingWizard/EditListingWizard.css
+++ b/src/components/EditListingWizard/EditListingWizard.css
@@ -102,3 +102,14 @@
margin-bottom: 0;
}
}
+
+.modalTitle {
+ @apply --marketplaceModalTitleStyles;
+}
+
+.modalPayoutDetailsWrapper {
+ @media (--viewportMedium) {
+ width: 604px;
+ padding-top: 11px;
+ }
+}
diff --git a/src/components/EditListingWizard/EditListingWizard.js b/src/components/EditListingWizard/EditListingWizard.js
index 262b3680c6..6871dc51b4 100644
--- a/src/components/EditListingWizard/EditListingWizard.js
+++ b/src/components/EditListingWizard/EditListingWizard.js
@@ -264,7 +264,7 @@ class EditListingWizard extends Component {
onClose={this.handlePayoutModalClose}
onManageDisableScrolling={onManageDisableScrolling}
>
-
+
@@ -273,14 +273,14 @@ class EditListingWizard extends Component {
+
-
);
diff --git a/src/components/FieldRadioButton/FieldRadioButton.css b/src/components/FieldRadioButton/FieldRadioButton.css
index f2a933be1f..e5df682ab6 100644
--- a/src/components/FieldRadioButton/FieldRadioButton.css
+++ b/src/components/FieldRadioButton/FieldRadioButton.css
@@ -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 {
diff --git a/src/components/IconAdd/IconAdd.css b/src/components/IconAdd/IconAdd.css
new file mode 100644
index 0000000000..d6ab5b7e4e
--- /dev/null
+++ b/src/components/IconAdd/IconAdd.css
@@ -0,0 +1,5 @@
+@import '../../marketplace.css';
+
+.root {
+ fill: var(--marketplaceColor);
+}
diff --git a/src/components/IconAdd/IconAdd.example.js b/src/components/IconAdd/IconAdd.example.js
new file mode 100644
index 0000000000..217260a4af
--- /dev/null
+++ b/src/components/IconAdd/IconAdd.example.js
@@ -0,0 +1,7 @@
+import IconAdd from './IconAdd';
+
+export const Icon = {
+ component: IconAdd,
+ props: {},
+ group: 'icons',
+};
diff --git a/src/components/IconAdd/IconAdd.js b/src/components/IconAdd/IconAdd.js
new file mode 100644
index 0000000000..09ce32f8b7
--- /dev/null
+++ b/src/components/IconAdd/IconAdd.js
@@ -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 (
+
+
+
+ );
+};
+
+const { string } = PropTypes;
+
+IconAdd.defaultProps = {
+ className: null,
+ rootClassName: null,
+};
+
+IconAdd.propTypes = {
+ className: string,
+ rootClassName: string,
+};
+
+export default IconAdd;
diff --git a/src/components/IconClose/IconClose.example.js b/src/components/IconClose/IconClose.example.js
index a369dff737..0bdfc3b843 100644
--- a/src/components/IconClose/IconClose.example.js
+++ b/src/components/IconClose/IconClose.example.js
@@ -5,3 +5,11 @@ export const Icon = {
props: {},
group: 'icons',
};
+
+export const IconSmall = {
+ component: IconClose,
+ props: {
+ size: 'small',
+ },
+ group: 'icons',
+};
diff --git a/src/components/IconClose/IconClose.js b/src/components/IconClose/IconClose.js
index b8c9858acb..7ff131ca05 100644
--- a/src/components/IconClose/IconClose.js
+++ b/src/components/IconClose/IconClose.js
@@ -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 (
+
+
+
+ );
+ }
+
return (
(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 = address.province && !address.state;
+
+ const addressValue = {
+ city: address.city,
+ line1: address.streetAddress,
+ postal_code: address.postalCode,
+ state: hasProvince ? address.province : address.state ? address.state : '',
+ };
- const hasProvince = province && !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 address = {
- city,
- line1: streetAddress,
- postal_code: postalCode,
- state: hasProvince ? province : 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,
diff --git a/src/examples.js b/src/examples.js
index aa1798837b..87ab1e3ec4 100644
--- a/src/examples.js
+++ b/src/examples.js
@@ -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,
diff --git a/src/forms/PayoutDetailsForm/PayoutDetailsAddress.js b/src/forms/PayoutDetailsForm/PayoutDetailsAddress.js
index 0e9ed0e186..bda6342fc9 100644
--- a/src/forms/PayoutDetailsForm/PayoutDetailsAddress.js
+++ b/src/forms/PayoutDetailsForm/PayoutDetailsAddress.js
@@ -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 (
-
+
+ {showTitle ?
{addressTitle} : null}
+
{showAddressLine ? (
{
label={streetAddressLabel}
placeholder={streetAddressPlaceholder}
validate={streetAddressRequired}
- onUnmount={() => form.change('streetAddress', undefined)}
+ onUnmount={() => form.change(`${fieldId}.streetAddress`, undefined)}
/>
) : null}
{showPostalCode ? (
{
label={postalCodeLabel}
placeholder={postalCodePlaceholder}
validate={postalCodeRequired}
- onUnmount={() => form.change('postalCode', undefined)}
+ onUnmount={() => form.change(`${fieldId}.postalCode`, undefined)}
/>
) : null}
{showCity ? (
{
label={cityLabel}
placeholder={cityPlaceholder}
validate={cityRequired}
- onUnmount={() => form.change('city', undefined)}
+ onUnmount={() => form.change(`${fieldId}.city`, undefined)}
/>
) : null}
{showState ? (
{
label={stateLabel}
placeholder={statePlaceholder}
validate={stateRequired}
- onUnmount={() => form.change('state', undefined)}
+ onUnmount={() => form.change(`${fieldId}.state`, undefined)}
/>
) : null}
{showProvince ? (
{
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;
diff --git a/src/forms/PayoutDetailsForm/PayoutDetailsBankDetails.js b/src/forms/PayoutDetailsForm/PayoutDetailsBankDetails.js
new file mode 100644
index 0000000000..ee5381f60d
--- /dev/null
+++ b/src/forms/PayoutDetailsForm/PayoutDetailsBankDetails.js
@@ -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 (
+
+
+
+
+
+
+ );
+};
+PayoutDetailsBankDetails.defaultProps = {
+ country: null,
+ disabled: false,
+ fieldId: null,
+};
+
+PayoutDetailsBankDetails.propTypes = {
+ country: string,
+ disabled: bool,
+ fieldId: string,
+};
+
+export default PayoutDetailsBankDetails;
diff --git a/src/forms/PayoutDetailsForm/PayoutDetailsForm.css b/src/forms/PayoutDetailsForm/PayoutDetailsForm.css
index 7101f65d68..3cc8235c0a 100644
--- a/src/forms/PayoutDetailsForm/PayoutDetailsForm.css
+++ b/src/forms/PayoutDetailsForm/PayoutDetailsForm.css
@@ -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;
+}
diff --git a/src/forms/PayoutDetailsForm/PayoutDetailsForm.js b/src/forms/PayoutDetailsForm/PayoutDetailsForm.js
index 2be1746402..4964c8b3a9 100644
--- a/src/forms/PayoutDetailsForm/PayoutDetailsForm.js
+++ b/src/forms/PayoutDetailsForm/PayoutDetailsForm.js
@@ -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 => (
{
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 => (
);
+
return (
);
}}
diff --git a/src/forms/PayoutDetailsForm/PayoutDetailsFormCompany.js b/src/forms/PayoutDetailsForm/PayoutDetailsFormCompany.js
new file mode 100644
index 0000000000..12597d589b
--- /dev/null
+++ b/src/forms/PayoutDetailsForm/PayoutDetailsFormCompany.js
@@ -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 (
+
+ {country ? (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {showPersonalAddressField ? (
+
+ ) : null}
+
+ {showAdditionalOwnersField ? (
+
+
+ {({ fields }) =>
+ fields.map((name, index) => (
+
+
fields.remove(index)}
+ style={{ cursor: 'pointer' }}
+ >
+
+
+
+
+
+
+ {showPersonalAddressField ? (
+
+ ) : null}
+
+ ))
+ }
+
+
+ {!hasAdditionalOwners || !hasMaxNumberOfAdditionalOwners ? (
+
push('company.additionalOwners', undefined)}
+ >
+
+
+
+
+
+ ) : null}
+
+ ) : null}
+
+ ) : null}
+
+ );
+};
+
+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;
diff --git a/src/forms/PayoutDetailsForm/PayoutDetailsFormIndividual.js b/src/forms/PayoutDetailsForm/PayoutDetailsFormIndividual.js
new file mode 100644
index 0000000000..55ffe0c90c
--- /dev/null
+++ b/src/forms/PayoutDetailsForm/PayoutDetailsFormIndividual.js
@@ -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 (
+
+
+
+
+
+ );
+};
+
+PayoutDetailsFormIndividualComponent.defaultProps = {
+ disabled: false,
+};
+
+PayoutDetailsFormIndividualComponent.propTypes = {
+ disabled: bool,
+
+ // from injectIntl
+ intl: intlShape.isRequired,
+};
+
+const PayoutDetailsFormIndividual = compose(injectIntl)(PayoutDetailsFormIndividualComponent);
+
+export default PayoutDetailsFormIndividual;
diff --git a/src/forms/PayoutDetailsForm/PayoutDetailsPersonalDetails.js b/src/forms/PayoutDetailsForm/PayoutDetailsPersonalDetails.js
new file mode 100644
index 0000000000..3b4e7026ca
--- /dev/null
+++ b/src/forms/PayoutDetailsForm/PayoutDetailsPersonalDetails.js
@@ -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 (
+
+
{personalDetailsTitle}
+
+
+
+
+
+
+
+
+ {showPersonalIdNumber ? (
+
+ ) : null}
+
+ );
+};
+PayoutDetailsPersonalDetails.defaultProps = {
+ country: null,
+ disabled: false,
+ fieldId: null,
+};
+
+PayoutDetailsPersonalDetails.propTypes = {
+ country: string,
+ disabled: bool,
+ fieldId: string,
+ intl: intlShape.isRequired,
+};
+
+export default PayoutDetailsPersonalDetails;
diff --git a/src/stripe-config.js b/src/stripe-config.js
index ec2a94c6c4..611d9bba74 100644
--- a/src/stripe-config.js
+++ b/src/stripe-config.js
@@ -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
diff --git a/src/translations/en.json b/src/translations/en.json
index 4900d5ffdd..c948ddfab5 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -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)",
diff --git a/src/translations/fr.json b/src/translations/fr.json
index 6d2c20892c..893147a7c1 100644
--- a/src/translations/fr.json
+++ b/src/translations/fr.json
@@ -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)",