react-stripe-checkout
Version:
Easily inject checkout.js as a react component. Will load the script on demand and supports all the options from stripe docs.
537 lines (460 loc) • 20.1 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var scriptLoading = false;
var scriptLoaded = false;
var scriptDidError = false;
var ReactStripeCheckout = function (_React$Component) {
_inherits(ReactStripeCheckout, _React$Component);
function ReactStripeCheckout(props) {
_classCallCheck(this, ReactStripeCheckout);
var _this = _possibleConstructorReturn(this, (ReactStripeCheckout.__proto__ || Object.getPrototypeOf(ReactStripeCheckout)).call(this, props));
_this.onScriptLoaded = function () {
if (!ReactStripeCheckout.stripeHandler) {
ReactStripeCheckout.stripeHandler = StripeCheckout.configure({
key: _this.props.stripeKey
});
if (_this.hasPendingClick) {
_this.showStripeDialog();
}
}
};
_this.onScriptError = function () {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
_this.hideLoadingDialog();
if (_this.props.onScriptError) {
_this.props.onScriptError.apply(_this, args);
}
};
_this.onClosed = function () {
for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
if (_this._isMounted) _this.setState({ open: false });
if (_this.props.closed) {
_this.props.closed.apply(_this, args);
}
};
_this.onOpened = function () {
for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
args[_key3] = arguments[_key3];
}
_this.setState({ open: true });
if (_this.props.opened) {
_this.props.opened.apply(_this, args);
}
};
_this.getConfig = function () {
return ['token', 'image', 'name', 'description', 'amount', 'locale', 'currency', 'panelLabel', 'zipCode', 'shippingAddress', 'billingAddress', 'email', 'allowRememberMe', 'bitcoin', 'alipay', 'alipayReusable'].reduce(function (config, key) {
return _extends({}, config, _this.props.hasOwnProperty(key) && _defineProperty({}, key, _this.props[key]));
}, {
opened: _this.onOpened,
closed: _this.onClosed
});
};
_this.onClick = function () {
// eslint-disable-line react/sort-comp
if (_this.props.disabled) {
return;
}
if (scriptDidError) {
try {
throw new Error('Tried to call onClick, but StripeCheckout failed to load');
} catch (x) {} // eslint-disable-line no-empty
} else if (ReactStripeCheckout.stripeHandler) {
_this.showStripeDialog();
} else {
_this.showLoadingDialog();
_this.hasPendingClick = true;
}
};
_this.handleOnMouseDown = function () {
_this.setState({
buttonActive: true
});
};
_this.handleOnMouseUp = function () {
_this.setState({
buttonActive: false
});
};
_this.state = {
open: false,
buttonActive: false
};
return _this;
}
_createClass(ReactStripeCheckout, [{
key: 'componentDidMount',
value: function componentDidMount() {
var _this2 = this;
this._isMounted = true;
if (scriptLoaded) {
return;
}
if (scriptLoading) {
return;
}
scriptLoading = true;
var script = document.createElement('script');
if (typeof this.props.onScriptTagCreated === 'function') {
this.props.onScriptTagCreated(script);
}
script.src = 'https://checkout.stripe.com/checkout.js';
script.async = 1;
this.loadPromise = function () {
var canceled = false;
var promise = new Promise(function (resolve, reject) {
script.onload = function () {
scriptLoaded = true;
scriptLoading = false;
resolve();
_this2.onScriptLoaded();
};
script.onerror = function (event) {
scriptDidError = true;
scriptLoading = false;
reject(event);
_this2.onScriptError(event);
};
});
var wrappedPromise = new Promise(function (accept, cancel) {
promise.then(function () {
return canceled ? cancel({ isCanceled: true }) : accept();
}); // eslint-disable-line no-confusing-arrow
promise.catch(function (error) {
return canceled ? cancel({ isCanceled: true }) : cancel(error);
}); // eslint-disable-line no-confusing-arrow
});
return {
promise: wrappedPromise,
cancel: function cancel() {
canceled = true;
}
};
}();
this.loadPromise.promise.then(this.onScriptLoaded).catch(this.onScriptError);
document.body.appendChild(script);
}
}, {
key: 'componentDidUpdate',
value: function componentDidUpdate() {
if (!scriptLoading) {
this.updateStripeHandler();
}
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
this._isMounted = false;
if (this.loadPromise) {
this.loadPromise.cancel();
}
if (ReactStripeCheckout.stripeHandler && this.state.open) {
ReactStripeCheckout.stripeHandler.close();
}
}
}, {
key: 'updateStripeHandler',
value: function updateStripeHandler() {
if (!ReactStripeCheckout.stripeHandler || this.props.reconfigureOnUpdate) {
ReactStripeCheckout.stripeHandler = StripeCheckout.configure({
key: this.props.stripeKey
});
}
}
}, {
key: 'showLoadingDialog',
value: function showLoadingDialog() {
if (this.props.showLoadingDialog) {
for (var _len4 = arguments.length, args = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
args[_key4] = arguments[_key4];
}
this.props.showLoadingDialog.apply(this, args);
}
}
}, {
key: 'hideLoadingDialog',
value: function hideLoadingDialog() {
if (this.props.hideLoadingDialog) {
for (var _len5 = arguments.length, args = Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
args[_key5] = arguments[_key5];
}
this.props.hideLoadingDialog.apply(this, args);
}
}
}, {
key: 'showStripeDialog',
value: function showStripeDialog() {
this.hideLoadingDialog();
ReactStripeCheckout.stripeHandler.open(this.getConfig());
}
}, {
key: 'renderDefaultStripeButton',
value: function renderDefaultStripeButton() {
return _react2.default.createElement(
'button',
_extends({}, _defineProperty({}, this.props.triggerEvent, this.onClick), {
className: this.props.className,
onMouseDown: this.handleOnMouseDown,
onFocus: this.handleOnMouseDown,
onMouseUp: this.handleOnMouseUp,
onMouseOut: this.handleOnMouseUp,
onBlur: this.handleOnMouseUp,
style: _extends({}, {
overflow: 'hidden',
display: 'inline-block',
background: 'linear-gradient(#28a0e5,#015e94)',
border: 0,
padding: 1,
textDecoration: 'none',
borderRadius: 5,
boxShadow: '0 1px 0 rgba(0,0,0,0.2)',
cursor: 'pointer',
visibility: 'visible',
userSelect: 'none'
}, this.state.buttonActive && {
background: '#005d93'
}, this.props.style)
}),
_react2.default.createElement(
'span',
{
style: _extends({}, {
backgroundImage: 'linear-gradient(#7dc5ee,#008cdd 85%,#30a2e4)',
fontFamily: '"Helvetica Neue",Helvetica,Arial,sans-serif',
fontSize: 14,
position: 'relative',
padding: '0 12px',
display: 'block',
height: 30,
lineHeight: '30px',
color: '#fff',
fontWeight: 'bold',
boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.25)',
textShadow: '0 -1px 0 rgba(0,0,0,0.25)',
borderRadius: 4
}, this.state.buttonActive && {
color: '#eee',
boxShadow: 'inset 0 1px 0 rgba(0,0,0,0.1)',
backgroundImage: 'linear-gradient(#008cdd,#008cdd 85%,#239adf)'
}, this.props.textStyle)
},
this.props.label
)
);
}
}, {
key: 'renderDisabledButton',
value: function renderDisabledButton() {
return _react2.default.createElement(
'button',
{
disabled: true,
style: {
background: 'rgba(0,0,0,0.2)',
overflow: 'hidden',
display: 'inline-block',
border: 0,
padding: 1,
textDecoration: 'none',
borderRadius: 5,
userSelect: 'none'
}
},
_react2.default.createElement(
'span',
{
style: {
boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.25)',
fontFamily: '"Helvetica Neue",Helvetica,Arial,sans-serif',
fontSize: 14,
position: 'relative',
padding: '0 12px',
display: 'block',
height: 30,
lineHeight: '30px',
borderRadius: 4,
color: '#999',
background: '#f8f9fa',
textShadow: '0 1px 0 rgba(255,255,255,0.5)'
}
},
this.props.label
)
);
}
}, {
key: 'render',
value: function render() {
if (this.props.desktopShowModal === true && !this.state.open) {
this.onClick();
} else if (this.props.desktopShowModal === false && this.state.open) {
ReactStripeCheckout.stripeHandler.close();
}
var ComponentClass = this.props.ComponentClass;
if (this.props.children) {
return _react2.default.createElement(ComponentClass, _extends({}, _defineProperty({}, this.props.triggerEvent, this.onClick), {
children: this.props.children
}));
}
return this.props.disabled ? this.renderDisabledButton() : this.renderDefaultStripeButton();
}
}]);
return ReactStripeCheckout;
}(_react2.default.Component);
ReactStripeCheckout.defaultProps = {
className: 'StripeCheckout',
label: 'Pay With Card',
locale: 'auto',
ComponentClass: 'span',
reconfigureOnUpdate: false,
triggerEvent: 'onClick'
};
ReactStripeCheckout.propTypes = {
// Opens / closes the checkout modal by value
// WARNING: does not work on mobile due to browser security restrictions
// NOTE: Must be set to false when receiving token to prevent modal from
// opening automatically after closing
desktopShowModal: _propTypes2.default.bool,
triggerEvent: _propTypes2.default.oneOf(['onClick', 'onTouchTap', 'onTouchStart']),
// If included, will render the default blue button with label text.
// (Requires including stripe-checkout.css or adding the .styl file
// to your pipeline)
label: _propTypes2.default.string,
// Custom styling for default button
style: _propTypes2.default.object,
// Custom styling for <span> tag inside default button
textStyle: _propTypes2.default.object,
// Prevents any events from opening the popup
// Adds the disabled prop to the button and adjusts the styling as well
disabled: _propTypes2.default.bool,
// Named component to wrap button (eg. div)
ComponentClass: _propTypes2.default.string,
// Show a loading indicator
showLoadingDialog: _propTypes2.default.func,
// Hide the loading indicator
hideLoadingDialog: _propTypes2.default.func,
// Run this method when the scrupt fails to load. Will run if the internet
// connection is offline when attemting to load the script.
onScriptError: _propTypes2.default.func,
// Runs when the script tag is created, but before it is added to the DOM
onScriptTagCreated: _propTypes2.default.func,
// By default, any time the React component is updated, it will call
// StripeCheckout.configure, which may result in additional XHR calls to the
// stripe API. If you know the first configuration is all you need, you
// can set this to false. Subsequent updates will affect the StripeCheckout.open
// (e.g. different prices)
reconfigureOnUpdate: _propTypes2.default.bool,
// =====================================================
// Required by stripe
// see Stripe docs for more info:
// https://stripe.com/docs/checkout#integration-custom
// =====================================================
// Your publishable key (test or live).
// can't use "key" as a prop in react, so have to change the keyname
stripeKey: _propTypes2.default.string.isRequired,
// The callback to invoke when the Checkout process is complete.
// function(token)
// token is the token object created.
// token.id can be used to create a charge or customer.
// token.email contains the email address entered by the user.
token: _propTypes2.default.func.isRequired,
// ==========================
// Highly Recommended Options
// ==========================
// Name of the company or website.
name: _propTypes2.default.string,
// A description of the product or service being purchased.
description: _propTypes2.default.string,
// A relative URL pointing to a square image of your brand or product. The
// recommended minimum size is 128x128px. The recommended image types are
// .gif, .jpeg, and .png.
image: _propTypes2.default.string,
// The amount (in cents) that's shown to the user. Note that you will still
// have to explicitly include it when you create a charge using the API.
amount: _propTypes2.default.number,
// Specify auto to display Checkout in the user's preferred language, if
// available. English will be used by default.
//
// https://stripe.com/docs/checkout#supported-languages
// for more info.
locale: _propTypes2.default.oneOf(['auto', // (Default) Automatically chosen by checkout
'zh', // Simplified Chinese
'da', // Danish
'nl', // Dutch
'en', // English
'fr', // French
'de', // German
'it', // Italian
'ja', // Japanease
'no', // Norwegian
'es', // Spanish
'sv']),
// ==============
// Optional Props
// ==============
// The currency of the amount (3-letter ISO code). The default is USD.
currency: _propTypes2.default.oneOf(['AED', 'AFN', 'ALL', 'AMD', 'ANG', 'AOA', 'ARS', 'AUD', 'AWG', 'AZN', 'BAM', 'BBD', // eslint-disable-line comma-spacing
'BDT', 'BGN', 'BIF', 'BMD', 'BND', 'BOB', 'BRL', 'BSD', 'BWP', 'BZD', 'CAD', 'CDF', // eslint-disable-line comma-spacing
'CHF', 'CLP', 'CNY', 'COP', 'CRC', 'CVE', 'CZK', 'DJF', 'DKK', 'DOP', 'DZD', 'EEK', // eslint-disable-line comma-spacing
'EGP', 'ETB', 'EUR', 'FJD', 'FKP', 'GBP', 'GEL', 'GIP', 'GMD', 'GNF', 'GTQ', 'GYD', // eslint-disable-line comma-spacing
'HKD', 'HNL', 'HRK', 'HTG', 'HUF', 'IDR', 'ILS', 'INR', 'ISK', 'JMD', 'JPY', 'KES', // eslint-disable-line comma-spacing
'KGS', 'KHR', 'KMF', 'KRW', 'KYD', 'KZT', 'LAK', 'LBP', 'LKR', 'LRD', 'LSL', 'LTL', // eslint-disable-line comma-spacing
'LVL', 'MAD', 'MDL', 'MGA', 'MKD', 'MNT', 'MOP', 'MRO', 'MUR', 'MVR', 'MWK', 'MXN', // eslint-disable-line comma-spacing
'MYR', 'MZN', 'NAD', 'NGN', 'NIO', 'NOK', 'NPR', 'NZD', 'PAB', 'PEN', 'PGK', 'PHP', // eslint-disable-line comma-spacing
'PKR', 'PLN', 'PYG', 'QAR', 'RON', 'RSD', 'RUB', 'RWF', 'SAR', 'SBD', 'SCR', 'SEK', // eslint-disable-line comma-spacing
'SGD', 'SHP', 'SLL', 'SOS', 'SRD', 'STD', 'SVC', 'SZL', 'THB', 'TJS', 'TOP', 'TRY', // eslint-disable-line comma-spacing
'TTD', 'TWD', 'TZS', 'UAH', 'UGX', 'USD', 'UYU', 'UZS', 'VND', 'VUV', 'WST', 'XAF', // eslint-disable-line comma-spacing
'XCD', 'XOF', 'XPF', 'YER', 'ZAR', 'ZMW']),
// The label of the payment button in the Checkout form (e.g. “Subscribe”,
// “Pay {{amount}}”, etc.). If you include {{amount}}, it will be replaced
// by the provided amount. Otherwise, the amount will be appended to the
// end of your label.
panelLabel: _propTypes2.default.string,
// Specify whether Checkout should validate the billing ZIP code (true or
// false)
zipCode: _propTypes2.default.bool,
// Specify whether Checkout should collect the user's billing address
// (true or false). The default is false.
billingAddress: _propTypes2.default.bool,
// Specify whether Checkout should collect the user's shipping address
// (true or false). The default is false.
shippingAddress: _propTypes2.default.bool,
// Specify whether Checkout should validate the billing ZIP code (true or
// false). The default is false.
email: _propTypes2.default.string,
// Specify whether to include the option to "Remember Me" for future
// purchases (true or false). The default is true.
allowRememberMe: _propTypes2.default.bool,
// Specify whether to accept Bitcoin in Checkout. The default is false.
bitcoin: _propTypes2.default.bool,
// Specify whether to accept Alipay ('auto', true, or false). The default
// is false.
alipay: _propTypes2.default.oneOf(['auto', true, false]),
// Specify if you need reusable access to the customer's Alipay account
// (true or false). The default is false.
alipayReusable: _propTypes2.default.bool,
// function() The callback to invoke when Checkout is opened (not supported
// in IE6 and IE7).
opened: _propTypes2.default.func,
// function() The callback to invoke when Checkout is closed (not supported
// in IE6 and IE7).
closed: _propTypes2.default.func
};
ReactStripeCheckout._isMounted = false;
exports.default = ReactStripeCheckout;