Skip to content

Commit c5e5edd

Browse files
committed
added stripe session user reg API
1 parent cfac2f6 commit c5e5edd

File tree

5 files changed

+6493
-6309
lines changed

5 files changed

+6493
-6309
lines changed

.env.local

Lines changed: 0 additions & 9 deletions
This file was deleted.

src/api/routes/payment.js

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,4 +152,145 @@ router.post(
152152
}
153153
);
154154

155+
/* webhook setup to handle successful Payments (https://docs.stripe.com/checkout/fulfillment) */
156+
router.post(
157+
'/sessionRegister',
158+
[
159+
check('sessionId', 'Session ID is required')
160+
.not()
161+
.isEmpty()
162+
.trim()
163+
.escape(),
164+
],
165+
async (req, res) => {
166+
const errors = validationResult(req);
167+
if (!errors.isEmpty()) {
168+
logger.info(errors);
169+
return res.status(500).json({ message: errors.array() });
170+
}
171+
172+
const { sessionId } = req.body;
173+
174+
let session = null;
175+
let paymentDetails = {};
176+
177+
try {
178+
session = await APICall.getStripeSessionData(sessionId);
179+
180+
if (!session || session.status === 'unpaid') {
181+
return res
182+
.status(500)
183+
.json({ message: 'Payment incomplete or invalid.' });
184+
}
185+
186+
paymentDetails.uhID = session.custom_fields.find(
187+
(field) => field.key === 'uhID'
188+
).numeric.value;
189+
190+
paymentDetails.shirtSize = session.custom_fields.find(
191+
(field) => field.key === 'shirtSize'
192+
).dropdown.value;
193+
194+
paymentDetails.paidUntil = session.metadata.tenure;
195+
} catch (err) {
196+
logger.error(
197+
`${err.status || 500} - ${err.message} - ${req.originalUrl} - ${
198+
req.method
199+
} - ${req.ip}`
200+
);
201+
return res
202+
.status(500)
203+
.json({ message: 'Failed to retrieve session' });
204+
}
205+
206+
try {
207+
const customer = await APICall.getStripeCustomerData(
208+
session.customer
209+
);
210+
211+
paymentDetails = {
212+
firstName: customer.name ? customer.name.split(' ')[0] : null,
213+
lastName: customer.name
214+
? customer.name.split(' ').slice(1).join(' ')
215+
: '',
216+
email: customer.email,
217+
phone: customer.phone,
218+
};
219+
} catch (err) {
220+
await APICall.sendEmail(
221+
[
222+
223+
224+
225+
],
226+
{ name: 'Payment Failure', email: '[email protected]' },
227+
'CougarCS Cloud API - postContact Failed',
228+
JSON.stringify({
229+
sessionId: session.id,
230+
err: err.message,
231+
})
232+
);
233+
234+
logger.error(
235+
`${err.status || 500} - ${err.message} - ${req.originalUrl} - ${
236+
req.method
237+
} - ${req.ip}`
238+
);
239+
240+
return res
241+
.status(500)
242+
.json({ message: 'Failed to retrieve customer' });
243+
}
244+
245+
const {
246+
firstName,
247+
lastName,
248+
email,
249+
uhID,
250+
shirtSize,
251+
paidUntil,
252+
phone,
253+
} = paymentDetails;
254+
255+
try {
256+
await APICall.postContact({
257+
transaction: `Payment via Stripe on ${new Date().toLocaleDateString()}`,
258+
firstName,
259+
lastName,
260+
email,
261+
uhID,
262+
phone,
263+
shirtSize,
264+
paidUntil,
265+
});
266+
} catch (err) {
267+
await APICall.sendEmail(
268+
[
269+
270+
271+
272+
],
273+
{ name: 'Payment Failure', email: '[email protected]' },
274+
'CougarCS Cloud API - postContact Failed',
275+
JSON.stringify({
276+
name: `${firstName} ${lastName}`,
277+
email,
278+
uhID,
279+
err: err.message,
280+
})
281+
);
282+
283+
logger.error(
284+
`${err.status || 500} - ${err.message} - ${req.originalUrl} - ${
285+
req.method
286+
} - ${req.ip}`
287+
);
288+
289+
return res.status(500).json({ message: 'Failed to write to DB' });
290+
}
291+
292+
return res.status(200).json({ message: 'OK' });
293+
}
294+
);
295+
155296
export default router;

