mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-26 06:47:17 +10:00
Merge pull request #968 from sharetribe/add-missing-stripe-countries
Add missing stripe countries
This commit is contained in:
commit
2625b4427c
14 changed files with 855 additions and 259 deletions
|
|
@ -1,6 +1,8 @@
|
|||
|
||||
# Mandatory configuration
|
||||
#
|
||||
# Note: You also need to set Stripe secret key in Flex Console.
|
||||
#
|
||||
REACT_APP_SHARETRIBE_SDK_CLIENT_ID=
|
||||
REACT_APP_STRIPE_PUBLISHABLE_KEY=
|
||||
REACT_APP_MAPBOX_ACCESS_TOKEN=
|
||||
|
|
|
|||
|
|
@ -14,10 +14,16 @@ way to update this template, but currently, we follow a pattern:
|
|||
|
||||
## Upcoming version 2018-XX-XX
|
||||
|
||||
- [add] Add Stripe support for new countries: Canada, New Zealand, Switzerland, Norway, and Hong
|
||||
Kong. Also add more required fields for US and Australia.
|
||||
- StripeBankAccountTokenInputField component and PayoutDetailsForm have some changes
|
||||
- Stripe related configuration is separated to new stripe-config.js file.
|
||||
- Multiple new translation keys were added and they might not be translated into French yet. If
|
||||
you use French translation check PR for the changed keys.
|
||||
[#968](https://github.com/sharetribe/flex-template-web/pull/968)
|
||||
- [change] Remove generic perUnit translations and replace them with specific night, day and unit
|
||||
translations depending on booking unit chosen in config.
|
||||
[#970](https://github.com/sharetribe/flex-template-web/pull/970)
|
||||
|
||||
- [fix] Formatting docs with newest Prettier - related commit was lost in #967 at some point.
|
||||
[#975](https://github.com/sharetribe/flex-template-web/pull/975)
|
||||
- [change] Improved documents related to onboarding: env.md, deploying-to-production.md,
|
||||
|
|
|
|||
|
|
@ -104,3 +104,20 @@ export const AU_AUD = {
|
|||
},
|
||||
group: 'custom inputs',
|
||||
};
|
||||
|
||||
// CA
|
||||
export const CA_CAD = {
|
||||
component: formComponent('CA'),
|
||||
props: {
|
||||
formName: 'CA_CAD',
|
||||
onChange: formState => {
|
||||
if (formState.dirty) {
|
||||
console.log('form values changed to:', formState.values);
|
||||
}
|
||||
},
|
||||
onSubmit: values => {
|
||||
console.log('values submitted:', values);
|
||||
},
|
||||
},
|
||||
group: 'custom inputs',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -134,15 +134,8 @@ class TokenInputFieldComponent extends Component {
|
|||
};
|
||||
|
||||
// Include input values with correct stripe keys
|
||||
inputsNeeded.forEach(inputType => {
|
||||
// Stripe fails if there are spaces within the number, this is
|
||||
// why we have to clean value up first.
|
||||
const inputValueObj = mapInputsToStripeAccountKeys(
|
||||
inputType,
|
||||
cleanedString(values[inputType])
|
||||
);
|
||||
accountData = { ...accountData, ...inputValueObj };
|
||||
});
|
||||
const inputValueObj = mapInputsToStripeAccountKeys(country, values);
|
||||
accountData = { ...accountData, ...inputValueObj };
|
||||
|
||||
// https://stripe.com/docs/stripe-js/reference#collecting-bank-account-details
|
||||
this.stripe
|
||||
|
|
|
|||
|
|
@ -6,6 +6,14 @@ import config from '../../config';
|
|||
export const ACCOUNT_NUMBER = 'accountNumber';
|
||||
// Australian equivalent for routing number
|
||||
export const BSB = 'bsb';
|
||||
// Needed for creating full routing number in Canada
|
||||
export const INSTITUTION_NUMBER = 'institutionNumber';
|
||||
// Needed for creating full routing number in Canada
|
||||
export const TRANSIT_NUMBER = 'transitNumber';
|
||||
// Needed for creating full routing number in Hong Kong
|
||||
export const CLEARING_CODE = 'clearingCode';
|
||||
// Needed for creating full routing number in Hong Kong
|
||||
export const BRANCH_CODE = 'branchCode';
|
||||
// International bank account number (e.g. EU countries use this)
|
||||
export const IBAN = 'iban';
|
||||
// Routing number to separate bank account in different areas
|
||||
|
|
@ -15,7 +23,17 @@ export const SORT_CODE = 'sortCode';
|
|||
|
||||
// Currently supported bank account inputs
|
||||
// the order here matters: account number input is asked after routing number and its equivalents
|
||||
export const BANK_ACCOUNT_INPUTS = [BSB, SORT_CODE, ROUTING_NUMBER, ACCOUNT_NUMBER, IBAN];
|
||||
export const BANK_ACCOUNT_INPUTS = [
|
||||
BSB,
|
||||
TRANSIT_NUMBER,
|
||||
INSTITUTION_NUMBER,
|
||||
CLEARING_CODE,
|
||||
BRANCH_CODE,
|
||||
SORT_CODE,
|
||||
ROUTING_NUMBER,
|
||||
ACCOUNT_NUMBER,
|
||||
IBAN,
|
||||
];
|
||||
|
||||
export const supportedCountries = config.stripe.supportedCountries.map(c => c.code);
|
||||
|
||||
|
|
@ -117,7 +135,7 @@ export const translateStripeError = (country, intl, stripeError) => {
|
|||
*
|
||||
* @return {Object} key - value in Object literal.
|
||||
*/
|
||||
export const mapInputsToStripeAccountKeys = (inputType, value) => {
|
||||
export const mapInputsToStripeAccountKeys = (country, values) => {
|
||||
// Stripe documentation speaks about actual bank account terms of different countries
|
||||
// (like BSB, sort code, routing number), but all of those get mapped to one of
|
||||
// the two different request keys: routing_number or account_number
|
||||
|
|
@ -126,17 +144,63 @@ export const mapInputsToStripeAccountKeys = (inputType, value) => {
|
|||
// We use those country specific terms since we want to show correct labels and errors for users,
|
||||
// so this mapping is needed before sending data to Stripe.
|
||||
|
||||
switch (inputType) {
|
||||
case IBAN:
|
||||
case ACCOUNT_NUMBER:
|
||||
return { account_number: value };
|
||||
case BSB:
|
||||
case SORT_CODE:
|
||||
case ROUTING_NUMBER:
|
||||
return { routing_number: value };
|
||||
// Stripe fails if there are spaces within the number, this is
|
||||
// why we have to clean value up first.
|
||||
|
||||
switch (country) {
|
||||
case 'AT':
|
||||
case 'BE':
|
||||
case 'DK':
|
||||
case 'FI':
|
||||
case 'FR':
|
||||
case 'DE':
|
||||
case 'IE':
|
||||
case 'IT':
|
||||
case 'LU':
|
||||
case 'NL':
|
||||
case 'PT':
|
||||
case 'ES':
|
||||
case 'SE':
|
||||
case 'CH':
|
||||
case 'NO':
|
||||
return { account_number: cleanedString(values[IBAN]) };
|
||||
case 'NZ':
|
||||
// NZ account number is typically presented in the format xx-xxxx-xxxxxxx-xxx
|
||||
// '-' separators must be removed before sending value to Stripe API
|
||||
return { account_number: cleanedString(values[ACCOUNT_NUMBER]).replace(/-/g, '') };
|
||||
case 'AU':
|
||||
return {
|
||||
routing_number: cleanedString(values[BSB]),
|
||||
account_number: cleanedString(values[ACCOUNT_NUMBER]),
|
||||
};
|
||||
case 'CA':
|
||||
return {
|
||||
routing_number: cleanedString(values[TRANSIT_NUMBER]).concat(
|
||||
cleanedString(values[INSTITUTION_NUMBER])
|
||||
),
|
||||
account_number: cleanedString(values[ACCOUNT_NUMBER]),
|
||||
};
|
||||
case 'GB':
|
||||
return {
|
||||
routing_number: cleanedString(values[SORT_CODE]),
|
||||
account_number: cleanedString(values[ACCOUNT_NUMBER]),
|
||||
};
|
||||
case 'US':
|
||||
return {
|
||||
routing_number: cleanedString(values[ROUTING_NUMBER]),
|
||||
account_number: cleanedString(values[ACCOUNT_NUMBER]),
|
||||
};
|
||||
case 'HK':
|
||||
return {
|
||||
routing_number: cleanedString(values[CLEARING_CODE]).concat(
|
||||
'-',
|
||||
cleanedString(values[BRANCH_CODE])
|
||||
),
|
||||
account_number: cleanedString(values[ACCOUNT_NUMBER]),
|
||||
};
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown inputType (${inputType}) given to validator`);
|
||||
throw new Error(`Not supported country (${country}) given to validator`);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
157
src/config.js
157
src/config.js
|
|
@ -1,5 +1,6 @@
|
|||
import * as custom from './marketplace-custom-config.js';
|
||||
import defaultLocationSearches from './default-location-searches';
|
||||
import { stripePublishableKey, stripeSupportedCountries } from './stripe-config';
|
||||
|
||||
const env = process.env.REACT_APP_ENV;
|
||||
const dev = process.env.REACT_APP_ENV === 'development';
|
||||
|
|
@ -79,162 +80,6 @@ const currencyConfig = {
|
|||
maximumFractionDigits: 2,
|
||||
};
|
||||
|
||||
const stripePublishableKey = process.env.REACT_APP_STRIPE_PUBLISHABLE_KEY;
|
||||
|
||||
// Stripe only supports payments in certain countries, see full list
|
||||
// at https://stripe.com/global
|
||||
//
|
||||
// We currently only support EU countries, US, and AU.
|
||||
const stripeSupportedCountries = [
|
||||
{
|
||||
// Australia
|
||||
code: 'AU',
|
||||
currency: 'AUD',
|
||||
payoutAddressRequired: false,
|
||||
accountConfig: {
|
||||
bsb: true,
|
||||
accountNumber: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Austria
|
||||
code: 'AT',
|
||||
currency: 'EUR',
|
||||
payoutAddressRequired: true,
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Belgium
|
||||
code: 'BE',
|
||||
currency: 'EUR',
|
||||
payoutAddressRequired: true,
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Denmark
|
||||
code: 'DK',
|
||||
currency: 'DKK',
|
||||
payoutAddressRequired: true,
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Finland
|
||||
code: 'FI',
|
||||
currency: 'EUR',
|
||||
payoutAddressRequired: true,
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// France
|
||||
code: 'FR',
|
||||
currency: 'EUR',
|
||||
payoutAddressRequired: true,
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Germany
|
||||
code: 'DE',
|
||||
currency: 'EUR',
|
||||
payoutAddressRequired: true,
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Ireland
|
||||
code: 'IE',
|
||||
currency: 'EUR',
|
||||
payoutAddressRequired: true,
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Italy
|
||||
code: 'IT',
|
||||
currency: 'EUR',
|
||||
payoutAddressRequired: true,
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Luxembourg
|
||||
code: 'LU',
|
||||
currency: 'EUR',
|
||||
payoutAddressRequired: true,
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Netherlands
|
||||
code: 'NL',
|
||||
currency: 'EUR',
|
||||
payoutAddressRequired: true,
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Portugal
|
||||
code: 'PT',
|
||||
currency: 'EUR',
|
||||
payoutAddressRequired: true,
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Spain
|
||||
code: 'ES',
|
||||
currency: 'EUR',
|
||||
payoutAddressRequired: true,
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Sweden
|
||||
code: 'SE',
|
||||
currency: 'SEK',
|
||||
payoutAddressRequired: true,
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// United Kingdom
|
||||
code: 'GB',
|
||||
currency: 'GBP',
|
||||
payoutAddressRequired: true,
|
||||
accountConfig: {
|
||||
sortCode: true,
|
||||
accountNumber: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// United States
|
||||
code: 'US',
|
||||
currency: 'USD',
|
||||
payoutAddressRequired: false,
|
||||
accountConfig: {
|
||||
routingNumber: true,
|
||||
accountNumber: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// Address information is used in SEO schema for Organization (http://schema.org/PostalAddress)
|
||||
const addressCountry = 'FI';
|
||||
const addressRegion = 'Helsinki';
|
||||
|
|
|
|||
|
|
@ -397,15 +397,24 @@ export const createStripeAccount = payoutDetails => (dispatch, getState, sdk) =>
|
|||
streetAddress,
|
||||
postalCode,
|
||||
city,
|
||||
state,
|
||||
province,
|
||||
bankAccountToken,
|
||||
personalIdNumber,
|
||||
} = payoutDetails;
|
||||
|
||||
const hasProvince = province && !state;
|
||||
|
||||
const address = {
|
||||
city,
|
||||
line1: streetAddress,
|
||||
postal_code: postalCode,
|
||||
state: hasProvince ? province : state,
|
||||
};
|
||||
|
||||
const idNumber =
|
||||
country === 'US' ? { ssn_last_4: personalIdNumber } : { personal_id_number: personalIdNumber };
|
||||
|
||||
// Params for Stripe SDK
|
||||
const params = {
|
||||
legal_entity: {
|
||||
|
|
@ -414,6 +423,7 @@ export const createStripeAccount = payoutDetails => (dispatch, getState, sdk) =>
|
|||
address: omitBy(address, isUndefined),
|
||||
dob: birthDate,
|
||||
type: 'individual',
|
||||
...idNumber,
|
||||
},
|
||||
tos_shown_and_accepted: true,
|
||||
};
|
||||
|
|
|
|||
186
src/forms/PayoutDetailsForm/PayoutDetailsAddress.js
Normal file
186
src/forms/PayoutDetailsForm/PayoutDetailsAddress.js
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
import React from 'react';
|
||||
import { bool, object, string } from 'prop-types';
|
||||
import { FieldSelect, FieldTextInput } from '../../components';
|
||||
import * as validators from '../../util/validators';
|
||||
|
||||
import { stripeCountryConfigs } from './PayoutDetailsForm';
|
||||
import css from './PayoutDetailsForm.css';
|
||||
|
||||
const CANADIAN_PROVINCES = [
|
||||
'AB',
|
||||
'BC',
|
||||
'MB',
|
||||
'NB',
|
||||
'NL',
|
||||
'NS',
|
||||
'NT',
|
||||
'NU',
|
||||
'ON',
|
||||
'PE',
|
||||
'QC',
|
||||
'SK',
|
||||
'YT',
|
||||
];
|
||||
|
||||
const PayoutDetailsAddress = props => {
|
||||
const { country, intl, disabled, form } = props;
|
||||
const countryConfig = country ? stripeCountryConfigs(country).addressConfig : null;
|
||||
|
||||
const isRequired = (countryConfig, field) => {
|
||||
return countryConfig[field];
|
||||
};
|
||||
|
||||
const showAddressLine = country && isRequired(countryConfig, 'addressLine');
|
||||
|
||||
const streetAddressLabel = intl.formatMessage({
|
||||
id: 'PayoutDetailsForm.streetAddressLabel',
|
||||
});
|
||||
const streetAddressPlaceholder = intl.formatMessage({
|
||||
id: 'PayoutDetailsForm.streetAddressPlaceholder',
|
||||
});
|
||||
const streetAddressRequired = validators.required(
|
||||
intl.formatMessage({
|
||||
id: 'PayoutDetailsForm.streetAddressRequired',
|
||||
})
|
||||
);
|
||||
|
||||
const showPostalCode = country && isRequired(countryConfig, 'postalCode');
|
||||
|
||||
const postalCodeLabel = intl.formatMessage({ id: 'PayoutDetailsForm.postalCodeLabel' });
|
||||
const postalCodePlaceholder = intl.formatMessage({
|
||||
id: 'PayoutDetailsForm.postalCodePlaceholder',
|
||||
});
|
||||
const postalCodeRequired = validators.required(
|
||||
intl.formatMessage({
|
||||
id: 'PayoutDetailsForm.postalCodeRequired',
|
||||
})
|
||||
);
|
||||
|
||||
const showCity = country && isRequired(countryConfig, 'city');
|
||||
|
||||
const cityLabel = intl.formatMessage({ id: 'PayoutDetailsForm.cityLabel' });
|
||||
const cityPlaceholder = intl.formatMessage({ id: 'PayoutDetailsForm.cityPlaceholder' });
|
||||
const cityRequired = validators.required(
|
||||
intl.formatMessage({
|
||||
id: 'PayoutDetailsForm.cityRequired',
|
||||
})
|
||||
);
|
||||
|
||||
const showState = country && isRequired(countryConfig, 'state');
|
||||
|
||||
const stateLabel = intl.formatMessage({ id: 'PayoutDetailsForm.stateLabel' });
|
||||
const statePlaceholder = intl.formatMessage({ id: 'PayoutDetailsForm.statePlaceholder' });
|
||||
const stateRequired = validators.required(
|
||||
intl.formatMessage({
|
||||
id: 'PayoutDetailsForm.stateRequired',
|
||||
})
|
||||
);
|
||||
|
||||
const showProvince = country && isRequired(countryConfig, 'province');
|
||||
|
||||
const provinceLabel = intl.formatMessage({ id: 'PayoutDetailsForm.canadianProvinceLabel' });
|
||||
const provincePlaceholder = intl.formatMessage({
|
||||
id: 'PayoutDetailsForm.canadianProvincePlaceholder',
|
||||
});
|
||||
const provinceRequired = validators.required(
|
||||
intl.formatMessage({
|
||||
id: 'PayoutDetailsForm.canadianProvinceRequired',
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{showAddressLine ? (
|
||||
<FieldTextInput
|
||||
id="streetAddress"
|
||||
name="streetAddress"
|
||||
disabled={disabled}
|
||||
className={css.field}
|
||||
type="text"
|
||||
autoComplete="street-address"
|
||||
label={streetAddressLabel}
|
||||
placeholder={streetAddressPlaceholder}
|
||||
validate={streetAddressRequired}
|
||||
onUnmount={() => form.change('streetAddress', undefined)}
|
||||
/>
|
||||
) : null}
|
||||
<div className={css.formRow}>
|
||||
{showPostalCode ? (
|
||||
<FieldTextInput
|
||||
id="postalCode"
|
||||
name="postalCode"
|
||||
disabled={disabled}
|
||||
className={css.postalCode}
|
||||
type="text"
|
||||
autoComplete="postal-code"
|
||||
label={postalCodeLabel}
|
||||
placeholder={postalCodePlaceholder}
|
||||
validate={postalCodeRequired}
|
||||
onUnmount={() => form.change('postalCode', undefined)}
|
||||
/>
|
||||
) : null}
|
||||
{showCity ? (
|
||||
<FieldTextInput
|
||||
id="city"
|
||||
name="city"
|
||||
disabled={disabled}
|
||||
className={css.city}
|
||||
type="text"
|
||||
autoComplete="address-level2"
|
||||
label={cityLabel}
|
||||
placeholder={cityPlaceholder}
|
||||
validate={cityRequired}
|
||||
onUnmount={() => form.change('city', undefined)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
{showState ? (
|
||||
<FieldTextInput
|
||||
id="state"
|
||||
name="state"
|
||||
disabled={disabled}
|
||||
className={css.state}
|
||||
type="text"
|
||||
autoComplete="state"
|
||||
label={stateLabel}
|
||||
placeholder={statePlaceholder}
|
||||
validate={stateRequired}
|
||||
onUnmount={() => form.change('state', undefined)}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{showProvince ? (
|
||||
<FieldSelect
|
||||
id="province"
|
||||
name="province"
|
||||
disabled={disabled}
|
||||
className={css.selectCountry}
|
||||
autoComplete="province"
|
||||
label={provinceLabel}
|
||||
validate={provinceRequired}
|
||||
>
|
||||
<option disabled value="">
|
||||
{provincePlaceholder}
|
||||
</option>
|
||||
{CANADIAN_PROVINCES.map(p => (
|
||||
<option key={p} value={p}>
|
||||
{intl.formatMessage({ id: `PayoutDetailsForm.canadianProvinceNames.${p}` })}
|
||||
</option>
|
||||
))}
|
||||
</FieldSelect>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
PayoutDetailsAddress.defaultProps = {
|
||||
country: null,
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
PayoutDetailsAddress.propTypes = {
|
||||
country: string,
|
||||
disabled: bool,
|
||||
form: object.isRequired,
|
||||
};
|
||||
|
||||
export default PayoutDetailsAddress;
|
||||
|
|
@ -17,6 +17,7 @@ import {
|
|||
import * as validators from '../../util/validators';
|
||||
import { isStripeInvalidPostalCode } from '../../util/errors';
|
||||
|
||||
import PayoutDetailsAddress from './PayoutDetailsAddress';
|
||||
import css from './PayoutDetailsForm.css';
|
||||
|
||||
const MIN_STRIPE_ACCOUNT_AGE = 18;
|
||||
|
|
@ -32,11 +33,6 @@ export const stripeCountryConfigs = countryCode => {
|
|||
return country;
|
||||
};
|
||||
|
||||
const requiresAddress = countryCode => {
|
||||
const country = stripeCountryConfigs(countryCode);
|
||||
return country.payoutAddressRequired;
|
||||
};
|
||||
|
||||
const countryCurrency = countryCode => {
|
||||
const country = stripeCountryConfigs(countryCode);
|
||||
return country.currency;
|
||||
|
|
@ -114,43 +110,54 @@ const PayoutDetailsFormComponent = props => (
|
|||
})
|
||||
);
|
||||
|
||||
const streetAddressLabel = intl.formatMessage({
|
||||
id: 'PayoutDetailsForm.streetAddressLabel',
|
||||
});
|
||||
const streetAddressPlaceholder = intl.formatMessage({
|
||||
id: 'PayoutDetailsForm.streetAddressPlaceholder',
|
||||
});
|
||||
const streetAddressRequired = validators.required(
|
||||
intl.formatMessage({
|
||||
id: 'PayoutDetailsForm.streetAddressRequired',
|
||||
})
|
||||
);
|
||||
|
||||
const postalCodeLabel = intl.formatMessage({ id: 'PayoutDetailsForm.postalCodeLabel' });
|
||||
const postalCodePlaceholder = intl.formatMessage({
|
||||
id: 'PayoutDetailsForm.postalCodePlaceholder',
|
||||
});
|
||||
const postalCodeRequired = validators.required(
|
||||
intl.formatMessage({
|
||||
id: 'PayoutDetailsForm.postalCodeRequired',
|
||||
})
|
||||
);
|
||||
|
||||
const cityLabel = intl.formatMessage({ id: 'PayoutDetailsForm.cityLabel' });
|
||||
const cityPlaceholder = intl.formatMessage({ id: 'PayoutDetailsForm.cityPlaceholder' });
|
||||
const cityRequired = validators.required(
|
||||
intl.formatMessage({
|
||||
id: 'PayoutDetailsForm.cityRequired',
|
||||
})
|
||||
);
|
||||
|
||||
const showAddressFields = country && requiresAddress(country);
|
||||
|
||||
// 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,
|
||||
});
|
||||
|
|
@ -178,7 +185,6 @@ const PayoutDetailsFormComponent = props => (
|
|||
<FormattedMessage id="PayoutDetailsForm.stripeConnectedAccountTermsLink" />
|
||||
</ExternalLink>
|
||||
);
|
||||
|
||||
return (
|
||||
<Form className={classes} onSubmit={handleSubmit}>
|
||||
<div className={css.sectionContainer}>
|
||||
|
|
@ -245,48 +251,8 @@ const PayoutDetailsFormComponent = props => (
|
|||
</option>
|
||||
))}
|
||||
</FieldSelect>
|
||||
{showAddressFields ? (
|
||||
<div>
|
||||
<FieldTextInput
|
||||
id="streetAddress"
|
||||
name="streetAddress"
|
||||
disabled={disabled}
|
||||
className={css.field}
|
||||
type="text"
|
||||
autoComplete="street-address"
|
||||
label={streetAddressLabel}
|
||||
placeholder={streetAddressPlaceholder}
|
||||
validate={streetAddressRequired}
|
||||
onUnmount={() => form.change('streetAddress', undefined)}
|
||||
/>
|
||||
<div className={css.formRow}>
|
||||
<FieldTextInput
|
||||
id="postalCode"
|
||||
name="postalCode"
|
||||
disabled={disabled}
|
||||
className={css.postalCode}
|
||||
type="text"
|
||||
autoComplete="postal-code"
|
||||
label={postalCodeLabel}
|
||||
placeholder={postalCodePlaceholder}
|
||||
validate={postalCodeRequired}
|
||||
onUnmount={() => form.change('postalCode', undefined)}
|
||||
/>
|
||||
<FieldTextInput
|
||||
id="city"
|
||||
name="city"
|
||||
disabled={disabled}
|
||||
className={css.city}
|
||||
type="text"
|
||||
autoComplete="address-level2"
|
||||
label={cityLabel}
|
||||
placeholder={cityPlaceholder}
|
||||
validate={cityRequired}
|
||||
onUnmount={() => form.change('city', undefined)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<PayoutDetailsAddress country={country} intl={intl} disabled={disabled} form={form} />
|
||||
</div>
|
||||
{country ? (
|
||||
<div className={css.sectionContainer}>
|
||||
|
|
@ -303,7 +269,27 @@ const PayoutDetailsFormComponent = props => (
|
|||
/>
|
||||
</div>
|
||||
) : 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"
|
||||
|
|
|
|||
298
src/stripe-config.js
Normal file
298
src/stripe-config.js
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
/*
|
||||
* Stripe related configuration.
|
||||
*/
|
||||
|
||||
// NOTE: REACT_APP_STRIPE_PUBLISHABLE_KEY is mandatory environment variable.
|
||||
// This variable is set in a hidden file: .env
|
||||
// To make Stripe connection work, you also need to set Stripe's private key in the Flex Console.
|
||||
export const stripePublishableKey = process.env.REACT_APP_STRIPE_PUBLISHABLE_KEY;
|
||||
|
||||
// Stripe only supports payments in certain countries, see full list
|
||||
// at https://stripe.com/global
|
||||
//
|
||||
export const stripeSupportedCountries = [
|
||||
{
|
||||
// Australia
|
||||
code: 'AU',
|
||||
currency: 'AUD',
|
||||
addressConfig: {
|
||||
addressLine: true,
|
||||
city: true,
|
||||
postalCode: true,
|
||||
state: true,
|
||||
},
|
||||
accountConfig: {
|
||||
bsb: true,
|
||||
accountNumber: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Austria
|
||||
code: 'AT',
|
||||
currency: 'EUR',
|
||||
addressConfig: {
|
||||
addressLine: true,
|
||||
city: true,
|
||||
postalCode: true,
|
||||
},
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Belgium
|
||||
code: 'BE',
|
||||
currency: 'EUR',
|
||||
addressConfig: {
|
||||
addressLine: true,
|
||||
city: true,
|
||||
postalCode: true,
|
||||
},
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Canada
|
||||
code: 'CA',
|
||||
currency: 'CAD',
|
||||
addressConfig: {
|
||||
addressLine: true,
|
||||
city: true,
|
||||
postalCode: true,
|
||||
province: true,
|
||||
},
|
||||
accountConfig: {
|
||||
transitNumber: true,
|
||||
institutionNumber: true,
|
||||
accountNumber: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Denmark
|
||||
code: 'DK',
|
||||
currency: 'DKK',
|
||||
addressConfig: {
|
||||
addressLine: true,
|
||||
city: true,
|
||||
postalCode: true,
|
||||
},
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Finland
|
||||
code: 'FI',
|
||||
currency: 'EUR',
|
||||
addressConfig: {
|
||||
addressLine: true,
|
||||
city: true,
|
||||
postalCode: true,
|
||||
},
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// France
|
||||
code: 'FR',
|
||||
currency: 'EUR',
|
||||
addressConfig: {
|
||||
addressLine: true,
|
||||
city: true,
|
||||
postalCode: true,
|
||||
},
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Germany
|
||||
code: 'DE',
|
||||
currency: 'EUR',
|
||||
addressConfig: {
|
||||
addressLine: true,
|
||||
city: true,
|
||||
postalCode: true,
|
||||
},
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Hong Kong
|
||||
code: 'HK',
|
||||
currency: 'HKD',
|
||||
addressConfig: {
|
||||
addressLine: true,
|
||||
city: true,
|
||||
},
|
||||
accountConfig: {
|
||||
clearingCode: true,
|
||||
branchCode: true,
|
||||
accountNumber: true,
|
||||
},
|
||||
personalIdNumberRequired: true,
|
||||
},
|
||||
{
|
||||
// Ireland
|
||||
code: 'IE',
|
||||
currency: 'EUR',
|
||||
addressConfig: {
|
||||
addressLine: true,
|
||||
city: true,
|
||||
postalCode: true,
|
||||
},
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Italy
|
||||
code: 'IT',
|
||||
currency: 'EUR',
|
||||
addressConfig: {
|
||||
addressLine: true,
|
||||
city: true,
|
||||
postalCode: true,
|
||||
},
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Luxembourg
|
||||
code: 'LU',
|
||||
currency: 'EUR',
|
||||
addressConfig: {
|
||||
addressLine: true,
|
||||
city: true,
|
||||
postalCode: true,
|
||||
},
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Netherlands
|
||||
code: 'NL',
|
||||
currency: 'EUR',
|
||||
addressConfig: {
|
||||
addressLine: true,
|
||||
city: true,
|
||||
postalCode: true,
|
||||
},
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// New Zealand
|
||||
code: 'NZ',
|
||||
currency: 'NZD',
|
||||
addressConfig: {
|
||||
addressLine: true,
|
||||
city: true,
|
||||
postalCode: true,
|
||||
},
|
||||
accountConfig: {
|
||||
accountNumber: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Norway
|
||||
code: 'NO',
|
||||
currency: 'EUR',
|
||||
addressConfig: {
|
||||
addressLine: true,
|
||||
city: true,
|
||||
postalCode: true,
|
||||
},
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Portugal
|
||||
code: 'PT',
|
||||
currency: 'EUR',
|
||||
addressConfig: {
|
||||
addressLine: true,
|
||||
city: true,
|
||||
postalCode: true,
|
||||
},
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Spain
|
||||
code: 'ES',
|
||||
currency: 'EUR',
|
||||
addressConfig: {
|
||||
addressLine: true,
|
||||
city: true,
|
||||
postalCode: true,
|
||||
},
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Sweden
|
||||
code: 'SE',
|
||||
currency: 'SEK',
|
||||
addressConfig: {
|
||||
addressLine: true,
|
||||
city: true,
|
||||
postalCode: true,
|
||||
},
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Switzerland
|
||||
code: 'CH',
|
||||
currency: 'CHF',
|
||||
addressConfig: {
|
||||
addressLine: true,
|
||||
city: true,
|
||||
postalCode: true,
|
||||
},
|
||||
accountConfig: {
|
||||
iban: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// United Kingdom
|
||||
code: 'GB',
|
||||
currency: 'GBP',
|
||||
addressConfig: {
|
||||
addressLine: true,
|
||||
city: true,
|
||||
postalCode: true,
|
||||
},
|
||||
accountConfig: {
|
||||
sortCode: true,
|
||||
accountNumber: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
// United States
|
||||
code: 'US',
|
||||
currency: 'USD',
|
||||
addressConfig: {
|
||||
addressLine: true,
|
||||
city: true,
|
||||
postalCode: true,
|
||||
state: true,
|
||||
},
|
||||
accountConfig: {
|
||||
routingNumber: true,
|
||||
accountNumber: true,
|
||||
},
|
||||
ssnLast4Required: true,
|
||||
},
|
||||
];
|
||||
|
|
@ -445,6 +445,22 @@
|
|||
"PayoutDetailsForm.birthdayMonthPlaceholder": "mm",
|
||||
"PayoutDetailsForm.birthdayRequired": "Birthday is required and must be a valid date.",
|
||||
"PayoutDetailsForm.birthdayYearPlaceholder": "yyyy",
|
||||
"PayoutDetailsForm.canadianProvinceLabel": "Province",
|
||||
"PayoutDetailsForm.canadianProvinceNames.AB": "Alberta",
|
||||
"PayoutDetailsForm.canadianProvinceNames.BC": "British Columbia",
|
||||
"PayoutDetailsForm.canadianProvinceNames.MB": "Manitoba",
|
||||
"PayoutDetailsForm.canadianProvinceNames.NB": "New Brunswick",
|
||||
"PayoutDetailsForm.canadianProvinceNames.NL": "Newfoundland and Labrador",
|
||||
"PayoutDetailsForm.canadianProvinceNames.NS": "Nova Scotia",
|
||||
"PayoutDetailsForm.canadianProvinceNames.NT": "Northwest Territories",
|
||||
"PayoutDetailsForm.canadianProvinceNames.NU": "Nunavut",
|
||||
"PayoutDetailsForm.canadianProvinceNames.ON": "Ontario",
|
||||
"PayoutDetailsForm.canadianProvinceNames.PE": "Prince Edward Island",
|
||||
"PayoutDetailsForm.canadianProvinceNames.QC": "Quebec",
|
||||
"PayoutDetailsForm.canadianProvinceNames.SK": "Saskatchewan",
|
||||
"PayoutDetailsForm.canadianProvinceNames.YT": "Yukon",
|
||||
"PayoutDetailsForm.canadianProvincePlaceholder": "Province",
|
||||
"PayoutDetailsForm.canadianProvinceRequired": "This field is required",
|
||||
"PayoutDetailsForm.cityLabel": "City",
|
||||
"PayoutDetailsForm.cityPlaceholder": "Helsinki",
|
||||
"PayoutDetailsForm.cityRequired": "This field is required",
|
||||
|
|
@ -452,16 +468,21 @@
|
|||
"PayoutDetailsForm.countryNames.AT": "Austria",
|
||||
"PayoutDetailsForm.countryNames.AU": "Australia",
|
||||
"PayoutDetailsForm.countryNames.BE": "Belgium",
|
||||
"PayoutDetailsForm.countryNames.CA": "Canada",
|
||||
"PayoutDetailsForm.countryNames.CH": "Switzerland",
|
||||
"PayoutDetailsForm.countryNames.DE": "Germany",
|
||||
"PayoutDetailsForm.countryNames.DK": "Denmark",
|
||||
"PayoutDetailsForm.countryNames.ES": "Spain",
|
||||
"PayoutDetailsForm.countryNames.FI": "Finland",
|
||||
"PayoutDetailsForm.countryNames.FR": "France",
|
||||
"PayoutDetailsForm.countryNames.GB": "United Kingdom",
|
||||
"PayoutDetailsForm.countryNames.HK": "Hong Kong",
|
||||
"PayoutDetailsForm.countryNames.IE": "Ireland",
|
||||
"PayoutDetailsForm.countryNames.IT": "Italy",
|
||||
"PayoutDetailsForm.countryNames.LU": "Luxembourg",
|
||||
"PayoutDetailsForm.countryNames.NL": "Netherlands",
|
||||
"PayoutDetailsForm.countryNames.NO": "Norway",
|
||||
"PayoutDetailsForm.countryNames.NZ": "New Zealand",
|
||||
"PayoutDetailsForm.countryNames.PT": "Portugal",
|
||||
"PayoutDetailsForm.countryNames.SE": "Sweden",
|
||||
"PayoutDetailsForm.countryNames.US": "United States",
|
||||
|
|
@ -477,9 +498,19 @@
|
|||
"PayoutDetailsForm.lastNamePlaceholder": "Doe",
|
||||
"PayoutDetailsForm.lastNameRequired": "This field is required",
|
||||
"PayoutDetailsForm.personalDetailsTitle": "Personal details",
|
||||
"PayoutDetailsForm.personalIdNumberTitle": "Personal id number",
|
||||
"PayoutDetailsForm.personalIdNumberLabel.HK": "Hong Kong Identity Card Number (HKID)",
|
||||
"PayoutDetailsForm.personalIdNumberLabel.US": "Last 4 digits of social security number (SSN)",
|
||||
"PayoutDetailsForm.personalIdNumberPlaceholder.HK": "XY123456",
|
||||
"PayoutDetailsForm.personalIdNumberPlaceholder.US": "1234",
|
||||
"PayoutDetailsForm.personalIdNumberRequired": "This field is required",
|
||||
"PayoutDetailsForm.personalIdNumberValid": "Invalid value",
|
||||
"PayoutDetailsForm.postalCodeLabel": "Postal code",
|
||||
"PayoutDetailsForm.postalCodePlaceholder": "00100",
|
||||
"PayoutDetailsForm.postalCodeRequired": "This field is required",
|
||||
"PayoutDetailsForm.stateLabel": "State",
|
||||
"PayoutDetailsForm.statePlaceholder": "Enter your state",
|
||||
"PayoutDetailsForm.stateRequired": "This field is required",
|
||||
"PayoutDetailsForm.streetAddressLabel": "Street address",
|
||||
"PayoutDetailsForm.streetAddressPlaceholder": "Enter your street address…",
|
||||
"PayoutDetailsForm.streetAddressRequired": "This field is required",
|
||||
|
|
@ -629,16 +660,28 @@
|
|||
"StripeBankAccountTokenInputField.accountNumber.placeholder": "Type in bank account number…",
|
||||
"StripeBankAccountTokenInputField.accountNumber.required": "Bank account number is required",
|
||||
"StripeBankAccountTokenInputField.andBeforeLastItemInAList": " and",
|
||||
"StripeBankAccountTokenInputField.branchCode.inline": "Branch code",
|
||||
"StripeBankAccountTokenInputField.branchCode.label": "Branch code",
|
||||
"StripeBankAccountTokenInputField.branchCode.placeholder": "Type in branch code…",
|
||||
"StripeBankAccountTokenInputField.branchCode.required": "Branch code is required",
|
||||
"StripeBankAccountTokenInputField.bsb.inline": "BSB",
|
||||
"StripeBankAccountTokenInputField.bsb.label": "BSB",
|
||||
"StripeBankAccountTokenInputField.bsb.placeholder": "Type in BSB…",
|
||||
"StripeBankAccountTokenInputField.bsb.required": "BSB is required",
|
||||
"StripeBankAccountTokenInputField.clearingCode.inline": "Clearing code",
|
||||
"StripeBankAccountTokenInputField.clearingCode.label": "Clearing code",
|
||||
"StripeBankAccountTokenInputField.clearingCode.placeholder": "Type in clearing code…",
|
||||
"StripeBankAccountTokenInputField.clearingCode.required": "Clearing code is required",
|
||||
"StripeBankAccountTokenInputField.genericStripeError": "Could not connect account number. Please double-check that your {inputs} are valid in {country}",
|
||||
"StripeBankAccountTokenInputField.genericStripeErrorIban": "Could not connect account number. Please double-check that your account number is valid in {country}",
|
||||
"StripeBankAccountTokenInputField.iban.inline": "IBAN",
|
||||
"StripeBankAccountTokenInputField.iban.label": "Bank account number (IBAN)",
|
||||
"StripeBankAccountTokenInputField.iban.placeholder": "DE89 3704 0044 0532 0130 00",
|
||||
"StripeBankAccountTokenInputField.iban.required": "Bank account number (IBAN) is required",
|
||||
"StripeBankAccountTokenInputField.institutionNumber.inline": "institution number",
|
||||
"StripeBankAccountTokenInputField.institutionNumber.label": "Institution number",
|
||||
"StripeBankAccountTokenInputField.institutionNumber.placeholder": "Type in institution number",
|
||||
"StripeBankAccountTokenInputField.institutionNumber.required": "Institution number is required",
|
||||
"StripeBankAccountTokenInputField.routingNumber.inline": "routing number",
|
||||
"StripeBankAccountTokenInputField.routingNumber.label": "Routing number",
|
||||
"StripeBankAccountTokenInputField.routingNumber.placeholder": "Type in routing number…",
|
||||
|
|
@ -647,6 +690,10 @@
|
|||
"StripeBankAccountTokenInputField.sortCode.label": "Sort code",
|
||||
"StripeBankAccountTokenInputField.sortCode.placeholder": "Type in sort code…",
|
||||
"StripeBankAccountTokenInputField.sortCode.required": "Sort code is required",
|
||||
"StripeBankAccountTokenInputField.transitNumber.inline": "transit number",
|
||||
"StripeBankAccountTokenInputField.transitNumber.label": "Transit number",
|
||||
"StripeBankAccountTokenInputField.transitNumber.placeholder": "Type in transit number",
|
||||
"StripeBankAccountTokenInputField.transitNumber.required": "Transit number is required",
|
||||
"StripeBankAccountTokenInputField.unsupportedCountry": "Country not supported: {country}",
|
||||
"StripePaymentForm.creditCardDetails": "Credit card details",
|
||||
"StripePaymentForm.genericError": "Could not handle payment data. Please try again.",
|
||||
|
|
|
|||
|
|
@ -445,6 +445,22 @@
|
|||
"PayoutDetailsForm.birthdayMonthPlaceholder": "mm",
|
||||
"PayoutDetailsForm.birthdayRequired": "La date de naissance est requise et doit être valide.",
|
||||
"PayoutDetailsForm.birthdayYearPlaceholder": "aaaa",
|
||||
"PayoutDetailsForm.canadianProvinceLabel": "Province",
|
||||
"PayoutDetailsForm.canadianProvinceNames.AB": "Alberta",
|
||||
"PayoutDetailsForm.canadianProvinceNames.BC": "British Columbia",
|
||||
"PayoutDetailsForm.canadianProvinceNames.MB": "Manitoba",
|
||||
"PayoutDetailsForm.canadianProvinceNames.NB": "New Brunswick",
|
||||
"PayoutDetailsForm.canadianProvinceNames.NL": "Newfoundland and Labrador",
|
||||
"PayoutDetailsForm.canadianProvinceNames.NS": "Nova Scotia",
|
||||
"PayoutDetailsForm.canadianProvinceNames.NT": "Northwest Territories",
|
||||
"PayoutDetailsForm.canadianProvinceNames.NU": "Nunavut",
|
||||
"PayoutDetailsForm.canadianProvinceNames.ON": "Ontario",
|
||||
"PayoutDetailsForm.canadianProvinceNames.PE": "Prince Edward Island",
|
||||
"PayoutDetailsForm.canadianProvinceNames.QC": "Quebec",
|
||||
"PayoutDetailsForm.canadianProvinceNames.SK": "Saskatchewan",
|
||||
"PayoutDetailsForm.canadianProvinceNames.YT": "Yukon",
|
||||
"PayoutDetailsForm.canadianProvincePlaceholder": "Province",
|
||||
"PayoutDetailsForm.canadianProvinceRequired": "This field is required",
|
||||
"PayoutDetailsForm.cityLabel": "Ville",
|
||||
"PayoutDetailsForm.cityPlaceholder": "Helsinki",
|
||||
"PayoutDetailsForm.cityRequired": "Ce champ est requis.",
|
||||
|
|
@ -452,16 +468,21 @@
|
|||
"PayoutDetailsForm.countryNames.AT": "Autriche",
|
||||
"PayoutDetailsForm.countryNames.AU": "Australie",
|
||||
"PayoutDetailsForm.countryNames.BE": "Belgique",
|
||||
"PayoutDetailsForm.countryNames.CA": "Canada",
|
||||
"PayoutDetailsForm.countryNames.CH": "Switzerland",
|
||||
"PayoutDetailsForm.countryNames.DE": "Allemagne",
|
||||
"PayoutDetailsForm.countryNames.DK": "Danemark",
|
||||
"PayoutDetailsForm.countryNames.ES": "Espagne",
|
||||
"PayoutDetailsForm.countryNames.FI": "Finlande",
|
||||
"PayoutDetailsForm.countryNames.FR": "France",
|
||||
"PayoutDetailsForm.countryNames.GB": "Royaune-Uni",
|
||||
"PayoutDetailsForm.countryNames.HK": "Hong Kong",
|
||||
"PayoutDetailsForm.countryNames.IE": "Irelande",
|
||||
"PayoutDetailsForm.countryNames.IT": "Italie",
|
||||
"PayoutDetailsForm.countryNames.LU": "Luxembourg",
|
||||
"PayoutDetailsForm.countryNames.NL": "Pays-Bas",
|
||||
"PayoutDetailsForm.countryNames.NO": "Norway",
|
||||
"PayoutDetailsForm.countryNames.NZ": "New Zealand",
|
||||
"PayoutDetailsForm.countryNames.PT": "Portugal",
|
||||
"PayoutDetailsForm.countryNames.SE": "Suède",
|
||||
"PayoutDetailsForm.countryNames.US": "États-Unis",
|
||||
|
|
@ -477,9 +498,19 @@
|
|||
"PayoutDetailsForm.lastNamePlaceholder": "Nom",
|
||||
"PayoutDetailsForm.lastNameRequired": "Ce champ est requis.",
|
||||
"PayoutDetailsForm.personalDetailsTitle": "Détails",
|
||||
"PayoutDetailsForm.personalIdNumberLabel.HK": "Hong Kong Identity Card Number (HKID)",
|
||||
"PayoutDetailsForm.personalIdNumberLabel.US": "Last 4 digits of social security number (SSN)",
|
||||
"PayoutDetailsForm.personalIdNumberPlaceholder.HK": "XY123456",
|
||||
"PayoutDetailsForm.personalIdNumberPlaceholder.US": "1234",
|
||||
"PayoutDetailsForm.personalIdNumberRequired": "This field is required",
|
||||
"PayoutDetailsForm.personalIdNumberTitle": "Personal id number",
|
||||
"PayoutDetailsForm.personalIdNumberValid": "Invalid value",
|
||||
"PayoutDetailsForm.postalCodeLabel": "Code postal",
|
||||
"PayoutDetailsForm.postalCodePlaceholder": "00100",
|
||||
"PayoutDetailsForm.postalCodeRequired": "Ce champ est requis.",
|
||||
"PayoutDetailsForm.stateLabel": "State",
|
||||
"PayoutDetailsForm.statePlaceholder": "Enter your state",
|
||||
"PayoutDetailsForm.stateRequired": "This field is required",
|
||||
"PayoutDetailsForm.streetAddressLabel": "Adresse",
|
||||
"PayoutDetailsForm.streetAddressPlaceholder": "Entrez votre adresse",
|
||||
"PayoutDetailsForm.streetAddressRequired": "Ce champ est requis.",
|
||||
|
|
@ -499,8 +530,8 @@
|
|||
"PayoutPreferencesPage.title": "Paiements",
|
||||
"PriceFilter.clear": "Effacer",
|
||||
"PriceFilter.label": "Prix",
|
||||
"PriceFilter.labelSelectedPlain": "Prix : {minPrice} - {maxPrice}",
|
||||
"PriceFilter.labelSelectedButton": "{minPrice} - {maxPrice}",
|
||||
"PriceFilter.labelSelectedPlain": "Prix : {minPrice} - {maxPrice}",
|
||||
"PriceFilterForm.cancel": "Annuler",
|
||||
"PriceFilterForm.clear": "Effacer",
|
||||
"PriceFilterForm.label": "Gamme de prix :",
|
||||
|
|
@ -629,16 +660,28 @@
|
|||
"StripeBankAccountTokenInputField.accountNumber.placeholder": "Entrez votre numéro de compte bancaire",
|
||||
"StripeBankAccountTokenInputField.accountNumber.required": "Le numéro de compte bancaire est requis.",
|
||||
"StripeBankAccountTokenInputField.andBeforeLastItemInAList": " et",
|
||||
"StripeBankAccountTokenInputField.branchCode.inline": "Branch code",
|
||||
"StripeBankAccountTokenInputField.branchCode.label": "Branch code",
|
||||
"StripeBankAccountTokenInputField.branchCode.placeholder": "Type in branch code…",
|
||||
"StripeBankAccountTokenInputField.branchCode.required": "Branch code is required",
|
||||
"StripeBankAccountTokenInputField.bsb.inline": "BSB",
|
||||
"StripeBankAccountTokenInputField.bsb.label": "BSB",
|
||||
"StripeBankAccountTokenInputField.bsb.placeholder": "Entrez le BSB…",
|
||||
"StripeBankAccountTokenInputField.bsb.required": "Le BSB est requis.",
|
||||
"StripeBankAccountTokenInputField.clearingCode.inline": "Clearing code",
|
||||
"StripeBankAccountTokenInputField.clearingCode.label": "Clearing code",
|
||||
"StripeBankAccountTokenInputField.clearingCode.placeholder": "Type in clearing code…",
|
||||
"StripeBankAccountTokenInputField.clearingCode.required": "Clearing code is required",
|
||||
"StripeBankAccountTokenInputField.genericStripeError": "Impossible de connecter ce numéro de compte bancaire. Veuillez vérifier que {inputs} est valide en {country}",
|
||||
"StripeBankAccountTokenInputField.genericStripeErrorIban": "Impossible de connecter ce numéro de compte bancaire. Veuillez vérifier ce numéro est valide en {country}",
|
||||
"StripeBankAccountTokenInputField.iban.inline": "IBAN",
|
||||
"StripeBankAccountTokenInputField.iban.label": "Numéro de compte bancaire (IBAN)",
|
||||
"StripeBankAccountTokenInputField.iban.placeholder": "Par exemple FR76 3000 6000 0112 3456 7890 189",
|
||||
"StripeBankAccountTokenInputField.iban.required": "Le numéro de compte bancaire (IBAN) est requis.",
|
||||
"StripeBankAccountTokenInputField.institutionNumber.inline": "institution number",
|
||||
"StripeBankAccountTokenInputField.institutionNumber.label": "Institution number",
|
||||
"StripeBankAccountTokenInputField.institutionNumber.placeholder": "Type in institution number",
|
||||
"StripeBankAccountTokenInputField.institutionNumber.required": "Institution number is required",
|
||||
"StripeBankAccountTokenInputField.routingNumber.inline": "numéro de routage number",
|
||||
"StripeBankAccountTokenInputField.routingNumber.label": "Numéro de routage",
|
||||
"StripeBankAccountTokenInputField.routingNumber.placeholder": "Entrez le numéro de routage…",
|
||||
|
|
@ -647,6 +690,10 @@
|
|||
"StripeBankAccountTokenInputField.sortCode.label": "Code de tri",
|
||||
"StripeBankAccountTokenInputField.sortCode.placeholder": "Entrez un code de tri…",
|
||||
"StripeBankAccountTokenInputField.sortCode.required": "Un code de tri est requis.",
|
||||
"StripeBankAccountTokenInputField.transitNumber.inline": "transit number",
|
||||
"StripeBankAccountTokenInputField.transitNumber.label": "Transit number",
|
||||
"StripeBankAccountTokenInputField.transitNumber.placeholder": "Type in transit number",
|
||||
"StripeBankAccountTokenInputField.transitNumber.required": "Transit number is required",
|
||||
"StripeBankAccountTokenInputField.unsupportedCountry": "Pays non supporté : {country}",
|
||||
"StripePaymentForm.creditCardDetails": "Votre carte bancaire",
|
||||
"StripePaymentForm.genericError": "Impossible de traiter ces détails de paiement. Veuillez essayer de nouveau.",
|
||||
|
|
@ -749,4 +796,4 @@
|
|||
"UserCard.heading": "Bonjour, je suis {name}.",
|
||||
"UserCard.showFullBioLink": "plus",
|
||||
"UserCard.viewProfileLink": "Voir le profil"
|
||||
}
|
||||
}
|
||||
|
|
@ -136,5 +136,70 @@ export const ageAtLeast = (message, minYears) => value => {
|
|||
return message;
|
||||
};
|
||||
|
||||
export const validSsnLast4 = message => value => {
|
||||
return value.length === 4 ? VALID : message;
|
||||
};
|
||||
|
||||
export const validHKID = message => value => {
|
||||
// Accept value 000000000 for testing Stripe
|
||||
if (value.length === 9 && value.match(/([0]{9})/)) {
|
||||
return VALID;
|
||||
}
|
||||
|
||||
// HKID format example: AB364912(5)
|
||||
// ID can start with one or two letters and the check digit in the end can be in brackets or not
|
||||
if (value.length < 8) {
|
||||
return message;
|
||||
}
|
||||
|
||||
// Handle possible brackets in value
|
||||
if (value.charAt(value.length - 3) === '(' && value.charAt(value.length - 1) === ')') {
|
||||
value = value.substring(0, value.length - 3) + value.charAt(value.length - 2);
|
||||
}
|
||||
value = value.toUpperCase();
|
||||
|
||||
// Check that pattern is correct and split value to array
|
||||
const hkidPattern = /^([A-Z]{1,2})([0-9]{6})([A0-9])$/;
|
||||
const matchArray = value.match(hkidPattern);
|
||||
|
||||
if (!matchArray) {
|
||||
return message;
|
||||
}
|
||||
|
||||
const charPart = matchArray[1];
|
||||
const numPart = matchArray[2];
|
||||
const checkDigit = matchArray[3];
|
||||
|
||||
// Calculate the checksum for character part.
|
||||
// Transfer letters to numbers so that A=10, B=11, C=12 etc.
|
||||
// If there is only one letter in the ID use 36 as the first value
|
||||
// Total calculation is weighted so that 1st digit is x9, 2nd digit x8, 3rd digit x7 etc.
|
||||
|
||||
const strValidChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
let checkSum = 0;
|
||||
|
||||
if (charPart.length === 2) {
|
||||
checkSum += 9 * (10 + strValidChars.indexOf(charPart.charAt(0)));
|
||||
checkSum += 8 * (10 + strValidChars.indexOf(charPart.charAt(1)));
|
||||
} else {
|
||||
checkSum += 9 * 36;
|
||||
checkSum += 8 * (10 + strValidChars.indexOf(charPart));
|
||||
}
|
||||
|
||||
// Calculate the checksum for numeric part
|
||||
|
||||
for (let i = 0, j = 7; i < numPart.length; i++, j--) {
|
||||
checkSum += j * numPart.charAt(i);
|
||||
}
|
||||
|
||||
// Verify the check digit
|
||||
const remaining = checkSum % 11;
|
||||
let verify = remaining === 0 ? 0 : 11 - remaining;
|
||||
verify = verify.toString();
|
||||
const isValid = verify === checkDigit || (verify === 10 && checkDigit === 'A');
|
||||
|
||||
return isValid ? VALID : message;
|
||||
};
|
||||
|
||||
export const composeValidators = (...validators) => value =>
|
||||
validators.reduce((error, validator) => error || validator(value), VALID);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
maxLength,
|
||||
moneySubUnitAmountAtLeast,
|
||||
composeValidators,
|
||||
validHKID,
|
||||
} from './validators';
|
||||
|
||||
const { Money } = sdkTypes;
|
||||
|
|
@ -184,4 +185,33 @@ describe('validators', () => {
|
|||
expect(validateLength('')).toEqual('minLength');
|
||||
});
|
||||
});
|
||||
describe('validHKID()', () => {
|
||||
it('should fail on too short HKID', () => {
|
||||
expect(validHKID('fail')('AB987')).toEqual('fail');
|
||||
});
|
||||
it('should fail if value is in wrong format: no letters', () => {
|
||||
expect(validHKID('fail')('13278240')).toEqual('fail');
|
||||
});
|
||||
it('should fail if value is in wrong format: too many letters', () => {
|
||||
expect(validHKID('fail')('ABE9876543')).toEqual('fail');
|
||||
});
|
||||
it('should fail if value is in wrong format: too many numbers', () => {
|
||||
expect(validHKID('fail')('E13278240')).toEqual('fail');
|
||||
});
|
||||
it('should fail if check digit is wrong', () => {
|
||||
expect(validHKID('fail')('E327824(3)')).toEqual('fail');
|
||||
});
|
||||
it('should allow string of nine digits with all zeros', () => {
|
||||
expect(validHKID('pass')('000000000')).toBeUndefined();
|
||||
});
|
||||
it('should allow string with valid HKID using two letters', () => {
|
||||
expect(validHKID('pass')('AB9876543')).toBeUndefined();
|
||||
});
|
||||
it('should allow string with valid HKID using one letter', () => {
|
||||
expect(validHKID('pass')('E3278240')).toBeUndefined();
|
||||
});
|
||||
it('should allow string with valid HKID using brackets in check digit', () => {
|
||||
expect(validHKID('pass')('E327824(0)')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue