Skip to content

Commit

Permalink
Merge pull request #836 from sharetribe/stripe-createToken-for-account
Browse files Browse the repository at this point in the history
Stripe create token for account
  • Loading branch information
Gnito authored May 18, 2018
2 parents f81f01e + db2c8f8 commit a52ea81
Show file tree
Hide file tree
Showing 11 changed files with 134 additions and 82 deletions.
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)}
/>
<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

0 comments on commit a52ea81

Please sign in to comment.