mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-26 06:47:17 +10:00
Merge pull request #108 from sharetribe/stripe-payment-form
Stripe payment form
This commit is contained in:
commit
2e4989ac34
7 changed files with 213 additions and 3 deletions
|
|
@ -11,9 +11,18 @@
|
|||
<body>
|
||||
<div id="root"><!--!body--></div>
|
||||
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyAIhL8ykNE8WcPihRdpkL33TXng-0SYDr0&libraries=places"></script>
|
||||
<!-- Stripe script should be on every page, not just the pages that use the API:
|
||||
https://stripe.com/docs/stripe.js#including-stripejs -->
|
||||
<!--
|
||||
Stripe script should be on every page, not just the pages that
|
||||
use the API:
|
||||
|
||||
https://stripe.com/docs/stripe.js#including-stripejs
|
||||
|
||||
We need both v2 and v3. v2 is used for creating the bank
|
||||
account token for providers, and v3 is used for getting
|
||||
the payment token using Stripe elements.
|
||||
-->
|
||||
<script src="https://js.stripe.com/v2/"></script>
|
||||
<script src="https://js.stripe.com/v3/"></script>
|
||||
<!--!preloadedStateScript-->
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
5
src/containers/StripePaymentForm/StripePaymentForm.css
Normal file
5
src/containers/StripePaymentForm/StripePaymentForm.css
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
.card {}
|
||||
|
||||
.submitButton {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import React, { Component } from 'react';
|
||||
import { fakeIntl } from '../../util/test-data';
|
||||
import StripePaymentForm from './StripePaymentForm';
|
||||
|
||||
class StripePaymentFormExample extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { token: null };
|
||||
}
|
||||
render() {
|
||||
const handleSubmit = token => {
|
||||
this.setState({ token });
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<StripePaymentForm {...this.props} onSubmit={handleSubmit} />
|
||||
{this.state.token ? <p>Token: {this.state.token}</p> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const Empty = {
|
||||
component: StripePaymentFormExample,
|
||||
props: { intl: fakeIntl },
|
||||
};
|
||||
144
src/containers/StripePaymentForm/StripePaymentForm.js
Normal file
144
src/containers/StripePaymentForm/StripePaymentForm.js
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
|
||||
import { Button } from '../../components';
|
||||
import config from '../../config';
|
||||
|
||||
import css from './StripePaymentForm.css';
|
||||
|
||||
/**
|
||||
* Translate a Stripe API error object.
|
||||
*
|
||||
* To keep up with possible keys from the Stripe API, see:
|
||||
*
|
||||
* https://stripe.com/docs/api#errors
|
||||
*
|
||||
* Note that at least at moment, the above link doesn't list all the
|
||||
* error codes that the API returns.
|
||||
*
|
||||
* @param {Object} intl - react-intl object from injectIntl
|
||||
* @param {Object} stripeError - error object from Stripe API
|
||||
*
|
||||
* @return {String} translation message for the specific Stripe error,
|
||||
* or the given error message (not translated) if the specific error
|
||||
* type/code is not defined in the translations
|
||||
*
|
||||
*/
|
||||
const stripeErrorTranslation = (intl, stripeError) => {
|
||||
const { message, code, type } = stripeError;
|
||||
|
||||
if (!code || !type) {
|
||||
// Not a proper Stripe error object
|
||||
return intl.formatMessage({ id: 'StripePaymentForm.genericError' });
|
||||
}
|
||||
|
||||
const translationId = type === 'validation_error'
|
||||
? `StripePaymentForm.stripe.validation_error.${code}`
|
||||
: `StripePaymentForm.stripe.${type}`;
|
||||
|
||||
return intl.formatMessage({
|
||||
id: translationId,
|
||||
defaultMessage: message,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Payment form that asks for credit card info using Stripe Elements.
|
||||
*
|
||||
* When the card is valid and the user submits the form, a request is
|
||||
* sent to the Stripe API to fetch a token that is passed to the
|
||||
* onSubmit prop of this form.
|
||||
*
|
||||
* See: https://stripe.com/docs/elements
|
||||
*/
|
||||
class StripePaymentForm extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { error: null, submitting: false, cardValueValid: false };
|
||||
this.handleCardValueChange = this.handleCardValueChange.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
componentDidMount() {
|
||||
if (!window.Stripe) {
|
||||
throw new Error('Stripe must be loaded for StripePaymentForm');
|
||||
}
|
||||
this.stripe = window.Stripe(config.stripe.publishableKey);
|
||||
const elements = this.stripe.elements();
|
||||
this.card = elements.create('card');
|
||||
this.card.mount(this.cardContainer);
|
||||
this.card.addEventListener('change', this.handleCardValueChange);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
if (this.card) {
|
||||
this.card.removeEventListener('change', this.handleCardValueChange);
|
||||
}
|
||||
}
|
||||
handleCardValueChange(event) {
|
||||
const { intl } = this.props;
|
||||
const { error, complete } = event;
|
||||
this.setState({
|
||||
error: error ? stripeErrorTranslation(intl, error) : null,
|
||||
cardValueValid: complete,
|
||||
});
|
||||
}
|
||||
handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (this.state.submitting || !this.state.cardValueValid) {
|
||||
// Already submitting or card value incomplete/invalid
|
||||
return;
|
||||
}
|
||||
|
||||
const { intl, onSubmit } = this.props;
|
||||
this.setState({ submitting: true });
|
||||
|
||||
this.stripe
|
||||
.createToken(this.card)
|
||||
.then(result => {
|
||||
const { error, token } = result;
|
||||
if (error) {
|
||||
this.setState({
|
||||
submitting: false,
|
||||
error: stripeErrorTranslation(intl, error),
|
||||
});
|
||||
} else {
|
||||
this.setState({ submitting: false });
|
||||
onSubmit(token.id);
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
this.setState({
|
||||
submitting: false,
|
||||
error: stripeErrorTranslation(intl, e),
|
||||
});
|
||||
});
|
||||
}
|
||||
render() {
|
||||
const allowSubmit = !this.state.submitting && this.state.cardValueValid;
|
||||
return (
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<label htmlFor={css.card} />
|
||||
<div
|
||||
id={css.card}
|
||||
ref={el => {
|
||||
this.cardContainer = el;
|
||||
}}
|
||||
/>
|
||||
{this.state.error ? <span style={{ color: 'red' }}>{this.state.error}</span> : null}
|
||||
<Button className={css.submitButton} type="submit" disabled={!allowSubmit}>
|
||||
<FormattedMessage id="StripePaymentForm.submitPaymentInfo" />
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const { func } = PropTypes;
|
||||
|
||||
StripePaymentForm.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
onSubmit: func.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(StripePaymentForm);
|
||||
|
|
@ -23,6 +23,7 @@ import SalesConversationPage from './SalesConversationPage/SalesConversationPage
|
|||
import SearchPage from './SearchPage/SearchPage';
|
||||
import SecurityPage from './SecurityPage/SecurityPage';
|
||||
import SignUpForm from './SignUpForm/SignUpForm';
|
||||
import StripePaymentForm from './StripePaymentForm/StripePaymentForm';
|
||||
import StyleguidePage from './StyleguidePage/StyleguidePage';
|
||||
import Topbar from './Topbar/Topbar';
|
||||
|
||||
|
|
@ -52,6 +53,7 @@ export {
|
|||
SearchPage,
|
||||
SecurityPage,
|
||||
SignUpForm,
|
||||
StripePaymentForm,
|
||||
StyleguidePage,
|
||||
Topbar,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import * as LoginForm from './containers/LoginForm/LoginForm.example';
|
|||
import * as PasswordForgottenForm
|
||||
from './containers/PasswordForgottenForm/PasswordForgottenForm.example';
|
||||
import * as SignUpForm from './containers/SignUpForm/SignUpForm.example';
|
||||
import * as StripePaymentForm from './containers/StripePaymentForm/StripePaymentForm.example';
|
||||
|
||||
export {
|
||||
AddImages,
|
||||
|
|
@ -41,4 +42,5 @@ export {
|
|||
PasswordForgottenForm,
|
||||
SignUpForm,
|
||||
StripeBankAccountToken,
|
||||
StripePaymentForm,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -36,5 +36,27 @@
|
|||
"StripeBankAccountToken.createBankAccountTokenErrorIban": "Could not connect account number. Please double-check that your account number is valid in {country} when using {currency}",
|
||||
"StripeBankAccountToken.invalidRoutingNumber": "Invalid routing number {number} for country {country}",
|
||||
"StripeBankAccountToken.routingNumberPlaceholder": "routing number",
|
||||
"StripeBankAccountToken.unsupportedCountry": "Country not supported: {country}"
|
||||
"StripeBankAccountToken.unsupportedCountry": "Country not supported: {country}",
|
||||
"StripePaymentForm.genericError": "Could not handle payment data. Please try again.",
|
||||
"StripePaymentForm.stripe.api_connection_error": "Could not connect to Stripe API.",
|
||||
"StripePaymentForm.stripe.api_error": "Error in Stripe API.",
|
||||
"StripePaymentForm.stripe.authentication_error": "Could not authenticate with Stripe.",
|
||||
"StripePaymentForm.stripe.card_error": "Payment card cannot be handled.",
|
||||
"StripePaymentForm.stripe.invalid_request_error": "Invalid request to Stripe API.",
|
||||
"StripePaymentForm.stripe.rate_limit_error": "Too many requests to Stripe API.",
|
||||
"StripePaymentForm.stripe.validation_error.card_declined": "The card was declined.",
|
||||
"StripePaymentForm.stripe.validation_error.expired_card": "The card has expired.",
|
||||
"StripePaymentForm.stripe.validation_error.incomplete_number": "Your card number is incomplete.",
|
||||
"StripePaymentForm.stripe.validation_error.incorrect_cvc": "The card's security code is incorrect.",
|
||||
"StripePaymentForm.stripe.validation_error.incorrect_number": "The card number is incorrect.",
|
||||
"StripePaymentForm.stripe.validation_error.incorrect_zip": "The card's zip code failed validation.",
|
||||
"StripePaymentForm.stripe.validation_error.invalid_cvc": "The card's security code is invalid.",
|
||||
"StripePaymentForm.stripe.validation_error.invalid_expiry_month": "The card's expiration month is invalid.",
|
||||
"StripePaymentForm.stripe.validation_error.invalid_expiry_month_past": "Your card's expiration date is in the past.",
|
||||
"StripePaymentForm.stripe.validation_error.invalid_expiry_year": "The card's expiration year is invalid.",
|
||||
"StripePaymentForm.stripe.validation_error.invalid_number": "The card number is not a valid credit card number.",
|
||||
"StripePaymentForm.stripe.validation_error.invalid_swipe_data": "The card's swipe data is invalid.",
|
||||
"StripePaymentForm.stripe.validation_error.missing": "There is no card on a customer that is being charged.",
|
||||
"StripePaymentForm.stripe.validation_error.processing_error": "An error occurred while processing the card.",
|
||||
"StripePaymentForm.submitPaymentInfo": "Send request"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue