feat: add Stripe card form (#43433)
* eat: add Stripe card form * Apply suggestions from code review Co-authored-by: Shaun Hamilton <shauhami020@gmail.com> * feat: adjust payload and error handling * feat: readjust error handling * Apply suggestions from code review Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> Co-authored-by: Shaun Hamilton <shauhami020@gmail.com> * feat: refactors from comments * feat: prevent submition during processing * feat: redefine isSubmitting Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> * fix: show the proper paypal button on donate page * fix: handle errors from stripe Co-authored-by: Shaun Hamilton <shauhami020@gmail.com> Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> Co-authored-by: moT01 <20648924+moT01@users.noreply.github.com>
This commit is contained in:
@ -1,12 +1,14 @@
|
||||
import debug from 'debug';
|
||||
import Stripe from 'stripe';
|
||||
|
||||
import { donationSubscriptionConfig } from '../../../../config/donation-settings';
|
||||
import keys from '../../../../config/secrets';
|
||||
import {
|
||||
getAsyncPaypalToken,
|
||||
verifyWebHook,
|
||||
updateUser,
|
||||
verifyWebHookType
|
||||
verifyWebHookType,
|
||||
createStripeCardDonation
|
||||
} from '../utils/donation';
|
||||
import { validStripeForm } from '../utils/stripeHelpers';
|
||||
|
||||
@ -26,6 +28,18 @@ export default function donateBoot(app, done) {
|
||||
});
|
||||
}
|
||||
|
||||
async function handleStripeCardDonation(req, res) {
|
||||
return createStripeCardDonation(req, res, stripe, app).catch(err => {
|
||||
if (err.type === 'AlreadyDonatingError')
|
||||
return res.status(402).send({ error: err });
|
||||
if (err.type === 'InvalidRequest')
|
||||
return res.status(400).send({ error: err });
|
||||
return res.status(500).send({
|
||||
error: 'Donation failed due to a server error.'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createStripeDonation(req, res) {
|
||||
const { user, body } = req;
|
||||
|
||||
@ -184,7 +198,6 @@ export default function donateBoot(app, done) {
|
||||
const stripeSecretInvalid = !secKey || secKey === 'sk_from_stripe_dashboard';
|
||||
const stripPublicInvalid =
|
||||
!stripeKey || stripeKey === 'pk_from_stripe_dashboard';
|
||||
|
||||
const paypalSecretInvalid =
|
||||
!paypalKey || paypalKey === 'id_from_paypal_dashboard';
|
||||
const paypalPublicInvalid =
|
||||
@ -201,6 +214,7 @@ export default function donateBoot(app, done) {
|
||||
done();
|
||||
} else {
|
||||
api.post('/charge-stripe', createStripeDonation);
|
||||
api.post('/charge-stripe-card', handleStripeCardDonation);
|
||||
api.post('/add-donation', addDonation);
|
||||
hooks.post('/update-paypal', updatePaypal);
|
||||
donateRouter.use('/donate', api);
|
||||
|
@ -1,6 +1,7 @@
|
||||
/* eslint-disable camelcase */
|
||||
import axios from 'axios';
|
||||
import debug from 'debug';
|
||||
import { donationSubscriptionConfig } from '../../../../config/donation-settings';
|
||||
import keys from '../../../../config/secrets';
|
||||
|
||||
const log = debug('fcc:boot:donate');
|
||||
@ -171,3 +172,79 @@ export async function updateUser(body, app) {
|
||||
type: 'UnsupportedWebhookType'
|
||||
};
|
||||
}
|
||||
|
||||
export async function createStripeCardDonation(req, res, stripe) {
|
||||
const {
|
||||
body: {
|
||||
token: { id: tokenId },
|
||||
amount,
|
||||
duration
|
||||
},
|
||||
user: { name, id: userId, email },
|
||||
user
|
||||
} = req;
|
||||
|
||||
if (!tokenId || !amount || !duration || !name || !userId || !email) {
|
||||
throw {
|
||||
message: 'Request is not valid',
|
||||
type: 'InvalidRequest'
|
||||
};
|
||||
}
|
||||
|
||||
if (user.isDonating && duration !== 'onetime') {
|
||||
throw {
|
||||
message: `User already has active recurring donation(s).`,
|
||||
type: 'AlreadyDonatingError'
|
||||
};
|
||||
}
|
||||
|
||||
let customerId;
|
||||
try {
|
||||
const customer = await stripe.customers.create({
|
||||
email,
|
||||
card: tokenId,
|
||||
name
|
||||
});
|
||||
customerId = customer?.id;
|
||||
} catch {
|
||||
throw {
|
||||
type: 'customerCreationFailed',
|
||||
message: 'Failed to create stripe customer'
|
||||
};
|
||||
}
|
||||
log(`Stripe customer with id ${customerId} created`);
|
||||
|
||||
let subscriptionId;
|
||||
try {
|
||||
const subscription = await stripe.subscriptions.create({
|
||||
customer: customerId,
|
||||
items: [
|
||||
{
|
||||
plan: `${donationSubscriptionConfig.duration[
|
||||
duration
|
||||
].toLowerCase()}-donation-${amount}`
|
||||
}
|
||||
]
|
||||
});
|
||||
subscriptionId = subscription?.id;
|
||||
} catch {
|
||||
throw {
|
||||
type: 'subscriptionCreationFailed',
|
||||
message: 'Failed to create stripe subscription'
|
||||
};
|
||||
}
|
||||
log(`Stripe subscription with id ${subscriptionId} created`);
|
||||
|
||||
// save Donation
|
||||
let donation = {
|
||||
email,
|
||||
amount,
|
||||
duration,
|
||||
provider: 'stripe',
|
||||
subscriptionId,
|
||||
customerId,
|
||||
startDate: new Date().toISOString()
|
||||
};
|
||||
await createAsyncUserDonation(user, donation);
|
||||
return res.status(200).json({ isDonating: true });
|
||||
}
|
||||
|
Reference in New Issue
Block a user