Skip to content

Commit

Permalink
Stripe oauth flow and connection disconnect routes (#100)
Browse files Browse the repository at this point in the history
Co-authored-by: russell-pollari <pollarir@mgail.com>
  • Loading branch information
Russell-Pollari and russell-pollari committed Oct 2, 2023
1 parent 0b9b925 commit d9db738
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 25 deletions.
22 changes: 13 additions & 9 deletions client/components/QBOConnection.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import * as React from 'react';

import ConnectionCard from './ConnectionCard';
import { useGetCompanyInfoQuery } from '../services/api';
import {
useGetCompanyInfoQuery,
useDisconnectQBOMutation,
} from '../services/api';

const connect = () => {
fetch('/api/qbo/oauth2')
Expand All @@ -16,20 +19,21 @@ const connect = () => {

const QBOConnection = () => {
const { data: qboInfo, isLoading, error } = useGetCompanyInfoQuery();
// const [disconnect] = useDisconnectStripeMutation();
const [disconnect] = useDisconnectQBOMutation();

return (
<ConnectionCard
isConnected={!!qboInfo}
title="QBO account"
isLoading={isLoading}
disconnect={() => {
// disconnect()
// .unwrap()
// // eslint-disable-next-line @typescript-eslint/no-explicit-any
// .catch((e: any) => {
// // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
// alert(e?.data?.detail);
// });
disconnect()
.unwrap()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.catch((e: any) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
alert(e?.data?.detail);
});
}}
>
{qboInfo && !error ? (
Expand Down
3 changes: 1 addition & 2 deletions client/components/StripeConnection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ const StripeConnection = () => {
disconnect={() => {
disconnect()
.unwrap()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.catch((e: any) => {
.catch((e) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
alert(e?.data?.detail);
});
Expand Down
13 changes: 9 additions & 4 deletions client/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import SettingsPage from './pages/SettingsPage';
import SignupPage from './pages/SignupPage';
import TransactionsPage from './pages/TransactionsPage';
import QBOCallback from './pages/QBOCallback';
import StripeCallback from './pages/StripeCallback';

const router = createBrowserRouter([
{
Expand All @@ -23,16 +24,20 @@ const router = createBrowserRouter([
path: '/settings',
element: <SettingsPage />,
},
{
path: '/qbo/oauth2/callback',
element: <QBOCallback />,
},
{
path: '/stripe/oauth2/callback',
element: <StripeCallback />,
},
],
},
{
path: '/signup',
element: <SignupPage />,
},
{
path: '/qbo/oauth2/callback',
element: <QBOCallback />,
},
]);

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
Expand Down
11 changes: 7 additions & 4 deletions client/pages/QBOCallback.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import * as React from 'react';
import { useEffect, useState } from 'react';
import { useLocation } from 'react-router';

import { useSetQBOTokenMutation } from '../services/api';
import LoadingSpinner from '../components/LoadingSpinner';

const QBOCallback = () => {
const { search } = useLocation();
Expand All @@ -17,7 +19,7 @@ const QBOCallback = () => {
setQBOCode({ code, realmId })
.unwrap()
.then(() => {
location.href = '/';
location.href = '/settings';
})
.catch((e) => {
// eslint-disable-next-line
Expand All @@ -28,10 +30,11 @@ const QBOCallback = () => {
}, [code, realmId]);

return (
<div className="grid h-screen place-items-center">
<div className="text-2xl font-semibold">
<div className="grid h-1/2 place-items-center">
<div className="font-semibold">
<LoadingSpinner />
Connecting to QuickBooks...
{error && <div className="text-red-500">{error}</div>}
{error && <div className="text-red-500 my-4">{error}</div>}
</div>
</div>
);
Expand Down
42 changes: 42 additions & 0 deletions client/pages/StripeCallback.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as React from 'react';
import { useEffect, useState } from 'react';
import { useLocation } from 'react-router';

import { useSetStripeTokenMutation } from '../services/api';
import LoadingSpinner from '../components/LoadingSpinner';

const StripeCallback = () => {
const { search } = useLocation();
const params = new URLSearchParams(search);
const code = params.get('code');

const [setStripeToken] = useSetStripeTokenMutation();
const [error, setError] = useState<string | null>(null);

useEffect(() => {
if (code) {
setStripeToken(code)
.unwrap()
.then(() => {
location.href = '/settings';
})
.catch((e) => {
// eslint-disable-next-line
setError(e?.data?.detail);
console.error(e);
});
}
}, [code]);

return (
<div className="grid h-1/2 place-items-center">
<div className="font-semibold">
<LoadingSpinner />
Connecting to Stripe...
{error && <div className="text-red-500 my-4">{error}</div>}
</div>
</div>
);
};

export default StripeCallback;
22 changes: 21 additions & 1 deletion client/services/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const api = createApi({
return headers;
},
}),
tagTypes: ['Settings', 'Stripe', 'User', 'Transaction'],
tagTypes: ['Settings', 'Stripe', 'User', 'Transaction', 'QBO'],
endpoints: (builder) => ({
// AUTH
login: builder.mutation<void, { email: string; password: string }>({
Expand Down Expand Up @@ -97,6 +97,13 @@ export const api = createApi({
}),

// STRIPE
setStripeToken: builder.mutation<void, string>({
query: (code) => ({
url: 'stripe/oauth2/callback?code=' + code,
method: 'POST',
}),
invalidatesTags: ['User', 'Stripe'],
}),
getStripeInfo: builder.query<StripeInfo, void>({
query: () => 'stripe/info',
providesTags: ['Stripe'],
Expand All @@ -119,19 +126,30 @@ export const api = createApi({
body.realmId,
method: 'POST',
}),
invalidatesTags: ['User', 'QBO'],
}),
disconnectQBO: builder.mutation<void, void>({
query: () => ({
url: 'qbo/disconnect',
method: 'POST',
}),
invalidatesTags: ['User'],
}),
getCompanyInfo: builder.query<QBOCompanyInfo, void>({
query: () => 'qbo/info',
providesTags: ['QBO'],
}),
getAccounts: builder.query<QBOAccount[], string>({
query: () => 'qbo/accounts',
providesTags: ['QBO'],
}),
getVendors: builder.query<QBOVendor[], string>({
query: () => 'qbo/vendors',
providesTags: ['QBO'],
}),
getTaxCodes: builder.query<QBOTaxCode[], string>({
query: () => 'qbo/taxcodes',
providesTags: ['QBO'],
}),

// settings
Expand Down Expand Up @@ -225,10 +243,12 @@ export const {
useSignupMutation,
useGetCurrentUserQuery,

useSetStripeTokenMutation,
useGetStripeInfoQuery,
useDisconnectStripeMutation,

useSetQBOTokenMutation,
useDisconnectQBOMutation,
useGetAccountsQuery,
useGetTaxCodesQuery,
useGetVendorsQuery,
Expand Down
9 changes: 9 additions & 0 deletions stripe2qbo/api/routers/qbo.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ def qbo_oauth_callback(
return "ok"


@router.post("/disconnect")
def disconnect_qbo(
db: Annotated[Session, Depends(get_db)],
user: Annotated[User, Depends(get_current_user_from_token)],
) -> None:
db.query(QBOToken).filter(QBOToken.user_id == user.id).delete()
db.commit()


@router.get("/info")
async def get_qbo_info(token: Annotated[Token, Depends(get_qbo_token)]) -> CompanyInfo:
try:
Expand Down
11 changes: 6 additions & 5 deletions stripe2qbo/api/routers/stripe_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

from sqlalchemy.orm import Session
from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import RedirectResponse
import stripe
from dotenv import load_dotenv
from stripe2qbo.api.auth import get_current_user_from_token
Expand Down Expand Up @@ -35,16 +34,18 @@ async def stripe_oauth_url() -> str:
return get_auth_url()


@router.get("/oauth2/callback")
@router.post("/oauth2/callback")
async def stripe_oauth_callback(
code: str,
db: Annotated[Session, Depends(get_db)],
user: Annotated[User, Depends(get_current_user_from_token)],
):
token = generate_auth_token(code)
user.stripe_user_id = token.stripe_user_id
db.query(User).filter(User.id == user.id).update(
{"stripe_user_id": token.stripe_user_id}
)
db.commit()
return RedirectResponse(url="/")
return "ok"


@router.post("/disconnect")
Expand All @@ -56,7 +57,7 @@ async def disconnect_stripe(
raise HTTPException(
status_code=400, detail="Not allowed while STRIPE_ACCOUNT_ID is set"
)
user.stripe_user_id = None
db.query(User).filter(User.id == user.id).update({"stripe_user_id": None})
db.commit()


Expand Down

0 comments on commit d9db738

Please sign in to comment.