fix: remove all year-end gift implementation (#38030)

This commit is contained in:
mrugesh 2020-01-09 02:37:50 +05:30 committed by GitHub
parent 86127a24f7
commit 059e826465
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 24 additions and 488 deletions

View File

@ -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);
}
}

View File

@ -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 (
<Alert bsStyle={style} className='donation-completion'>
@ -43,7 +34,7 @@ function DonateCompletion({
name='line-scale'
/>
)}
{success && !yearEndGift && (
{success && (
<div>
<p>
Your donations will support free technology education for people
@ -55,11 +46,6 @@ function DonateCompletion({
</p>
</div>
)}
{success && yearEndGift && (
<div>
<p>You should receive a receipt in your email.</p>
</div>
)}
{error && <p>{error}</p>}
</div>
<div className='donation-completion-buttons'>

View File

@ -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();

View File

@ -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 (
<div>
<StripeProvider stripe={stripe}>
<Elements>
<DonateFormChildViewForHOC
showCloseBtn={showCloseBtn}
defaultTheme={defaultTheme}
donationAmount={donationAmount}
donationDuration='onetime'
getDonationButtonLabel={this.getDonationButtonLabel}
yearEndGift={true}
/>
</Elements>
</StripeProvider>
</div>
);
}
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 (
<form className='radio-container'>
<label>Suggested gift amounts:</label>
<ul>
<li>
<label>
<input
type='radio'
value={100000}
checked={this.state.donationAmount === 100000}
onChange={this.handleSelectAmount}
/>
$1,000
</label>
</li>
<li>
<label>
<input
type='radio'
value={25000}
checked={this.state.donationAmount === 25000}
onChange={this.handleSelectAmount}
/>
$250
</label>
</li>
<li>
<label>
<input
type='radio'
value={10000}
checked={this.state.donationAmount === 10000}
onChange={this.handleSelectAmount}
/>
$100
</label>
</li>
</ul>
</form>
);
}
renderCustomAmountInput() {
return (
<form>
<FormGroup controlId='formBasicText'>
<ControlLabel>Or give a custom amount:</ControlLabel>
<FormControl
type='text'
value={this.state.donationAmount / 100}
placeholder='Enter Amount'
onChange={this.handleChange}
/>
<FormControl.Feedback />
</FormGroup>
</form>
);
}
renderPayPalDonations() {
return (
<form
action='https://www.paypal.com/cgi-bin/webscr'
method='post'
target='_top'
>
<input type='hidden' name='cmd' value='_s-xclick' />
<input type='hidden' name='hosted_button_id' value='9C73W6CWSLNPW' />
<button
type='submit'
name='submit'
className='btn btn-block btn-cta paypal-button'
alt='donate with paypal'
>
<DonateWithPayPal />
</button>
</form>
);
}
renderForm(item) {
return (
<form
action='https://www.paypal.com/cgi-bin/webscr'
method='post'
target='_blank'
>
<input defaultValue='_s-xclick' name='cmd' type='hidden' />{' '}
<input
defaultValue={item.defaultValueHash}
name='hosted_button_id'
type='hidden'
/>{' '}
<input
className='btn btn-block'
value={item.defaultValue}
name='submit'
type='submit'
/>
</form>
);
}
handleClick() {
this.setState({ showOtherAmounts: true, donationAmount: 25000 });
}
renderOtherPaymentButton() {
return (
<>
<Button className='btn-link' onClick={this.handleClick}>
<b>Or give a custom amount</b>
</Button>
<Spacer />
</>
);
}
render() {
return (
<Row>
<Col sm={10} smOffset={1} xs={12}>
<b>
Thank you again for supporting freeCodeCamp.org with a one-time
year-end gift. Please enter your credit card information below.
</b>
<Spacer />
</Col>
<Col sm={10} smOffset={1} xs={12}>
{this.renderAmountRadio()}
</Col>
<Col sm={10} smOffset={1} xs={12}>
{this.state.showOtherAmounts
? this.renderCustomAmountInput()
: this.renderOtherPaymentButton()}
</Col>
<Col sm={10} smOffset={1} xs={12}>
{this.renderDonationOptions()}
<Spacer />
</Col>
<Col sm={10} smOffset={1} xs={12} style={{ marginBottom: '5px' }}>
<b>Or give using PayPal:</b>
</Col>
<Col sm={10} smOffset={1} xs={12}>
{this.renderPayPalDonations()}
<Spacer />
</Col>
<Col sm={10} smOffset={1} xs={12}>
<b>
If you need a receipt from your taxes, reply to Quincy's email he
sent you.
</b>
</Col>
</Row>
);
}
}
YearEndDonationForm.displayName = 'YearEndDonationForm';
YearEndDonationForm.propTypes = propTypes;
export default YearEndDonationForm;

View File

@ -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;
}

View File

@ -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 (
<>
<Helmet title='Support our nonprofit | freeCodeCamp.org' />
<Grid>
<main>
<Spacer size={3} />
<FullWidthRow>
<Alert bsStyle='success' className='donation-completion'>
<div>
<h4>
<b>Thank you for your donation.</b>
</h4>
<p>You should receive a receipt in your email.</p>
</div>
</Alert>
</FullWidthRow>
<Spacer size={2} />
</main>
</Grid>
</>
);
}
YearEndGiftPage.displayName = 'YearEndGiftPage';
export default YearEndGiftPage;

View File

@ -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 (
<>
<Helmet title='Support our nonprofit | freeCodeCamp.org' />
<Grid>
<main>
<Spacer />
<FullWidthRow>
<YearEndDonationForm defaultTheme='light' />
</FullWidthRow>
<Spacer />
<Spacer />
</main>
</Grid>
</>
);
}
YearEndGiftPage.displayName = 'YearEndGiftPage';
export default YearEndGiftPage;

View File

@ -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) {

View File

@ -29,11 +29,7 @@ export default function layoutSelector({ element, props }) {
</DefaultLayout>
);
}
if (
/^\/donation(\/.*)*|^\/donate(\/.*)*/.test(pathname) ||
/^\/year-end-gift-successful(\/.*)*/.test(pathname) ||
/^\/year-end-gift(\/.*)*/.test(pathname)
) {
if (/^\/donation(\/.*)*|^\/donate(\/.*)*/.test(pathname)) {
return (
<DefaultLayout pathname={pathname} useTheme={false}>
{element}