diff --git a/client/package-lock.json b/client/package-lock.json
index 440a20fb47..dffc33abd2 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -4264,8 +4264,7 @@
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "optional": true
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"slice-ansi": {
"version": "3.0.0",
@@ -7805,8 +7804,7 @@
},
"ansi-regex": {
"version": "2.1.1",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"aproba": {
"version": "1.2.0",
@@ -7824,13 +7822,11 @@
},
"balanced-match": {
"version": "1.0.0",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
- "optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -7843,18 +7839,15 @@
},
"code-point-at": {
"version": "1.1.0",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"concat-map": {
"version": "0.0.1",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"console-control-strings": {
"version": "1.1.0",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"core-util-is": {
"version": "1.0.2",
@@ -7957,8 +7950,7 @@
},
"inherits": {
"version": "2.0.4",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"ini": {
"version": "1.3.5",
@@ -7968,7 +7960,6 @@
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
- "optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -7981,20 +7972,17 @@
"minimatch": {
"version": "3.0.4",
"bundled": true,
- "optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "1.2.5",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"minipass": {
"version": "2.9.0",
"bundled": true,
- "optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -8011,7 +7999,6 @@
"mkdirp": {
"version": "0.5.3",
"bundled": true,
- "optional": true,
"requires": {
"minimist": "^1.2.5"
}
@@ -8067,8 +8054,7 @@
},
"npm-normalize-package-bin": {
"version": "1.0.1",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"npm-packlist": {
"version": "1.4.8",
@@ -8093,8 +8079,7 @@
},
"number-is-nan": {
"version": "1.0.1",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"object-assign": {
"version": "4.1.1",
@@ -8104,7 +8089,6 @@
"once": {
"version": "1.4.0",
"bundled": true,
- "optional": true,
"requires": {
"wrappy": "1"
}
@@ -8173,8 +8157,7 @@
},
"safe-buffer": {
"version": "5.1.2",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -8204,7 +8187,6 @@
"string-width": {
"version": "1.0.2",
"bundled": true,
- "optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -8222,7 +8204,6 @@
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
- "optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -8261,13 +8242,11 @@
},
"wrappy": {
"version": "1.0.2",
- "bundled": true,
- "optional": true
+ "bundled": true
},
"yallist": {
"version": "3.1.1",
- "bundled": true,
- "optional": true
+ "bundled": true
}
}
},
@@ -10303,14 +10282,12 @@
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
- "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
- "optional": true
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
},
"ansi-styles": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
"integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
- "optional": true,
"requires": {
"@types/color-name": "^1.1.1",
"color-convert": "^2.0.1"
@@ -10351,7 +10328,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "optional": true,
"requires": {
"color-name": "~1.1.4"
}
@@ -10359,8 +10335,7 @@
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "optional": true
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"emoji-regex": {
"version": "8.0.0",
@@ -10377,8 +10352,7 @@
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "optional": true
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"mimic-fn": {
"version": "2.1.0",
@@ -10441,7 +10415,6 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
- "optional": true,
"requires": {
"ansi-regex": "^5.0.0"
}
@@ -15560,15 +15533,6 @@
}
}
},
- "react-paypal-button-v2": {
- "version": "2.6.1",
- "resolved": "https://registry.npmjs.org/react-paypal-button-v2/-/react-paypal-button-v2-2.6.1.tgz",
- "integrity": "sha512-ia1zzdRgziSfnJ/UueM6i0omEocbv4cge3mBRmpA6WSyxsX6c501HWHVwvXMEbTDCPK5+0ZKewhBzf/wq618Cg==",
- "requires": {
- "prop-types": "^15.7.2",
- "rimraf": "^3.0.0"
- }
- },
"react-prop-types": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/react-prop-types/-/react-prop-types-0.4.0.tgz",
diff --git a/client/package.json b/client/package.json
index d1895ab4f1..5d8205c4bd 100644
--- a/client/package.json
+++ b/client/package.json
@@ -56,7 +56,6 @@
"react-identicons": "^1.1.7",
"react-instantsearch-dom": "^6.0.0-beta.0",
"react-monaco-editor": "^0.31.0",
- "react-paypal-button-v2": "^2.6.1",
"react-redux": "^5.0.7",
"react-reflex": "^3.0.18",
"react-responsive": "^6.1.1",
diff --git a/client/src/components/Donation/DonateForm.js b/client/src/components/Donation/DonateForm.js
index 4e98441220..60925abead 100644
--- a/client/src/components/Donation/DonateForm.js
+++ b/client/src/components/Donation/DonateForm.js
@@ -240,18 +240,6 @@ class DonateForm extends Component {
this.setState({ processing: hide });
}
- renderPayPalMeLink(donationAmount) {
- const payPalMeLinkURL =
- 'https://paypal.me/freecodecamp/' + donationAmount / 100 + 'USD';
- return (
-
- );
- }
-
renderDonationOptions() {
const { handleProcessing, isSignedIn } = this.props;
const { donationAmount, donationDuration } = this.state;
@@ -308,17 +296,14 @@ class DonateForm extends Component {
/>
- {isOneTime ? (
- this.renderPayPalMeLink(donationAmount)
- ) : (
-
- )}
+
);
diff --git a/client/src/components/Donation/PayPalButtonScriptLoader.js b/client/src/components/Donation/PayPalButtonScriptLoader.js
new file mode 100644
index 0000000000..b333b9df15
--- /dev/null
+++ b/client/src/components/Donation/PayPalButtonScriptLoader.js
@@ -0,0 +1,100 @@
+/* eslint-disable camelcase */
+
+import React, { Component } from 'react';
+import ReactDOM from 'react-dom';
+import PropTypes from 'prop-types';
+
+import { scriptLoader, scriptRemover } from '../../utils/scriptLoaders';
+import { Loader } from '../../components/helpers';
+
+export class PayPalButtonScriptLoader extends Component {
+ state = { isSdkLoaded: window.paypal && true, isSubscription: true };
+
+ static getDerivedStateFromProps(props, state) {
+ const { isSubscription } = props;
+ if (isSubscription !== state.isSubscription) {
+ return { isSubscription: isSubscription };
+ }
+ return null;
+ }
+
+ componentDidMount() {
+ if (!window.paypal) {
+ this.loadScript(this.props.isSubscription);
+ }
+ }
+
+ componentDidUpdate(prevProps) {
+ if (prevProps.isSubscription !== this.state.isSubscription) {
+ // eslint-disable-next-line react/no-did-update-set-state
+ this.setState({ isSdkLoaded: false });
+ this.loadScript(this.state.isSubscription, true);
+ }
+ }
+
+ loadScript(subscription, deleteScript) {
+ if (deleteScript) scriptRemover('paypal-sdk');
+ let queries = `?client-id=${this.props.clinetId}&disable-funding=credit,card`;
+ if (subscription) queries += '&vault=true';
+
+ scriptLoader(
+ 'paypal-sdk',
+ 'paypal-sdk',
+ true,
+ `https://www.paypal.com/sdk/js${queries}`,
+ this.OnScriptLoad
+ );
+ }
+
+ OnScriptLoad = () => {
+ this.setState({ isSdkLoaded: true });
+ };
+
+ render() {
+ const { isSdkLoaded, isSubscription } = this.state;
+ const {
+ onApprove,
+ onError,
+ onCancel,
+ createSubscription,
+ createOrder,
+ style
+ } = this.props;
+
+ if (!isSdkLoaded) return ;
+
+ const Button = window.paypal.Buttons.driver('react', {
+ React,
+ ReactDOM
+ });
+
+ return (
+
+ );
+ }
+}
+
+const propTypes = {
+ clinetId: PropTypes.string,
+ createOrder: PropTypes.func,
+ createSubscription: PropTypes.func,
+ donationAmount: PropTypes.number,
+ donationDuration: PropTypes.string,
+ isSubscription: PropTypes.bool,
+ onApprove: PropTypes.func,
+ onCancel: PropTypes.func,
+ onError: PropTypes.func,
+ style: PropTypes.object
+};
+
+PayPalButtonScriptLoader.displayName = 'PayPalButtonScriptLoader';
+PayPalButtonScriptLoader.propTypes = propTypes;
+
+export default PayPalButtonScriptLoader;
diff --git a/client/src/components/Donation/PaypalButton.js b/client/src/components/Donation/PaypalButton.js
index 0f498e16d3..38ecc55ed8 100644
--- a/client/src/components/Donation/PaypalButton.js
+++ b/client/src/components/Donation/PaypalButton.js
@@ -4,7 +4,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
-import { PayPalButton } from 'react-paypal-button-v2';
+import PayPalButtonScriptLoader from './PayPalButtonScriptLoader';
import { paypalClientId, deploymentEnv } from '../../../config/env.json';
import { verifySubscriptionPaypal } from '../../utils/ajax';
import {
@@ -38,7 +38,7 @@ export class PaypalButton extends Component {
handleApproval = data => {
const { amount, duration } = this.state;
const { skipAddDonation = false } = this.props;
- if (!skipAddDonation) {
+ if (!skipAddDonation || duration === 'oneTime') {
this.props.handleProcessing(
duration,
amount,
@@ -73,42 +73,52 @@ export class PaypalButton extends Component {
};
render() {
- const { duration, planId } = this.state;
- const isOneTimePayment = duration === 'onetime';
-
- if (!isOneTimePayment) {
- return (
- {
- return actions.subscription.create({
- plan_id: planId
- });
- }}
- onApprove={data => {
+ const { duration, planId, amount } = this.state;
+ const isSubscription = duration !== 'onetime';
+ return (
+ {
+ return actions.order.create({
+ purchase_units: [
+ {
+ amount: {
+ currency_code: 'USD',
+ value: (amount / 100).toString()
+ }
+ }
+ ]
+ });
+ }}
+ createSubscription={(data, actions) => {
+ return actions.subscription.create({
+ plan_id: planId
+ });
+ }}
+ isSubscription={isSubscription}
+ onApprove={(data, actions) => {
+ return actions.order.capture().then(function(data) {
this.handleApproval(data);
- }}
- onCancel={() => {
- this.props.onDonationStateChange(
- false,
- false,
- `Uh - oh. It looks like your transaction didn't go through. Could you please try again?`
- );
- }}
- onError={() =>
- this.props.onDonationStateChange(false, false, 'Please try again.')
- }
- options={{
- vault: true,
- disableFunding: 'credit,card',
- clientId: paypalClientId
- }}
- style={{
- tagline: false,
- height: 43
- }}
- />
- );
- } else return '';
+ });
+ }}
+ onCancel={() => {
+ this.props.onDonationStateChange(
+ false,
+ false,
+ `Uh - oh. It looks like your transaction didn't go through. Could you please try again?`
+ );
+ }}
+ onError={() =>
+ this.props.onDonationStateChange(false, false, 'Please try again.')
+ }
+ plantId={planId}
+ style={{
+ tagline: false,
+ height: 43
+ }}
+ />
+ );
}
}
diff --git a/client/src/utils/scriptLoaders.js b/client/src/utils/scriptLoaders.js
index c241cff2fa..404db62234 100644
--- a/client/src/utils/scriptLoaders.js
+++ b/client/src/utils/scriptLoaders.js
@@ -10,6 +10,13 @@ export const scriptLoader = (id, key, async, src, onload, text) => {
document.getElementsByTagName('head')[0].appendChild(s);
};
+export const scriptRemover = id => {
+ let script = document.getElementById(id);
+ if (script) {
+ script.remove();
+ }
+};
+
export const stripeScriptLoader = onload =>
scriptLoader(
'stripe-js',