From e98222363ed2594e99635b67925edc921d17e9b4 Mon Sep 17 00:00:00 2001 From: Jenni Nurmi Date: Fri, 20 Dec 2019 13:06:22 +0200 Subject: [PATCH] Update from upstream --- CHANGELOG.md | 29 +- package.json | 4 +- src/components/Button/Button.js | 20 +- .../EditListingWizard/EditListingWizard.js | 177 ++++- src/components/IconSuccess/IconSuccess.css | 8 + .../IconSuccess/IconSuccess.example.js | 7 + src/components/IconSuccess/IconSuccess.js | 38 + .../LayoutWrapperAccountSettingsSideNav.js | 6 +- .../ModalMissingInformation.js | 2 +- .../StripeAccountReminder.js | 2 +- .../StripeBankAccountRequiredInput.js | 5 +- .../StripeBankAccountTokenInputField.css | 22 +- ...tripeBankAccountTokenInputField.example.js | 18 + .../StripeBankAccountTokenInputField.js | 6 + .../StripeBankAccountTokenInputField.util.js | 14 +- .../StripeConnectAccountStatusBox.css | 115 +++ .../StripeConnectAccountStatusBox.js | 95 +++ src/components/index.js | 2 + src/config.js | 4 +- .../EditListingPage/EditListingPage.duck.js | 92 ++- .../EditListingPage/EditListingPage.js | 65 +- .../EditListingPage.test.js.snap | 1 - .../PayoutPreferencesPage.duck.js | 68 -- .../PayoutPreferencesPage.js | 149 ---- .../StripePayoutPage.css} | 1 + .../StripePayoutPage/StripePayoutPage.duck.js | 85 +++ .../StripePayoutPage/StripePayoutPage.js | 281 +++++++ .../StripePayoutPage.test.js} | 19 +- .../StripePayoutPage.test.js.snap} | 101 ++- src/containers/index.js | 2 +- src/containers/reducers.js | 4 +- src/ducks/index.js | 2 + src/ducks/stripe.duck.js | 403 ---------- src/ducks/stripeConnectAccount.duck.js | 265 +++++++ src/ducks/user.duck.js | 2 +- src/examples.js | 4 +- .../EditListingDescriptionForm.example.js | 2 + .../EditListingDescriptionForm.test.js | 4 +- .../EditListingDescriptionForm.test.js.snap | 31 - .../EditListingFeaturesForm.example.js | 2 + .../EditListingLocationForm.example.js | 2 + .../EditListingLocationForm.test.js | 2 + .../EditListingLocationForm.test.js.snap | 2 + .../EditListingPhotosForm.example.js | 1 + .../EditListingPhotosForm.test.js | 2 + .../EditListingPhotosForm.test.js.snap | 1 + .../EditListingPoliciesForm.example.js | 2 + .../EditListingPoliciesForm.test.js | 2 + .../EditListingPoliciesForm.test.js.snap | 2 + .../EditListingPricingForm.example.js | 2 + .../EditListingPricingForm.test.js | 2 + .../PayoutDetailsForm/PayoutDetailsForm.js | 14 + .../StripeConnectAccountForm.css | 95 +++ .../StripeConnectAccountForm.js | 292 +++++++ src/forms/index.js | 2 +- src/marketplace.css | 3 + src/routeConfiguration.js | 25 +- src/stripe-config.js | 710 ++++++++++++------ src/translations/en.json | 85 ++- src/util/types.js | 1 + yarn.lock | 8 +- 61 files changed, 2401 insertions(+), 1011 deletions(-) create mode 100644 src/components/IconSuccess/IconSuccess.css create mode 100644 src/components/IconSuccess/IconSuccess.example.js create mode 100644 src/components/IconSuccess/IconSuccess.js create mode 100644 src/components/StripeConnectAccountStatusBox/StripeConnectAccountStatusBox.css create mode 100644 src/components/StripeConnectAccountStatusBox/StripeConnectAccountStatusBox.js delete mode 100644 src/containers/PayoutPreferencesPage/PayoutPreferencesPage.duck.js delete mode 100644 src/containers/PayoutPreferencesPage/PayoutPreferencesPage.js rename src/containers/{PayoutPreferencesPage/PayoutPreferencesPage.css => StripePayoutPage/StripePayoutPage.css} (95%) create mode 100644 src/containers/StripePayoutPage/StripePayoutPage.duck.js create mode 100644 src/containers/StripePayoutPage/StripePayoutPage.js rename src/containers/{PayoutPreferencesPage/PayoutPreferencesPage.test.js => StripePayoutPage/StripePayoutPage.test.js} (77%) rename src/containers/{PayoutPreferencesPage/__snapshots__/PayoutPreferencesPage.test.js.snap => StripePayoutPage/__snapshots__/StripePayoutPage.test.js.snap} (59%) create mode 100644 src/ducks/stripeConnectAccount.duck.js create mode 100644 src/forms/StripeConnectAccountForm/StripeConnectAccountForm.css create mode 100644 src/forms/StripeConnectAccountForm/StripeConnectAccountForm.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b3761200..1df0872f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,33 @@ https://github.com/sharetribe/flex-template-web/ ## Upcoming version 2019-XX-XX +## [v6.0.0] 2019-12-20 + +This is update from from [upstream](https://github.com/sharetribe/ftw-daily): v4.0.0 + +- [change] Use Stripe's [Connect onboarding](https://stripe.com/docs/connect/connect-onboarding) for + adding and updating the identity information of the Stripe account. + - Before updating to this version you should check + [the related pull request](https://github.com/sharetribe/ftw-daily/pull/1234) + - Read more from documentation: + [How to handle provider onboarding and identity verification on FTW](https://www.sharetribe.com/docs/guides/provider-onboarding-and-identity-verification/) + +**Note:** In this update we have deprecated the old `PayoutDetailsForm` and `PayoutPreferencesPage`. +Form now on Stripe will handle collecting the identity information required for verificating the +Stripe account. On FTW we will only handle creating the new account and adding and updating +information about bank account (e.g. IBAN number). If you want to keep using the custom form inside +your application you need to make sure that you are collecting all the required information and +enabling users to update the account so that it doesn't get restricted. + +- [fix] Add missing props to examples related to EditListingWizard + [#1247](https://github.com/sharetribe/ftw-daily/pull/1247) +- [fix] Add missing props to tests related to EditListingWizard + [#1246](https://github.com/sharetribe/ftw-daily/pull/1246) +- [fix] Update links to API Reference docs. + [#1231](https://github.com/sharetribe/ftw-daily/pull/1231) + +[v6.0.0]: https://github.com/sharetribe/ftw-hourly/compare/v5.1.0...v6.0.0 + ## [v5.1.0] 2019-12-09 - [change] Make it easier to reorder EditListingWizard tabs/panels. @@ -26,7 +53,7 @@ https://github.com/sharetribe/flex-template-web/ https://support.stripe.com/questions/connect-address-validation). - [add] Add IconEdit [#1237](https://github.com/sharetribe/ftw-daily/pull/1237) - [v5.1.0]: https://github.com/sharetribe/flex-template-web/compare/v5.0.3...v5.1.0 + [v5.1.0]: https://github.com/sharetribe/ftw-hourly/compare/v5.0.3...v5.1.0 ## [v5.0.3] 2019-12-09 diff --git a/package.json b/package.json index 2523e82bd..1e84cad8a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "app", - "version": "v5.1.0", + "version": "v6.0.0", "private": true, "license": "Apache-2.0", "dependencies": { @@ -53,7 +53,7 @@ "redux": "^4.0.1", "redux-thunk": "^2.3.0", "seedrandom": "^3.0.3", - "sharetribe-flex-sdk": "^1.5.0", + "sharetribe-flex-sdk": "^1.8.0", "sharetribe-scripts": "3.1.1", "smoothscroll-polyfill": "^0.4.0", "source-map-support": "^0.5.9", diff --git a/src/components/Button/Button.js b/src/components/Button/Button.js index b009bbf01..f934c25af 100644 --- a/src/components/Button/Button.js +++ b/src/components/Button/Button.js @@ -14,7 +14,17 @@ class Button extends Component { this.setState({ mounted: true }); // eslint-disable-line react/no-did-mount-set-state } render() { - const { children, className, rootClassName, inProgress, ready, disabled, ...rest } = this.props; + const { + children, + className, + rootClassName, + spinnerClassName, + checkmarkClassName, + inProgress, + ready, + disabled, + ...rest + } = this.props; const rootClass = rootClassName || css.root; const classes = classNames(rootClass, className, { @@ -25,9 +35,9 @@ class Button extends Component { let content; if (inProgress) { - content = ; + content = ; } else if (ready) { - content = ; + content = ; } else { content = children; } @@ -50,6 +60,8 @@ const { node, string, bool } = PropTypes; Button.defaultProps = { rootClassName: null, className: null, + spinnerClassName: null, + checkmarkClassName: null, inProgress: false, ready: false, disabled: false, @@ -59,6 +71,8 @@ Button.defaultProps = { Button.propTypes = { rootClassName: string, className: string, + spinnerClassName: string, + checkmarkClassName: string, inProgress: bool, ready: bool, diff --git a/src/components/EditListingWizard/EditListingWizard.js b/src/components/EditListingWizard/EditListingWizard.js index 90904b3fa..411d3f1e8 100644 --- a/src/components/EditListingWizard/EditListingWizard.js +++ b/src/components/EditListingWizard/EditListingWizard.js @@ -5,15 +5,18 @@ import { compose } from 'redux'; import { FormattedMessage, injectIntl, intlShape } from '../../util/reactIntl'; import classNames from 'classnames'; import config from '../../config'; +import routeConfiguration from '../../routeConfiguration'; +import { createResourceLocatorString } from '../../util/routes'; import { withViewport } from '../../util/contextHelpers'; import { LISTING_PAGE_PARAM_TYPE_DRAFT, LISTING_PAGE_PARAM_TYPE_NEW, LISTING_PAGE_PARAM_TYPES, } from '../../util/urlHelpers'; -import { ensureListing, ensureCurrentUser } from '../../util/data'; -import { PayoutDetailsForm } from '../../forms'; -import { Modal, NamedRedirect, Tabs } from '../../components'; +import { ensureCurrentUser, ensureListing } from '../../util/data'; + +import { Modal, NamedRedirect, Tabs, StripeConnectAccountStatusBox } from '../../components'; +import { StripeConnectAccountForm } from '../../forms'; import EditListingWizardTab, { AVAILABILITY, @@ -172,6 +175,43 @@ const scrollToTab = (tabPrefix, tabId) => { } }; +// Create return URL for the Stripe onboarding form +const createReturnURL = (returnURLType, rootURL, routes, pathParams) => { + const path = createResourceLocatorString( + 'EditListingStripeOnboardingPage', + routes, + { ...pathParams, returnURLType }, + {} + ); + const root = rootURL.replace(/\/$/, ''); + return `${root}${path}`; +}; + +// Get attribute: stripeAccountData +const getStripeAccountData = stripeAccount => stripeAccount.attributes.stripeAccountData || null; + +// Get last 4 digits of bank account returned in Stripe account +const getBankAccountLast4Digits = stripeAccountData => + stripeAccountData && stripeAccountData.external_accounts.data.length > 0 + ? stripeAccountData.external_accounts.data[0].last4 + : null; + +// Check if there's requirements on selected type: 'past_due', 'currently_due' etc. +const hasRequirements = (stripeAccountData, requirementType) => + stripeAccountData != null && + stripeAccountData.requirements && + Array.isArray(stripeAccountData.requirements[requirementType]) && + stripeAccountData.requirements[requirementType].length > 0; + +// Redirect user to Stripe's hosted Connect account onboarding form +const handleGetStripeConnectAccountLinkFn = (getLinkFn, commonParams) => type => () => { + getLinkFn({ type, ...commonParams }) + .then(url => { + window.location.href = url; + }) + .catch(err => console.error(err)); +}; + // Create a new or edit listing through EditListingWizard class EditListingWizard extends Component { constructor(props) { @@ -191,15 +231,32 @@ class EditListingWizard extends Component { this.handlePayoutSubmit = this.handlePayoutSubmit.bind(this); } + componentDidMount() { + const { stripeOnboardingReturnURL } = this.props; + + if (stripeOnboardingReturnURL != null) { + this.setState({ showPayoutDetails: true }); + } + } + handleCreateFlowTabScrolling(shouldScroll) { this.hasScrolledToTab = shouldScroll; } handlePublishListing(id) { - const { onPublishListingDraft, currentUser } = this.props; + const { onPublishListingDraft, currentUser, stripeAccount } = this.props; + const stripeConnected = currentUser && currentUser.stripeAccount && !!currentUser.stripeAccount.id; - if (stripeConnected) { + + const stripeAccountData = stripeConnected ? getStripeAccountData(stripeAccount) : null; + + const requirementsMissing = + stripeAccount && + (hasRequirements(stripeAccountData, 'past_due') || + hasRequirements(stripeAccountData, 'currently_due')); + + if (stripeConnected && !requirementsMissing) { onPublishListingDraft(id); } else { this.setState({ @@ -216,10 +273,8 @@ class EditListingWizard extends Component { handlePayoutSubmit(values) { this.props .onPayoutDetailsSubmit(values) - .then(() => { - this.setState({ showPayoutDetails: false }); + .then(response => { this.props.onManageDisableScrolling('EditListingWizard.payoutModal', false); - this.props.onPublishListingDraft(this.state.draftId); }) .catch(() => { // do nothing @@ -237,8 +292,18 @@ class EditListingWizard extends Component { intl, errors, fetchInProgress, + payoutDetailsSaveInProgress, + payoutDetailsSaved, onManageDisableScrolling, onPayoutDetailsFormChange, + onGetStripeConnectAccountLink, + getAccountLinkInProgress, + createStripeAccountError, + updateStripeAccountError, + fetchStripeAccountError, + stripeAccountFetched, + stripeAccount, + currentUser, ...rest } = this.props; @@ -286,6 +351,44 @@ class EditListingWizard extends Component { this.setState({ portalRoot: document.getElementById('portal-root') }); } }; + const formDisabled = getAccountLinkInProgress; + const ensuredCurrentUser = ensureCurrentUser(currentUser); + const currentUserLoaded = !!ensuredCurrentUser.id; + const stripeConnected = currentUserLoaded && !!stripeAccount && !!stripeAccount.id; + + const rootURL = config.canonicalRootURL; + const routes = routeConfiguration(); + const { returnURLType, ...pathParams } = params; + const successURL = createReturnURL('success', rootURL, routes, pathParams); + const failureURL = createReturnURL('failure', rootURL, routes, pathParams); + + const accountId = stripeConnected ? stripeAccount.id : null; + const stripeAccountData = stripeConnected ? getStripeAccountData(stripeAccount) : null; + + const requirementsMissing = + stripeAccount && + (hasRequirements(stripeAccountData, 'past_due') || + hasRequirements(stripeAccountData, 'currently_due')); + + const savedCountry = stripeAccountData ? stripeAccountData.country : null; + + const handleGetStripeConnectAccountLink = handleGetStripeConnectAccountLinkFn( + onGetStripeConnectAccountLink, + { + accountId, + successURL, + failureURL, + } + ); + + const returnedNormallyFromStripe = returnURLType === 'success'; + const showVerificationError = returnURLType === 'failure'; + const showVerificationNeeded = stripeConnected && requirementsMissing; + + // Redirect from success URL to basic path for StripePayoutPage + if (returnedNormallyFromStripe && stripeConnected && !requirementsMissing) { + return ; + } return ( @@ -335,14 +438,49 @@ class EditListingWizard extends Component { - + {!currentUserLoaded ? ( + + ) : ( + + {stripeConnected && (showVerificationError || showVerificationNeeded) ? ( + + ) : stripeConnected && savedCountry ? ( + + ) : null} + + )} @@ -369,6 +507,10 @@ EditListingWizard.propTypes = { type: oneOf(LISTING_PAGE_PARAM_TYPES).isRequired, tab: oneOf(TABS).isRequired, }).isRequired, + history: shape({ + push: func.isRequired, + replace: func.isRequired, + }).isRequired, // We cannot use propTypes.listing since the listing might be a draft. listing: shape({ @@ -391,8 +533,11 @@ EditListingWizard.propTypes = { createStripeAccountError: object, }).isRequired, fetchInProgress: bool.isRequired, + payoutDetailsSaveInProgress: bool.isRequired, + payoutDetailsSaved: bool.isRequired, onPayoutDetailsFormChange: func.isRequired, onPayoutDetailsSubmit: func.isRequired, + onGetStripeConnectAccountLink: func.isRequired, onManageDisableScrolling: func.isRequired, updateInProgress: bool, diff --git a/src/components/IconSuccess/IconSuccess.css b/src/components/IconSuccess/IconSuccess.css new file mode 100644 index 000000000..f6794b8d2 --- /dev/null +++ b/src/components/IconSuccess/IconSuccess.css @@ -0,0 +1,8 @@ +@import '../../marketplace.css'; + +.root { +} + +.fillColor { + fill: var(--successColor); +} diff --git a/src/components/IconSuccess/IconSuccess.example.js b/src/components/IconSuccess/IconSuccess.example.js new file mode 100644 index 000000000..98f28d104 --- /dev/null +++ b/src/components/IconSuccess/IconSuccess.example.js @@ -0,0 +1,7 @@ +import IconSuccess from './IconSuccess'; + +export const Icon = { + component: IconSuccess, + props: {}, + group: 'icons', +}; diff --git a/src/components/IconSuccess/IconSuccess.js b/src/components/IconSuccess/IconSuccess.js new file mode 100644 index 000000000..414f012b3 --- /dev/null +++ b/src/components/IconSuccess/IconSuccess.js @@ -0,0 +1,38 @@ +import React from 'react'; +import { string } from 'prop-types'; +import classNames from 'classnames'; + +import css from './IconSuccess.css'; + +const IconSuccess = props => { + const { rootClassName, className, fillColor } = props; + const classes = classNames(rootClassName || css.root, className); + return ( + + + + + + + ); +}; + +IconSuccess.defaultProps = { + rootClassName: null, + className: null, + fillColor: null, +}; + +IconSuccess.propTypes = { + rootClassName: string, + className: string, + fillColor: string, +}; + +export default IconSuccess; diff --git a/src/components/LayoutWrapperAccountSettingsSideNav/LayoutWrapperAccountSettingsSideNav.js b/src/components/LayoutWrapperAccountSettingsSideNav/LayoutWrapperAccountSettingsSideNav.js index 86d62d9bb..79a30e9f9 100644 --- a/src/components/LayoutWrapperAccountSettingsSideNav/LayoutWrapperAccountSettingsSideNav.js +++ b/src/components/LayoutWrapperAccountSettingsSideNav/LayoutWrapperAccountSettingsSideNav.js @@ -61,10 +61,10 @@ const LayoutWrapperAccountSettingsSideNavComponent = props => { }, { text: , - selected: currentTab === 'PayoutPreferencesPage', - id: 'PayoutPreferencesPageTab', + selected: currentTab === 'StripePayoutPage', + id: 'StripePayoutPageTab', linkProps: { - name: 'PayoutPreferencesPage', + name: 'StripePayoutPage', }, }, { diff --git a/src/components/ModalMissingInformation/ModalMissingInformation.js b/src/components/ModalMissingInformation/ModalMissingInformation.js index 83ce894de..9a5645fc8 100644 --- a/src/components/ModalMissingInformation/ModalMissingInformation.js +++ b/src/components/ModalMissingInformation/ModalMissingInformation.js @@ -18,7 +18,7 @@ const MISSING_INFORMATION_MODAL_WHITELIST = [ 'ContactDetailsPage', 'EmailVerificationPage', 'PasswordResetPage', - 'PayoutPreferencesPage', + 'StripePayoutPage', ]; const EMAIL_VERIFICATION = 'EMAIL_VERIFICATION'; diff --git a/src/components/ModalMissingInformation/StripeAccountReminder.js b/src/components/ModalMissingInformation/StripeAccountReminder.js index 4b1f2181e..c2372f774 100644 --- a/src/components/ModalMissingInformation/StripeAccountReminder.js +++ b/src/components/ModalMissingInformation/StripeAccountReminder.js @@ -16,7 +16,7 @@ const StripeAccountReminder = props => {
{inputError}
{message}
- -