Allow adding a message in the StripePaymentForm

This commit is contained in:
Kimmo Puputti 2017-11-08 14:23:56 +02:00
parent 59209c6edb
commit 27ea4a66f2
7 changed files with 93 additions and 17 deletions

View file

@ -104,16 +104,21 @@ export const speculateTransactionError = e => ({
/* ================ Thunks ================ */
export const initiateOrder = params => (dispatch, getState, sdk) => {
export const initiateOrder = (orderParams, initialMessage) => (dispatch, getState, sdk) => {
dispatch(initiateOrderRequest());
const bodyParams = {
transition: 'transition/preauthorize',
params,
params: orderParams,
};
let orderResponse;
return sdk.transactions
.initiate(bodyParams)
.then(response => {
const orderId = response.data.data.id;
orderResponse = response;
console.log(`TODO: save initial message: "${initialMessage}"`);
})
.then(() => {
const orderId = orderResponse.data.data.id;
dispatch(initiateOrderSuccess(orderId));
dispatch(fetchCurrentUserHasOrdersSuccess(true));
return orderId;
@ -121,9 +126,9 @@ export const initiateOrder = params => (dispatch, getState, sdk) => {
.catch(e => {
dispatch(initiateOrderError(storableError(e)));
log.error(e, 'initiate-order-failed', {
listingId: params.listingId.uuid,
bookingStart: params.bookingStart,
bookingEnd: params.bookingEnd,
listingId: orderParams.listingId.uuid,
bookingStart: orderParams.bookingStart,
bookingEnd: orderParams.bookingEnd,
});
throw e;
});

View file

@ -103,6 +103,7 @@ export class CheckoutPageComponent extends Component {
this.setState({ submitting: true });
const cardToken = values.token;
const initialMessage = values.message;
const { history, sendOrderRequest, speculatedTransaction } = this.props;
const requestParams = {
listingId: this.state.pageData.listing.id,
@ -111,7 +112,7 @@ export class CheckoutPageComponent extends Component {
bookingEnd: speculatedTransaction.booking.attributes.end,
};
sendOrderRequest(requestParams)
sendOrderRequest(requestParams, initialMessage)
.then(orderId => {
this.setState({ submitting: false });
const orderDetailsPath = pathByRouteName('OrderDetailsPage', routeConfiguration(), {
@ -327,6 +328,7 @@ export class CheckoutPageComponent extends Component {
inProgress={this.state.submitting}
formId="CheckoutPagePaymentForm"
paymentInfo={intl.formatMessage({ id: 'CheckoutPage.paymentInfo' })}
authorDisplayName={currentAuthor.attributes.profile.displayName}
/>
) : null}
</section>
@ -431,7 +433,7 @@ const mapStateToProps = state => {
};
const mapDispatchToProps = dispatch => ({
sendOrderRequest: params => dispatch(initiateOrder(params)),
sendOrderRequest: (params, initialMessage) => dispatch(initiateOrder(params, initialMessage)),
fetchSpeculatedTransaction: (listingId, bookingStart, bookingEnd) =>
dispatch(speculateTransaction(listingId, bookingStart, bookingEnd)),
});

View file

@ -95,6 +95,7 @@ exports[`CheckoutPage matches snapshot 1`] = `
</div>
<section>
<InjectIntl(StripePaymentForm)
authorDisplayName="author display name"
formId="CheckoutPagePaymentForm"
inProgress={false}
onSubmit={[Function]}

View file

@ -64,6 +64,10 @@
}
}
.message {
border-bottom-color: var(--matterColorAnti);
}
.submitContainer {
margin-top: auto;

View file

@ -5,6 +5,7 @@ export const Empty = {
component: StripePaymentForm,
props: {
formId: 'StripePaymentFormExample',
authorDisplayName: 'Janne K',
onChange: values => {
console.log('form onChange:', values);
},

View file

@ -2,7 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
import classNames from 'classnames';
import { Form, PrimaryButton } from '../../components';
import { Form, PrimaryButton, ExpandingTextarea } from '../../components';
import * as log from '../../util/log';
import config from '../../config';
@ -70,6 +70,14 @@ const cardStyles = {
},
};
const initialState = {
error: null,
submitting: false,
cardValueValid: false,
token: null,
message: '',
};
/**
* Payment form that asks for credit card info using Stripe Elements.
*
@ -82,7 +90,7 @@ const cardStyles = {
class StripePaymentForm extends Component {
constructor(props) {
super(props);
this.state = { error: null, submitting: false, cardValueValid: false };
this.state = initialState;
this.handleCardValueChange = this.handleCardValueChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
@ -105,6 +113,9 @@ class StripePaymentForm extends Component {
}
});
}
componentWillReceiveProps() {
this.setState(initialState);
}
componentWillUnmount() {
if (this.card) {
this.card.removeEventListener('change', this.handleCardValueChange);
@ -114,11 +125,21 @@ class StripePaymentForm extends Component {
handleCardValueChange(event) {
const { intl, onChange } = this.props;
const { error, complete } = event;
this.setState({
error: error ? stripeErrorTranslation(intl, error) : null,
cardValueValid: complete,
// A change in the card should clear the token and trigger a call
// to the onChange prop with the cleared token and the current
// message.
this.setState(prevState => {
const { message } = prevState;
const token = null;
onChange({ token, message });
return {
error: error ? stripeErrorTranslation(intl, error) : null,
cardValueValid: complete,
token,
};
});
onChange({ token: null });
}
handleSubmit(event) {
event.preventDefault();
@ -129,6 +150,13 @@ class StripePaymentForm extends Component {
}
const { intl, onSubmit } = this.props;
if (this.state.token) {
// Token already fetched for the current card value
onSubmit({ token: this.state.token, message: this.state.message.trim() });
return;
}
this.setState({ submitting: true });
this.stripe
@ -139,10 +167,11 @@ class StripePaymentForm extends Component {
this.setState({
submitting: false,
error: stripeErrorTranslation(intl, error),
token: null,
});
} else {
this.setState({ submitting: false });
onSubmit({ token: token.id });
this.setState({ submitting: false, token: token.id });
onSubmit({ token: token.id, message: this.state.message.trim() });
}
})
.catch(e => {
@ -158,7 +187,16 @@ class StripePaymentForm extends Component {
});
}
render() {
const { className, rootClassName, inProgress, formId, paymentInfo } = this.props;
const {
className,
rootClassName,
inProgress,
formId,
paymentInfo,
onChange,
authorDisplayName,
intl,
} = this.props;
const submitInProgress = this.state.submitting || inProgress;
const submitDisabled = !this.state.cardValueValid || submitInProgress;
const classes = classNames(rootClassName || css.root, className);
@ -167,6 +205,23 @@ class StripePaymentForm extends Component {
[css.cardError]: this.state.error,
});
const messagePlaceholder = intl.formatMessage(
{ id: 'StripePaymentForm.messagePlaceholder' },
{ 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;
});
};
return (
<Form className={classes} onSubmit={this.handleSubmit}>
<h3 className={css.paymentHeading}>
@ -186,6 +241,12 @@ class StripePaymentForm extends Component {
<h3 className={css.messageHeading}>
<FormattedMessage id="StripePaymentForm.messageHeading" />
</h3>
<ExpandingTextarea
className={css.message}
placeholder={messagePlaceholder}
value={this.state.message}
onChange={handleMessageChange}
/>
<div className={css.submitContainer}>
{paymentInfo ? <p className={css.paymentInfo}>{paymentInfo}</p> : null}
<PrimaryButton
@ -221,6 +282,7 @@ StripePaymentForm.propTypes = {
onSubmit: func.isRequired,
onChange: func,
paymentInfo: string,
authorDisplayName: string.isRequired,
};
export default injectIntl(StripePaymentForm);

View file

@ -521,6 +521,7 @@
"StripePaymentForm.creditCardDetails": "Credit card details",
"StripePaymentForm.genericError": "Could not handle payment data. Please try again.",
"StripePaymentForm.messageHeading": "Message",
"StripePaymentForm.messagePlaceholder": "Hello {name}! I'm looking forward to…",
"StripePaymentForm.paymentHeading": "Payment",
"StripePaymentForm.stripe.api_connection_error": "Could not connect to Stripe API.",
"StripePaymentForm.stripe.api_error": "Error in Stripe API.",