Skip to content

Commit

Permalink
Merge pull request #1088 from sharetribe/use-final-form-in-stripe-pay…
Browse files Browse the repository at this point in the history
…ment-form

Use final form in StripePaymentForm
  • Loading branch information
OtterleyW authored May 16, 2019
2 parents 07cd93a + c772af8 commit f4a487f
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 60 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ way to update this template, but currently, we follow a pattern:

## Upcoming version 2019-XX-XX

- [cghange] Use Final Form on `StripePaymentForm` for consistency. Note that card form Stripe
Elements in `StripePaymentForm` is not a Final Form field so it's not available trough Final Form
but handled separately. [#1088](https://github.com/sharetribe/flex-template-web/pull/1088)
- [change] Move Stripe SDK call from `StripePaymentForm` to `stripe.duck.js` for consistency.
[#1086](https://github.com/sharetribe/flex-template-web/pull/1086)

Expand Down
6 changes: 4 additions & 2 deletions src/containers/CheckoutPage/CheckoutPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,8 @@ CheckoutPageComponent.defaultProps = {
enquiredTransaction: null,
currentUser: null,
stripePaymentToken: null,
stripePaymentTokenInProgress: false,
stripePaymentTokenError: null,
};

CheckoutPageComponent.propTypes = {
Expand All @@ -539,8 +541,8 @@ CheckoutPageComponent.propTypes = {
}).isRequired,
sendOrderRequest: func.isRequired,
onCreateStripePaymentToken: func.isRequired,
stripePaymentTokenInProgress: bool.isRequired,
stripePaymentTokenError: bool.isRequired,
stripePaymentTokenInProgress: bool,
stripePaymentTokenError: propTypes.error,
stripePaymentToken: object,

// from connect
Expand Down
2 changes: 1 addition & 1 deletion src/containers/CheckoutPage/CheckoutPage.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('CheckoutPage', () => {
scrollingDisabled: false,
onCreateStripePaymentToken: noop,
stripePaymentTokenInProgress: false,
stripePaymentTokenError: false,
stripePaymentTokenError: null,
};
const tree = renderShallow(<CheckoutPageComponent {...props} />);
expect(tree).toMatchSnapshot();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ exports[`CheckoutPage matches snapshot 1`] = `
paymentInfo="CheckoutPage.paymentInfo"
showInitialMessageInput={true}
stripePaymentToken={null}
stripePaymentTokenError={false}
stripePaymentTokenError={null}
stripePaymentTokenInProgress={false}
/>
</section>
Expand Down
37 changes: 17 additions & 20 deletions src/ducks/stripe.duck.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ 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';
export const CREATE_PAYMENT_TOKEN_REQUEST = 'app/stripe/CREATE_PAYMENT_TOKEN_REQUEST';
export const CREATE_PAYMENT_TOKEN_SUCCESS = 'app/stripe/CREATE_PAYMENT_TOKEN_SUCCESS';
export const CREATE_PAYMENT_TOKEN_ERROR = 'app/stripe/CREATE_PAYMENT_TOKEN_ERROR';

// ================ Reducer ================ //

Expand All @@ -34,7 +34,7 @@ const initialState = {
stripeAccount: null,
stripeAccountFetched: false,
stripePaymentTokenInProgress: false,
stripePaymentTokenError: false,
stripePaymentTokenError: null,
stripePaymentToken: null,
};

Expand Down Expand Up @@ -101,15 +101,15 @@ export default function reducer(state = initialState, action = {}) {
}),
};

case STRIPE_PAYMENT_TOKEN_CREATE_REQUEST:
case CREATE_PAYMENT_TOKEN_REQUEST:
return {
...state,
stripePaymentTokenError: null,
stripePaymentTokenInProgress: true,
};
case STRIPE_PAYMENT_TOKEN_CREATE_SUCCESS:
case CREATE_PAYMENT_TOKEN_SUCCESS:
return { ...state, stripePaymentTokenInProgress: false, stripePaymentToken: payload };
case STRIPE_PAYMENT_TOKEN_CREATE_ERROR:
case CREATE_PAYMENT_TOKEN_ERROR:
console.error(payload);
return { ...state, stripePaymentTokenError: payload, stripePaymentTokenInProgress: false };

Expand Down Expand Up @@ -169,17 +169,17 @@ export const personCreateError = payload => ({
error: true,
});

export const stripePaymentTokenCreateRequest = () => ({
type: STRIPE_PAYMENT_TOKEN_CREATE_REQUEST,
export const createPaymentTokenRequest = () => ({
type: CREATE_PAYMENT_TOKEN_REQUEST,
});

export const stripePaymentTokenCreateSuccess = payload => ({
type: STRIPE_PAYMENT_TOKEN_CREATE_SUCCESS,
export const createPaymentTokenSuccess = payload => ({
type: CREATE_PAYMENT_TOKEN_SUCCESS,
payload,
});

export const stripePaymentTokenCreateError = payload => ({
type: STRIPE_PAYMENT_TOKEN_CREATE_ERROR,
export const createPaymentTokenError = payload => ({
type: CREATE_PAYMENT_TOKEN_ERROR,
payload,
error: true,
});
Expand Down Expand Up @@ -499,21 +499,18 @@ export const createStripePaymentToken = params => dispatch => {
// so that's why Stripe needs to be passed here and we can't create a new instance.
const { stripe, card } = params;

dispatch(stripePaymentTokenCreateRequest());
dispatch(createPaymentTokenRequest());

return stripe
.createToken(card)
.then(response => {
dispatch(stripePaymentTokenCreateSuccess(response.token));
dispatch(createPaymentTokenSuccess(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;
dispatch(createPaymentTokenError(e));
const stripeMessage = e.message;
log.error(err, 'create-stripe-payment-token-failed', { stripeMessage });
throw e;
});
Expand Down
2 changes: 1 addition & 1 deletion src/forms/StripePaymentForm/StripePaymentForm.example.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const Empty = {
intl: fakeIntl,
onCreateStripePaymentToken: noop,
stripePaymentTokenInProgress: false,
stripePaymentTokenError: false,
stripePaymentTokenError: null,
},
group: 'forms',
};
77 changes: 42 additions & 35 deletions src/forms/StripePaymentForm/StripePaymentForm.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
/**
* Note: This form is using card from Stripe Elements https://stripe.com/docs/stripe-js#elements
* Card is not a Final Form field so it's not available trough Final Form.
* It's also handled separately in handleSubmit function.
*/
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
import { Form as FinalForm } from 'react-final-form';
import classNames from 'classnames';
import { Form, PrimaryButton, ExpandingTextarea } from '../../components';
import config from '../../config';
import { propTypes } from '../../util/types';
import { Form, PrimaryButton, FieldTextInput } from '../../components';

import css from './StripePaymentForm.css';

Expand Down Expand Up @@ -74,7 +81,6 @@ const initialState = {
submitting: false,
cardValueValid: false,
token: null,
message: '',
};

/**
Expand All @@ -92,6 +98,7 @@ class StripePaymentForm extends Component {
this.state = initialState;
this.handleCardValueChange = this.handleCardValueChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.paymentForm = this.paymentForm.bind(this);
}
componentDidMount() {
if (!window.Stripe) {
Expand Down Expand Up @@ -139,9 +146,9 @@ class StripePaymentForm extends Component {
};
});
}
handleSubmit(event) {
event.preventDefault();
handleSubmit(values) {
const { onSubmit, stripePaymentTokenInProgress, stripePaymentToken } = this.props;
const initialMessage = values.initialMessage ? values.initialMessage.trim() : null;

if (stripePaymentTokenInProgress || !this.state.cardValueValid) {
// Already submitting or card value incomplete/invalid
Expand All @@ -150,7 +157,7 @@ class StripePaymentForm extends Component {

if (stripePaymentToken) {
// Token already fetched for the current card value
onSubmit({ token: stripePaymentToken, message: this.state.message.trim() });
onSubmit({ token: stripePaymentToken.id, message: initialMessage });
return;
}

Expand All @@ -160,25 +167,28 @@ class StripePaymentForm extends Component {
};

this.props.onCreateStripePaymentToken(params).then(() => {
onSubmit({ token: this.props.stripePaymentToken.id, message: this.state.message.trim() });
onSubmit({ token: this.props.stripePaymentToken.id, message: initialMessage });
});
}
render() {

paymentForm(formRenderProps) {
const {
className,
rootClassName,
inProgress,
formId,
paymentInfo,
onChange,
authorDisplayName,
showInitialMessageInput,
intl,
stripePaymentTokenInProgress,
stripePaymentTokenError,
} = this.props;
invalid,
handleSubmit,
} = formRenderProps;

const submitInProgress = stripePaymentTokenInProgress || inProgress;
const submitDisabled = !this.state.cardValueValid || submitInProgress;
const submitDisabled = invalid || !this.state.cardValueValid || submitInProgress;
const classes = classNames(rootClassName || css.root, className);
const cardClasses = classNames(css.card, {
[css.cardSuccess]: this.state.cardValueValid,
Expand All @@ -190,44 +200,34 @@ class StripePaymentForm extends Component {
{ name: authorDisplayName }
);

const handleMessageChange = e => {
// A change in the message should call the onChange prop with
// the current token and the new message.
const message = e.target.value;
this.setState(prevState => {
const { token } = prevState;
const newState = { token, message };
onChange(newState);
return newState;
});
};
const messageOptionalText = intl.formatMessage({
id: 'StripePaymentForm.messageOptionalText',
});

const messageOptionalText = (
<span className={css.messageOptional}>
<FormattedMessage id="StripePaymentForm.messageOptionalText" />
</span>
const initialMessageLabel = intl.formatMessage(
{ id: 'StripePaymentForm.messageLabel' },
{ messageOptionalText: messageOptionalText }
);

const initialMessage = showInitialMessageInput ? (
<div>
<h3 className={css.messageHeading}>
<FormattedMessage id="StripePaymentForm.messageHeading" />
</h3>
<label className={css.messageLabel} htmlFor={`${formId}-message`}>
<FormattedMessage id="StripePaymentForm.messageLabel" values={{ messageOptionalText }} />
</label>
<ExpandingTextarea

<FieldTextInput
type="textarea"
id={`${formId}-message`}
className={css.message}
name="initialMessage"
label={initialMessageLabel}
placeholder={messagePlaceholder}
value={this.state.message}
onChange={handleMessageChange}
className={css.message}
/>
</div>
) : null;

return config.stripe.publishableKey ? (
<Form className={classes} onSubmit={this.handleSubmit}>
<Form className={classes} onSubmit={handleSubmit}>
<h3 className={css.paymentHeading}>
<FormattedMessage id="StripePaymentForm.paymentHeading" />
</h3>
Expand Down Expand Up @@ -263,6 +263,11 @@ class StripePaymentForm extends Component {
</div>
);
}

render() {
const { onSubmit, ...rest } = this.props;
return <FinalForm onSubmit={this.handleSubmit} {...rest} render={this.paymentForm} />;
}
}

StripePaymentForm.defaultProps = {
Expand All @@ -272,6 +277,8 @@ StripePaymentForm.defaultProps = {
onChange: () => null,
showInitialMessageInput: true,
stripePaymentToken: null,
stripePaymentTokenInProgress: false,
stripePaymentTokenError: null,
};

const { bool, func, string, object } = PropTypes;
Expand All @@ -288,8 +295,8 @@ StripePaymentForm.propTypes = {
authorDisplayName: string.isRequired,
showInitialMessageInput: bool,
onCreateStripePaymentToken: func.isRequired,
stripePaymentTokenInProgress: bool.isRequired,
stripePaymentTokenError: bool.isRequired,
stripePaymentTokenInProgress: bool,
stripePaymentTokenError: propTypes.error,
stripePaymentToken: object,
};

Expand Down

0 comments on commit f4a487f

Please sign in to comment.