src/utils/api/calls.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,20 @@ exports.createStripeCustomer = async function createStripeCustomer(
116116
});
117117
};
118118

119+
exports.getStripeSessionData = async function getStripeSessionData(sessionId) {
120+
const session = await stripe.checkout.sessions.retrieve(sessionId, {
121+
expand: ['line_items'],
122+
});
123+
return session;
124+
};
125+
126+
exports.getStripeCustomerData = async function getStripeCustomerData(
127+
customerId
128+
) {
129+
const customer = stripe.customers.retrieve(customerId);
130+
return customer;
131+
};
132+
119133
exports.getTutors = async function getTutors() {
120134
const notion = new Client({
121135
auth: NOTION_TOKEN,

test/integration/payment.test.js

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,4 +330,166 @@ describe('Payment API test', () => {
330330
expect(res.body).toHaveProperty('message');
331331
expect(res.body.message).toBe('OK');
332332
});
333+
334+
test('Stripe Session CCS Cloud Session ID Missing', async () => {
335+
const res = await agent
336+
.post('/api/payment/sessionRegister')
337+
.send({ sessionId: '' });
338+
339+
expect(res.status).toBe(500);
340+
expect(res.body).toHaveProperty('message');
341+
expect(res.body.message[0].msg).toBe('Session ID is required');
342+
});
343+
344+
test('Stripe Session CCS Cloud Post Contact Fail', async () => {
345+
jest.spyOn(apiCall, 'getStripeSessionData').mockImplementationOnce(
346+
() => {
347+
return {
348+
status: 'complete',
349+
custom_fields: [
350+
{ key: 'uhID', numeric: { value: 1234567 } },
351+
{ key: 'shirtSize', dropdown: { value: 'M' } },
352+
],
353+
metadata: { tenure: 'semester' },
354+
};
355+
}
356+
);
357+
358+
jest.spyOn(apiCall, 'getStripeCustomerData').mockImplementationOnce(
359+
() => {
360+
return {
361+
name: 'John Doe',
362+
363+
phone: '123-456-7890',
364+
};
365+
}
366+
);
367+
368+
jest.spyOn(apiCall, 'sendEmail').mockImplementationOnce(() => true);
369+
370+
jest.spyOn(apiCall, 'postContact').mockImplementationOnce(() => {
371+
throw new Error();
372+
});
373+
374+
const res = await agent.post('/api/payment/sessionRegister').send({
375+
sessionId:
376+
'cs_test_a11YYufWQzNY63zpQ6QSNRQhkUpVph4WRmzW0zWJO2znZKdVujZ0N0S22u',
377+
});
378+
379+
expect(res.status).toBe(500);
380+
expect(res.body).toHaveProperty('message');
381+
expect(res.body.message).toBe('Failed to write to DB');
382+
});
383+
384+
test('Stripe Session CCS Cloud Post Contact', async () => {
385+
jest.spyOn(apiCall, 'getStripeSessionData').mockImplementationOnce(
386+
() => {
387+
return {
388+
status: 'complete',
389+
custom_fields: [
390+
{ key: 'uhID', numeric: { value: 1234567 } },
391+
{ key: 'shirtSize', dropdown: { value: 'M' } },
392+
],
393+
metadata: { tenure: 'semester' },
394+
};
395+
}
396+
);
397+
398+
jest.spyOn(apiCall, 'getStripeCustomerData').mockImplementationOnce(
399+
() => {
400+
return {
401+
name: 'John Doe',
402+
403+
phone: '123-456-7890',
404+
};
405+
}
406+
);
407+
408+
jest.spyOn(apiCall, 'postContact').mockImplementationOnce(() => true);
409+
410+
const res = await agent.post('/api/payment/sessionRegister').send({
411+
sessionId:
412+
'cs_test_a11YYufWQzNY63zpQ6QSNRQhkUpVph4WRmzW0zWJO2znZKdVujZ0N0S22u',
413+
});
414+
415+
expect(res.status).toBe(200);
416+
expect(res.body).toHaveProperty('message');
417+
expect(res.body.message).toBe('OK');
418+
});
419+
420+
test('Stripe Session CCS Cloud Get Session Fail', async () => {
421+
jest.spyOn(apiCall, 'getStripeSessionData').mockImplementationOnce(
422+
() => {
423+
throw new Error();
424+
}
425+
);
426+
427+
jest.spyOn(apiCall, 'sendEmail').mockImplementationOnce(() => true);
428+
429+
const res = await agent.post('/api/payment/sessionRegister').send({
430+
sessionId:
431+
'cs_test_a11YYufWQzNY63zpQ6QSNRQhkUpVph4WRmzW0zWJO2znZKdVujZ0N0S22u',
432+
});
433+
434+
expect(res.status).toBe(500);
435+
expect(res.body).toHaveProperty('message');
436+
expect(res.body.message).toBe('Failed to retrieve session');
437+
});
438+
439+
test('Stripe Session CCS Cloud Invalid Session', async () => {
440+
jest.spyOn(apiCall, 'getStripeSessionData').mockImplementationOnce(
441+
() => null
442+
);
443+
444+
jest.spyOn(apiCall, 'getStripeCustomerData').mockImplementationOnce(
445+
() => null
446+
);
447+
448+
jest.spyOn(apiCall, 'postContact').mockImplementationOnce(() => true);
449+
450+
jest.spyOn(apiCall, 'sendEmail').mockImplementationOnce(() => true);
451+
452+
const res = await agent.post('/api/payment/sessionRegister').send({
453+
sessionId:
454+
'cs_test_a11YYufWQzNY63zpQ6QSNRQhkUpVph4WRmzW0zWJO2znZKdVujZ0N0S22u',
455+
});
456+
457+
expect(res.status).toBe(500);
458+
expect(res.body).toHaveProperty('message');
459+
expect(res.body.message).toBe('Payment incomplete or invalid.');
460+
});
461+
462+
test('Stripe Session CCS Cloud Invalid Customer', async () => {
463+
jest.spyOn(apiCall, 'getStripeSessionData').mockImplementationOnce(
464+
() => {
465+
return {
466+
status: 'complete',
467+
custom_fields: [
468+
{ key: 'uhID', numeric: { value: 1234567 } },
469+
{ key: 'shirtSize', dropdown: { value: 'M' } },
470+
],
471+
metadata: { tenure: 'semester' },
472+
};
473+
}
474+
);
475+
476+
jest.spyOn(apiCall, 'getStripeCustomerData').mockImplementationOnce(
477+
() => {
478+
return null;
479+
}
480+
);
481+
482+
jest.spyOn(apiCall, 'sendEmail').mockImplementationOnce(() => true);
483+
484+
jest.spyOn(apiCall, 'postContact').mockImplementationOnce(() => true);
485+
486+
const res = await agent.post('/api/payment/sessionRegister').send({
487+
sessionId:
488+
'cs_test_a11YYufWQzNY63zpQ6QSNRQhkUpVph4WRmzW0zWJO2znZKdVujZ0N0S22u',
489+
});
490+
491+
expect(res.status).toBe(500);
492+
expect(res.body).toHaveProperty('message');
493+
expect(res.body.message).toBe('Failed to retrieve customer');
494+
});
333495
});

0 commit comments

Comments
 (0)