diff --git a/api-server/server/boot/donate.js b/api-server/server/boot/donate.js index f63a4ac4de..092adb7c36 100644 --- a/api-server/server/boot/donate.js +++ b/api-server/server/boot/donate.js @@ -14,7 +14,6 @@ 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 donateRouter = app.loopback.Router(); @@ -170,6 +169,16 @@ export default function donateBoot(app, done) { }; return Promise.resolve(user) + .then(nonDonatingUser => { + const { isDonating } = nonDonatingUser; + if (isDonating) { + throw { + message: `User already has active donation(s).`, + type: 'AlreadyDonatingError' + }; + } + return nonDonatingUser; + }) .then(createCustomer) .then(customer => { return duration === 'onetime' @@ -184,92 +193,10 @@ export default function donateBoot(app, done) { }) .then(createAsyncUserDonation) .catch(err => { - if (err.type === 'StripeCardError') { - return res.status(402).send({ error: err.message }); - } - return res - .status(500) - .send({ error: 'Donation failed due to a server error.' }); - }); - } - - function createStripeDonationYearEnd(req, res) { - const { user, body } = req; - - const { - amount, - duration, - token: { email, id } - } = body; - - if (amount < 1 || duration !== 'onetime' || !isEmail(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('is new 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 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(createCustomer) - .then(customer => { - return createOneTimeCharge(customer).then(charge => { - donation.subscriptionId = 'one-time-charge-prefix-' + charge.id; - return res.send(charge); - }); - }) - .then(createAsyncUserDonation) - .catch(err => { - if (err.type === 'StripeCardError') { + if ( + err.type === 'StripeCardError' || + err.type === 'AlreadyDonatingError' + ) { return res.status(402).send({ error: err.message }); } return res @@ -333,12 +260,10 @@ export default function donateBoot(app, done) { done(); } else { api.post('/charge-stripe', createStripeDonation); - api.post('/charge-stripe-year-end', createStripeDonationYearEnd); api.post('/create-hmac-hash', createHmacHash); donateRouter.use('/donate', api); app.use(donateRouter); app.use('/internal', donateRouter); - app.use('/unauthenticated', donateRouter); connectToStripe().then(done); } } diff --git a/client/src/components/Donation/DonateCompletion.js b/client/src/components/Donation/DonateCompletion.js index 745fad87b8..9fd9f670b5 100644 --- a/client/src/components/Donation/DonateCompletion.js +++ b/client/src/components/Donation/DonateCompletion.js @@ -9,25 +9,16 @@ const propTypes = { error: PropTypes.string, processing: PropTypes.bool, reset: PropTypes.func.isRequired, - success: PropTypes.bool, - yearEndGift: PropTypes.bool + success: PropTypes.bool }; -function DonateCompletion({ - processing, - reset, - success, - error = null, - yearEndGift = false -}) { +function DonateCompletion({ processing, reset, success, error = null }) { /* eslint-disable no-nested-ternary */ const style = processing ? 'info' : success ? 'success' : 'danger'; const heading = processing ? 'We are processing your donation.' : success - ? yearEndGift - ? 'Thank you for your donation.' - : 'Thank you for being a supporter.' + ? 'Thank you for being a supporter.' : 'Something went wrong with your donation.'; return ( @@ -43,7 +34,7 @@ function DonateCompletion({ name='line-scale' /> )} - {success && !yearEndGift && ( + {success && (

Your donations will support free technology education for people @@ -55,11 +46,6 @@ function DonateCompletion({

)} - {success && yearEndGift && ( -
-

You should receive a receipt in your email.

-
- )} {error &&

{error}

}
diff --git a/client/src/components/Donation/DonateFormChildViewForHOC.js b/client/src/components/Donation/DonateFormChildViewForHOC.js index 54c2de93ea..57a825ace7 100644 --- a/client/src/components/Donation/DonateFormChildViewForHOC.js +++ b/client/src/components/Donation/DonateFormChildViewForHOC.js @@ -29,8 +29,7 @@ const propTypes = { stripe: PropTypes.shape({ createToken: PropTypes.func.isRequired }), - theme: PropTypes.string, - yearEndGift: PropTypes.bool + theme: PropTypes.string }; const initialState = { donationState: { @@ -134,7 +133,6 @@ class DonateFormChildViewForHOC extends Component { postDonation(token) { const { donationAmount: amount, donationDuration: duration } = this.state; - const { yearEndGift } = this.props; this.setState(state => ({ ...state, donationState: { @@ -152,7 +150,7 @@ class DonateFormChildViewForHOC extends Component { this.props.showCloseBtn(); } - return postChargeStripe(yearEndGift, { + return postChargeStripe({ token, amount, duration @@ -275,14 +273,12 @@ class DonateFormChildViewForHOC extends Component { const { donationState: { processing, success, error } } = this.state; - const { yearEndGift } = this.props; if (processing || success || error) { return this.renderCompletion({ processing, success, error, - reset: this.resetDonation, - yearEndGift + reset: this.resetDonation }); } return this.renderDonateForm(); diff --git a/client/src/components/YearEndGift/YearEndDonationForm.js b/client/src/components/YearEndGift/YearEndDonationForm.js deleted file mode 100644 index 53fefdad19..0000000000 --- a/client/src/components/YearEndGift/YearEndDonationForm.js +++ /dev/null @@ -1,284 +0,0 @@ -/* eslint-disable react/sort-prop-types */ -/* eslint-disable react/jsx-sort-props */ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { - Row, - Col, - ControlLabel, - FormControl, - FormGroup, - Button -} from '@freecodecamp/react-bootstrap'; -import { StripeProvider, Elements } from 'react-stripe-elements'; -import { Spacer } from '../helpers'; - -// eslint-disable-next-line max-len -import DonateFormChildViewForHOC from '../Donation/DonateFormChildViewForHOC'; - -import './YearEndGift.css'; -import '../Donation/Donation.css'; -import { stripePublicKey } from '../../../../config/env.json'; -import { stripeScriptLoader } from '../../utils/scriptLoaders'; -import DonateWithPayPal from '../../assets/icons/DonateWithPayPal'; - -const numToCommas = num => - num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,'); - -const propTypes = { - showCloseBtn: PropTypes.func, - defaultTheme: PropTypes.string, - isDonating: PropTypes.bool, - stripe: PropTypes.shape({ - createToken: PropTypes.func.isRequired - }) -}; - -class YearEndDonationForm extends Component { - constructor(...args) { - super(...args); - this.state = { - donationAmount: 25000, - showOtherAmounts: false, - stripe: null - }; - this.handleStripeLoad = this.handleStripeLoad.bind(this); - this.getDonationButtonLabel = this.getDonationButtonLabel.bind(this); - this.handleSelectAmount = this.handleSelectAmount.bind(this); - this.handleChange = this.handleChange.bind(this); - this.handleClick = this.handleClick.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); - } - } - - handleStripeLoad() { - // Create Stripe instance once Stripe.js loads - if (stripePublicKey) { - this.setState(state => ({ - ...state, - stripe: window.Stripe(stripePublicKey) - })); - } - } - - getDonationButtonLabel() { - const { donationAmount } = this.state; - let donationBtnLabel = `Confirm your donation`; - donationBtnLabel = `Confirm your one-time donation of $${numToCommas( - donationAmount / 100 - )}`; - return donationBtnLabel; - } - - renderDonationOptions() { - const { donationAmount, stripe } = this.state; - - const { showCloseBtn, defaultTheme } = this.props; - return ( -
- - - - - -
- ); - } - - handleSelectAmount(e) { - this.setState({ donationAmount: Number(e.target.value) }); - } - - handleChange(e) { - if (isNaN(e.target.value)) return; - const amount = Math.floor(e.target.value) * 100; - this.setState({ donationAmount: amount }); - } - - renderAmountRadio() { - return ( -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
- ); - } - - renderCustomAmountInput() { - return ( -
- - Or give a custom amount: - - - -
- ); - } - - renderPayPalDonations() { - return ( -
- - - -
- ); - } - - renderForm(item) { - return ( -
- {' '} - {' '} - -
- ); - } - - handleClick() { - this.setState({ showOtherAmounts: true, donationAmount: 25000 }); - } - - renderOtherPaymentButton() { - return ( - <> - - - - ); - } - - render() { - return ( - - - - Thank you again for supporting freeCodeCamp.org with a one-time - year-end gift. Please enter your credit card information below. - - - - - {this.renderAmountRadio()} - - - {this.state.showOtherAmounts - ? this.renderCustomAmountInput() - : this.renderOtherPaymentButton()} - - - {this.renderDonationOptions()} - - - - Or give using PayPal: - - - {this.renderPayPalDonations()} - - - - - If you need a receipt from your taxes, reply to Quincy's email he - sent you. - - - - ); - } -} - -YearEndDonationForm.displayName = 'YearEndDonationForm'; -YearEndDonationForm.propTypes = propTypes; - -export default YearEndDonationForm; diff --git a/client/src/components/YearEndGift/YearEndGift.css b/client/src/components/YearEndGift/YearEndGift.css deleted file mode 100644 index 4f6ef4b025..0000000000 --- a/client/src/components/YearEndGift/YearEndGift.css +++ /dev/null @@ -1,18 +0,0 @@ -.radio-container ul { - list-style-type: none; - padding-left: 0px; -} - -.radio-container li label { - padding: 7px 20px 6px 0px; - max-width: 100%; - height: 40px; - cursor: pointer; -} - -.radio-container li label input { - height: 17px; - width: 17px; - margin: auto 5px auto 0; - cursor: pointer; -} diff --git a/client/src/pages/year-end-gift-successful.js b/client/src/pages/year-end-gift-successful.js deleted file mode 100644 index 922d57174a..0000000000 --- a/client/src/pages/year-end-gift-successful.js +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; -import Helmet from 'react-helmet'; -import { Grid, Alert } from '@freecodecamp/react-bootstrap'; - -import { Spacer, FullWidthRow } from '../components/helpers'; - -import '../components/Donation/Donation.css'; - -function YearEndGiftPage() { - return ( - <> - - -
- - - -
-

- Thank you for your donation. -

-

You should receive a receipt in your email.

-
-
-
- -
-
- - ); -} - -YearEndGiftPage.displayName = 'YearEndGiftPage'; - -export default YearEndGiftPage; diff --git a/client/src/pages/year-end-gift.js b/client/src/pages/year-end-gift.js deleted file mode 100644 index 05f4477410..0000000000 --- a/client/src/pages/year-end-gift.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; -import Helmet from 'react-helmet'; -import { Grid } from '@freecodecamp/react-bootstrap'; - -import { Spacer, FullWidthRow } from '../components/helpers'; -import YearEndDonationForm from '../components/YearEndGift/YearEndDonationForm'; - -function YearEndGiftPage() { - return ( - <> - - -
- - - - - - -
-
- - ); -} - -YearEndGiftPage.displayName = 'YearEndGiftPage'; - -export default YearEndGiftPage; diff --git a/client/src/utils/ajax.js b/client/src/utils/ajax.js index 9c80c484ca..f9c77d1169 100644 --- a/client/src/utils/ajax.js +++ b/client/src/utils/ajax.js @@ -50,10 +50,8 @@ export function getArticleById(shortId) { } /** POST **/ -export function postChargeStripe(yearEndGift, body) { - return yearEndGift - ? postUnauthenticated('/donate/charge-stripe-year-end', body) - : post('/donate/charge-stripe', body); +export function postChargeStripe(body) { + return post('/donate/charge-stripe', body); } export function postCreateHmacHash(body) { diff --git a/client/utils/gatsby/layoutSelector.js b/client/utils/gatsby/layoutSelector.js index 4b1dfe2b03..91b98f3814 100644 --- a/client/utils/gatsby/layoutSelector.js +++ b/client/utils/gatsby/layoutSelector.js @@ -29,11 +29,7 @@ export default function layoutSelector({ element, props }) { ); } - if ( - /^\/donation(\/.*)*|^\/donate(\/.*)*/.test(pathname) || - /^\/year-end-gift-successful(\/.*)*/.test(pathname) || - /^\/year-end-gift(\/.*)*/.test(pathname) - ) { + if (/^\/donation(\/.*)*|^\/donate(\/.*)*/.test(pathname)) { return ( {element}