UNPKG

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
'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;