mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
feat: add update-stripe-card route (#52389)
Co-authored-by: Naomi Carrigan <nhcarrigan@gmail.com>
This commit is contained in:
@@ -8,7 +8,8 @@ import {
|
||||
verifyWebHook,
|
||||
updateUser,
|
||||
verifyWebHookType,
|
||||
createStripeCardDonation
|
||||
createStripeCardDonation,
|
||||
handleStripeCardUpdateSession
|
||||
} from '../utils/donation';
|
||||
import { validStripeForm } from '../utils/stripeHelpers';
|
||||
|
||||
@@ -181,6 +182,19 @@ export default function donateBoot(app, done) {
|
||||
});
|
||||
}
|
||||
|
||||
async function handleStripeCardUpdate(req, res, next) {
|
||||
try {
|
||||
const sessionIdObj = await handleStripeCardUpdateSession(
|
||||
req,
|
||||
app,
|
||||
stripe
|
||||
);
|
||||
return res.status(200).json(sessionIdObj);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
}
|
||||
|
||||
function updatePaypal(req, res) {
|
||||
const { headers, body } = req;
|
||||
return Promise.resolve(req)
|
||||
@@ -220,6 +234,7 @@ export default function donateBoot(app, done) {
|
||||
} else {
|
||||
api.post('/charge-stripe', createStripeDonation);
|
||||
api.post('/charge-stripe-card', handleStripeCardDonation);
|
||||
api.put('/update-stripe-card', handleStripeCardUpdate);
|
||||
api.post('/add-donation', addDonation);
|
||||
hooks.post('/update-paypal', updatePaypal);
|
||||
donateRouter.use('/donate', api);
|
||||
|
||||
@@ -314,3 +314,40 @@ export async function createStripeCardDonation(req, res, stripe) {
|
||||
await createAsyncUserDonation(user, donation);
|
||||
return res.status(200).json({ isDonating: true });
|
||||
}
|
||||
|
||||
export async function handleStripeCardUpdateSession(req, app, stripe) {
|
||||
const {
|
||||
user: { id }
|
||||
} = req;
|
||||
|
||||
const { Donation } = app.models;
|
||||
log('Updating stripe card for user: ', id);
|
||||
|
||||
// multiple donations support should be added
|
||||
const donation = await Donation.findOne({
|
||||
where: { userId: id, provider: 'stripe' }
|
||||
});
|
||||
|
||||
if (!donation) throw Error('Stripe donation record not found');
|
||||
|
||||
const { customerId, subscriptionId } = donation;
|
||||
|
||||
log(subscriptionId);
|
||||
|
||||
// Create a Stripe checkout session
|
||||
// updating customer payment method is handled by webhook handler
|
||||
const session = await stripe.checkout.sessions.create({
|
||||
payment_method_types: ['card'],
|
||||
mode: 'setup',
|
||||
customer: customerId,
|
||||
setup_intent_data: {
|
||||
metadata: {
|
||||
customer_id: customerId,
|
||||
subscription_id: subscriptionId
|
||||
}
|
||||
},
|
||||
success_url: `${process.env.HOME_LOCATION}/update-stripe-card?session_id={CHECKOUT_SESSION_ID}`,
|
||||
cancel_url: `${process.env.HOME_LOCATION}/update-stripe-card`
|
||||
});
|
||||
return { sessionId: session.id };
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/* eslint-disable camelcase */
|
||||
import axios from 'axios';
|
||||
import stripe from 'stripe';
|
||||
import { ObjectId } from 'mongodb';
|
||||
import keys from '../../../config/secrets';
|
||||
import {
|
||||
mockApp,
|
||||
@@ -14,10 +16,18 @@ import {
|
||||
verifyWebHook,
|
||||
updateUser,
|
||||
capitalizeKeys,
|
||||
createDonationObj
|
||||
createDonationObj,
|
||||
handleStripeCardUpdateSession
|
||||
} from './donation';
|
||||
|
||||
jest.mock('axios');
|
||||
jest.mock('stripe', () => ({
|
||||
checkout: {
|
||||
sessions: {
|
||||
create: jest.fn()
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
const verificationUrl = `https://api.sandbox.paypal.com/v1/notifications/verify-webhook-signature`;
|
||||
const tokenUrl = `https://api.sandbox.paypal.com/v1/oauth2/token`;
|
||||
@@ -155,4 +165,49 @@ describe('donation', () => {
|
||||
expect(updateUserAttr).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleStripeCardUpdateSession', () => {
|
||||
const mockUserId = ObjectId('507f1f77bcf86cd799439011');
|
||||
const mockDonation = {
|
||||
customerId: 'customer_123',
|
||||
subscriptionId: 'sub_123'
|
||||
};
|
||||
const req = { user: { id: mockUserId } };
|
||||
const app = {
|
||||
models: {
|
||||
Donation: { findOne: jest.fn().mockResolvedValue(mockDonation) }
|
||||
}
|
||||
};
|
||||
|
||||
stripe.checkout.sessions.create.mockResolvedValue({ id: 'session_123' });
|
||||
|
||||
it('creates a session successfully', async () => {
|
||||
const result = await handleStripeCardUpdateSession(req, app, stripe);
|
||||
expect(app.models.Donation.findOne).toHaveBeenCalledWith({
|
||||
where: { userId: mockUserId, provider: 'stripe' }
|
||||
});
|
||||
expect(stripe.checkout.sessions.create).toHaveBeenCalled();
|
||||
expect(result).toEqual({ sessionId: 'session_123' });
|
||||
});
|
||||
|
||||
it('throws an error when donation not found', async () => {
|
||||
const app = {
|
||||
models: { Donation: { findOne: jest.fn().mockResolvedValue(null) } }
|
||||
};
|
||||
|
||||
await expect(
|
||||
handleStripeCardUpdateSession(req, app, stripe)
|
||||
).rejects.toThrow('Stripe donation record not found');
|
||||
});
|
||||
|
||||
it('handles stripe session creation failure', async () => {
|
||||
stripe.checkout.sessions.create.mockRejectedValue(
|
||||
new Error('Stripe error')
|
||||
);
|
||||
|
||||
await expect(
|
||||
handleStripeCardUpdateSession(req, app, stripe)
|
||||
).rejects.toThrow('Stripe error');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user