Skip to content

Commit

Permalink
feat: support stripe checkout and portal sessions
Browse files Browse the repository at this point in the history
  • Loading branch information
ijemmao committed Apr 15, 2024
1 parent d92b49d commit be0a109
Show file tree
Hide file tree
Showing 17 changed files with 130 additions and 19 deletions.
23 changes: 23 additions & 0 deletions __mocks__/stripe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
class Stripe {
apiKey = '';
prices = {
list: jest.fn(() => ({ data: [{ id: 'price_id' }] })),
};
checkout = {
sessions: {
create: jest.fn(() => ({ url: 'checkout_session_url' })),
retrieve: jest.fn(() => ({ customer: 'checkout_session_customer' })),
},
};
billingPortal = {
sessions: {
create: jest.fn(() => ({ url: 'portal_session_url' })),
},
};

constructor(apiKey: string) {
this.apiKey = apiKey;
}
}

export default Stripe;
3 changes: 2 additions & 1 deletion __tests__/shared/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export const requestFixture = (
body?: { [key: string]: string },
params?: { [key: string]: string },
headers?: { [key: string]: string },
},
} = {},
options?: RequestOptions
) => ({
body,
Expand All @@ -87,5 +87,6 @@ export const statusSendMock = jest.fn();
export const responseFixture = () => ({
status: jest.fn(() => ({ send: statusSendMock })),
send: jest.fn(),
redirect: jest.fn(),
});
export const nextFunctionFixture = () => jest.fn();
1 change: 1 addition & 0 deletions functions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"shelljs": "^0.8.4",
"shx": "^0.3.3",
"string-similarity": "^4.0.2",
"stripe": "^15.1.0",
"tailwindcss": "3",
"typescript": "^4.0.3",
"unicharadata": "^9.0.0-alpha.6",
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@
"shelljs": "^0.8.4",
"shx": "^0.3.3",
"string-similarity": "^4.0.2",
"stripe": "^15.1.0",
"tailwindcss": "3",
"typescript": "^4.0.3",
"unicharadata": "^9.0.0-alpha.6",
Expand Down
5 changes: 4 additions & 1 deletion src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import bodyParser from 'body-parser';
import morgan from 'morgan';
import compression from 'compression';
import './shared/utils/wrapConsole';
import { router, routerV2, siteRouter, testRouter } from './routers';
import { router, routerV2, siteRouter, stripeRouter, testRouter } from './routers';
import cache from './middleware/cache';
import logger from './middleware/logger';
import errorHandler from './middleware/errorHandler';
Expand Down Expand Up @@ -35,6 +35,9 @@ app.use('/assets', cache(), express.static('./dist/assets'));
app.use('/fonts', cache(), express.static('./dist/fonts'));
app.use('/services', cache(), express.static('./services'));

/* Stripe */
app.use('/stripe', stripeRouter);

/* Grabs data from MongoDB */
app.use(`/api/${Version.VERSION_1}`, cache(86400, 172800), router);
app.use(`/api/${Version.VERSION_2}`, cache(86400, 172800), routerV2);
Expand Down
21 changes: 21 additions & 0 deletions src/controllers/__tests__/stripe.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
jest.mock('stripe');
import { requestFixture, responseFixture } from '../../../__tests__/shared/fixtures';
import { postCheckoutSession, postPortalSession } from '../stripe';

describe('Credentials', () => {
it('creates a new checkout session', async () => {
const res = responseFixture();
// @ts-expect-error Request fixture
await postCheckoutSession(requestFixture(), res);

expect(res.redirect).toHaveBeenCalledWith(303, 'checkout_session_url');
});

it('creates a new portal session', async () => {
const res = responseFixture();
// @ts-expect-error Request fixture
await postPortalSession(requestFixture(), res);

expect(res.redirect).toHaveBeenCalledWith(303, 'portal_session_url');
});
});
46 changes: 46 additions & 0 deletions src/controllers/stripe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Request, Response } from 'express';
import Stripe from 'stripe';
import { API_ROUTE } from '../config';

const STRIPE_SECRET_KEY = 'sk_test_hpwuITjteocLizB8Afq7H3cV00FEEViC1s';
const stripe = new Stripe(STRIPE_SECRET_KEY);

export const postCheckoutSession = async (req: Request, res: Response) => {
const prices = await stripe.prices.list({
lookup_keys: [req.body.lookup_key],
expand: ['data.product'],
});

const session = await stripe.checkout.sessions.create({
billing_address_collection: 'auto',
line_items: [
{
price: prices.data[0].id,
quantity: 1,
},
],
mode: 'subscription',
success_url: `${API_ROUTE}/?success=true&session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${API_ROUTE}/?canceled=true`,
});

return res.redirect(303, session.url || '/');
};

export const postPortalSession = async (req: Request, res: Response) => {
const { sessionId } = req.body;
const checkoutSession = await stripe.checkout.sessions.retrieve(sessionId);

if (!checkoutSession.customer) {
throw new Error('No associated customer with checkout session');
}

const returnUrl = API_ROUTE;

const portalSession = await stripe.billingPortal.sessions.create({
customer: `${checkoutSession.customer}`,
return_url: returnUrl,
});

return res.redirect(303, portalSession.url);
};
3 changes: 1 addition & 2 deletions src/pages/dashboard/credentials.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Box, Heading, IconButton, Input, Text, Tooltip } from '@chakra-ui/react
import { FiEye, FiEyeOff, FiCopy } from 'react-icons/fi';

import DashboardLayout from './layout';
import { Developer } from '../../types';

