diff --git a/api-server/package-lock.json b/api-server/package-lock.json index a56bc8fd41..bb18999642 100644 --- a/api-server/package-lock.json +++ b/api-server/package-lock.json @@ -16574,24 +16574,6 @@ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true }, - "stripe": { - "version": "6.36.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-6.36.0.tgz", - "integrity": "sha512-7vjyVO4NCWvX38CH1AuSQH16uRxcQN+UhUTBPs4UHsIl5+SJXLBvCsHrMgd+bY9k1YDliT0fQB1fH9OI3GrEhw==", - "requires": { - "lodash.isplainobject": "^4.0.6", - "qs": "^6.6.0", - "safe-buffer": "^5.1.1", - "uuid": "^3.3.2" - }, - "dependencies": { - "qs": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", - "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==" - } - } - }, "strong-error-handler": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/strong-error-handler/-/strong-error-handler-3.5.0.tgz", diff --git a/api-server/package.json b/api-server/package.json index e04045abae..58d554be99 100644 --- a/api-server/package.json +++ b/api-server/package.json @@ -69,7 +69,6 @@ "passport-mock-strategy": "^2.0.0", "query-string": "^6.14.0", "rx": "^4.1.0", - "stripe": "^6.36.0", "uuid": "^3.4.0", "validator": "^9.4.1" }, diff --git a/api-server/src/server/boot/donate.js b/api-server/src/server/boot/donate.js index abf52df6ef..40a39b8982 100644 --- a/api-server/src/server/boot/donate.js +++ b/api-server/src/server/boot/donate.js @@ -1,231 +1,19 @@ -import Stripe from 'stripe'; import debug from 'debug'; -import { isEmail, isNumeric } from 'validator'; - import { getAsyncPaypalToken, verifyWebHook, updateUser, verifyWebHookType } from '../utils/donation'; -import { - durationKeysConfig, - donationOneTimeConfig, - donationSubscriptionConfig, - durationsConfig, - onetimeSKUConfig, - donationUrls -} from '../../../../config/donation-settings'; import keys from '../../../../config/secrets'; -import { deploymentEnv } from '../../../../config/env'; const log = debug('fcc:boot:donate'); export default function donateBoot(app, done) { - let stripe = false; - const { User } = app.models; const api = app.loopback.Router(); const hooks = app.loopback.Router(); const donateRouter = app.loopback.Router(); - const subscriptionPlans = Object.keys( - donationSubscriptionConfig.plans - ).reduce( - (prevDuration, duration) => - prevDuration.concat( - donationSubscriptionConfig.plans[duration].reduce( - (prevAmount, amount) => - prevAmount.concat({ - amount: amount, - interval: duration, - product: { - name: `${ - donationSubscriptionConfig.duration[duration] - } Donation to freeCodeCamp.org - Thank you ($${amount / 100})`, - metadata: { - /* eslint-disable camelcase */ - sb_service: `freeCodeCamp.org`, - sb_tier: `${donationSubscriptionConfig.duration[duration]} $${ - amount / 100 - } Donation` - /* eslint-enable camelcase */ - } - }, - currency: 'usd', - id: `${donationSubscriptionConfig.duration[ - duration - ].toLowerCase()}-donation-${amount}` - }), - [] - ) - ), - [] - ); - - function validStripeForm(amount, duration, email) { - return isEmail('' + email) && - isNumeric('' + amount) && - durationKeysConfig.includes(duration) && - duration === 'onetime' - ? donationOneTimeConfig.includes(amount) - : donationSubscriptionConfig.plans[duration]; - } - - function connectToStripe() { - return new Promise(function (resolve) { - // connect to stripe API - stripe = Stripe(keys.stripe.secret); - // parse stripe plans - stripe.plans.list({}, function (err, stripePlans) { - if (err) { - throw err; - } - const requiredPlans = subscriptionPlans.map(plan => plan.id); - const availablePlans = stripePlans.data.map(plan => plan.id); - if (process.env.STRIPE_CREATE_PLANS === 'true') { - requiredPlans.forEach(requiredPlan => { - if (!availablePlans.includes(requiredPlan)) { - createStripePlan( - subscriptionPlans.find(plan => plan.id === requiredPlan) - ); - } - }); - } else { - log(`Skipping plan creation`); - } - }); - resolve(); - }); - } - - function createStripePlan(plan) { - log(`Creating subscription plan: ${plan.product.name}`); - stripe.plans.create(plan, function (err) { - if (err) { - log(err); - } - log(`Created plan with plan id: ${plan.id}`); - return; - }); - } - - function createStripeDonation(req, res) { - const { user, body } = req; - - const { - amount, - duration, - token: { email, id } - } = body; - - if (!validStripeForm(amount, duration, email)) { - return res.status(500).send({ - error: 'The donation form had invalid values for this submission.' - }); - } - - const fccUser = user - ? Promise.resolve(user) - : new Promise((resolve, reject) => - User.findOrCreate( - { where: { email } }, - { email }, - (err, instance, isNew) => { - log('createing a new donating user instance: ', isNew); - if (err) { - return reject(err); - } - return resolve(instance); - } - ) - ); - - let donatingUser = {}; - let donation = { - email, - amount, - duration, - provider: 'stripe', - startDate: new Date(Date.now()).toISOString() - }; - - const createCustomer = user => { - donatingUser = user; - return stripe.customers.create({ - email, - card: id - }); - }; - - const createSubscription = customer => { - donation.customerId = customer.id; - return stripe.subscriptions.create({ - customer: customer.id, - items: [ - { - plan: `${donationSubscriptionConfig.duration[ - duration - ].toLowerCase()}-donation-${amount}` - } - ] - }); - }; - - const createOneTimeCharge = customer => { - donation.customerId = customer.id; - return stripe.charges.create({ - amount: amount, - currency: 'usd', - customer: customer.id - }); - }; - - const createAsyncUserDonation = () => { - donatingUser - .createDonation(donation) - .toPromise() - .catch(err => { - throw new Error(err); - }); - }; - - return Promise.resolve(fccUser) - .then(nonDonatingUser => { - const { isDonating } = nonDonatingUser; - if (isDonating && duration !== 'onetime') { - throw { - message: `User already has active recurring donation(s).`, - type: 'AlreadyDonatingError' - }; - } - return nonDonatingUser; - }) - .then(createCustomer) - .then(customer => { - return duration === 'onetime' - ? createOneTimeCharge(customer).then(charge => { - donation.subscriptionId = 'one-time-charge-prefix-' + charge.id; - return res.send(charge); - }) - : createSubscription(customer).then(subscription => { - donation.subscriptionId = subscription.id; - return res.send(subscription); - }); - }) - .then(createAsyncUserDonation) - .catch(err => { - if ( - err.type === 'StripeCardError' || - err.type === 'AlreadyDonatingError' - ) { - return res.status(402).send({ error: err.message }); - } - return res - .status(500) - .send({ error: 'Donation failed due to a server error.' }); - }); - } - function addDonation(req, res) { const { user, body } = req; @@ -250,53 +38,6 @@ export default function donateBoot(app, done) { }); } - async function createStripeSession(req, res) { - const { - body, - body: { donationAmount, donationDuration } - } = req; - if (!body) { - return res - .status(500) - .send({ type: 'danger', message: 'Request has not completed.' }); - } - const isSubscription = donationDuration !== 'onetime'; - const getSKUId = () => { - const { id } = onetimeSKUConfig[deploymentEnv || 'staging'].find( - skuConfig => skuConfig.amount === `${donationAmount}` - ); - return id; - }; - const price = isSubscription - ? `${durationsConfig[donationDuration]}-donation-${donationAmount}` - : getSKUId(); - - /* eslint-disable camelcase */ - try { - const session = await stripe.checkout.sessions.create({ - payment_method_types: ['card'], - line_items: [ - { - price, - quantity: 1 - } - ], - metadata: { ...body }, - mode: isSubscription ? 'subscription' : 'payment', - success_url: donationUrls.successUrl, - cancel_url: donationUrls.cancelUrl - }); - /* eslint-enable camelcase */ - return res.status(200).json({ id: session.id }); - } catch (err) { - log(err.message); - return res.status(500).send({ - type: 'danger', - message: 'Something went wrong.' - }); - } - } - function updatePaypal(req, res) { const { headers, body } = req; return Promise.resolve(req) @@ -311,13 +52,8 @@ export default function donateBoot(app, done) { .finally(() => res.status(200).json({ message: 'received paypal hook' })); } - const stripeKey = keys.stripe.public; - const secKey = keys.stripe.secret; const paypalKey = keys.paypal.client; const paypalSec = keys.paypal.secret; - const stripeSecretInvalid = !secKey || secKey === 'sk_from_stripe_dashboard'; - const stripPublicInvalid = - !stripeKey || stripeKey === 'pk_from_stripe_dashboard'; const paypalSecretInvalid = !paypalKey || paypalKey === 'id_from_paypal_dashboard'; @@ -325,22 +61,19 @@ export default function donateBoot(app, done) { !paypalSec || paypalSec === 'secret_from_paypal_dashboard'; const paypalInvalid = paypalPublicInvalid || paypalSecretInvalid; - const stripeInvalid = stripeSecretInvalid || stripPublicInvalid; - if (stripeInvalid || paypalInvalid) { + if (paypalInvalid) { if (process.env.FREECODECAMP_NODE_ENV === 'production') { throw new Error('Donation API keys are required to boot the server!'); } log('Donation disabled in development unless ALL test keys are provided'); done(); } else { - api.post('/charge-stripe', createStripeDonation); - api.post('/create-stripe-session', createStripeSession); api.post('/add-donation', addDonation); hooks.post('/update-paypal', updatePaypal); donateRouter.use('/donate', api); donateRouter.use('/hooks', hooks); app.use(donateRouter); - connectToStripe().then(done); + done(); } } diff --git a/api-server/src/server/middlewares/csurf.js b/api-server/src/server/middlewares/csurf.js index ebce81abc3..9c790509c5 100644 --- a/api-server/src/server/middlewares/csurf.js +++ b/api-server/src/server/middlewares/csurf.js @@ -12,9 +12,7 @@ export default function getCsurf() { const { path } = req; if ( // eslint-disable-next-line max-len - /^\/hooks\/update-paypal$|^\/hooks\/update-stripe$|^\/donate\/charge-stripe$/.test( - path - ) + /^\/hooks\/update-paypal$/.test(path) ) { return next(); } diff --git a/api-server/src/server/middlewares/request-authorization.js b/api-server/src/server/middlewares/request-authorization.js index 28b6367773..18e0f88289 100644 --- a/api-server/src/server/middlewares/request-authorization.js +++ b/api-server/src/server/middlewares/request-authorization.js @@ -23,11 +23,7 @@ const signinRE = /^\/signin/; const statusRE = /^\/status\/ping$/; const unsubscribedRE = /^\/unsubscribed\//; const unsubscribeRE = /^\/u\/|^\/unsubscribe\/|^\/ue\//; -const updateHooksRE = /^\/hooks\/update-paypal$|^\/hooks\/update-stripe$/; -const createStripeSession = /^\/donate\/create-stripe-session/; - -// note: this would be replaced by webhooks later -const donateRE = /^\/donate\/charge-stripe$/; +const updateHooksRE = /^\/hooks\/update-paypal$/; const _pathsAllowedREs = [ authRE, @@ -41,9 +37,7 @@ const _pathsAllowedREs = [ statusRE, unsubscribedRE, unsubscribeRE, - updateHooksRE, - donateRE, - createStripeSession + updateHooksRE ]; export function isAllowedPath(path, pathsAllowedREs = _pathsAllowedREs) { diff --git a/api-server/src/server/middlewares/request-authorization.test.js b/api-server/src/server/middlewares/request-authorization.test.js index f71c43ccfa..552669f40e 100644 --- a/api-server/src/server/middlewares/request-authorization.test.js +++ b/api-server/src/server/middlewares/request-authorization.test.js @@ -47,7 +47,7 @@ describe('request-authorization', () => { const statusRE = /^\/status\/ping$/; const unsubscribedRE = /^\/unsubscribed\//; const unsubscribeRE = /^\/u\/|^\/unsubscribe\/|^\/ue\//; - const updateHooksRE = /^\/hooks\/update-paypal$|^\/hooks\/update-stripe$/; + const updateHooksRE = /^\/hooks\/update-paypal$/; const allowedPathsList = [ authRE, @@ -79,11 +79,9 @@ describe('request-authorization', () => { allowedPathsList ); const resultC = isAllowedPath('/hooks/update-paypal', allowedPathsList); - const resultD = isAllowedPath('/hooks/update-stripe', allowedPathsList); expect(resultA).toBe(true); expect(resultB).toBe(true); expect(resultC).toBe(true); - expect(resultD).toBe(true); }); it('returns false for a non-white-listed path', () => { diff --git a/client.Dockerfile b/client.Dockerfile index 2289fcf806..5c8b4d2e1b 100644 --- a/client.Dockerfile +++ b/client.Dockerfile @@ -9,7 +9,6 @@ ARG FORUM_LOCATION ARG NEWS_LOCATION ARG CLIENT_LOCALE ARG CURRICULUM_LOCALE -ARG STRIPE_PUBLIC_KEY ARG ALGOLIA_APP_ID ARG ALGOLIA_API_KEY ARG PAYPAL_CLIENT_ID diff --git a/client/gatsby-node.js b/client/gatsby-node.js index 6ffe2d7e96..97305f548e 100644 --- a/client/gatsby-node.js +++ b/client/gatsby-node.js @@ -53,15 +53,6 @@ exports.createPages = function createPages({ graphql, actions, reporter }) { } } - if (!env.stripePublicKey) { - if (process.env.FREECODECAMP_NODE_ENV === 'production') { - throw new Error('Stripe public key is required to start the client!'); - } else { - reporter.info( - 'Stripe public key missing or invalid. Required for donations.' - ); - } - } const { createPage } = actions; return new Promise((resolve, reject) => { @@ -186,8 +177,7 @@ exports.onCreateWebpackConfig = ({ stage, plugins, actions }) => { plugins.define({ HOME_PATH: JSON.stringify( process.env.HOME_PATH || 'http://localhost:3000' - ), - STRIPE_PUBLIC_KEY: JSON.stringify(process.env.STRIPE_PUBLIC_KEY || '') + ) }), // We add the shims of the node globals to the global scope new webpack.ProvidePlugin({ diff --git a/client/i18n/locales/english/translations.json b/client/i18n/locales/english/translations.json index 772921a8d2..15bd0852f0 100644 --- a/client/i18n/locales/english/translations.json +++ b/client/i18n/locales/english/translations.json @@ -297,6 +297,7 @@ "help-more": "Help us do more", "error": "Something went wrong with your donation.", "free-tech": "Your donations will support free technology education for people all over the world.", + "no-halo": "If you don't see a gold halo around your profile picture, contact donors@freecodecamp.org.", "gift-frequency": "Select gift frequency:", "gift-amount": "Select gift amount:", "confirm": "Confirm your donation", diff --git a/client/package-lock.json b/client/package-lock.json index 7c8b7c5627..bde293b648 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -3224,14 +3224,6 @@ "@sinonjs/commons": "^1.7.0" } }, - "@stripe/react-stripe-js": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-1.4.0.tgz", - "integrity": "sha512-Pz5QmG8PgJ3pi8gOWxlngk+ns63p2L1Ds192fn55ykZNRKfGz3G6sfssUVThHn/NAt2Hp1eCEsy/hvlKnXJI6g==", - "requires": { - "prop-types": "^15.7.2" - } - }, "@szmarczak/http-timer": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", diff --git a/client/package.json b/client/package.json index b564db4e98..f17843d9ea 100644 --- a/client/package.json +++ b/client/package.json @@ -52,7 +52,6 @@ "@freecodecamp/react-calendar-heatmap": "1.0.0", "@loadable/component": "5.14.1", "@reach/router": "1.3.4", - "@stripe/react-stripe-js": "1.4.0", "algoliasearch": "3.35.1", "assert": "2.0.0", "axios": "0.21.1", diff --git a/client/src/client-only-routes/ShowCertification.js b/client/src/client-only-routes/ShowCertification.js index e98c7af2cd..f177432832 100644 --- a/client/src/client-only-routes/ShowCertification.js +++ b/client/src/client-only-routes/ShowCertification.js @@ -168,11 +168,7 @@ const ShowCertification = props => { setIsDonationClosed(true); }; - const handleProcessing = ( - duration, - amount, - action = 'stripe form submission' - ) => { + const handleProcessing = (duration, amount, action) => { props.executeGA({ type: 'event', data: { diff --git a/client/src/components/Donation/DonateCompletion.js b/client/src/components/Donation/DonateCompletion.js index 107f74c570..1745109a4b 100644 --- a/client/src/components/Donation/DonateCompletion.js +++ b/client/src/components/Donation/DonateCompletion.js @@ -49,6 +49,7 @@ function DonateCompletion({ {success && (

{t('donate.free-tech')}

+

{t('donate.no-halo')}

)} {error &&

{error}

} diff --git a/client/src/components/Donation/DonateForm.js b/client/src/components/Donation/DonateForm.js index 3672b3742e..4b78137620 100644 --- a/client/src/components/Donation/DonateForm.js +++ b/client/src/components/Donation/DonateForm.js @@ -3,9 +3,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import { Elements } from '@stripe/react-stripe-js'; import { - Button, Col, Row, Tab, @@ -20,23 +18,16 @@ import { durationsConfig, defaultAmount, defaultDonation, - donationUrls, modalDefaultDonation } from '../../../../config/donation-settings'; -import envData from '../../../../config/env.json'; -import { stripeScriptLoader } from '../../utils/scriptLoaders'; import Spacer from '../helpers/Spacer'; import PaypalButton from './PaypalButton'; import DonateCompletion from './DonateCompletion'; -import StripeCardForm from './StripeCardForm'; import { isSignedInSelector, signInLoadingSelector, donationFormStateSelector, - hardGoTo as navigate, addDonation, - createStripeSession, - postChargeStripe, updateDonationFormState, defaultDonationFormState, userSelector @@ -44,14 +35,11 @@ import { import './Donation.css'; -const { stripePublicKey } = envData; - const numToCommas = num => num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,'); const propTypes = { addDonation: PropTypes.func, - createStripeSession: PropTypes.func, defaultTheme: PropTypes.string, donationFormState: PropTypes.object, email: PropTypes.string, @@ -59,8 +47,6 @@ const propTypes = { isDonating: PropTypes.bool, isMinimalForm: PropTypes.bool, isSignedIn: PropTypes.bool, - navigate: PropTypes.func.isRequired, - postChargeStripe: PropTypes.func.isRequired, showLoading: PropTypes.bool.isRequired, t: PropTypes.func.isRequired, theme: PropTypes.string, @@ -83,10 +69,7 @@ const mapStateToProps = createSelector( const mapDispatchToProps = { addDonation, - navigate, - postChargeStripe, - updateDonationFormState, - createStripeSession + updateDonationFormState }; class DonateForm extends Component { @@ -102,63 +85,26 @@ class DonateForm extends Component { this.state = { ...initialAmountAndDuration, - processing: false, - stripe: null + processing: false }; - this.handleStripeLoad = this.handleStripeLoad.bind(this); this.onDonationStateChange = this.onDonationStateChange.bind(this); this.getActiveDonationAmount = this.getActiveDonationAmount.bind(this); this.getDonationButtonLabel = this.getDonationButtonLabel.bind(this); this.handleSelectAmount = this.handleSelectAmount.bind(this); this.handleSelectDuration = this.handleSelectDuration.bind(this); - this.handleStripeCheckoutRedirect = this.handleStripeCheckoutRedirect.bind( - this - ); this.hideAmountOptionsCB = this.hideAmountOptionsCB.bind(this); this.resetDonation = this.resetDonation.bind(this); - this.postStripeDonation = this.postStripeDonation.bind(this); - } - - componentDidMount() { - if (window.Stripe) { - this.handleStripeLoad(); - } else if (document.querySelector('#stripe-js')) { - document - .querySelector('#stripe-js') - .addEventListener('load', this.handleStripeLoad); - } else { - stripeScriptLoader(this.handleStripeLoad); - } } componentWillUnmount() { - const stripeMountPoint = document.querySelector('#stripe-js'); - if (stripeMountPoint) { - stripeMountPoint.removeEventListener('load', this.handleStripeLoad); - } this.resetDonation(); } - handleStripeLoad() { - // Create Stripe instance once Stripe.js loads - if (stripePublicKey) { - this.setState(state => ({ - ...state, - stripe: window.Stripe(stripePublicKey) - })); - } - } - onDonationStateChange(donationState) { // scroll to top window.scrollTo(0, 0); - this.props.updateDonationFormState(donationState); - // send donation made on the donate page to related news article - if (donationState.success && !this.props.isMinimalForm) { - this.props.navigate(donationUrls.successUrl); - } } getActiveDonationAmount(durationSelected, amountSelected) { @@ -204,41 +150,6 @@ class DonateForm extends Component { this.setState({ donationAmount }); } - postStripeDonation(token) { - const { donationAmount: amount, donationDuration: duration } = this.state; - window.scrollTo(0, 0); - - // change the donation modal button label to close - // or display the close button for the cert donation section - if (this.props.handleProcessing) { - this.props.handleProcessing(duration, amount); - } - this.props.postChargeStripe({ token, amount, duration }); - } - - async handleStripeCheckoutRedirect(e, paymentMethod) { - e.preventDefault(); - const { stripe, donationAmount, donationDuration } = this.state; - const { handleProcessing, email } = this.props; - - handleProcessing( - donationDuration, - donationAmount, - `stripe (${paymentMethod}) button click` - ); - - this.props.createStripeSession({ - stripe, - data: { - donationAmount, - donationDuration, - clickedPaymentMethod: paymentMethod, - email, - context: 'donate page' - } - }); - } - renderAmountButtons(duration) { return this.amounts[duration].map(amount => (
-
@@ -366,22 +276,13 @@ class DonateForm extends Component { } renderModalForm() { - const { donationAmount, donationDuration, stripe } = this.state; - const { - handleProcessing, - addDonation, - email, - theme, - t, - defaultTheme - } = this.props; + const { donationAmount, donationDuration } = this.state; + const { handleProcessing, addDonation, defaultTheme, theme } = this.props; return ( - - {this.getDonationButtonLabel()} {t('donate.paypal')} - + {this.getDonationButtonLabel()}: - - - {t('donate.credit-card-2')} - - - - - ); } diff --git a/client/src/components/Donation/Donation.css b/client/src/components/Donation/Donation.css index b119f2ce79..ded24dc7c7 100644 --- a/client/src/components/Donation/Donation.css +++ b/client/src/components/Donation/Donation.css @@ -48,6 +48,10 @@ font-weight: 700; } +.paypal-buttons-container { + min-height: 142px; +} + .donate-input-element { padding-top: 8px; } @@ -56,8 +60,7 @@ color: #707070; } -.donation-form .form-control:focus, -.StripeElement--focus { +.donation-form .form-control:focus { border-color: #66afe9; outline: 0; box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), @@ -66,7 +69,7 @@ .donation-form .email--invalid.form-control:focus, .donation-form .email--invalid, -.donation-form .StripeElement--invalid { +.donation-form { border-color: #eb1c26; color: #eb1c26; } @@ -383,7 +386,8 @@ button#confirm-donation-btn:hover { .donate-btn-group { display: flex; - flex-direction: column; + flex-direction: row; + justify-content: center; } .donate-btn-group > * { diff --git a/client/src/components/Donation/DonationModal.js b/client/src/components/Donation/DonationModal.js index 909c26fd87..8ae23380a8 100644 --- a/client/src/components/Donation/DonationModal.js +++ b/client/src/components/Donation/DonationModal.js @@ -55,11 +55,7 @@ function DonateModal({ }) { const [closeLabel, setCloseLabel] = React.useState(false); const { t } = useTranslation(); - const handleProcessing = ( - duration, - amount, - action = 'stripe form submission' - ) => { + const handleProcessing = (duration, amount, action) => { executeGA({ type: 'event', data: { diff --git a/client/src/components/Donation/PayPalButtonScriptLoader.js b/client/src/components/Donation/PayPalButtonScriptLoader.js index bd0a1bef36..1a753ab627 100644 --- a/client/src/components/Donation/PayPalButtonScriptLoader.js +++ b/client/src/components/Donation/PayPalButtonScriptLoader.js @@ -25,7 +25,10 @@ export class PayPalButtonScriptLoader extends Component { } componentDidUpdate(prevProps) { - if (prevProps.isSubscription !== this.state.isSubscription) { + if ( + prevProps.isSubscription !== this.state.isSubscription || + prevProps.style !== this.props.style + ) { // eslint-disable-next-line react/no-did-update-set-state this.setState({ isSdkLoaded: false }); this.loadScript(this.state.isSubscription, true); @@ -34,7 +37,7 @@ export class PayPalButtonScriptLoader extends Component { loadScript(subscription, deleteScript) { if (deleteScript) scriptRemover('paypal-sdk'); - let queries = `?client-id=${this.props.clientId}&disable-funding=credit,card,bancontact,blik,eps,giropay,ideal,mybank,p24,sepa,sofort,venmo`; + let queries = `?client-id=${this.props.clientId}&disable-funding=credit,bancontact,blik,eps,giropay,ideal,mybank,p24,sepa,sofort,venmo`; if (subscription) queries += '&vault=true&intent=subscription'; scriptLoader( diff --git a/client/src/components/Donation/PaypalButton.js b/client/src/components/Donation/PaypalButton.js index 92d28533ba..c2b15ca9ff 100644 --- a/client/src/components/Donation/PaypalButton.js +++ b/client/src/components/Donation/PaypalButton.js @@ -58,58 +58,61 @@ export class PaypalButton extends Component { render() { const { duration, planId, amount } = this.state; - const { t } = this.props; + const { t, theme } = this.props; const isSubscription = duration !== 'onetime'; - + const buttonColor = theme === 'night' ? 'white' : 'gold'; if (!paypalClientId) { return null; } return ( - { - return actions.order.create({ - purchase_units: [ - { - amount: { - currency_code: 'USD', - value: (amount / 100).toString() +
+ { + 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 => { - this.handleApproval(data, isSubscription); - }} - onCancel={() => { - this.props.onDonationStateChange({ - processing: false, - success: false, - error: t('donate.failed-pay') - }); - }} - onError={() => - this.props.onDonationStateChange({ - processing: false, - success: false, - error: t('donate.try-again') - }) - } - plantId={planId} - style={{ - tagline: false, - height: 43 - }} - /> + ] + }); + }} + createSubscription={(data, actions) => { + return actions.subscription.create({ + plan_id: planId + }); + }} + isSubscription={isSubscription} + onApprove={data => { + this.handleApproval(data, isSubscription); + }} + onCancel={() => { + this.props.onDonationStateChange({ + processing: false, + success: false, + error: t('donate.failed-pay') + }); + }} + onError={() => + this.props.onDonationStateChange({ + processing: false, + success: false, + error: t('donate.try-again') + }) + } + plantId={planId} + style={{ + tagline: false, + height: 43, + color: buttonColor + }} + /> +
); } } @@ -122,7 +125,8 @@ const propTypes = { isDonating: PropTypes.bool, onDonationStateChange: PropTypes.func, skipAddDonation: PropTypes.bool, - t: PropTypes.func.isRequired + t: PropTypes.func.isRequired, + theme: PropTypes.string }; const mapStateToProps = createSelector( diff --git a/client/src/components/Donation/StripeCardForm.js b/client/src/components/Donation/StripeCardForm.js deleted file mode 100644 index bc4e89b74a..0000000000 --- a/client/src/components/Donation/StripeCardForm.js +++ /dev/null @@ -1,198 +0,0 @@ -import React, { useState } from 'react'; -import { - CardNumberElement, - CardExpiryElement, - useStripe, - useElements -} from '@stripe/react-stripe-js'; -import PropTypes from 'prop-types'; -import isEmail from 'validator/lib/isEmail'; - -import { - Row, - Col, - ControlLabel, - FormGroup, - Image, - Button, - Form, - FormControl, - Alert -} from '@freecodecamp/react-bootstrap'; -import { withTranslation } from 'react-i18next'; - -const initialPaymentInfoValidityState = { - cardNumber: { - complete: false, - error: null - }, - cardExpiry: { - complete: false, - error: null - } -}; - -const propTypes = { - getDonationButtonLabel: PropTypes.func.isRequired, - onDonationStateChange: PropTypes.func, - postStripeDonation: PropTypes.func, - t: PropTypes.func.isRequired, - theme: PropTypes.string, - userEmail: PropTypes.string -}; - -const StripeCardForm = ({ - getDonationButtonLabel, - theme, - t, - onDonationStateChange, - postStripeDonation, - userEmail -}) => { - const [isSubmissionValid, setSubmissionValidity] = useState(true); - const [email, setEmail] = useState(userEmail); - const [isEmailValid, setEmailValidity] = useState(true); - const [paymentInfoValidation, setPaymentValidity] = useState( - initialPaymentInfoValidityState - ); - - const stripe = useStripe(); - const elements = useElements(); - - function handleInputChange(event) { - const { elementType, error, complete } = event; - setPaymentValidity({ - ...paymentInfoValidation, - [elementType]: { - error, - complete - } - }); - } - - function isPaymentInfoValid() { - return Object.keys(paymentInfoValidation) - .map(key => paymentInfoValidation[key]) - .every(({ complete, error }) => complete && !error); - } - - const options = { - style: { - base: { - fontSize: '18px', - color: `${theme === 'night' ? '#fff' : '#0a0a23'}` - } - } - }; - - const handleSubmit = async event => { - event.preventDefault(); - - if (!isEmailValid || !isPaymentInfoValid()) - return setSubmissionValidity(false); - else setSubmissionValidity(true); - - if (!isEmail(email)) { - return onDonationStateChange({ - error: t('donate.need-email') - }); - } - - const { error, token } = await stripe.createToken( - elements.getElement(CardNumberElement), - { email } - ); - - if (error) { - return onDonationStateChange({ - error: t('donate.went-wrong') - }); - } - return postStripeDonation(token); - }; - - const handleEmailChange = e => { - const newValue = e.target.value; - setEmail(newValue); - setEmailValidity(true); - }; - - const handleEmailBlur = () => { - const newValidation = isEmail(email); - setEmailValidity(newValidation); - }; - - const renderErrorMessage = () => { - let message = ''; - if (!isEmailValid && !isPaymentInfoValid()) - message =

{t('donate.valid-info')}

; - else if (!isEmailValid) message =

{t('donate.valid-email')}

; - else message =

{t('donate.valid-card')}

; - return {message}; - }; - - return ( -
-
{!isSubmissionValid ? renderErrorMessage() : ''}
- - {t('donate.email-receipt')} - - -
- - {t('donate.card-number')} - - - - {t('donate.expiration')} - - - - - - payment options - - - -
- -
- ); -}; - -StripeCardForm.displayName = 'StripeCardForm'; -StripeCardForm.propTypes = propTypes; - -export default withTranslation()(StripeCardForm); diff --git a/client/src/components/Header/index.js b/client/src/components/Header/index.js index 3767aa83dd..768f9b22b5 100644 --- a/client/src/components/Header/index.js +++ b/client/src/components/Header/index.js @@ -2,7 +2,6 @@ import React from 'react'; import Helmet from 'react-helmet'; import PropTypes from 'prop-types'; -import stripeObserver from './stripeIframesFix'; import UniversalNav from './components/UniversalNav'; import './header.css'; @@ -26,10 +25,6 @@ export class Header extends React.Component { componentDidMount() { document.addEventListener('click', this.handleClickOutside); - - // Remove stacking Stripe iframes with each navigation - // after visiting /donate - stripeObserver(); } componentWillUnmount() { diff --git a/client/src/components/Header/stripeIframesFix.js b/client/src/components/Header/stripeIframesFix.js deleted file mode 100644 index 2fa62d5b6f..0000000000 --- a/client/src/components/Header/stripeIframesFix.js +++ /dev/null @@ -1,30 +0,0 @@ -const stripeObserver = () => { - const config = { attributes: false, childList: true, subtree: false }; - - const filterNodes = nl => - Array.from(nl) - .filter(b => b.nodeName === 'IFRAME') - .filter(b => /__privateStripeController/.test(b.name)); - - const mutationCallback = a => { - const controllerAdded = a.reduce( - (acc, curr) => - curr.type === 'childList' - ? [...acc, ...filterNodes(curr.addedNodes)] - : acc, - [] - )[0]; - if (controllerAdded) { - const allControllers = filterNodes(document.body.childNodes); - allControllers.forEach(controller => { - if (controller.name !== controllerAdded.name) { - controller.remove(); - } - }); - } - }; - - return new MutationObserver(mutationCallback).observe(document.body, config); -}; - -export default stripeObserver; diff --git a/client/src/pages/donate.js b/client/src/pages/donate.js index 0fc1b6f3ba..2263d22fe3 100644 --- a/client/src/pages/donate.js +++ b/client/src/pages/donate.js @@ -62,7 +62,7 @@ class DonatePage extends Component { }); } - handleProcessing(duration, amount, action = 'stripe button click') { + handleProcessing(duration, amount, action) { this.props.executeGA({ type: 'event', data: { diff --git a/client/src/redux/donation-saga.js b/client/src/redux/donation-saga.js index c918d397fe..ed4594fc29 100644 --- a/client/src/redux/donation-saga.js +++ b/client/src/redux/donation-saga.js @@ -1,12 +1,4 @@ -import { - put, - select, - takeEvery, - takeLeading, - delay, - call, - take -} from 'redux-saga/effects'; +import { put, select, takeEvery, delay, call, take } from 'redux-saga/effects'; import { openDonationModal, @@ -16,16 +8,10 @@ import { recentlyClaimedBlockSelector, addDonationComplete, addDonationError, - postChargeStripeComplete, - postChargeStripeError, types as appTypes } from './'; -import { - addDonation, - postChargeStripe, - postCreateStripeSession -} from '../utils/ajax'; +import { addDonation } from '../utils/ajax'; const defaultDonationError = `Something is not right. Please contact donors@freecodecamp.org`; @@ -59,42 +45,9 @@ function* addDonationSaga({ payload }) { } } -function* createStripeSessionSaga({ payload: { stripe, data } }) { - try { - const session = yield call(postCreateStripeSession, { - ...data, - location: window.location.href - }); - stripe.redirectToCheckout({ - sessionId: session.data.id - }); - } catch (error) { - const err = - error.response && error.response.data - ? error.response.data.message - : defaultDonationError; - yield put(addDonationError(err)); - } -} - -function* postChargeStripeSaga({ payload }) { - try { - yield call(postChargeStripe, payload); - yield put(postChargeStripeComplete()); - } catch (error) { - const err = - error.response && error.response.data - ? error.response.data.error - : defaultDonationError; - yield put(postChargeStripeError(err)); - } -} - export function createDonationSaga(types) { return [ takeEvery(types.tryToShowDonationModal, showDonateModalSaga), - takeEvery(types.addDonation, addDonationSaga), - takeLeading(types.postChargeStripe, postChargeStripeSaga), - takeLeading(types.createStripeSession, createStripeSessionSaga) + takeEvery(types.addDonation, addDonationSaga) ]; } diff --git a/client/src/redux/index.js b/client/src/redux/index.js index 9e3175f7c5..25883a8a8d 100644 --- a/client/src/redux/index.js +++ b/client/src/redux/index.js @@ -82,8 +82,6 @@ export const types = createTypes( 'updateDonationFormState', ...createAsyncTypes('fetchUser'), ...createAsyncTypes('addDonation'), - ...createAsyncTypes('createStripeSession'), - ...createAsyncTypes('postChargeStripe'), ...createAsyncTypes('fetchProfileForUser'), ...createAsyncTypes('acceptTerms'), ...createAsyncTypes('showCert'), @@ -152,14 +150,6 @@ export const addDonation = createAction(types.addDonation); export const addDonationComplete = createAction(types.addDonationComplete); export const addDonationError = createAction(types.addDonationError); -export const createStripeSession = createAction(types.createStripeSession); - -export const postChargeStripe = createAction(types.postChargeStripe); -export const postChargeStripeComplete = createAction( - types.postChargeStripeComplete -); -export const postChargeStripeError = createAction(types.postChargeStripeError); - export const fetchProfileForUser = createAction(types.fetchProfileForUser); export const fetchProfileForUserComplete = createAction( types.fetchProfileForUserComplete @@ -404,10 +394,6 @@ export const reducer = handleActions( ...state, donationFormState: { ...state.donationFormState, ...payload } }), - [types.createStripeSession]: state => ({ - ...state, - donationFormState: { ...defaultDonationFormState, redirecting: true } - }), [types.addDonation]: state => ({ ...state, donationFormState: { ...defaultDonationFormState, processing: true } @@ -431,29 +417,6 @@ export const reducer = handleActions( ...state, donationFormState: { ...defaultDonationFormState, error: payload } }), - [types.postChargeStripe]: state => ({ - ...state, - donationFormState: { ...defaultDonationFormState, processing: true } - }), - [types.postChargeStripeComplete]: state => { - const { appUsername } = state; - return { - ...state, - user: { - ...state.user, - [appUsername]: { - ...state.user[appUsername], - isDonating: true - } - }, - - donationFormState: { ...defaultDonationFormState, success: true } - }; - }, - [types.postChargeStripeError]: (state, { payload }) => ({ - ...state, - donationFormState: { ...defaultDonationFormState, error: payload } - }), [types.fetchUser]: state => ({ ...state, userFetchState: { ...defaultFetchState } diff --git a/client/src/utils/ajax.js b/client/src/utils/ajax.js index 1cb9e50172..d9fb422c02 100644 --- a/client/src/utils/ajax.js +++ b/client/src/utils/ajax.js @@ -60,18 +60,11 @@ export function getArticleById(shortId) { } /** POST **/ -export function postChargeStripe(body) { - return post('/donate/charge-stripe', body); -} export function addDonation(body) { return post('/donate/add-donation', body); } -export function postCreateStripeSession(body) { - return post('/donate/create-stripe-session', body); -} - export function putUpdateLegacyCert(body) { return post('/update-my-projects', body); } diff --git a/client/src/utils/scriptLoaders.js b/client/src/utils/scriptLoaders.js index c343fe9958..35f9ff5f5e 100644 --- a/client/src/utils/scriptLoaders.js +++ b/client/src/utils/scriptLoaders.js @@ -17,15 +17,6 @@ export const scriptRemover = id => { } }; -export const stripeScriptLoader = onload => - scriptLoader( - 'stripe-js', - 'stripe-js', - false, - 'https://js.stripe.com/v3/', - onload - ); - export const mathJaxScriptLoader = () => scriptLoader( 'mathjax', diff --git a/client/utils/tags.js b/client/utils/tags.js index cb9111bf57..7c69fcc21d 100644 --- a/client/utils/tags.js +++ b/client/utils/tags.js @@ -109,8 +109,6 @@ export const injectConditionalTags = (tagsArray, homeLocation) => { export const getPostBodyComponents = pathname => { let scripts = []; - const challengesPathRE = new RegExp('/learn/[^/]+/[^/]+/[^/]+/?$'); - const donatePathRE = new RegExp('/donate/?$'); const mathJaxScriptElement = (