Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stripe create token for account #836

Merged
merged 9 commits into from
May 18, 2018
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ way to update this template, but currently, we follow a pattern:
## Upcoming version

* Remove custom touched handling from `FieldCheckboxGroup` as it has has become obsolete now that
Final Form is replacing Redux Form. [#837](https://github.com/sharetribe/flex-template-web/pull/837)
Final Form is replacing Redux Form. [#837](https://github.com/sharetribe/flex-template-web/pull/837)
* Create Stripe account directly instead of passing payout details to Flex API (deprecated way).
[#836](https://github.com/sharetribe/flex-template-web/pull/836)

## v0.2.0

Expand Down
27 changes: 2 additions & 25 deletions src/components/EditListingWizard/EditListingWizard.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { array, bool, func, number, object, oneOf, shape, string } from 'prop-ty
import { compose } from 'redux';
import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
import classNames from 'classnames';
import { omitBy, isUndefined } from 'lodash';
import { withViewport } from '../../util/contextHelpers';
import { ensureListing } from '../../util/data';
import { PayoutDetailsForm } from '../../forms';
Expand Down Expand Up @@ -144,31 +143,9 @@ class EditListingWizard extends Component {
}

handlePayoutSubmit(values) {
const {
fname: firstName,
lname: lastName,
birthDate,
country,
streetAddress,
postalCode,
city,
bankAccountToken,
} = values;
const address = {
country,
city,
addressLine: streetAddress,
postalCode,
};
const params = {
firstName,
lastName,
birthDate,
bankAccountToken,
address: omitBy(address, isUndefined),
};
const { fname: firstName, lname: lastName, ...rest } = values;
this.props
.onPayoutDetailsSubmit(params)
.onPayoutDetailsSubmit({ firstName, lastName, ...rest })
.then(() => {
this.setState({ showPayoutDetails: false });
this.props.onManageDisableScrolling('EditListingWizard.payoutModal', false);
Expand Down
28 changes: 12 additions & 16 deletions src/components/FieldBirthdayInput/FieldBirthdayInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,42 +29,38 @@ const pad = num => {
};

const parseNum = str => {
const num = parseInt(str, 10);
return isNaN(num) ? null : num;
const num = Number.parseInt(str, 10);
return Number.isNaN(num) ? null : num;
};

// Validate that the given date has the same info as the selected
// value, i.e. it has not e.g. rolled over to the next month if the
// selected month doesn't have as many days as selected.
//
// Note the UTC handling, we want to deal with UTC date info even
// though the date object itself is in the user's local timezone.
const isValidDate = (date, year, month, day) => {
const yearsMatch = date.getUTCFullYear() === year;
const monthsMatch = date.getUTCMonth() + 1 === month;
const daysMatch = date.getUTCDate() === day;
const yearsMatch = date.getFullYear() === year;
const monthsMatch = date.getMonth() + 1 === month;
const daysMatch = date.getDate() === day;
return yearsMatch && monthsMatch && daysMatch;
};

// Create a UTC Date from the selected values. Return null if the date
// Create a Date from the selected values. Return null if the date
// is invalid.
const dateFromSelected = ({ day, month, year }) => {
const dayNum = parseNum(day);
const monthNum = parseNum(month);
const yearNum = parseNum(year);
if (dayNum !== null && monthNum !== null && yearNum !== null) {
const d = new Date(Date.UTC(yearNum, monthNum - 1, dayNum));
return isValidDate(d, yearNum, monthNum, dayNum) ? d : null;
const d = new Date(yearNum, monthNum - 1, dayNum);
return isValidDate(d, yearNum, monthNum, dayNum) ? { year, month, day } : null;
}
return null;
};

// Get the UTC year/month/day info from the date object in local
// timezone.
// Get the year/month/day info from the date object in local timezone.
const selectedFromDate = date => ({
day: date.getUTCDate(),
month: date.getUTCMonth() + 1,
year: date.getUTCFullYear(),
day: date.getDate(),
month: date.getMonth() + 1,
year: date.getFullYear(),
});

// Always show 31 days per month
Expand Down
27 changes: 2 additions & 25 deletions src/containers/PayoutPreferencesPage/PayoutPreferencesPage.duck.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { omitBy, isUndefined } from 'lodash';
import { fetchCurrentUser, createStripeAccount } from '../../ducks/user.duck';

// ================ Action types ================ //
Expand Down Expand Up @@ -53,30 +52,8 @@ export const savePayoutDetailsSuccess = () => ({

export const savePayoutDetails = values => (dispatch, getState, sdk) => {
dispatch(savePayoutDetailsRequest());
const {
firstName,
lastName,
birthDate,
country,
streetAddress,
postalCode,
city,
bankAccountToken,
} = values;
const address = {
country,
city,
addressLine: streetAddress,
postalCode,
};
const params = {
firstName,
lastName,
birthDate,
bankAccountToken,
address: omitBy(address, isUndefined),
};
return dispatch(createStripeAccount(params))

return dispatch(createStripeAccount(values))
.then(() => dispatch(savePayoutDetailsSuccess()))
.catch(() => dispatch(savePayoutDetailsError()));
};
Expand Down
5 changes: 2 additions & 3 deletions src/containers/PayoutPreferencesPage/PayoutPreferencesPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,8 @@ export const PayoutPreferencesPageComponent = props => {
}

const handlePayoutDetailsSubmit = values => {
const { fname, lname, ...rest } = values;
const params = { firstName: fname, lastName: lname, ...rest };
onPayoutDetailsFormSubmit(params);
const { fname: firstName, lname: lastName, ...rest } = values;
onPayoutDetailsFormSubmit({ firstName, lastName, ...rest });
};

const showForm =
Expand Down
45 changes: 43 additions & 2 deletions src/ducks/user.duck.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { omitBy, isUndefined } from 'lodash';
import config from '../config';
import { denormalisedResponseEntities } from '../util/data';
import { storableError } from '../util/errors';
import { TRANSITION_REQUEST, TRANSITION_REQUEST_AFTER_ENQUIRY } from '../util/types';
Expand Down Expand Up @@ -370,12 +372,51 @@ export const fetchCurrentUser = () => (dispatch, getState, sdk) => {
};

export const createStripeAccount = payoutDetails => (dispatch, getState, sdk) => {
if (typeof window === 'undefined' || !window.Stripe) {
throw new Error('Stripe must be loaded for submitting PayoutPreferences');
}

const stripe = window.Stripe(config.stripe.publishableKey);

dispatch(stripeAccountCreateRequest());

const {
firstName,
lastName,
birthDate,
country,
streetAddress,
postalCode,
city,
bankAccountToken,
} = payoutDetails;

const address = {
city,
line1: streetAddress,
postal_code: postalCode,
};

// Params for Stripe SDK
const params = {
legal_entity: {
first_name: firstName,
last_name: lastName,
address: omitBy(address, isUndefined),
dob: birthDate,
type: 'individual',
},
tos_shown_and_accepted: true,
};

let accountResponse;

return sdk.currentUser
.createStripeAccount(payoutDetails)
return stripe
.createToken('account', params)
.then(response => {
const accountToken = response.token.id;
return sdk.currentUser.createStripeAccount({ accountToken, bankAccountToken, country });
})
.then(response => {
accountResponse = response;
return dispatch(fetchCurrentUser());
Expand Down
19 changes: 19 additions & 0 deletions src/forms/PayoutDetailsForm/PayoutDetailsForm.css
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,22 @@
.error {
@apply --marketplaceModalErrorStyles;
}

.termsText {
@apply --marketplaceModalHelperText;
margin-bottom: 12px;
text-align: center;

@media (--viewportMedium) {
margin-bottom: 16px;
}
}

.termsLink {
@apply --marketplaceModalHelperLink;

&:hover {
text-decoration: underline;
cursor: pointer;
}
}
25 changes: 18 additions & 7 deletions src/forms/PayoutDetailsForm/PayoutDetailsForm.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
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 classNames from 'classnames';
import config from '../../config';
import {
Button,
ExternalLink,
StripeBankAccountTokenInputField,
FieldSelect,
FieldBirthdayInput,
Expand Down Expand Up @@ -48,8 +49,8 @@ const PayoutDetailsFormComponent = props => (
const {
className,
createStripeAccountError,
change,
disabled,
form,
handleSubmit,
inProgress,
intl,
Expand Down Expand Up @@ -172,6 +173,12 @@ const PayoutDetailsFormComponent = props => (
);
}

const stripeConnectedAccountTermsLink = (
<ExternalLink href="https://stripe.com/connect-account/legal" className={css.termsLink}>
<FormattedMessage id="PayoutDetailsForm.stripeConnectedAccountTermsLink" />
</ExternalLink>
);

return (
<Form className={classes} onSubmit={handleSubmit}>
<div className={css.sectionContainer}>
Expand Down Expand Up @@ -248,7 +255,7 @@ const PayoutDetailsFormComponent = props => (
label={streetAddressLabel}
placeholder={streetAddressPlaceholder}
validate={streetAddressRequired}
onUnmount={() => change('streetAddress', undefined)}
onUnmount={() => form.change('streetAddress', undefined)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, what's this for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't use deprecated 'change' but 'form.change' 1a0e9ae

Dropdown affects visible fields: this clears those fields.

/>
<div className={css.formRow}>
<FieldTextInput
Expand All @@ -261,7 +268,7 @@ const PayoutDetailsFormComponent = props => (
label={postalCodeLabel}
placeholder={postalCodePlaceholder}
validate={postalCodeRequired}
onUnmount={() => change('postalCode', undefined)}
onUnmount={() => form.change('postalCode', undefined)}
/>
<FieldTextInput
id="city"
Expand All @@ -273,7 +280,7 @@ const PayoutDetailsFormComponent = props => (
label={cityLabel}
placeholder={cityPlaceholder}
validate={cityRequired}
onUnmount={() => change('city', undefined)}
onUnmount={() => form.change('city', undefined)}
/>
</div>
</div>
Expand All @@ -295,6 +302,12 @@ const PayoutDetailsFormComponent = props => (
</div>
) : null}
{error}
<p className={css.termsText}>
<FormattedMessage
id="PayoutDetailsForm.stripeToSText"
values={{ stripeConnectedAccountTermsLink }}
/>
</p>
<Button
className={css.submitButton}
type="submit"
Expand Down Expand Up @@ -324,8 +337,6 @@ PayoutDetailsFormComponent.defaultProps = {
submitButtonText: null,
};

const { bool, object, string } = PropTypes;

PayoutDetailsFormComponent.propTypes = {
className: string,
createStripeAccountError: object,
Expand Down
2 changes: 2 additions & 0 deletions src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,8 @@
"PayoutDetailsForm.streetAddressLabel": "Street address",
"PayoutDetailsForm.streetAddressPlaceholder": "Enter your street address…",
"PayoutDetailsForm.streetAddressRequired": "This field is required",
"PayoutDetailsForm.stripeConnectedAccountTermsLink": "Stripe Connected Account Agreement",
"PayoutDetailsForm.stripeToSText": "By saving details, you agree to the {stripeConnectedAccountTermsLink}",
"PayoutDetailsForm.submitButtonText": "Save details & publish listing",
"PayoutDetailsForm.title": "One more thing: payout preferences",
"PayoutPreferencesPage.contactDetailsTabTitle": "Contact details",
Expand Down
12 changes: 12 additions & 0 deletions src/util/polyfills.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,15 @@ require('object.values').shim();
if (typeof Number.parseFloat === 'undefined' && typeof window !== 'undefined') {
Number.parseFloat = window.parseFloat;
}

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/parseInt
if (typeof Number.parseInt === 'undefined' && typeof window !== 'undefined') {
Number.parseInt = window.parseInt;
}

// NaN is the only value in javascript which is not equal to itself.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN
if (typeof Number.isNaN === 'undefined') {
// eslint-disable-next-line no-self-compare
Number.isNaN = value => value !== value;
}
22 changes: 19 additions & 3 deletions src/util/validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,26 @@ export const moneySubUnitAmountAtLeast = (message, minValue) => value => {
return value instanceof Money && value.amount >= minValue ? VALID : message;
};

const parseNum = str => {
const num = Number.parseInt(str, 10);
return Number.isNaN(num) ? null : num;
};

export const ageAtLeast = (message, minYears) => value => {
const now = moment();
const ageInYears = now.diff(moment(value), 'years', true);
return value && value instanceof Date && ageInYears >= minYears ? VALID : message;
const { year, month, day } = value;
const dayNum = parseNum(day);
const monthNum = parseNum(month);
const yearNum = parseNum(year);

// day, month, and year needs to be numbers
if (dayNum !== null && monthNum !== null && yearNum !== null) {
const now = moment();
const age = new Date(yearNum, monthNum - 1, dayNum);
const ageInYears = now.diff(moment(age), 'years', true);

return age && age instanceof Date && ageInYears >= minYears ? VALID : message;
}
return message;
};

export const composeValidators = (...validators) => value =>
Expand Down