mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: use OAuth for stripe connect
This commit is contained in:
parent
93c0838fab
commit
0449d6f87c
9 changed files with 266 additions and 50 deletions
|
|
@ -74,7 +74,9 @@ class RedisConfig(BaseModel):
|
||||||
class InternalStripeConfig(BaseModel):
|
class InternalStripeConfig(BaseModel):
|
||||||
stripe_secret_key: str | None
|
stripe_secret_key: str | None
|
||||||
stripe_publishable_key: str | None
|
stripe_publishable_key: str | None
|
||||||
stripe_webhook_secret: str | None
|
stripe_webhook_standard_secret: str | None
|
||||||
|
stripe_webhook_connect_secret: str | None
|
||||||
|
stripe_client_id: str | None
|
||||||
|
|
||||||
|
|
||||||
class InternalPaymentsConfig(BaseModel):
|
class InternalPaymentsConfig(BaseModel):
|
||||||
|
|
@ -275,7 +277,9 @@ def get_learnhouse_config() -> LearnHouseConfig:
|
||||||
# Payments config
|
# Payments config
|
||||||
env_stripe_secret_key = os.environ.get("LEARNHOUSE_STRIPE_SECRET_KEY")
|
env_stripe_secret_key = os.environ.get("LEARNHOUSE_STRIPE_SECRET_KEY")
|
||||||
env_stripe_publishable_key = os.environ.get("LEARNHOUSE_STRIPE_PUBLISHABLE_KEY")
|
env_stripe_publishable_key = os.environ.get("LEARNHOUSE_STRIPE_PUBLISHABLE_KEY")
|
||||||
env_stripe_webhook_secret = os.environ.get("LEARNHOUSE_STRIPE_WEBHOOK_SECRET")
|
env_stripe_webhook_standard_secret = os.environ.get("LEARNHOUSE_STRIPE_WEBHOOK_STANDARD_SECRET")
|
||||||
|
env_stripe_webhook_connect_secret = os.environ.get("LEARNHOUSE_STRIPE_WEBHOOK_CONNECT_SECRET")
|
||||||
|
env_stripe_client_id = os.environ.get("LEARNHOUSE_STRIPE_CLIENT_ID")
|
||||||
|
|
||||||
stripe_secret_key = env_stripe_secret_key or yaml_config.get("payments_config", {}).get(
|
stripe_secret_key = env_stripe_secret_key or yaml_config.get("payments_config", {}).get(
|
||||||
"stripe", {}
|
"stripe", {}
|
||||||
|
|
@ -285,9 +289,17 @@ def get_learnhouse_config() -> LearnHouseConfig:
|
||||||
"stripe", {}
|
"stripe", {}
|
||||||
).get("stripe_publishable_key")
|
).get("stripe_publishable_key")
|
||||||
|
|
||||||
stripe_webhook_secret = env_stripe_webhook_secret or yaml_config.get("payments_config", {}).get(
|
stripe_webhook_standard_secret = env_stripe_webhook_standard_secret or yaml_config.get("payments_config", {}).get(
|
||||||
"stripe", {}
|
"stripe", {}
|
||||||
).get("stripe_webhook_secret")
|
).get("stripe_webhook_standard_secret")
|
||||||
|
|
||||||
|
stripe_webhook_connect_secret = env_stripe_webhook_connect_secret or yaml_config.get("payments_config", {}).get(
|
||||||
|
"stripe", {}
|
||||||
|
).get("stripe_webhook_connect_secret")
|
||||||
|
|
||||||
|
stripe_client_id = env_stripe_client_id or yaml_config.get("payments_config", {}).get(
|
||||||
|
"stripe", {}
|
||||||
|
).get("stripe_client_id")
|
||||||
|
|
||||||
# Create HostingConfig and DatabaseConfig objects
|
# Create HostingConfig and DatabaseConfig objects
|
||||||
hosting_config = HostingConfig(
|
hosting_config = HostingConfig(
|
||||||
|
|
@ -335,7 +347,9 @@ def get_learnhouse_config() -> LearnHouseConfig:
|
||||||
stripe=InternalStripeConfig(
|
stripe=InternalStripeConfig(
|
||||||
stripe_secret_key=stripe_secret_key,
|
stripe_secret_key=stripe_secret_key,
|
||||||
stripe_publishable_key=stripe_publishable_key,
|
stripe_publishable_key=stripe_publishable_key,
|
||||||
stripe_webhook_secret=stripe_webhook_secret
|
stripe_webhook_standard_secret=stripe_webhook_standard_secret,
|
||||||
|
stripe_webhook_connect_secret=stripe_webhook_connect_secret,
|
||||||
|
stripe_client_id=stripe_client_id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,8 @@ payments_config:
|
||||||
stripe:
|
stripe:
|
||||||
stripe_secret_key: ""
|
stripe_secret_key: ""
|
||||||
stripe_publishable_key: ""
|
stripe_publishable_key: ""
|
||||||
stripe_webhook_secret: ""
|
stripe_webhook_standard_secret: ""
|
||||||
|
stripe_client_id: ""
|
||||||
|
|
||||||
ai_config:
|
ai_config:
|
||||||
chromadb_config:
|
chromadb_config:
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ from src.services.payments.payments_courses import (
|
||||||
get_courses_by_product,
|
get_courses_by_product,
|
||||||
)
|
)
|
||||||
from src.services.payments.payments_users import get_owned_courses
|
from src.services.payments.payments_users import get_owned_courses
|
||||||
from src.services.payments.payments_stripe import create_checkout_session, update_stripe_account_id
|
from src.services.payments.payments_stripe import create_checkout_session, handle_stripe_oauth_callback, update_stripe_account_id
|
||||||
from src.services.payments.payments_access import check_course_paid_access
|
from src.services.payments.payments_access import check_course_paid_access
|
||||||
from src.services.payments.payments_customers import get_customers
|
from src.services.payments.payments_customers import get_customers
|
||||||
from src.services.payments.payments_stripe import generate_stripe_connect_link
|
from src.services.payments.payments_stripe import generate_stripe_connect_link
|
||||||
|
|
@ -165,7 +165,14 @@ async def api_handle_connected_accounts_stripe_webhook(
|
||||||
request: Request,
|
request: Request,
|
||||||
db_session: Session = Depends(get_db_session),
|
db_session: Session = Depends(get_db_session),
|
||||||
):
|
):
|
||||||
return await handle_stripe_webhook(request, db_session)
|
return await handle_stripe_webhook(request, "standard", db_session)
|
||||||
|
|
||||||
|
@router.post("/stripe/webhook/connect")
|
||||||
|
async def api_handle_connected_accounts_stripe_webhook_connect(
|
||||||
|
request: Request,
|
||||||
|
db_session: Session = Depends(get_db_session),
|
||||||
|
):
|
||||||
|
return await handle_stripe_webhook(request, "connect", db_session)
|
||||||
|
|
||||||
# Payments checkout
|
# Payments checkout
|
||||||
|
|
||||||
|
|
@ -246,3 +253,13 @@ async def api_generate_stripe_connect_link(
|
||||||
return await generate_stripe_connect_link(
|
return await generate_stripe_connect_link(
|
||||||
request, org_id, redirect_uri, current_user, db_session
|
request, org_id, redirect_uri, current_user, db_session
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@router.get("/stripe/oauth/callback")
|
||||||
|
async def stripe_oauth_callback(
|
||||||
|
request: Request,
|
||||||
|
code: str,
|
||||||
|
org_id: int,
|
||||||
|
current_user: PublicUser = Depends(get_current_user),
|
||||||
|
db_session: Session = Depends(get_db_session),
|
||||||
|
):
|
||||||
|
return await handle_stripe_oauth_callback(request, org_id, code, current_user, db_session)
|
||||||
|
|
@ -52,7 +52,8 @@ async def get_stripe_internal_credentials(
|
||||||
return {
|
return {
|
||||||
"stripe_secret_key": learnhouse_config.payments_config.stripe.stripe_secret_key,
|
"stripe_secret_key": learnhouse_config.payments_config.stripe.stripe_secret_key,
|
||||||
"stripe_publishable_key": learnhouse_config.payments_config.stripe.stripe_publishable_key,
|
"stripe_publishable_key": learnhouse_config.payments_config.stripe.stripe_publishable_key,
|
||||||
"stripe_webhook_secret": learnhouse_config.payments_config.stripe.stripe_webhook_secret,
|
"stripe_webhook_standard_secret": learnhouse_config.payments_config.stripe.stripe_webhook_standard_secret,
|
||||||
|
"stripe_webhook_connect_secret": learnhouse_config.payments_config.stripe.stripe_webhook_connect_secret,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -332,30 +333,20 @@ async def generate_stripe_connect_link(
|
||||||
# Get credentials
|
# Get credentials
|
||||||
creds = await get_stripe_internal_credentials()
|
creds = await get_stripe_internal_credentials()
|
||||||
stripe.api_key = creds.get("stripe_secret_key")
|
stripe.api_key = creds.get("stripe_secret_key")
|
||||||
|
|
||||||
|
# Get learnhouse config for client_id
|
||||||
|
learnhouse_config = get_learnhouse_config()
|
||||||
|
client_id = learnhouse_config.payments_config.stripe.stripe_client_id
|
||||||
|
|
||||||
|
if not client_id:
|
||||||
|
raise HTTPException(status_code=400, detail="Stripe client ID not configured")
|
||||||
|
|
||||||
try:
|
state = f"org_id={org_id}"
|
||||||
# Try to get existing account ID
|
|
||||||
stripe_acc_id = await get_stripe_connected_account_id(request, org_id, current_user, db_session)
|
# Generate OAuth link for existing accounts
|
||||||
except HTTPException:
|
oauth_link = f"https://connect.stripe.com/oauth/authorize?response_type=code&client_id={client_id}&scope=read_write&redirect_uri={redirect_uri}&state={state}"
|
||||||
# If no account exists, create one
|
|
||||||
stripe_account = await create_stripe_account(
|
|
||||||
request,
|
|
||||||
org_id,
|
|
||||||
"standard",
|
|
||||||
current_user,
|
|
||||||
db_session
|
|
||||||
)
|
|
||||||
stripe_acc_id = stripe_account
|
|
||||||
|
|
||||||
# Generate OAuth link
|
return {"connect_url": oauth_link}
|
||||||
connect_link = stripe.AccountLink.create(
|
|
||||||
account=str(stripe_acc_id),
|
|
||||||
type="account_onboarding",
|
|
||||||
return_url=redirect_uri,
|
|
||||||
refresh_url=redirect_uri,
|
|
||||||
)
|
|
||||||
|
|
||||||
return {"connect_url": connect_link.url}
|
|
||||||
|
|
||||||
async def create_stripe_account(
|
async def create_stripe_account(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
|
@ -438,3 +429,45 @@ async def update_stripe_account_id(
|
||||||
)
|
)
|
||||||
|
|
||||||
return {"message": "Stripe account ID updated successfully"}
|
return {"message": "Stripe account ID updated successfully"}
|
||||||
|
|
||||||
|
async def handle_stripe_oauth_callback(
|
||||||
|
request: Request,
|
||||||
|
org_id: int,
|
||||||
|
code: str,
|
||||||
|
current_user: PublicUser | AnonymousUser | InternalUser,
|
||||||
|
db_session: Session,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Handle the OAuth callback from Stripe and complete the account connection
|
||||||
|
"""
|
||||||
|
creds = await get_stripe_internal_credentials()
|
||||||
|
stripe.api_key = creds.get("stripe_secret_key")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Exchange the authorization code for an access token
|
||||||
|
response = stripe.OAuth.token(
|
||||||
|
grant_type='authorization_code',
|
||||||
|
code=code,
|
||||||
|
)
|
||||||
|
|
||||||
|
connected_account_id = response.stripe_user_id
|
||||||
|
if not connected_account_id:
|
||||||
|
raise HTTPException(status_code=400, detail="No account ID received from Stripe")
|
||||||
|
|
||||||
|
# Now connected_account_id is guaranteed to be a string
|
||||||
|
await update_stripe_account_id(
|
||||||
|
request,
|
||||||
|
org_id,
|
||||||
|
connected_account_id,
|
||||||
|
current_user,
|
||||||
|
db_session,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"success": True, "account_id": connected_account_id}
|
||||||
|
|
||||||
|
except stripe.StripeError as e:
|
||||||
|
logging.error(f"Error connecting Stripe account: {str(e)}")
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail=f"Error connecting Stripe account: {str(e)}"
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
from typing import Literal
|
||||||
from fastapi import HTTPException, Request
|
from fastapi import HTTPException, Request
|
||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
import stripe
|
import stripe
|
||||||
|
|
@ -15,11 +16,12 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
async def handle_stripe_webhook(
|
async def handle_stripe_webhook(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
webhook_type: Literal["connect", "standard"],
|
||||||
db_session: Session,
|
db_session: Session,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
# Get Stripe credentials
|
# Get Stripe credentials
|
||||||
creds = await get_stripe_internal_credentials()
|
creds = await get_stripe_internal_credentials()
|
||||||
webhook_secret = creds.get('stripe_webhook_secret')
|
webhook_secret = creds.get(f'stripe_webhook_{webhook_type}_secret')
|
||||||
stripe.api_key = creds.get("stripe_secret_key")
|
stripe.api_key = creds.get("stripe_secret_key")
|
||||||
|
|
||||||
if not webhook_secret:
|
if not webhook_secret:
|
||||||
|
|
|
||||||
124
apps/web/app/payments/stripe/connect/oauth/page.tsx
Normal file
124
apps/web/app/payments/stripe/connect/oauth/page.tsx
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
'use client'
|
||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { useRouter, useSearchParams } from 'next/navigation'
|
||||||
|
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||||
|
import { useOrg } from '@components/Contexts/OrgContext'
|
||||||
|
import { getUriWithOrg } from '@services/config/config'
|
||||||
|
import { Check, Loader2, AlertTriangle } from 'lucide-react'
|
||||||
|
import { motion } from 'framer-motion'
|
||||||
|
import toast from 'react-hot-toast'
|
||||||
|
import { verifyStripeConnection } from '@services/payments/payments'
|
||||||
|
import Image from 'next/image'
|
||||||
|
import learnhouseIcon from 'public/learnhouse_bigicon_1.png'
|
||||||
|
|
||||||
|
function StripeConnectCallback() {
|
||||||
|
const router = useRouter()
|
||||||
|
const searchParams = useSearchParams()
|
||||||
|
const session = useLHSession() as any
|
||||||
|
const [status, setStatus] = useState<'processing' | 'success' | 'error'>('processing')
|
||||||
|
const [message, setMessage] = useState('')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const verifyConnection = async () => {
|
||||||
|
try {
|
||||||
|
const code = searchParams.get('code')
|
||||||
|
const state = searchParams.get('state')
|
||||||
|
const orgId = state?.split('=')[1] // Extract org_id value after '='
|
||||||
|
|
||||||
|
if (!code || !orgId) {
|
||||||
|
throw new Error('Missing required parameters')
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await verifyStripeConnection(
|
||||||
|
parseInt(orgId),
|
||||||
|
code,
|
||||||
|
session?.data?.tokens?.access_token
|
||||||
|
)
|
||||||
|
|
||||||
|
// Wait for 1 second to show processing state
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
|
|
||||||
|
setStatus('success')
|
||||||
|
setMessage('Successfully connected to Stripe!')
|
||||||
|
|
||||||
|
// Close the window after 2 seconds of showing success
|
||||||
|
setTimeout(() => {
|
||||||
|
window.close()
|
||||||
|
}, 2000)
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error verifying Stripe connection:', error)
|
||||||
|
setStatus('error')
|
||||||
|
setMessage('Failed to complete Stripe connection')
|
||||||
|
toast.error('Failed to connect to Stripe')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session) {
|
||||||
|
verifyConnection()
|
||||||
|
}
|
||||||
|
}, [session, router, searchParams])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-screen w-full bg-[#f8f8f8] flex items-center justify-center">
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<div className="mb-10">
|
||||||
|
<Image
|
||||||
|
quality={100}
|
||||||
|
width={50}
|
||||||
|
height={50}
|
||||||
|
src={learnhouseIcon}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
className="bg-white p-8 rounded-xl nice-shadow max-w-md w-full mx-4"
|
||||||
|
>
|
||||||
|
<div className="flex flex-col items-center text-center space-y-4">
|
||||||
|
{status === 'processing' && (
|
||||||
|
<>
|
||||||
|
<Loader2 className="h-12 w-12 text-blue-500 animate-spin" />
|
||||||
|
<h2 className="text-xl font-semibold text-gray-800">
|
||||||
|
Completing Stripe Connection
|
||||||
|
</h2>
|
||||||
|
<p className="text-gray-500">
|
||||||
|
Please wait while we finish setting up your Stripe integration...
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{status === 'success' && (
|
||||||
|
<>
|
||||||
|
<div className="bg-green-100 p-3 rounded-full">
|
||||||
|
<Check className="h-8 w-8 text-green-600" />
|
||||||
|
</div>
|
||||||
|
<h2 className="text-xl font-semibold text-gray-800">{message}</h2>
|
||||||
|
<p className="text-gray-500">
|
||||||
|
You can now return to the dashboard to start using payments.
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{status === 'error' && (
|
||||||
|
<>
|
||||||
|
<div className="bg-red-100 p-3 rounded-full">
|
||||||
|
<AlertTriangle className="h-8 w-8 text-red-600" />
|
||||||
|
</div>
|
||||||
|
<h2 className="text-xl font-semibold text-gray-800">{message}</h2>
|
||||||
|
<p className="text-gray-500">
|
||||||
|
Please try again or contact support if the problem persists.
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StripeConnectCallback
|
||||||
|
|
@ -5,7 +5,7 @@ import { SiStripe } from '@icons-pack/react-simple-icons'
|
||||||
import { useLHSession } from '@components/Contexts/LHSessionContext';
|
import { useLHSession } from '@components/Contexts/LHSessionContext';
|
||||||
import { getPaymentConfigs, initializePaymentConfig, updatePaymentConfig, deletePaymentConfig, updateStripeAccountID, getStripeOnboardingLink } from '@services/payments/payments';
|
import { getPaymentConfigs, initializePaymentConfig, updatePaymentConfig, deletePaymentConfig, updateStripeAccountID, getStripeOnboardingLink } from '@services/payments/payments';
|
||||||
import FormLayout, { ButtonBlack, Input, Textarea, FormField, FormLabelAndMessage, Flex } from '@components/StyledElements/Form/Form';
|
import FormLayout, { ButtonBlack, Input, Textarea, FormField, FormLabelAndMessage, Flex } from '@components/StyledElements/Form/Form';
|
||||||
import { AlertTriangle, BarChart2, Check, Coins, CreditCard, Edit, ExternalLink, Info, Loader2, RefreshCcw, Trash2 } from 'lucide-react';
|
import { AlertTriangle, BarChart2, Check, Coins, CreditCard, Edit, ExternalLink, Info, Loader2, RefreshCcw, Trash2, UnplugIcon } from 'lucide-react';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import useSWR, { mutate } from 'swr';
|
import useSWR, { mutate } from 'swr';
|
||||||
import Modal from '@components/StyledElements/Modal/Modal';
|
import Modal from '@components/StyledElements/Modal/Modal';
|
||||||
|
|
@ -13,6 +13,7 @@ import ConfirmationModal from '@components/StyledElements/ConfirmationModal/Conf
|
||||||
import { Button } from '@components/ui/button';
|
import { Button } from '@components/ui/button';
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@components/ui/alert';
|
import { Alert, AlertDescription, AlertTitle } from '@components/ui/alert';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { getUriWithoutOrg } from '@services/config/config';
|
||||||
|
|
||||||
const PaymentsConfigurationPage: React.FC = () => {
|
const PaymentsConfigurationPage: React.FC = () => {
|
||||||
const org = useOrg() as any;
|
const org = useOrg() as any;
|
||||||
|
|
@ -62,8 +63,8 @@ const PaymentsConfigurationPage: React.FC = () => {
|
||||||
const handleStripeOnboarding = async () => {
|
const handleStripeOnboarding = async () => {
|
||||||
try {
|
try {
|
||||||
setIsOnboardingLoading(true);
|
setIsOnboardingLoading(true);
|
||||||
const { connect_url } = await getStripeOnboardingLink(org.id, access_token, window.location.href);
|
const { connect_url } = await getStripeOnboardingLink(org.id, access_token, getUriWithoutOrg('/payments/stripe/connect/oauth'));
|
||||||
router.push(connect_url);
|
window.open(connect_url, '_blank');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error getting onboarding link:', error);
|
console.error('Error getting onboarding link:', error);
|
||||||
toast.error('Failed to start Stripe onboarding');
|
toast.error('Failed to start Stripe onboarding');
|
||||||
|
|
@ -152,37 +153,30 @@ const PaymentsConfigurationPage: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
{ (
|
{(!stripeConfig.provider_specific_id || !stripeConfig.active) && (
|
||||||
<Button
|
<Button
|
||||||
onClick={handleStripeOnboarding}
|
onClick={handleStripeOnboarding}
|
||||||
className="flex items-center space-x-2 px-4 py-2 bg-yellow-500 text-white text-sm rounded-full hover:bg-yellow-600 transition duration-300 disabled:opacity-50 disabled:cursor-not-allowed border-2 border-yellow-400 shadow-md"
|
className="flex items-center space-x-2 px-4 py-2 bg-green-500 text-white text-sm rounded-full hover:bg-green-600 transition duration-300 disabled:opacity-50 disabled:cursor-not-allowed border-2 border-green-400 shadow-md"
|
||||||
disabled={isOnboardingLoading}
|
disabled={isOnboardingLoading}
|
||||||
>
|
>
|
||||||
{isOnboardingLoading ? (
|
{isOnboardingLoading ? (
|
||||||
<Loader2 className="animate-spin h-4 w-4" />
|
<Loader2 className="animate-spin h-4 w-4" />
|
||||||
) : (
|
) : (
|
||||||
<AlertTriangle className="h-4 w-4" />
|
<UnplugIcon className="h-3 w-3" />
|
||||||
)}
|
)}
|
||||||
<span className="font-semibold">Complete Onboarding</span>
|
<span className="font-semibold">Connect with Stripe</span>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button
|
|
||||||
onClick={editConfig}
|
|
||||||
className="flex items-center space-x-2 px-4 py-2 bg-white text-purple-700 text-sm rounded-full hover:bg-gray-100 transition duration-300 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
||||||
>
|
|
||||||
<Edit size={16} />
|
|
||||||
<span>Edit Configuration</span>
|
|
||||||
</Button>
|
|
||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
confirmationButtonText="Delete Configuration"
|
confirmationButtonText="Remove Connection"
|
||||||
confirmationMessage="Are you sure you want to delete the Stripe configuration? This action cannot be undone."
|
confirmationMessage="Are you sure you want to remove the Stripe connection? This action cannot be undone."
|
||||||
dialogTitle="Delete Stripe Configuration"
|
dialogTitle="Remove Stripe Connection"
|
||||||
dialogTrigger={
|
dialogTrigger={
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center space-x-2 bg-red-500 text-white text-sm rounded-full hover:bg-red-600 transition duration-300 disabled:opacity-50 disabled:cursor-not-allowed"
|
className="flex items-center space-x-2 bg-red-500 text-white text-sm rounded-full hover:bg-red-600 transition duration-300 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
<Trash2 size={16} />
|
<Trash2 size={16} />
|
||||||
<span>Delete Configuration</span>
|
<span>Remove Connection</span>
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
functionToExecute={deleteConfig}
|
functionToExecute={deleteConfig}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ export const config = {
|
||||||
*/
|
*/
|
||||||
'/((?!api|_next|fonts|umami|examples|[\\w-]+\\.\\w+).*)',
|
'/((?!api|_next|fonts|umami|examples|[\\w-]+\\.\\w+).*)',
|
||||||
'/sitemap.xml',
|
'/sitemap.xml',
|
||||||
|
'/payments/stripe/connect/oauth',
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,6 +81,27 @@ export default async function middleware(req: NextRequest) {
|
||||||
return NextResponse.rewrite(new URL(`/editor${pathname}`, req.url))
|
return NextResponse.rewrite(new URL(`/editor${pathname}`, req.url))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the request is for the Stripe callback URL
|
||||||
|
if (req.nextUrl.pathname.startsWith('/payments/stripe/connect/oauth')) {
|
||||||
|
const searchParams = req.nextUrl.searchParams
|
||||||
|
const orgslug = searchParams.get('state')?.split('_')[0] // Assuming state parameter contains orgslug_randomstring
|
||||||
|
|
||||||
|
// Construct the new URL with the required parameters
|
||||||
|
const redirectUrl = new URL('/payments/stripe/connect/oauth', req.url)
|
||||||
|
|
||||||
|
// Preserve all original search parameters
|
||||||
|
searchParams.forEach((value, key) => {
|
||||||
|
redirectUrl.searchParams.append(key, value)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add orgslug if available
|
||||||
|
if (orgslug) {
|
||||||
|
redirectUrl.searchParams.set('orgslug', orgslug)
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.rewrite(redirectUrl)
|
||||||
|
}
|
||||||
|
|
||||||
// Auth Redirects
|
// Auth Redirects
|
||||||
if (pathname == '/redirect_from_auth') {
|
if (pathname == '/redirect_from_auth') {
|
||||||
if (cookie_orgslug) {
|
if (cookie_orgslug) {
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,15 @@ export async function getStripeOnboardingLink(orgId: number, access_token: strin
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function verifyStripeConnection(orgId: number, code: string, access_token: string) {
|
||||||
|
const result = await fetch(
|
||||||
|
`${getAPIUrl()}payments/stripe/oauth/callback?code=${code}&org_id=${orgId}`,
|
||||||
|
RequestBodyWithAuthHeader('GET', null, null, access_token)
|
||||||
|
);
|
||||||
|
const res = await errorHandling(result);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
export async function deletePaymentConfig(orgId: number, id: string, access_token: string) {
|
export async function deletePaymentConfig(orgId: number, id: string, access_token: string) {
|
||||||
const result = await fetch(
|
const result = await fetch(
|
||||||
`${getAPIUrl()}payments/${orgId}/config?id=${id}`,
|
`${getAPIUrl()}payments/${orgId}/config?id=${id}`,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue