fix(client): replace Stripe with PayPal (#41924)
* feat: remove stripe payment option from client * feat: remove stripe completely * fix: remove last Stripe remnants Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
18
api-server/package-lock.json
generated
18
api-server/package-lock.json
generated
@ -16574,24 +16574,6 @@
|
|||||||
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
|
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
|
||||||
"dev": true
|
"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": {
|
"strong-error-handler": {
|
||||||
"version": "3.5.0",
|
"version": "3.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/strong-error-handler/-/strong-error-handler-3.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/strong-error-handler/-/strong-error-handler-3.5.0.tgz",
|
||||||
|
@ -69,7 +69,6 @@
|
|||||||
"passport-mock-strategy": "^2.0.0",
|
"passport-mock-strategy": "^2.0.0",
|
||||||
"query-string": "^6.14.0",
|
"query-string": "^6.14.0",
|
||||||
"rx": "^4.1.0",
|
"rx": "^4.1.0",
|
||||||
"stripe": "^6.36.0",
|
|
||||||
"uuid": "^3.4.0",
|
"uuid": "^3.4.0",
|
||||||
"validator": "^9.4.1"
|
"validator": "^9.4.1"
|
||||||
},
|
},
|
||||||
|
@ -1,231 +1,19 @@
|
|||||||
import Stripe from 'stripe';
|
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
import { isEmail, isNumeric } from 'validator';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getAsyncPaypalToken,
|
getAsyncPaypalToken,
|
||||||
verifyWebHook,
|
verifyWebHook,
|
||||||
updateUser,
|
updateUser,
|
||||||
verifyWebHookType
|
verifyWebHookType
|
||||||
} from '../utils/donation';
|
} from '../utils/donation';
|
||||||
import {
|
|
||||||
durationKeysConfig,
|
|
||||||
donationOneTimeConfig,
|
|
||||||
donationSubscriptionConfig,
|
|
||||||
durationsConfig,
|
|
||||||
onetimeSKUConfig,
|
|
||||||
donationUrls
|
|
||||||
} from '../../../../config/donation-settings';
|
|
||||||
import keys from '../../../../config/secrets';
|
import keys from '../../../../config/secrets';
|
||||||
import { deploymentEnv } from '../../../../config/env';
|
|
||||||
|
|
||||||
const log = debug('fcc:boot:donate');
|
const log = debug('fcc:boot:donate');
|
||||||
|
|
||||||
export default function donateBoot(app, done) {
|
export default function donateBoot(app, done) {
|
||||||
let stripe = false;
|
|
||||||
const { User } = app.models;
|
|
||||||
const api = app.loopback.Router();
|
const api = app.loopback.Router();
|
||||||
const hooks = app.loopback.Router();
|
const hooks = app.loopback.Router();
|
||||||
const donateRouter = 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) {
|
function addDonation(req, res) {
|
||||||
const { user, body } = req;
|
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) {
|
function updatePaypal(req, res) {
|
||||||
const { headers, body } = req;
|
const { headers, body } = req;
|
||||||
return Promise.resolve(req)
|
return Promise.resolve(req)
|
||||||
@ -311,13 +52,8 @@ export default function donateBoot(app, done) {
|
|||||||
.finally(() => res.status(200).json({ message: 'received paypal hook' }));
|
.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 paypalKey = keys.paypal.client;
|
||||||
const paypalSec = keys.paypal.secret;
|
const paypalSec = keys.paypal.secret;
|
||||||
const stripeSecretInvalid = !secKey || secKey === 'sk_from_stripe_dashboard';
|
|
||||||
const stripPublicInvalid =
|
|
||||||
!stripeKey || stripeKey === 'pk_from_stripe_dashboard';
|
|
||||||
|
|
||||||
const paypalSecretInvalid =
|
const paypalSecretInvalid =
|
||||||
!paypalKey || paypalKey === 'id_from_paypal_dashboard';
|
!paypalKey || paypalKey === 'id_from_paypal_dashboard';
|
||||||
@ -325,22 +61,19 @@ export default function donateBoot(app, done) {
|
|||||||
!paypalSec || paypalSec === 'secret_from_paypal_dashboard';
|
!paypalSec || paypalSec === 'secret_from_paypal_dashboard';
|
||||||
|
|
||||||
const paypalInvalid = paypalPublicInvalid || paypalSecretInvalid;
|
const paypalInvalid = paypalPublicInvalid || paypalSecretInvalid;
|
||||||
const stripeInvalid = stripeSecretInvalid || stripPublicInvalid;
|
|
||||||
|
|
||||||
if (stripeInvalid || paypalInvalid) {
|
if (paypalInvalid) {
|
||||||
if (process.env.FREECODECAMP_NODE_ENV === 'production') {
|
if (process.env.FREECODECAMP_NODE_ENV === 'production') {
|
||||||
throw new Error('Donation API keys are required to boot the server!');
|
throw new Error('Donation API keys are required to boot the server!');
|
||||||
}
|
}
|
||||||
log('Donation disabled in development unless ALL test keys are provided');
|
log('Donation disabled in development unless ALL test keys are provided');
|
||||||
done();
|
done();
|
||||||
} else {
|
} else {
|
||||||
api.post('/charge-stripe', createStripeDonation);
|
|
||||||
api.post('/create-stripe-session', createStripeSession);
|
|
||||||
api.post('/add-donation', addDonation);
|
api.post('/add-donation', addDonation);
|
||||||
hooks.post('/update-paypal', updatePaypal);
|
hooks.post('/update-paypal', updatePaypal);
|
||||||
donateRouter.use('/donate', api);
|
donateRouter.use('/donate', api);
|
||||||
donateRouter.use('/hooks', hooks);
|
donateRouter.use('/hooks', hooks);
|
||||||
app.use(donateRouter);
|
app.use(donateRouter);
|
||||||
connectToStripe().then(done);
|
done();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,9 +12,7 @@ export default function getCsurf() {
|
|||||||
const { path } = req;
|
const { path } = req;
|
||||||
if (
|
if (
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/^\/hooks\/update-paypal$|^\/hooks\/update-stripe$|^\/donate\/charge-stripe$/.test(
|
/^\/hooks\/update-paypal$/.test(path)
|
||||||
path
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
@ -23,11 +23,7 @@ const signinRE = /^\/signin/;
|
|||||||
const statusRE = /^\/status\/ping$/;
|
const statusRE = /^\/status\/ping$/;
|
||||||
const unsubscribedRE = /^\/unsubscribed\//;
|
const unsubscribedRE = /^\/unsubscribed\//;
|
||||||
const unsubscribeRE = /^\/u\/|^\/unsubscribe\/|^\/ue\//;
|
const unsubscribeRE = /^\/u\/|^\/unsubscribe\/|^\/ue\//;
|
||||||
const updateHooksRE = /^\/hooks\/update-paypal$|^\/hooks\/update-stripe$/;
|
const updateHooksRE = /^\/hooks\/update-paypal$/;
|
||||||
const createStripeSession = /^\/donate\/create-stripe-session/;
|
|
||||||
|
|
||||||
// note: this would be replaced by webhooks later
|
|
||||||
const donateRE = /^\/donate\/charge-stripe$/;
|
|
||||||
|
|
||||||
const _pathsAllowedREs = [
|
const _pathsAllowedREs = [
|
||||||
authRE,
|
authRE,
|
||||||
@ -41,9 +37,7 @@ const _pathsAllowedREs = [
|
|||||||
statusRE,
|
statusRE,
|
||||||
unsubscribedRE,
|
unsubscribedRE,
|
||||||
unsubscribeRE,
|
unsubscribeRE,
|
||||||
updateHooksRE,
|
updateHooksRE
|
||||||
donateRE,
|
|
||||||
createStripeSession
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export function isAllowedPath(path, pathsAllowedREs = _pathsAllowedREs) {
|
export function isAllowedPath(path, pathsAllowedREs = _pathsAllowedREs) {
|
||||||
|
@ -47,7 +47,7 @@ describe('request-authorization', () => {
|
|||||||
const statusRE = /^\/status\/ping$/;
|
const statusRE = /^\/status\/ping$/;
|
||||||
const unsubscribedRE = /^\/unsubscribed\//;
|
const unsubscribedRE = /^\/unsubscribed\//;
|
||||||
const unsubscribeRE = /^\/u\/|^\/unsubscribe\/|^\/ue\//;
|
const unsubscribeRE = /^\/u\/|^\/unsubscribe\/|^\/ue\//;
|
||||||
const updateHooksRE = /^\/hooks\/update-paypal$|^\/hooks\/update-stripe$/;
|
const updateHooksRE = /^\/hooks\/update-paypal$/;
|
||||||
|
|
||||||
const allowedPathsList = [
|
const allowedPathsList = [
|
||||||
authRE,
|
authRE,
|
||||||
@ -79,11 +79,9 @@ describe('request-authorization', () => {
|
|||||||
allowedPathsList
|
allowedPathsList
|
||||||
);
|
);
|
||||||
const resultC = isAllowedPath('/hooks/update-paypal', allowedPathsList);
|
const resultC = isAllowedPath('/hooks/update-paypal', allowedPathsList);
|
||||||
const resultD = isAllowedPath('/hooks/update-stripe', allowedPathsList);
|
|
||||||
expect(resultA).toBe(true);
|
expect(resultA).toBe(true);
|
||||||
expect(resultB).toBe(true);
|
expect(resultB).toBe(true);
|
||||||
expect(resultC).toBe(true);
|
expect(resultC).toBe(true);
|
||||||
expect(resultD).toBe(true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns false for a non-white-listed path', () => {
|
it('returns false for a non-white-listed path', () => {
|
||||||
|
@ -9,7 +9,6 @@ ARG FORUM_LOCATION
|
|||||||
ARG NEWS_LOCATION
|
ARG NEWS_LOCATION
|
||||||
ARG CLIENT_LOCALE
|
ARG CLIENT_LOCALE
|
||||||
ARG CURRICULUM_LOCALE
|
ARG CURRICULUM_LOCALE
|
||||||
ARG STRIPE_PUBLIC_KEY
|
|
||||||
ARG ALGOLIA_APP_ID
|
ARG ALGOLIA_APP_ID
|
||||||
ARG ALGOLIA_API_KEY
|
ARG ALGOLIA_API_KEY
|
||||||
ARG PAYPAL_CLIENT_ID
|
ARG PAYPAL_CLIENT_ID
|
||||||
|
@ -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;
|
const { createPage } = actions;
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -186,8 +177,7 @@ exports.onCreateWebpackConfig = ({ stage, plugins, actions }) => {
|
|||||||
plugins.define({
|
plugins.define({
|
||||||
HOME_PATH: JSON.stringify(
|
HOME_PATH: JSON.stringify(
|
||||||
process.env.HOME_PATH || 'http://localhost:3000'
|
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
|
// We add the shims of the node globals to the global scope
|
||||||
new webpack.ProvidePlugin({
|
new webpack.ProvidePlugin({
|
||||||
|
@ -297,6 +297,7 @@
|
|||||||
"help-more": "Help us do more",
|
"help-more": "Help us do more",
|
||||||
"error": "Something went wrong with your donation.",
|
"error": "Something went wrong with your donation.",
|
||||||
"free-tech": "Your donations will support free technology education for people all over the world.",
|
"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-frequency": "Select gift frequency:",
|
||||||
"gift-amount": "Select gift amount:",
|
"gift-amount": "Select gift amount:",
|
||||||
"confirm": "Confirm your donation",
|
"confirm": "Confirm your donation",
|
||||||
|
8
client/package-lock.json
generated
8
client/package-lock.json
generated
@ -3224,14 +3224,6 @@
|
|||||||
"@sinonjs/commons": "^1.7.0"
|
"@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": {
|
"@szmarczak/http-timer": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz",
|
||||||
|
@ -52,7 +52,6 @@
|
|||||||
"@freecodecamp/react-calendar-heatmap": "1.0.0",
|
"@freecodecamp/react-calendar-heatmap": "1.0.0",
|
||||||
"@loadable/component": "5.14.1",
|
"@loadable/component": "5.14.1",
|
||||||
"@reach/router": "1.3.4",
|
"@reach/router": "1.3.4",
|
||||||
"@stripe/react-stripe-js": "1.4.0",
|
|
||||||
"algoliasearch": "3.35.1",
|
"algoliasearch": "3.35.1",
|
||||||
"assert": "2.0.0",
|
"assert": "2.0.0",
|
||||||
"axios": "0.21.1",
|
"axios": "0.21.1",
|
||||||
|
@ -168,11 +168,7 @@ const ShowCertification = props => {
|
|||||||
setIsDonationClosed(true);
|
setIsDonationClosed(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleProcessing = (
|
const handleProcessing = (duration, amount, action) => {
|
||||||
duration,
|
|
||||||
amount,
|
|
||||||
action = 'stripe form submission'
|
|
||||||
) => {
|
|
||||||
props.executeGA({
|
props.executeGA({
|
||||||
type: 'event',
|
type: 'event',
|
||||||
data: {
|
data: {
|
||||||
|
@ -49,6 +49,7 @@ function DonateCompletion({
|
|||||||
{success && (
|
{success && (
|
||||||
<div>
|
<div>
|
||||||
<p>{t('donate.free-tech')}</p>
|
<p>{t('donate.free-tech')}</p>
|
||||||
|
<p>{t('donate.no-halo')}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{error && <p>{error}</p>}
|
{error && <p>{error}</p>}
|
||||||
|
@ -3,9 +3,7 @@ import React, { Component } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { Elements } from '@stripe/react-stripe-js';
|
|
||||||
import {
|
import {
|
||||||
Button,
|
|
||||||
Col,
|
Col,
|
||||||
Row,
|
Row,
|
||||||
Tab,
|
Tab,
|
||||||
@ -20,23 +18,16 @@ import {
|
|||||||
durationsConfig,
|
durationsConfig,
|
||||||
defaultAmount,
|
defaultAmount,
|
||||||
defaultDonation,
|
defaultDonation,
|
||||||
donationUrls,
|
|
||||||
modalDefaultDonation
|
modalDefaultDonation
|
||||||
} from '../../../../config/donation-settings';
|
} from '../../../../config/donation-settings';
|
||||||
import envData from '../../../../config/env.json';
|
|
||||||
import { stripeScriptLoader } from '../../utils/scriptLoaders';
|
|
||||||
import Spacer from '../helpers/Spacer';
|
import Spacer from '../helpers/Spacer';
|
||||||
import PaypalButton from './PaypalButton';
|
import PaypalButton from './PaypalButton';
|
||||||
import DonateCompletion from './DonateCompletion';
|
import DonateCompletion from './DonateCompletion';
|
||||||
import StripeCardForm from './StripeCardForm';
|
|
||||||
import {
|
import {
|
||||||
isSignedInSelector,
|
isSignedInSelector,
|
||||||
signInLoadingSelector,
|
signInLoadingSelector,
|
||||||
donationFormStateSelector,
|
donationFormStateSelector,
|
||||||
hardGoTo as navigate,
|
|
||||||
addDonation,
|
addDonation,
|
||||||
createStripeSession,
|
|
||||||
postChargeStripe,
|
|
||||||
updateDonationFormState,
|
updateDonationFormState,
|
||||||
defaultDonationFormState,
|
defaultDonationFormState,
|
||||||
userSelector
|
userSelector
|
||||||
@ -44,14 +35,11 @@ import {
|
|||||||
|
|
||||||
import './Donation.css';
|
import './Donation.css';
|
||||||
|
|
||||||
const { stripePublicKey } = envData;
|
|
||||||
|
|
||||||
const numToCommas = num =>
|
const numToCommas = num =>
|
||||||
num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
|
num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
addDonation: PropTypes.func,
|
addDonation: PropTypes.func,
|
||||||
createStripeSession: PropTypes.func,
|
|
||||||
defaultTheme: PropTypes.string,
|
defaultTheme: PropTypes.string,
|
||||||
donationFormState: PropTypes.object,
|
donationFormState: PropTypes.object,
|
||||||
email: PropTypes.string,
|
email: PropTypes.string,
|
||||||
@ -59,8 +47,6 @@ const propTypes = {
|
|||||||
isDonating: PropTypes.bool,
|
isDonating: PropTypes.bool,
|
||||||
isMinimalForm: PropTypes.bool,
|
isMinimalForm: PropTypes.bool,
|
||||||
isSignedIn: PropTypes.bool,
|
isSignedIn: PropTypes.bool,
|
||||||
navigate: PropTypes.func.isRequired,
|
|
||||||
postChargeStripe: PropTypes.func.isRequired,
|
|
||||||
showLoading: PropTypes.bool.isRequired,
|
showLoading: PropTypes.bool.isRequired,
|
||||||
t: PropTypes.func.isRequired,
|
t: PropTypes.func.isRequired,
|
||||||
theme: PropTypes.string,
|
theme: PropTypes.string,
|
||||||
@ -83,10 +69,7 @@ const mapStateToProps = createSelector(
|
|||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
addDonation,
|
addDonation,
|
||||||
navigate,
|
updateDonationFormState
|
||||||
postChargeStripe,
|
|
||||||
updateDonationFormState,
|
|
||||||
createStripeSession
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class DonateForm extends Component {
|
class DonateForm extends Component {
|
||||||
@ -102,63 +85,26 @@ class DonateForm extends Component {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
...initialAmountAndDuration,
|
...initialAmountAndDuration,
|
||||||
processing: false,
|
processing: false
|
||||||
stripe: null
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.handleStripeLoad = this.handleStripeLoad.bind(this);
|
|
||||||
this.onDonationStateChange = this.onDonationStateChange.bind(this);
|
this.onDonationStateChange = this.onDonationStateChange.bind(this);
|
||||||
this.getActiveDonationAmount = this.getActiveDonationAmount.bind(this);
|
this.getActiveDonationAmount = this.getActiveDonationAmount.bind(this);
|
||||||
this.getDonationButtonLabel = this.getDonationButtonLabel.bind(this);
|
this.getDonationButtonLabel = this.getDonationButtonLabel.bind(this);
|
||||||
this.handleSelectAmount = this.handleSelectAmount.bind(this);
|
this.handleSelectAmount = this.handleSelectAmount.bind(this);
|
||||||
this.handleSelectDuration = this.handleSelectDuration.bind(this);
|
this.handleSelectDuration = this.handleSelectDuration.bind(this);
|
||||||
this.handleStripeCheckoutRedirect = this.handleStripeCheckoutRedirect.bind(
|
|
||||||
this
|
|
||||||
);
|
|
||||||
this.hideAmountOptionsCB = this.hideAmountOptionsCB.bind(this);
|
this.hideAmountOptionsCB = this.hideAmountOptionsCB.bind(this);
|
||||||
this.resetDonation = this.resetDonation.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() {
|
componentWillUnmount() {
|
||||||
const stripeMountPoint = document.querySelector('#stripe-js');
|
|
||||||
if (stripeMountPoint) {
|
|
||||||
stripeMountPoint.removeEventListener('load', this.handleStripeLoad);
|
|
||||||
}
|
|
||||||
this.resetDonation();
|
this.resetDonation();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleStripeLoad() {
|
|
||||||
// Create Stripe instance once Stripe.js loads
|
|
||||||
if (stripePublicKey) {
|
|
||||||
this.setState(state => ({
|
|
||||||
...state,
|
|
||||||
stripe: window.Stripe(stripePublicKey)
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onDonationStateChange(donationState) {
|
onDonationStateChange(donationState) {
|
||||||
// scroll to top
|
// scroll to top
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
|
|
||||||
this.props.updateDonationFormState(donationState);
|
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) {
|
getActiveDonationAmount(durationSelected, amountSelected) {
|
||||||
@ -204,41 +150,6 @@ class DonateForm extends Component {
|
|||||||
this.setState({ donationAmount });
|
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) {
|
renderAmountButtons(duration) {
|
||||||
return this.amounts[duration].map(amount => (
|
return this.amounts[duration].map(amount => (
|
||||||
<ToggleButton
|
<ToggleButton
|
||||||
@ -318,7 +229,14 @@ class DonateForm extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderDonationOptions() {
|
renderDonationOptions() {
|
||||||
const { handleProcessing, isSignedIn, addDonation, t } = this.props;
|
const {
|
||||||
|
handleProcessing,
|
||||||
|
isSignedIn,
|
||||||
|
addDonation,
|
||||||
|
t,
|
||||||
|
defaultTheme,
|
||||||
|
theme
|
||||||
|
} = this.props;
|
||||||
const { donationAmount, donationDuration } = this.state;
|
const { donationAmount, donationDuration } = this.state;
|
||||||
|
|
||||||
const isOneTime = donationDuration === 'onetime';
|
const isOneTime = donationDuration === 'onetime';
|
||||||
@ -334,15 +252,6 @@ class DonateForm extends Component {
|
|||||||
)}
|
)}
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<div className='donate-btn-group'>
|
<div className='donate-btn-group'>
|
||||||
<Button
|
|
||||||
block={true}
|
|
||||||
bsStyle='primary'
|
|
||||||
id='confirm-donation-btn'
|
|
||||||
onClick={e => this.handleStripeCheckoutRedirect(e, 'credit card')}
|
|
||||||
>
|
|
||||||
{}
|
|
||||||
<b>{t('donate.credit-card')} </b>
|
|
||||||
</Button>
|
|
||||||
<PaypalButton
|
<PaypalButton
|
||||||
addDonation={addDonation}
|
addDonation={addDonation}
|
||||||
donationAmount={donationAmount}
|
donationAmount={donationAmount}
|
||||||
@ -351,6 +260,7 @@ class DonateForm extends Component {
|
|||||||
isSubscription={isOneTime ? false : true}
|
isSubscription={isOneTime ? false : true}
|
||||||
onDonationStateChange={this.onDonationStateChange}
|
onDonationStateChange={this.onDonationStateChange}
|
||||||
skipAddDonation={!isSignedIn}
|
skipAddDonation={!isSignedIn}
|
||||||
|
theme={defaultTheme ? defaultTheme : theme}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -366,22 +276,13 @@ class DonateForm extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderModalForm() {
|
renderModalForm() {
|
||||||
const { donationAmount, donationDuration, stripe } = this.state;
|
const { donationAmount, donationDuration } = this.state;
|
||||||
const {
|
const { handleProcessing, addDonation, defaultTheme, theme } = this.props;
|
||||||
handleProcessing,
|
|
||||||
addDonation,
|
|
||||||
email,
|
|
||||||
theme,
|
|
||||||
t,
|
|
||||||
defaultTheme
|
|
||||||
} = this.props;
|
|
||||||
return (
|
return (
|
||||||
<Row>
|
<Row>
|
||||||
<Col lg={8} lgOffset={2} sm={10} smOffset={1} xs={12}>
|
<Col lg={8} lgOffset={2} sm={10} smOffset={1} xs={12}>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<b>
|
<b>{this.getDonationButtonLabel()}:</b>
|
||||||
{this.getDonationButtonLabel()} {t('donate.paypal')}
|
|
||||||
</b>
|
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<PaypalButton
|
<PaypalButton
|
||||||
addDonation={addDonation}
|
addDonation={addDonation}
|
||||||
@ -389,21 +290,8 @@ class DonateForm extends Component {
|
|||||||
donationDuration={donationDuration}
|
donationDuration={donationDuration}
|
||||||
handleProcessing={handleProcessing}
|
handleProcessing={handleProcessing}
|
||||||
onDonationStateChange={this.onDonationStateChange}
|
onDonationStateChange={this.onDonationStateChange}
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
<Col lg={8} lgOffset={2} sm={10} smOffset={1} xs={12}>
|
|
||||||
<Spacer />
|
|
||||||
<b>{t('donate.credit-card-2')}</b>
|
|
||||||
<Spacer />
|
|
||||||
<Elements stripe={stripe}>
|
|
||||||
<StripeCardForm
|
|
||||||
getDonationButtonLabel={this.getDonationButtonLabel}
|
|
||||||
onDonationStateChange={this.onDonationStateChange}
|
|
||||||
postStripeDonation={this.postStripeDonation}
|
|
||||||
theme={defaultTheme ? defaultTheme : theme}
|
theme={defaultTheme ? defaultTheme : theme}
|
||||||
userEmail={email}
|
|
||||||
/>
|
/>
|
||||||
</Elements>
|
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
|
@ -48,6 +48,10 @@
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.paypal-buttons-container {
|
||||||
|
min-height: 142px;
|
||||||
|
}
|
||||||
|
|
||||||
.donate-input-element {
|
.donate-input-element {
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
}
|
}
|
||||||
@ -56,8 +60,7 @@
|
|||||||
color: #707070;
|
color: #707070;
|
||||||
}
|
}
|
||||||
|
|
||||||
.donation-form .form-control:focus,
|
.donation-form .form-control:focus {
|
||||||
.StripeElement--focus {
|
|
||||||
border-color: #66afe9;
|
border-color: #66afe9;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
|
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.form-control:focus,
|
||||||
.donation-form .email--invalid,
|
.donation-form .email--invalid,
|
||||||
.donation-form .StripeElement--invalid {
|
.donation-form {
|
||||||
border-color: #eb1c26;
|
border-color: #eb1c26;
|
||||||
color: #eb1c26;
|
color: #eb1c26;
|
||||||
}
|
}
|
||||||
@ -383,7 +386,8 @@ button#confirm-donation-btn:hover {
|
|||||||
|
|
||||||
.donate-btn-group {
|
.donate-btn-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.donate-btn-group > * {
|
.donate-btn-group > * {
|
||||||
|
@ -55,11 +55,7 @@ function DonateModal({
|
|||||||
}) {
|
}) {
|
||||||
const [closeLabel, setCloseLabel] = React.useState(false);
|
const [closeLabel, setCloseLabel] = React.useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const handleProcessing = (
|
const handleProcessing = (duration, amount, action) => {
|
||||||
duration,
|
|
||||||
amount,
|
|
||||||
action = 'stripe form submission'
|
|
||||||
) => {
|
|
||||||
executeGA({
|
executeGA({
|
||||||
type: 'event',
|
type: 'event',
|
||||||
data: {
|
data: {
|
||||||
|
@ -25,7 +25,10 @@ export class PayPalButtonScriptLoader extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
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
|
// eslint-disable-next-line react/no-did-update-set-state
|
||||||
this.setState({ isSdkLoaded: false });
|
this.setState({ isSdkLoaded: false });
|
||||||
this.loadScript(this.state.isSubscription, true);
|
this.loadScript(this.state.isSubscription, true);
|
||||||
@ -34,7 +37,7 @@ export class PayPalButtonScriptLoader extends Component {
|
|||||||
|
|
||||||
loadScript(subscription, deleteScript) {
|
loadScript(subscription, deleteScript) {
|
||||||
if (deleteScript) scriptRemover('paypal-sdk');
|
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';
|
if (subscription) queries += '&vault=true&intent=subscription';
|
||||||
|
|
||||||
scriptLoader(
|
scriptLoader(
|
||||||
|
@ -58,14 +58,15 @@ export class PaypalButton extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { duration, planId, amount } = this.state;
|
const { duration, planId, amount } = this.state;
|
||||||
const { t } = this.props;
|
const { t, theme } = this.props;
|
||||||
const isSubscription = duration !== 'onetime';
|
const isSubscription = duration !== 'onetime';
|
||||||
|
const buttonColor = theme === 'night' ? 'white' : 'gold';
|
||||||
if (!paypalClientId) {
|
if (!paypalClientId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className={'paypal-buttons-container'}>
|
||||||
<PayPalButtonScriptLoader
|
<PayPalButtonScriptLoader
|
||||||
amount={amount}
|
amount={amount}
|
||||||
clientId={paypalClientId}
|
clientId={paypalClientId}
|
||||||
@ -107,9 +108,11 @@ export class PaypalButton extends Component {
|
|||||||
plantId={planId}
|
plantId={planId}
|
||||||
style={{
|
style={{
|
||||||
tagline: false,
|
tagline: false,
|
||||||
height: 43
|
height: 43,
|
||||||
|
color: buttonColor
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,7 +125,8 @@ const propTypes = {
|
|||||||
isDonating: PropTypes.bool,
|
isDonating: PropTypes.bool,
|
||||||
onDonationStateChange: PropTypes.func,
|
onDonationStateChange: PropTypes.func,
|
||||||
skipAddDonation: PropTypes.bool,
|
skipAddDonation: PropTypes.bool,
|
||||||
t: PropTypes.func.isRequired
|
t: PropTypes.func.isRequired,
|
||||||
|
theme: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = createSelector(
|
const mapStateToProps = createSelector(
|
||||||
|
@ -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 = <p>{t('donate.valid-info')}</p>;
|
|
||||||
else if (!isEmailValid) message = <p>{t('donate.valid-email')}</p>;
|
|
||||||
else message = <p>{t('donate.valid-card')}</p>;
|
|
||||||
return <Alert bsStyle='danger'>{message}</Alert>;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form className='donation-form' onSubmit={handleSubmit}>
|
|
||||||
<div>{!isSubmissionValid ? renderErrorMessage() : ''}</div>
|
|
||||||
<FormGroup className='donation-email-container'>
|
|
||||||
<ControlLabel>{t('donate.email-receipt')}</ControlLabel>
|
|
||||||
<FormControl
|
|
||||||
className={!isEmailValid && email ? 'email--invalid' : ''}
|
|
||||||
key='3'
|
|
||||||
onBlur={handleEmailBlur}
|
|
||||||
onChange={handleEmailChange}
|
|
||||||
placeholder='me@example.com'
|
|
||||||
required={true}
|
|
||||||
type='text'
|
|
||||||
value={email || ''}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
<div className='donation-elements'>
|
|
||||||
<FormGroup>
|
|
||||||
<ControlLabel>{t('donate.card-number')}</ControlLabel>
|
|
||||||
<CardNumberElement
|
|
||||||
className='form-control donate-input-element'
|
|
||||||
onChange={handleInputChange}
|
|
||||||
options={options}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
<FormGroup>
|
|
||||||
<ControlLabel>{t('donate.expiration')}</ControlLabel>
|
|
||||||
<Row>
|
|
||||||
<Col md={5} xs={12}>
|
|
||||||
<CardExpiryElement
|
|
||||||
className='form-control donate-input-element'
|
|
||||||
onChange={handleInputChange}
|
|
||||||
options={options}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
<Col className='form-payments-wrapper' md={7} xs={12}>
|
|
||||||
<Image
|
|
||||||
alt='payment options'
|
|
||||||
className='form-payment-methods'
|
|
||||||
src={
|
|
||||||
'https://cdn.freecodecamp.org' +
|
|
||||||
'/platform/universal/form-payments.png'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</FormGroup>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
block={true}
|
|
||||||
bsStyle='primary'
|
|
||||||
disabled={!stripe}
|
|
||||||
id='confirm-donation-btn'
|
|
||||||
type='submit'
|
|
||||||
>
|
|
||||||
{getDonationButtonLabel()}
|
|
||||||
</Button>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
StripeCardForm.displayName = 'StripeCardForm';
|
|
||||||
StripeCardForm.propTypes = propTypes;
|
|
||||||
|
|
||||||
export default withTranslation()(StripeCardForm);
|
|
@ -2,7 +2,6 @@ import React from 'react';
|
|||||||
import Helmet from 'react-helmet';
|
import Helmet from 'react-helmet';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import stripeObserver from './stripeIframesFix';
|
|
||||||
import UniversalNav from './components/UniversalNav';
|
import UniversalNav from './components/UniversalNav';
|
||||||
|
|
||||||
import './header.css';
|
import './header.css';
|
||||||
@ -26,10 +25,6 @@ export class Header extends React.Component {
|
|||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
document.addEventListener('click', this.handleClickOutside);
|
document.addEventListener('click', this.handleClickOutside);
|
||||||
|
|
||||||
// Remove stacking Stripe iframes with each navigation
|
|
||||||
// after visiting /donate
|
|
||||||
stripeObserver();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -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;
|
|
@ -62,7 +62,7 @@ class DonatePage extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleProcessing(duration, amount, action = 'stripe button click') {
|
handleProcessing(duration, amount, action) {
|
||||||
this.props.executeGA({
|
this.props.executeGA({
|
||||||
type: 'event',
|
type: 'event',
|
||||||
data: {
|
data: {
|
||||||
|
@ -1,12 +1,4 @@
|
|||||||
import {
|
import { put, select, takeEvery, delay, call, take } from 'redux-saga/effects';
|
||||||
put,
|
|
||||||
select,
|
|
||||||
takeEvery,
|
|
||||||
takeLeading,
|
|
||||||
delay,
|
|
||||||
call,
|
|
||||||
take
|
|
||||||
} from 'redux-saga/effects';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
openDonationModal,
|
openDonationModal,
|
||||||
@ -16,16 +8,10 @@ import {
|
|||||||
recentlyClaimedBlockSelector,
|
recentlyClaimedBlockSelector,
|
||||||
addDonationComplete,
|
addDonationComplete,
|
||||||
addDonationError,
|
addDonationError,
|
||||||
postChargeStripeComplete,
|
|
||||||
postChargeStripeError,
|
|
||||||
types as appTypes
|
types as appTypes
|
||||||
} from './';
|
} from './';
|
||||||
|
|
||||||
import {
|
import { addDonation } from '../utils/ajax';
|
||||||
addDonation,
|
|
||||||
postChargeStripe,
|
|
||||||
postCreateStripeSession
|
|
||||||
} from '../utils/ajax';
|
|
||||||
|
|
||||||
const defaultDonationError = `Something is not right. Please contact donors@freecodecamp.org`;
|
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) {
|
export function createDonationSaga(types) {
|
||||||
return [
|
return [
|
||||||
takeEvery(types.tryToShowDonationModal, showDonateModalSaga),
|
takeEvery(types.tryToShowDonationModal, showDonateModalSaga),
|
||||||
takeEvery(types.addDonation, addDonationSaga),
|
takeEvery(types.addDonation, addDonationSaga)
|
||||||
takeLeading(types.postChargeStripe, postChargeStripeSaga),
|
|
||||||
takeLeading(types.createStripeSession, createStripeSessionSaga)
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -82,8 +82,6 @@ export const types = createTypes(
|
|||||||
'updateDonationFormState',
|
'updateDonationFormState',
|
||||||
...createAsyncTypes('fetchUser'),
|
...createAsyncTypes('fetchUser'),
|
||||||
...createAsyncTypes('addDonation'),
|
...createAsyncTypes('addDonation'),
|
||||||
...createAsyncTypes('createStripeSession'),
|
|
||||||
...createAsyncTypes('postChargeStripe'),
|
|
||||||
...createAsyncTypes('fetchProfileForUser'),
|
...createAsyncTypes('fetchProfileForUser'),
|
||||||
...createAsyncTypes('acceptTerms'),
|
...createAsyncTypes('acceptTerms'),
|
||||||
...createAsyncTypes('showCert'),
|
...createAsyncTypes('showCert'),
|
||||||
@ -152,14 +150,6 @@ export const addDonation = createAction(types.addDonation);
|
|||||||
export const addDonationComplete = createAction(types.addDonationComplete);
|
export const addDonationComplete = createAction(types.addDonationComplete);
|
||||||
export const addDonationError = createAction(types.addDonationError);
|
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 fetchProfileForUser = createAction(types.fetchProfileForUser);
|
||||||
export const fetchProfileForUserComplete = createAction(
|
export const fetchProfileForUserComplete = createAction(
|
||||||
types.fetchProfileForUserComplete
|
types.fetchProfileForUserComplete
|
||||||
@ -404,10 +394,6 @@ export const reducer = handleActions(
|
|||||||
...state,
|
...state,
|
||||||
donationFormState: { ...state.donationFormState, ...payload }
|
donationFormState: { ...state.donationFormState, ...payload }
|
||||||
}),
|
}),
|
||||||
[types.createStripeSession]: state => ({
|
|
||||||
...state,
|
|
||||||
donationFormState: { ...defaultDonationFormState, redirecting: true }
|
|
||||||
}),
|
|
||||||
[types.addDonation]: state => ({
|
[types.addDonation]: state => ({
|
||||||
...state,
|
...state,
|
||||||
donationFormState: { ...defaultDonationFormState, processing: true }
|
donationFormState: { ...defaultDonationFormState, processing: true }
|
||||||
@ -431,29 +417,6 @@ export const reducer = handleActions(
|
|||||||
...state,
|
...state,
|
||||||
donationFormState: { ...defaultDonationFormState, error: payload }
|
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 => ({
|
[types.fetchUser]: state => ({
|
||||||
...state,
|
...state,
|
||||||
userFetchState: { ...defaultFetchState }
|
userFetchState: { ...defaultFetchState }
|
||||||
|
@ -60,18 +60,11 @@ export function getArticleById(shortId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** POST **/
|
/** POST **/
|
||||||
export function postChargeStripe(body) {
|
|
||||||
return post('/donate/charge-stripe', body);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function addDonation(body) {
|
export function addDonation(body) {
|
||||||
return post('/donate/add-donation', body);
|
return post('/donate/add-donation', body);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function postCreateStripeSession(body) {
|
|
||||||
return post('/donate/create-stripe-session', body);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function putUpdateLegacyCert(body) {
|
export function putUpdateLegacyCert(body) {
|
||||||
return post('/update-my-projects', body);
|
return post('/update-my-projects', body);
|
||||||
}
|
}
|
||||||
|
@ -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 = () =>
|
export const mathJaxScriptLoader = () =>
|
||||||
scriptLoader(
|
scriptLoader(
|
||||||
'mathjax',
|
'mathjax',
|
||||||
|
@ -109,8 +109,6 @@ export const injectConditionalTags = (tagsArray, homeLocation) => {
|
|||||||
|
|
||||||
export const getPostBodyComponents = pathname => {
|
export const getPostBodyComponents = pathname => {
|
||||||
let scripts = [];
|
let scripts = [];
|
||||||
const challengesPathRE = new RegExp('/learn/[^/]+/[^/]+/[^/]+/?$');
|
|
||||||
const donatePathRE = new RegExp('/donate/?$');
|
|
||||||
const mathJaxScriptElement = (
|
const mathJaxScriptElement = (
|
||||||
<script
|
<script
|
||||||
async={false}
|
async={false}
|
||||||
@ -120,15 +118,6 @@ export const getPostBodyComponents = pathname => {
|
|||||||
type='text/javascript'
|
type='text/javascript'
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const stripeScriptElement = (
|
|
||||||
<script
|
|
||||||
async={true}
|
|
||||||
id='stripe-js'
|
|
||||||
key='stripe-js'
|
|
||||||
src='https://js.stripe.com/v3/'
|
|
||||||
type='text/javascript'
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
pathname.includes('/learn/coding-interview-prep/rosetta-code') ||
|
pathname.includes('/learn/coding-interview-prep/rosetta-code') ||
|
||||||
@ -136,8 +125,6 @@ export const getPostBodyComponents = pathname => {
|
|||||||
) {
|
) {
|
||||||
scripts.push(mathJaxScriptElement);
|
scripts.push(mathJaxScriptElement);
|
||||||
}
|
}
|
||||||
if (challengesPathRE.test(pathname) || donatePathRE.test(pathname)) {
|
|
||||||
scripts.push(stripeScriptElement);
|
|
||||||
}
|
|
||||||
return scripts.filter(Boolean);
|
return scripts.filter(Boolean);
|
||||||
};
|
};
|
||||||
|
@ -21,7 +21,6 @@ const {
|
|||||||
CLIENT_LOCALE: clientLocale,
|
CLIENT_LOCALE: clientLocale,
|
||||||
CURRICULUM_LOCALE: curriculumLocale,
|
CURRICULUM_LOCALE: curriculumLocale,
|
||||||
SHOW_LOCALE_DROPDOWN_MENU: showLocaleDropdownMenu,
|
SHOW_LOCALE_DROPDOWN_MENU: showLocaleDropdownMenu,
|
||||||
STRIPE_PUBLIC_KEY: stripePublicKey,
|
|
||||||
ALGOLIA_APP_ID: algoliaAppId,
|
ALGOLIA_APP_ID: algoliaAppId,
|
||||||
ALGOLIA_API_KEY: algoliaAPIKey,
|
ALGOLIA_API_KEY: algoliaAPIKey,
|
||||||
PAYPAL_CLIENT_ID: paypalClientId,
|
PAYPAL_CLIENT_ID: paypalClientId,
|
||||||
@ -45,10 +44,6 @@ module.exports = Object.assign(locations, {
|
|||||||
showLocaleDropdownMenu: showLocaleDropdownMenu === 'true',
|
showLocaleDropdownMenu: showLocaleDropdownMenu === 'true',
|
||||||
deploymentEnv,
|
deploymentEnv,
|
||||||
environment: process.env.FREECODECAMP_NODE_ENV || 'development',
|
environment: process.env.FREECODECAMP_NODE_ENV || 'development',
|
||||||
stripePublicKey:
|
|
||||||
!stripePublicKey || stripePublicKey === 'pk_from_stripe_dashboard'
|
|
||||||
? null
|
|
||||||
: stripePublicKey,
|
|
||||||
algoliaAppId:
|
algoliaAppId:
|
||||||
!algoliaAppId || algoliaAppId === 'app_id_from_algolia_dashboard'
|
!algoliaAppId || algoliaAppId === 'app_id_from_algolia_dashboard'
|
||||||
? null
|
? null
|
||||||
|
@ -29,9 +29,6 @@ const {
|
|||||||
|
|
||||||
SENTRY_DSN,
|
SENTRY_DSN,
|
||||||
|
|
||||||
STRIPE_PUBLIC_KEY,
|
|
||||||
STRIPE_SECRET_KEY,
|
|
||||||
|
|
||||||
PAYPAL_CLIENT_ID,
|
PAYPAL_CLIENT_ID,
|
||||||
PAYPAL_SECRET,
|
PAYPAL_SECRET,
|
||||||
PAYPAL_VERIFY_WEBHOOK_URL,
|
PAYPAL_VERIFY_WEBHOOK_URL,
|
||||||
@ -95,11 +92,6 @@ module.exports = {
|
|||||||
dns: SENTRY_DSN
|
dns: SENTRY_DSN
|
||||||
},
|
},
|
||||||
|
|
||||||
stripe: {
|
|
||||||
public: STRIPE_PUBLIC_KEY,
|
|
||||||
secret: STRIPE_SECRET_KEY
|
|
||||||
},
|
|
||||||
|
|
||||||
paypal: {
|
paypal: {
|
||||||
client: PAYPAL_CLIENT_ID,
|
client: PAYPAL_CLIENT_ID,
|
||||||
secret: PAYPAL_SECRET,
|
secret: PAYPAL_SECRET,
|
||||||
|
@ -18,10 +18,6 @@ describe('The Document Metadata', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const scripts = {
|
const scripts = {
|
||||||
stripe: {
|
|
||||||
selector: 'body script[id="stripe-js"]',
|
|
||||||
src: 'https://js.stripe.com/v3/'
|
|
||||||
},
|
|
||||||
mathjax: {
|
mathjax: {
|
||||||
selector: 'body script[id="mathjax"]',
|
selector: 'body script[id="mathjax"]',
|
||||||
src:
|
src:
|
||||||
@ -63,10 +59,6 @@ describe('The Document Metadata', () => {
|
|||||||
social.description
|
social.description
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('landing page should not have stripe body script', () => {
|
|
||||||
cy.reload();
|
|
||||||
cy.get(scripts.stripe.selector).should('not.exist');
|
|
||||||
});
|
|
||||||
it('landing page should not have mathjax body script', () => {
|
it('landing page should not have mathjax body script', () => {
|
||||||
cy.reload();
|
cy.reload();
|
||||||
cy.get(scripts.mathjax.selector).should('not.exist');
|
cy.get(scripts.mathjax.selector).should('not.exist');
|
||||||
@ -76,24 +68,6 @@ describe('The Document Metadata', () => {
|
|||||||
cy.reload();
|
cy.reload();
|
||||||
cy.get(scripts.mathjax.selector).should('not.exist');
|
cy.get(scripts.mathjax.selector).should('not.exist');
|
||||||
});
|
});
|
||||||
it('donate page should have stripe body script', () => {
|
|
||||||
cy.visit('/donate');
|
|
||||||
cy.reload();
|
|
||||||
cy.get(scripts.stripe.selector).should(
|
|
||||||
'have.attr',
|
|
||||||
'src',
|
|
||||||
scripts.stripe.src
|
|
||||||
);
|
|
||||||
});
|
|
||||||
it('responsive webdesign challenges should have stripe body script', () => {
|
|
||||||
cy.visit(challengs.responsiveWebDesign);
|
|
||||||
cy.reload();
|
|
||||||
cy.get(scripts.stripe.selector).should(
|
|
||||||
'have.attr',
|
|
||||||
'src',
|
|
||||||
scripts.stripe.src
|
|
||||||
);
|
|
||||||
});
|
|
||||||
it('project euler challenges should have mathjax body script', () => {
|
it('project euler challenges should have mathjax body script', () => {
|
||||||
cy.visit(challengs.projectEuler);
|
cy.visit(challengs.projectEuler);
|
||||||
cy.reload();
|
cy.reload();
|
||||||
|
@ -31,11 +31,6 @@ ALGOLIA_API_KEY=api_key_from_algolia_dashboard
|
|||||||
# Donations
|
# Donations
|
||||||
# ---------------------
|
# ---------------------
|
||||||
|
|
||||||
# Stripe
|
|
||||||
STRIPE_CREATE_PLANS=true
|
|
||||||
STRIPE_PUBLIC_KEY=pk_from_stripe_dashboard
|
|
||||||
STRIPE_SECRET_KEY=sk_from_stripe_dashboard
|
|
||||||
|
|
||||||
# PayPal
|
# PayPal
|
||||||
PAYPAL_CLIENT_ID=id_from_paypal_dashboard
|
PAYPAL_CLIENT_ID=id_from_paypal_dashboard
|
||||||
PAYPAL_SECRET=secret_from_paypal_dashboard
|
PAYPAL_SECRET=secret_from_paypal_dashboard
|
||||||
|
@ -45,7 +45,7 @@ if (FREECODECAMP_NODE_ENV !== 'development') {
|
|||||||
'showUpcomingChanges'
|
'showUpcomingChanges'
|
||||||
];
|
];
|
||||||
const searchKeys = ['algoliaAppId', 'algoliaAPIKey'];
|
const searchKeys = ['algoliaAppId', 'algoliaAPIKey'];
|
||||||
const donationKeys = ['stripePublicKey', 'paypalClientId'];
|
const donationKeys = ['paypalClientId'];
|
||||||
|
|
||||||
const expectedVariables = locationKeys.concat(
|
const expectedVariables = locationKeys.concat(
|
||||||
deploymentKeys,
|
deploymentKeys,
|
||||||
|
Reference in New Issue
Block a user