Merge pull request #105 from sharetribe/create-stripe-account

Create Stripe account when posting a new listing
This commit is contained in:
Kimmo Puputti 2017-04-06 09:49:01 +03:00 committed by GitHub
commit 60ec9e9e5a
9 changed files with 118 additions and 55 deletions

View file

@ -4,7 +4,7 @@ import EditListingForm from './EditListingForm';
export const Empty = {
component: EditListingForm,
props: {
images: [],
stripeConnected: false,
onImageUpload: values => {
console.log(`onImageUpload with id (${values.id}) and file name (${values.file.name})`);
},

View file

@ -160,6 +160,7 @@ export class EditListingFormComponent extends Component {
saveActionMsg = 'Save listing',
submitting,
selectedPlace,
stripeConnected,
} = this.props;
const titleRequiredMessage = intl.formatMessage({ id: 'EditListingForm.titleRequired' });
const maxLengthMessage = intl.formatMessage(
@ -186,6 +187,7 @@ export class EditListingFormComponent extends Component {
const pricePlaceholderMessage = intl.formatMessage({ id: 'EditListingForm.pricePlaceholder' });
const country = selectedPlace ? selectedPlace.country : null;
const showBankAccountField = !stripeConnected && country;
const currency = config.currencyConfig.currency;
return (
@ -253,7 +255,7 @@ export class EditListingFormComponent extends Component {
validate={[required(descriptionRequiredMessage)]}
/>
{country
{showBankAccountField
? <Field
name="bankAccountToken"
component={StripeBankAccountToken}
@ -280,6 +282,7 @@ EditListingFormComponent.propTypes = {
...formPropTypes,
initData: shape({ title: string, description: string }),
selectedPlace: propTypes.place,
stripeConnected: bool.isRequired,
intl: intlShape.isRequired,
onImageUpload: func.isRequired,
onUpdateImageOrder: func.isRequired,

View file

@ -19,6 +19,7 @@ describe('EditListingForm', () => {
onImageUpload={v => v}
onSubmit={v => v}
onUpdateImageOrder={v => v}
stripeConnected={false}
/>
);
expect(tree).toMatchSnapshot();

View file

@ -1,4 +1,5 @@
import { showListingsSuccess as globalShowListingsSuccess } from '../../ducks/sdk.duck';
import { createStripeAccount } from '../../ducks/user.duck';
const requestAction = actionType => params => ({ type: actionType, payload: { params } });
@ -143,11 +144,15 @@ export function requestShowListing(actionPayload) {
export function requestCreateListing(data) {
return (dispatch, getState, sdk) => {
const { bankAccountToken, country, ...actionPayload } = data;
// eslint-disable-next-line no-console
console.log('TODO: call API with Stripe token:', bankAccountToken, 'and country:', country);
dispatch(createListing(actionPayload));
return sdk.listings
.create(actionPayload)
const shouldCreateStripeAccount = bankAccountToken && country;
const accountCreated = shouldCreateStripeAccount
? dispatch(createStripeAccount(bankAccountToken, country))
: Promise.resolve('already created');
return accountCreated
.then(() => sdk.listings.create(actionPayload))
.then(response => {
const id = response.data.data.id.uuid;
// Modify store to understand that we have created listing and can redirect away

View file

@ -5,7 +5,7 @@ import { types } from '../../util/sdkLoader';
import { NamedRedirect, PageLayout } from '../../components';
import { EditListingForm } from '../../containers';
import { getListingsById } from '../../ducks/sdk.duck';
import { fetchCurrentUser } from '../../ducks/Auth.duck';
import { fetchCurrentUser } from '../../ducks/user.duck';
import { createSlug } from '../../util/urlHelpers';
import * as propTypes from '../../util/propTypes';
import {
@ -87,8 +87,9 @@ export class EditListingPageComponent extends Component {
? intl.formatMessage({ id: 'EditListingPage.titleCreateListing' })
: intl.formatMessage({ id: 'EditListingPage.titleEditListing' });
// eslint-disable-next-line no-console
console.log('current user:', currentUser);
const stripeConnected = currentUser && currentUser.attributes
? currentUser.attributes.stripeConnected
: false;
return (
<PageLayout title={title}>
@ -100,6 +101,7 @@ export class EditListingPageComponent extends Component {
onSubmit={onSubmit(onCreateListing)}
onUpdateImageOrder={onUpdateImageOrder}
saveActionMsg={saveActionMsg}
stripeConnected={stripeConnected}
/>
</PageLayout>
);
@ -147,7 +149,7 @@ const mapStateToProps = state => {
return {
page: state.EditListingPage,
marketplaceData: state.data || {},
currentUser: state.Auth.currentUser,
currentUser: state.user.currentUser,
};
};

View file

@ -7,6 +7,7 @@ exports[`EditListingPageComponent matches snapshot 1`] = `
onImageUpload={[Function]}
onSubmit={[Function]}
onUpdateImageOrder={[Function]}
saveActionMsg="Create listing" />
saveActionMsg="Create listing"
stripeConnected={false} />
</Connect(withRouter(PageLayout))>
`;

View file

@ -8,10 +8,6 @@ const authenticated = authInfo => authInfo.grantType === 'refresh_token';
// ================ Action types ================ //
export const USERS_ME_REQUEST = 'app/Auth/USERS_ME_REQUEST';
export const USERS_ME_SUCCESS = 'app/Auth/USERS_ME_SUCCESS';
export const USERS_ME_ERROR = 'app/Auth/USERS_ME_ERROR';
export const AUTH_INFO_REQUEST = 'app/Auth/AUTH_INFO_REQUEST';
export const AUTH_INFO_SUCCESS = 'app/Auth/AUTH_INFO_SUCCESS';
export const AUTH_INFO_ERROR = 'app/Auth/AUTH_INFO_ERROR';
@ -27,10 +23,8 @@ export const LOGOUT_ERROR = 'app/Auth/LOGOUT_ERROR';
// ================ Reducer ================ //
const initialState = {
currentUser: null,
authInfoLoaded: false,
isAuthenticated: false,
currentUserError: null,
authInfoError: null,
loginError: null,
logoutError: null,
@ -39,13 +33,6 @@ const initialState = {
export default function reducer(state = initialState, action = {}) {
const { type, payload } = action;
switch (type) {
case USERS_ME_REQUEST:
return { ...state, currentUserError: null };
case USERS_ME_SUCCESS:
return { ...state, currentUser: payload };
case USERS_ME_ERROR:
return { ...state, currentUserError: payload };
case AUTH_INFO_REQUEST:
return { ...state, authInfoError: null };
case AUTH_INFO_SUCCESS:
@ -73,19 +60,6 @@ export default function reducer(state = initialState, action = {}) {
// ================ Action creators ================ //
export const usersMeRequest = () => ({ type: USERS_ME_REQUEST });
export const usersMeSuccess = user => ({
type: USERS_ME_SUCCESS,
payload: user,
});
export const usersMeError = e => ({
type: USERS_ME_ERROR,
payload: e,
error: true,
});
export const authInfo = () => ({ type: AUTH_INFO_REQUEST });
export const authInfoSuccess = info => ({ type: AUTH_INFO_SUCCESS, payload: info });
export const authInfoError = error => ({ type: AUTH_INFO_ERROR, payload: error, error: true });
@ -162,20 +136,3 @@ export function* watchAuth(sdk) {
}
}
}
// ================ Thunks ================ //
export const fetchCurrentUser = () =>
(dispatch, getState, sdk) => {
dispatch(usersMeRequest());
return sdk.users
.me()
.then(response => {
dispatch(usersMeSuccess(response.data.data));
return response;
})
.catch(e => {
dispatch(usersMeError(e));
throw e;
});
};

View file

@ -9,5 +9,6 @@ import Auth from './Auth.duck';
import FlashNotification from './FlashNotification.duck';
import LocationFilter from './LocationFilter.duck';
import sdkReducer from './sdk.duck';
import user from './user.duck';
export { form, Auth, FlashNotification, LocationFilter, sdkReducer as data };
export { form, Auth, FlashNotification, LocationFilter, sdkReducer as data, user };

93
src/ducks/user.duck.js Normal file
View file

@ -0,0 +1,93 @@
// ================ Action types ================ //
export const USERS_ME_REQUEST = 'app/user/USERS_ME_REQUEST';
export const USERS_ME_SUCCESS = 'app/user/USERS_ME_SUCCESS';
export const USERS_ME_ERROR = 'app/user/USERS_ME_ERROR';
export const STRIPE_ACCOUNT_CREATE_REQUEST = 'app/user/STRIPE_ACCOUNT_CREATE_REQUEST';
export const STRIPE_ACCOUNT_CREATE_SUCCESS = 'app/user/STRIPE_ACCOUNT_CREATE_SUCCESS';
export const STRIPE_ACCOUNT_CREATE_ERROR = 'app/user/STRIPE_ACCOUNT_CREATE_ERROR';
// ================ Reducer ================ //
const initialState = {
currentUser: null,
usersMeError: null,
};
export default function reducer(state = initialState, action = {}) {
const { type, payload } = action;
switch (type) {
case USERS_ME_REQUEST:
return { ...state, usersMeError: null };
case USERS_ME_SUCCESS:
return { ...state, currentUser: payload };
case USERS_ME_ERROR:
// eslint-disable-next-line no-console
console.error(payload);
return { ...state, usersMeError: payload };
default:
return state;
}
}
// ================ Action creators ================ //
export const usersMeRequest = () => ({ type: USERS_ME_REQUEST });
export const usersMeSuccess = user => ({
type: USERS_ME_SUCCESS,
payload: user,
});
export const usersMeError = e => ({
type: USERS_ME_ERROR,
payload: e,
error: true,
});
export const stripeAccountCreateRequest = () => ({ type: STRIPE_ACCOUNT_CREATE_REQUEST });
export const stripeAccountCreateSuccess = response => ({
type: STRIPE_ACCOUNT_CREATE_SUCCESS,
payload: response,
});
export const stripeAccountCreateError = e => ({
type: STRIPE_ACCOUNT_CREATE_ERROR,
payload: e,
error: true,
});
// ================ Thunks ================ //
export const fetchCurrentUser = () =>
(dispatch, getState, sdk) => {
dispatch(usersMeRequest());
return sdk.users
.me()
.then(response => {
dispatch(usersMeSuccess(response.data.data));
return response;
})
.catch(e => {
dispatch(usersMeError(e));
throw e;
});
};
export const createStripeAccount = (bankAccountToken, country) =>
(dispatch, getState, sdk) => {
dispatch(stripeAccountCreateRequest());
return sdk.users.createStripeAccount({ bankAccountToken, country })
.then(response => {
dispatch(stripeAccountCreateSuccess(response));
return response;
})
.catch(e => {
dispatch(stripeAccountCreateError(e));
throw e;
})
.then(() => dispatch(fetchCurrentUser()));
};