const Credentials = () => {
const [isApiKeyVisible, setIsApiKeyVisible] = useState(false);
Expand All @@ -19,7 +18,7 @@ const Credentials = () => {

return (
<DashboardLayout>
{({ developer }: { developer: Developer }) => (
{({ developer }) => (
<>
<Box mb={4}>
<Heading as="h1">API Keys</Heading>
Expand Down
3 changes: 1 addition & 2 deletions src/pages/dashboard/dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ import {
} from '@chakra-ui/react';
import moment from 'moment';
import DashboardLayout from './layout';
import { Developer } from '../../types';

const Dashboard = () => (
<DashboardLayout>
{({ developer }: { developer: Developer }) => (
{({ developer }) => (
<Skeleton isLoaded={Boolean(developer)}>
<Box mb={4}>
<Heading as="h1">Home</Heading>
Expand Down
3 changes: 2 additions & 1 deletion src/pages/dashboard/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import DashboardSideMenu from './components/DashboardSideMenu';
import { getDeveloper } from '../APIs/DevelopersAPI';
import { auth } from '../../services/firebase';
import { developerAtom } from '../atoms/dashboard';
import { Developer } from '../../types';

const DashboardLayout = ({ children }: { children: any }) => {
const DashboardLayout = ({ children }: { children: ({ developer } : { developer: Developer}) => any }) => {
const [developer, setDeveloper] = useAtom(developerAtom);

if (auth.currentUser && !developer) {
Expand Down
3 changes: 1 addition & 2 deletions src/pages/dashboard/profile.page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import React from 'react';
import { Avatar, Box, Heading, Text, Badge } from '@chakra-ui/react';
import DashboardLayout from './layout';
import { Developer } from '../../types';

const Profile = () => (
<DashboardLayout>
{({ developer }: { developer: Developer }) => (
{({ developer }) => (
<Box className="flex flex-col justify-center items-center">
<Avatar size="lg" />
<Heading as="h1">Profile</Heading>
Expand Down
3 changes: 2 additions & 1 deletion src/routers/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import router from './router';
import routerV2 from './routerV2';
import siteRouter from './siteRouter';
import stripeRouter from './stripeRouter';
import testRouter from './testRouter';

export { router, routerV2, siteRouter, testRouter };
export { router, routerV2, siteRouter, stripeRouter, testRouter };
4 changes: 2 additions & 2 deletions src/routers/router.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import express from 'express';
import { Router } from 'express';
import rateLimit from 'express-rate-limit';
import { MiddleWare } from '../types';
import { getWords, getWord } from '../controllers/words';
Expand All @@ -14,7 +14,7 @@ import attachRedisClient from '../middleware/attachRedisClient';
import analytics from '../middleware/analytics';
import developerAuthorization from '../middleware/developerAuthorization';

const router = express.Router();
const router = Router();

const FIFTEEN_MINUTES = 15 * 60 * 1000;
const REQUESTS_PER_MS = 20;
Expand Down
13 changes: 10 additions & 3 deletions src/routers/routerV2.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import express from 'express';
import { Router } from 'express';
import { getWords, getWord } from '../controllers/words';
import { getExample, getExamples } from '../controllers/examples';
import { getNsibidiCharacter, getNsibidiCharacters } from '../controllers/nsibidi';
Expand All @@ -7,14 +7,21 @@ import validateApiKey from '../middleware/validateApiKey';
import analytics from '../middleware/analytics';
import attachRedisClient from '../middleware/attachRedisClient';

const routerV2 = express.Router();
const routerV2 = Router();

routerV2.get('/words', analytics, validateApiKey, attachRedisClient, getWords);
routerV2.get('/words/:id', analytics, validateApiKey, validId, attachRedisClient, getWord);
routerV2.get('/examples', analytics, validateApiKey, attachRedisClient, getExamples);
routerV2.get('/examples/:id', analytics, validateApiKey, validId, attachRedisClient, getExample);
routerV2.get('/nsibidi', analytics, validateApiKey, attachRedisClient, getNsibidiCharacters);
routerV2.get('/nsibidi/:id', analytics, validateApiKey, validId, attachRedisClient, getNsibidiCharacter);
routerV2.get(
'/nsibidi/:id',
analytics,
validateApiKey,
validId,
attachRedisClient,
getNsibidiCharacter
);

// Redirects to V1
routerV2.post('/developers', (_, res) => res.redirect('/api/v1/developers'));
Expand Down
4 changes: 2 additions & 2 deletions src/routers/siteRouter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import express from 'express';
import { Router } from 'express';
import nextjs from 'next';
import compact from 'lodash/compact';
import { parse } from 'url';
Expand All @@ -8,7 +8,7 @@ const handle = nextApp.getRequestHandler();

const routes = compact([/^\/$/]);

const siteRouter = express.Router();
const siteRouter = Router();

siteRouter.use(async (req, res, next) => {
try {
Expand Down
9 changes: 9 additions & 0 deletions src/routers/stripeRouter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Router } from 'express';
import { postCheckoutSession, postPortalSession } from '../controllers/stripe';

const router = Router();

router.post('/checkout', postCheckoutSession);
router.post('/portal', postPortalSession);

export default router;
4 changes: 2 additions & 2 deletions src/routers/testRouter.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import express from 'express';
import { Router } from 'express';
import { getWordData } from '../controllers/words';
import { seedDatabase } from '../dictionaries/seed';

const testRouter = express.Router();
const testRouter = Router();

testRouter.get('/', (_, res) => {
res.send('Welcome to the Igbo English Dictionary API');
Expand Down

0 comments on commit be0a109

Please sign in to comment.