Merge pull request #1086 from sharetribe/create-payment-to-stripe-duck

Move creating payment token to stripe.duck.js
This commit is contained in:
Jenni Nurmi 2019-05-09 16:09:21 +03:00 committed by GitHub
commit 07cd93a649
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 120 additions and 38 deletions

View file

@ -14,6 +14,9 @@ way to update this template, but currently, we follow a pattern:
## Upcoming version 2019-XX-XX
- [change] Move Stripe SDK call from `StripePaymentForm` to `stripe.duck.js` for consistency.
[#1086](https://github.com/sharetribe/flex-template-web/pull/1086)
## [v2.16.0] 2019-05-08
This release makes 2 big updates to `sharetribe-scripts` package (which is our fork from Create

View file

@ -37,6 +37,7 @@ import {
setInitialValues,
speculateTransaction,
} from './CheckoutPage.duck';
import { createStripePaymentToken } from '../../ducks/stripe.duck.js';
import config from '../../config';
import { storeData, storedData, clearData } from './CheckoutPageSessionHelpers';
@ -206,6 +207,10 @@ export class CheckoutPageComponent extends Component {
intl,
params,
currentUser,
onCreateStripePaymentToken,
stripePaymentTokenInProgress,
stripePaymentTokenError,
stripePaymentToken,
} = this.props;
// Since the listing data is already given from the ListingPage
@ -464,6 +469,10 @@ export class CheckoutPageComponent extends Component {
paymentInfo={intl.formatMessage({ id: 'CheckoutPage.paymentInfo' })}
authorDisplayName={currentAuthor.attributes.profile.displayName}
showInitialMessageInput={showInitialMessageInput}
onCreateStripePaymentToken={onCreateStripePaymentToken}
stripePaymentTokenInProgress={stripePaymentTokenInProgress}
stripePaymentTokenError={stripePaymentTokenError}
stripePaymentToken={stripePaymentToken}
/>
) : null}
</section>
@ -506,6 +515,7 @@ CheckoutPageComponent.defaultProps = {
speculatedTransaction: null,
enquiredTransaction: null,
currentUser: null,
stripePaymentToken: null,
};
CheckoutPageComponent.propTypes = {
@ -528,6 +538,10 @@ CheckoutPageComponent.propTypes = {
slug: string,
}).isRequired,
sendOrderRequest: func.isRequired,
onCreateStripePaymentToken: func.isRequired,
stripePaymentTokenInProgress: bool.isRequired,
stripePaymentTokenError: bool.isRequired,
stripePaymentToken: object,
// from connect
dispatch: func.isRequired,
@ -553,6 +567,11 @@ const mapStateToProps = state => {
initiateOrderError,
} = state.CheckoutPage;
const { currentUser } = state.user;
const {
stripePaymentTokenInProgress,
stripePaymentTokenError,
stripePaymentToken,
} = state.stripe;
return {
scrollingDisabled: isScrollingDisabled(state),
currentUser,
@ -564,6 +583,9 @@ const mapStateToProps = state => {
enquiredTransaction,
listing,
initiateOrderError,
stripePaymentTokenInProgress,
stripePaymentTokenError,
stripePaymentToken,
};
};
@ -573,6 +595,7 @@ const mapDispatchToProps = dispatch => ({
sendOrderRequestAfterEnquiry: (transactionId, params) =>
dispatch(initiateOrderAfterEnquiry(transactionId, params)),
fetchSpeculatedTransaction: params => dispatch(speculateTransaction(params)),
onCreateStripePaymentToken: params => dispatch(createStripePaymentToken(params)),
});
const CheckoutPage = compose(

View file

@ -24,6 +24,9 @@ describe('CheckoutPage', () => {
fetchSpeculatedTransaction: noop,
speculateTransactionInProgress: false,
scrollingDisabled: false,
onCreateStripePaymentToken: noop,
stripePaymentTokenInProgress: false,
stripePaymentTokenError: false,
};
const tree = renderShallow(<CheckoutPageComponent {...props} />);
expect(tree).toMatchSnapshot();

View file

@ -90,9 +90,13 @@ exports[`CheckoutPage matches snapshot 1`] = `
authorDisplayName="author display name"
formId="CheckoutPagePaymentForm"
inProgress={false}
onCreateStripePaymentToken={[Function]}
onSubmit={[Function]}
paymentInfo="CheckoutPage.paymentInfo"
showInitialMessageInput={true}
stripePaymentToken={null}
stripePaymentTokenError={false}
stripePaymentTokenInProgress={false}
/>
</section>
</div>

View file

@ -18,6 +18,10 @@ export const PERSON_CREATE_REQUEST = 'app/stripe/PERSON_CREATE_REQUEST';
export const PERSON_CREATE_SUCCESS = 'app/stripe/PERSON_CREATE_SUCCESS';
export const PERSON_CREATE_ERROR = 'app/stripe/PERSON_CREATE_ERROR';
export const STRIPE_PAYMENT_TOKEN_CREATE_REQUEST = 'app/stripe/STRIPE_PAYMENT_TOKEN_CREATE_REQUEST';
export const STRIPE_PAYMENT_TOKEN_CREATE_SUCCESS = 'app/stripe/STRIPE_PAYMENT_TOKEN_CREATE_SUCCESS';
export const STRIPE_PAYMENT_TOKEN_CREATE_ERROR = 'app/stripe/STRIPE_PAYMENT_TOKEN_CREATE_ERROR';
// ================ Reducer ================ //
const initialState = {
@ -29,6 +33,9 @@ const initialState = {
persons: [],
stripeAccount: null,
stripeAccountFetched: false,
stripePaymentTokenInProgress: false,
stripePaymentTokenError: false,
stripePaymentToken: null,
};
export default function reducer(state = initialState, action = {}) {
@ -94,6 +101,18 @@ export default function reducer(state = initialState, action = {}) {
}),
};
case STRIPE_PAYMENT_TOKEN_CREATE_REQUEST:
return {
...state,
stripePaymentTokenError: null,
stripePaymentTokenInProgress: true,
};
case STRIPE_PAYMENT_TOKEN_CREATE_SUCCESS:
return { ...state, stripePaymentTokenInProgress: false, stripePaymentToken: payload };
case STRIPE_PAYMENT_TOKEN_CREATE_ERROR:
console.error(payload);
return { ...state, stripePaymentTokenError: payload, stripePaymentTokenInProgress: false };
default:
return state;
}
@ -150,6 +169,21 @@ export const personCreateError = payload => ({
error: true,
});
export const stripePaymentTokenCreateRequest = () => ({
type: STRIPE_PAYMENT_TOKEN_CREATE_REQUEST,
});
export const stripePaymentTokenCreateSuccess = payload => ({
type: STRIPE_PAYMENT_TOKEN_CREATE_SUCCESS,
payload,
});
export const stripePaymentTokenCreateError = payload => ({
type: STRIPE_PAYMENT_TOKEN_CREATE_ERROR,
payload,
error: true,
});
// ================ Thunks ================ //
// Util: rename address fields to match Stripe API specifications
@ -459,3 +493,28 @@ export const createStripeAccount = payoutDetails => (dispatch, getState, sdk) =>
return dispatch(createStripeCompanyAccount(payoutDetails, stripe));
}
};
export const createStripePaymentToken = params => dispatch => {
// It's required to use the same instance of Stripe as where the card has been created
// so that's why Stripe needs to be passed here and we can't create a new instance.
const { stripe, card } = params;
dispatch(stripePaymentTokenCreateRequest());
return stripe
.createToken(card)
.then(response => {
dispatch(stripePaymentTokenCreateSuccess(response.token));
return response;
})
.catch(err => {
const e = storableError(err);
dispatch(stripeAccountCreateError(e));
const stripeMessage =
e.apiErrors && e.apiErrors.length > 0 && e.apiErrors[0].meta
? e.apiErrors[0].meta.stripeMessage
: null;
log.error(err, 'create-stripe-payment-token-failed', { stripeMessage });
throw e;
});
};

View file

@ -1,6 +1,8 @@
import { fakeIntl } from '../../util/test-data';
import StripePaymentForm from './StripePaymentForm';
const noop = () => null;
export const Empty = {
component: StripePaymentForm,
props: {
@ -14,6 +16,9 @@ export const Empty = {
console.log('form onSubmit:', values);
},
intl: fakeIntl,
onCreateStripePaymentToken: noop,
stripePaymentTokenInProgress: false,
stripePaymentTokenError: false,
},
group: 'forms',
};

View file

@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
import classNames from 'classnames';
import { Form, PrimaryButton, ExpandingTextarea } from '../../components';
import * as log from '../../util/log';
import config from '../../config';
import css from './StripePaymentForm.css';
@ -142,48 +141,27 @@ class StripePaymentForm extends Component {
}
handleSubmit(event) {
event.preventDefault();
const { onSubmit, stripePaymentTokenInProgress, stripePaymentToken } = this.props;
if (this.state.submitting || !this.state.cardValueValid) {
if (stripePaymentTokenInProgress || !this.state.cardValueValid) {
// Already submitting or card value incomplete/invalid
return;
}
const { intl, onSubmit } = this.props;
if (this.state.token) {
if (stripePaymentToken) {
// Token already fetched for the current card value
onSubmit({ token: this.state.token, message: this.state.message.trim() });
onSubmit({ token: stripePaymentToken, message: this.state.message.trim() });
return;
}
this.setState({ submitting: true });
const params = {
stripe: this.stripe,
card: this.card,
};
this.stripe
.createToken(this.card)
.then(result => {
const { error, token } = result;
if (error) {
this.setState({
submitting: false,
error: stripeErrorTranslation(intl, error),
token: null,
});
} else {
this.setState({ submitting: false, token: token.id });
onSubmit({ token: token.id, message: this.state.message.trim() });
}
})
.catch(e => {
log.error(e, 'stripe-payment-form-submit-failed', {
stripeErrorType: e.type,
stripeErrorCode: e.code,
});
this.setState({
submitting: false,
error: stripeErrorTranslation(intl, e),
});
});
this.props.onCreateStripePaymentToken(params).then(() => {
onSubmit({ token: this.props.stripePaymentToken.id, message: this.state.message.trim() });
});
}
render() {
const {
@ -196,13 +174,15 @@ class StripePaymentForm extends Component {
authorDisplayName,
showInitialMessageInput,
intl,
stripePaymentTokenInProgress,
stripePaymentTokenError,
} = this.props;
const submitInProgress = this.state.submitting || inProgress;
const submitInProgress = stripePaymentTokenInProgress || inProgress;
const submitDisabled = !this.state.cardValueValid || submitInProgress;
const classes = classNames(rootClassName || css.root, className);
const cardClasses = classNames(css.card, {
[css.cardSuccess]: this.state.cardValueValid,
[css.cardError]: this.state.error && !submitInProgress,
[css.cardError]: stripePaymentTokenError && !submitInProgress,
});
const messagePlaceholder = intl.formatMessage(
@ -261,8 +241,8 @@ class StripePaymentForm extends Component {
this.cardContainer = el;
}}
/>
{this.state.error && !submitInProgress ? (
<span style={{ color: 'red' }}>{this.state.error}</span>
{stripePaymentTokenError && !submitInProgress ? (
<span style={{ color: 'red' }}>{stripePaymentTokenError}</span>
) : null}
{initialMessage}
<div className={css.submitContainer}>
@ -291,9 +271,10 @@ StripePaymentForm.defaultProps = {
inProgress: false,
onChange: () => null,
showInitialMessageInput: true,
stripePaymentToken: null,
};
const { bool, func, string } = PropTypes;
const { bool, func, string, object } = PropTypes;
StripePaymentForm.propTypes = {
className: string,
@ -306,6 +287,10 @@ StripePaymentForm.propTypes = {
paymentInfo: string.isRequired,
authorDisplayName: string.isRequired,
showInitialMessageInput: bool,
onCreateStripePaymentToken: func.isRequired,
stripePaymentTokenInProgress: bool.isRequired,
stripePaymentTokenError: bool.isRequired,
stripePaymentToken: object,
};
export default injectIntl(StripePaymentForm);