feat(api): add update-stripe-card endpoint (#55548)
Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>pull/55764/head
parent
65dfc044e8
commit
a1c12847e4
|
@ -4,7 +4,8 @@ import {
|
|||
devLogin,
|
||||
setupServer,
|
||||
superRequest,
|
||||
defaultUserEmail
|
||||
defaultUserEmail,
|
||||
defaultUserId
|
||||
} from '../../jest.utils';
|
||||
import { createUserInput } from '../utils/create-user';
|
||||
|
||||
|
@ -42,6 +43,21 @@ const userWithProgress: Prisma.userCreateInput = {
|
|||
}
|
||||
]
|
||||
};
|
||||
const donationMock = {
|
||||
endDate: null,
|
||||
startDate: {
|
||||
date: '2024-07-17T10:20:56.076Z',
|
||||
when: '2024-07-17T10:20:56.076+00:00'
|
||||
},
|
||||
id: '66979a414748aa2f3ba36d41',
|
||||
amount: 500,
|
||||
customerId: 'cust_test_id',
|
||||
duration: 'month',
|
||||
email: 'foo@bar.com',
|
||||
provider: 'stripe',
|
||||
subscriptionId: 'sub_test_id',
|
||||
userId: defaultUserId
|
||||
};
|
||||
const sharedDonationReqBody = {
|
||||
amount: 500,
|
||||
duration: 'month'
|
||||
|
@ -93,6 +109,9 @@ const mockSubRetrieveObj = {
|
|||
status: 'active'
|
||||
};
|
||||
const mockSubRetrieve = jest.fn(() => Promise.resolve(mockSubRetrieveObj));
|
||||
const mockCheckoutSessionCreate = jest.fn(() =>
|
||||
Promise.resolve({ id: 'checkout_session_id' })
|
||||
);
|
||||
const mockCustomerUpdate = jest.fn();
|
||||
const generateMockSubCreate = (status: string) => () =>
|
||||
Promise.resolve({
|
||||
|
@ -120,15 +139,22 @@ jest.mock('stripe', () => {
|
|||
subscriptions: {
|
||||
create: mockSubCreate,
|
||||
retrieve: mockSubRetrieve
|
||||
},
|
||||
checkout: {
|
||||
sessions: {
|
||||
create: mockCheckoutSessionCreate
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
describe('Donate', () => {
|
||||
let setCookies: string[];
|
||||
setupServer();
|
||||
describe('Authenticated User', () => {
|
||||
let superPost: ReturnType<typeof createSuperRequest>;
|
||||
let superPut: ReturnType<typeof createSuperRequest>;
|
||||
const verifyUpdatedUserAndNewDonation = async (email: string) => {
|
||||
const user = await fastifyTestInstance.prisma.user.findFirst({
|
||||
where: { email }
|
||||
|
@ -162,8 +188,9 @@ describe('Donate', () => {
|
|||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const setCookies = await devLogin();
|
||||
setCookies = await devLogin();
|
||||
superPost = createSuperRequest({ method: 'POST', setCookies });
|
||||
superPut = createSuperRequest({ method: 'PUT', setCookies });
|
||||
await fastifyTestInstance.prisma.user.updateMany({
|
||||
where: { email: userWithProgress.email },
|
||||
data: userWithProgress
|
||||
|
@ -302,6 +329,39 @@ describe('Donate', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('PUT /donate/update-stripe-card', () => {
|
||||
it('should return 200 and return session id', async () => {
|
||||
await fastifyTestInstance.prisma.donation.create({
|
||||
data: donationMock
|
||||
});
|
||||
const response = await superPut('/donate/update-stripe-card').send({});
|
||||
expect(mockCheckoutSessionCreate).toHaveBeenCalledWith({
|
||||
cancel_url: 'http://localhost:8000/update-stripe-card',
|
||||
customer: 'cust_test_id',
|
||||
mode: 'setup',
|
||||
payment_method_types: ['card'],
|
||||
setup_intent_data: {
|
||||
metadata: {
|
||||
customer_id: 'cust_test_id',
|
||||
subscription_id: 'sub_test_id'
|
||||
}
|
||||
},
|
||||
success_url:
|
||||
'http://localhost:8000/update-stripe-card?session_id={CHECKOUT_SESSION_ID}'
|
||||
});
|
||||
expect(response.body).toEqual({ sessionId: 'checkout_session_id' });
|
||||
expect(response.status).toBe(200);
|
||||
});
|
||||
it('should return 500 if there is no donation record', async () => {
|
||||
const response = await superPut('/donate/update-stripe-card').send({});
|
||||
expect(response.body).toEqual({
|
||||
message: 'flash.generic-error',
|
||||
type: 'danger'
|
||||
});
|
||||
expect(response.status).toBe(500);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /donate/create-stripe-payment-intent', () => {
|
||||
it('should return 200 and call stripe api properly', async () => {
|
||||
mockSubCreate.mockImplementationOnce(
|
||||
|
@ -432,16 +492,16 @@ describe('Donate', () => {
|
|||
});
|
||||
|
||||
describe('Unauthenticated User', () => {
|
||||
let setCookies: string[];
|
||||
// Get the CSRF cookies from an unprotected route
|
||||
beforeAll(async () => {
|
||||
const res = await superRequest('/status/ping', { method: 'GET' });
|
||||
setCookies = res.get('Set-Cookie');
|
||||
});
|
||||
|
||||
const endpoints: { path: string; method: 'POST' }[] = [
|
||||
const endpoints: { path: string; method: 'POST' | 'PUT' }[] = [
|
||||
{ path: '/donate/add-donation', method: 'POST' },
|
||||
{ path: '/donate/charge-stripe-card', method: 'POST' }
|
||||
{ path: '/donate/charge-stripe-card', method: 'POST' },
|
||||
{ path: '/donate/update-stripe-card', method: 'PUT' }
|
||||
];
|
||||
|
||||
endpoints.forEach(({ path, method }) => {
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
allStripeProductIdsArray
|
||||
} from '../../../shared/config/donation-settings';
|
||||
import * as schemas from '../schemas';
|
||||
import { STRIPE_SECRET_KEY } from '../utils/env';
|
||||
import { STRIPE_SECRET_KEY, HOME_LOCATION } from '../utils/env';
|
||||
import { inLastFiveMinutes } from '../utils/validate-donation';
|
||||
import { findOrCreateUser } from './helpers/auth-helpers';
|
||||
|
||||
|
@ -30,6 +30,35 @@ export const donateRoutes: FastifyPluginCallbackTypebox = (
|
|||
typescript: true
|
||||
});
|
||||
|
||||
fastify.put(
|
||||
'/donate/update-stripe-card',
|
||||
{
|
||||
schema: schemas.updateStripeCard
|
||||
},
|
||||
async req => {
|
||||
const donation = await fastify.prisma.donation.findFirst({
|
||||
where: { userId: req.user?.id, provider: 'stripe' }
|
||||
});
|
||||
if (!donation)
|
||||
throw Error(`Stripe donation record not found: ${req.user?.id}`);
|
||||
const { customerId, subscriptionId } = donation;
|
||||
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: `${HOME_LOCATION}/update-stripe-card?session_id={CHECKOUT_SESSION_ID}`,
|
||||
cancel_url: `${HOME_LOCATION}/update-stripe-card`
|
||||
});
|
||||
return { sessionId: session.id } as const;
|
||||
}
|
||||
);
|
||||
|
||||
fastify.post(
|
||||
'/donate/add-donation',
|
||||
{
|
||||
|
|
|
@ -14,6 +14,7 @@ export { deprecatedEndpoints } from './schemas/deprecated';
|
|||
export { chargeStripeCard } from './schemas/donate/charge-stripe-card';
|
||||
export { chargeStripe } from './schemas/donate/charge-stripe';
|
||||
export { createStripePaymentIntent } from './schemas/donate/create-stripe-payment-intent';
|
||||
export { updateStripeCard } from './schemas/donate/update-stripe-card';
|
||||
export { resubscribe } from './schemas/email-subscription/resubscribe';
|
||||
export { unsubscribe } from './schemas/email-subscription/unsubscribe';
|
||||
export { updateMyAbout } from './schemas/settings/update-my-about';
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import { Type } from '@fastify/type-provider-typebox';
|
||||
import { genericError } from '../types';
|
||||
|
||||
export const updateStripeCard = {
|
||||
body: Type.Object({}),
|
||||
response: {
|
||||
200: Type.Object({
|
||||
sessionId: Type.String()
|
||||
}),
|
||||
default: genericError
|
||||
}
|
||||
};
|
|
@ -243,7 +243,7 @@ export function addDonation(body: Donation): Promise<ResponseWithData<void>> {
|
|||
}
|
||||
|
||||
export function updateStripeCard() {
|
||||
return put('/donate/update-stripe-card');
|
||||
return put('/donate/update-stripe-card', {});
|
||||
}
|
||||
|
||||
export function postChargeStripe(
|
||||
|
|
Loading…
Reference in New Issue