diff --git a/apps/api/src/db/collections.py b/apps/api/src/db/collections.py
index fb0b1e94..7fd3c524 100644
--- a/apps/api/src/db/collections.py
+++ b/apps/api/src/db/collections.py
@@ -30,7 +30,7 @@ class CollectionUpdate(CollectionBase):
courses: Optional[list]
name: Optional[str]
public: Optional[bool]
- description: Optional[str]
+ description: Optional[str] = ""
class CollectionRead(CollectionBase):
diff --git a/apps/api/src/db/payments/payments.py b/apps/api/src/db/payments/payments.py
new file mode 100644
index 00000000..282fbf59
--- /dev/null
+++ b/apps/api/src/db/payments/payments.py
@@ -0,0 +1,49 @@
+from datetime import datetime
+from enum import Enum
+from typing import Literal, Optional
+from pydantic import BaseModel
+from sqlalchemy import JSON
+from sqlmodel import Field, SQLModel, Column, BigInteger, ForeignKey
+
+
+class StripeProviderConfig(BaseModel):
+ stripe_key: str = ""
+ stripe_secret_key: str = ""
+ stripe_webhook_secret: str = ""
+
+class PaymentProviderEnum(str, Enum):
+ STRIPE = "stripe"
+
+class PaymentsConfigBase(SQLModel):
+ enabled: bool = False
+ provider: PaymentProviderEnum = PaymentProviderEnum.STRIPE
+ provider_config: dict = Field(default={}, sa_column=Column(JSON))
+
+
+class PaymentsConfig(PaymentsConfigBase, table=True):
+ id: Optional[int] = Field(default=None, primary_key=True)
+ org_id: int = Field(
+ sa_column=Column(BigInteger, ForeignKey("organization.id", ondelete="CASCADE"))
+ )
+ creation_date: datetime = Field(default=datetime.now())
+ update_date: datetime = Field(default=datetime.now())
+
+
+class PaymentsConfigCreate(PaymentsConfigBase):
+ pass
+
+
+class PaymentsConfigUpdate(PaymentsConfigBase):
+ enabled: Optional[bool] = False
+ provider_config: Optional[dict] = None
+
+
+class PaymentsConfigRead(PaymentsConfigBase):
+ id: int
+ org_id: int
+ creation_date: datetime
+ update_date: datetime
+
+
+class PaymentsConfigDelete(SQLModel):
+ id: int
diff --git a/apps/api/src/router.py b/apps/api/src/router.py
index bc4aaaa8..ef3e226c 100644
--- a/apps/api/src/router.py
+++ b/apps/api/src/router.py
@@ -5,7 +5,7 @@ from src.routers import dev, trail, users, auth, orgs, roles
from src.routers.ai import ai
from src.routers.courses import chapters, collections, courses, assignments
from src.routers.courses.activities import activities, blocks
-from src.routers.ee import cloud_internal
+from src.routers.ee import cloud_internal, payments
from src.routers.install import install
from src.services.dev.dev import isDevModeEnabledOrRaise
from src.services.install.install import isInstallModeEnabled
@@ -32,6 +32,7 @@ v1_router.include_router(
)
v1_router.include_router(trail.router, prefix="/trail", tags=["trail"])
v1_router.include_router(ai.router, prefix="/ai", tags=["ai"])
+v1_router.include_router(payments.router, prefix="/payments", tags=["payments"])
if os.environ.get("CLOUD_INTERNAL_KEY"):
v1_router.include_router(
diff --git a/apps/api/src/routers/ee/payments.py b/apps/api/src/routers/ee/payments.py
new file mode 100644
index 00000000..d7eced09
--- /dev/null
+++ b/apps/api/src/routers/ee/payments.py
@@ -0,0 +1,53 @@
+from fastapi import APIRouter, Depends, Request
+from sqlmodel import Session
+from src.core.events.database import get_db_session
+from src.db.payments.payments import PaymentsConfig, PaymentsConfigBase, PaymentsConfigCreate, PaymentsConfigRead, PaymentsConfigUpdate
+from src.db.users import PublicUser
+from src.security.auth import get_current_user
+from src.services.payments.payments import (
+ create_payments_config,
+ get_payments_config,
+ update_payments_config,
+ delete_payments_config,
+)
+
+router = APIRouter()
+
+@router.post("/{org_id}/config")
+async def api_create_payments_config(
+ request: Request,
+ org_id: int,
+ payments_config: PaymentsConfigCreate,
+ current_user: PublicUser = Depends(get_current_user),
+ db_session: Session = Depends(get_db_session),
+) -> PaymentsConfig:
+ return await create_payments_config(request, org_id, payments_config, current_user, db_session)
+
+@router.get("/{org_id}/config")
+async def api_get_payments_config(
+ request: Request,
+ org_id: int,
+ current_user: PublicUser = Depends(get_current_user),
+ db_session: Session = Depends(get_db_session),
+) -> list[PaymentsConfigRead]:
+ return await get_payments_config(request, org_id, current_user, db_session)
+
+@router.put("/{org_id}/config")
+async def api_update_payments_config(
+ request: Request,
+ org_id: int,
+ payments_config: PaymentsConfigUpdate,
+ current_user: PublicUser = Depends(get_current_user),
+ db_session: Session = Depends(get_db_session),
+) -> PaymentsConfig:
+ return await update_payments_config(request, org_id, payments_config, current_user, db_session)
+
+@router.delete("/{org_id}/config")
+async def api_delete_payments_config(
+ request: Request,
+ org_id: int,
+ current_user: PublicUser = Depends(get_current_user),
+ db_session: Session = Depends(get_db_session),
+):
+ await delete_payments_config(request, org_id, current_user, db_session)
+ return {"message": "Payments config deleted successfully"}
diff --git a/apps/api/src/services/payments/payments.py b/apps/api/src/services/payments/payments.py
new file mode 100644
index 00000000..8b77043f
--- /dev/null
+++ b/apps/api/src/services/payments/payments.py
@@ -0,0 +1,127 @@
+from typing import Optional
+from fastapi import HTTPException, Request
+from sqlmodel import Session, select
+from src.db.payments.payments import (
+ PaymentsConfig,
+ PaymentsConfigCreate,
+ PaymentsConfigUpdate,
+ PaymentsConfigRead,
+)
+from src.db.users import PublicUser, AnonymousUser
+from src.db.organizations import Organization
+from src.services.orgs.orgs import rbac_check
+
+
+async def create_payments_config(
+ request: Request,
+ org_id: int,
+ payments_config: PaymentsConfigCreate,
+ current_user: PublicUser | AnonymousUser,
+ db_session: Session,
+) -> PaymentsConfig:
+ # Check if organization exists
+ statement = select(Organization).where(Organization.id == org_id)
+ org = db_session.exec(statement).first()
+ if not org:
+ raise HTTPException(status_code=404, detail="Organization not found")
+
+ # RBAC check
+ await rbac_check(request, org.org_uuid, current_user, "create", db_session)
+
+ # Check if payments config already exists for this organization
+ statement = select(PaymentsConfig).where(PaymentsConfig.org_id == org_id)
+ existing_config = db_session.exec(statement).first()
+ if existing_config:
+ raise HTTPException(
+ status_code=409,
+ detail="Payments config already exists for this organization",
+ )
+
+ # Create new payments config
+ new_config = PaymentsConfig(**payments_config.model_dump(), org_id=org_id)
+ db_session.add(new_config)
+ db_session.commit()
+ db_session.refresh(new_config)
+
+ return new_config
+
+
+async def get_payments_config(
+ request: Request,
+ org_id: int,
+ current_user: PublicUser | AnonymousUser,
+ db_session: Session,
+) -> list[PaymentsConfigRead]:
+ # Check if organization exists
+ statement = select(Organization).where(Organization.id == org_id)
+ org = db_session.exec(statement).first()
+ if not org:
+ raise HTTPException(status_code=404, detail="Organization not found")
+
+ # RBAC check
+ await rbac_check(request, org.org_uuid, current_user, "read", db_session)
+
+ # Get payments config
+ statement = select(PaymentsConfig).where(PaymentsConfig.org_id == org_id)
+ configs = db_session.exec(statement).all()
+
+ return [PaymentsConfigRead.model_validate(config) for config in configs]
+
+
+async def update_payments_config(
+ request: Request,
+ org_id: int,
+ payments_config: PaymentsConfigUpdate,
+ current_user: PublicUser | AnonymousUser,
+ db_session: Session,
+) -> PaymentsConfig:
+ # Check if organization exists
+ statement = select(Organization).where(Organization.id == org_id)
+ org = db_session.exec(statement).first()
+ if not org:
+ raise HTTPException(status_code=404, detail="Organization not found")
+
+ # RBAC check
+ await rbac_check(request, org.org_uuid, current_user, "update", db_session)
+
+ # Get existing payments config
+ statement = select(PaymentsConfig).where(PaymentsConfig.org_id == org_id)
+ config = db_session.exec(statement).first()
+ if not config:
+ raise HTTPException(status_code=404, detail="Payments config not found")
+
+ # Update config
+ for key, value in payments_config.model_dump().items():
+ setattr(config, key, value)
+
+ db_session.add(config)
+ db_session.commit()
+ db_session.refresh(config)
+
+ return config
+
+
+async def delete_payments_config(
+ request: Request,
+ org_id: int,
+ current_user: PublicUser | AnonymousUser,
+ db_session: Session,
+) -> None:
+ # Check if organization exists
+ statement = select(Organization).where(Organization.id == org_id)
+ org = db_session.exec(statement).first()
+ if not org:
+ raise HTTPException(status_code=404, detail="Organization not found")
+
+ # RBAC check
+ await rbac_check(request, org.org_uuid, current_user, "delete", db_session)
+
+ # Get existing payments config
+ statement = select(PaymentsConfig).where(PaymentsConfig.org_id == org_id)
+ config = db_session.exec(statement).first()
+ if not config:
+ raise HTTPException(status_code=404, detail="Payments config not found")
+
+ # Delete config
+ db_session.delete(config)
+ db_session.commit()
diff --git a/apps/web/app/orgs/[orgslug]/(withmenu)/layout.tsx b/apps/web/app/orgs/[orgslug]/(withmenu)/layout.tsx
index 96e5c1aa..f532da02 100644
--- a/apps/web/app/orgs/[orgslug]/(withmenu)/layout.tsx
+++ b/apps/web/app/orgs/[orgslug]/(withmenu)/layout.tsx
@@ -1,8 +1,8 @@
'use client'
import '@styles/globals.css'
-import { Menu } from '@components/Objects/Menu/Menu'
import { SessionProvider } from 'next-auth/react'
import Watermark from '@components/Watermark'
+import { OrgMenu } from '@components/Objects/Menus/OrgMenu/OrgMenu'
export default function RootLayout({
children,
@@ -14,7 +14,7 @@ export default function RootLayout({
return (
<>
-
+
{children}
diff --git a/apps/web/app/orgs/[orgslug]/dash/payments/[subpage]/page.tsx b/apps/web/app/orgs/[orgslug]/dash/payments/[subpage]/page.tsx
new file mode 100644
index 00000000..3c8035e1
--- /dev/null
+++ b/apps/web/app/orgs/[orgslug]/dash/payments/[subpage]/page.tsx
@@ -0,0 +1,139 @@
+'use client'
+import React, { useState, useEffect } from 'react'
+import { motion } from 'framer-motion'
+import BreadCrumbs from '@components/Dashboard/UI/BreadCrumbs'
+import Link from 'next/link'
+import { getUriWithOrg } from '@services/config/config'
+import { CreditCard, Settings, Repeat, BookOpen, Users, DollarSign } from 'lucide-react'
+import { useLHSession } from '@components/Contexts/LHSessionContext'
+import { useOrg } from '@components/Contexts/OrgContext'
+import PaymentsConfigurationPage from '@components/Dashboard/Payments/PaymentsConfigurationPage'
+
+
+
+export type PaymentsParams = {
+ subpage: string
+ orgslug: string
+}
+
+function PaymentsPage({ params }: { params: PaymentsParams }) {
+ const session = useLHSession() as any
+ const org = useOrg() as any
+ const [selectedSubPage, setSelectedSubPage] = useState(params.subpage || 'general')
+ const [H1Label, setH1Label] = useState('')
+ const [H2Label, setH2Label] = useState('')
+
+ useEffect(() => {
+ handleLabels()
+ }, [selectedSubPage])
+
+ function handleLabels() {
+ if (selectedSubPage === 'general') {
+ setH1Label('Payments')
+ setH2Label('Overview of your payment settings and transactions')
+ }
+ if (selectedSubPage === 'configuration') {
+ setH1Label('Payment Configuration')
+ setH2Label('Set up and manage your payment gateway')
+ }
+ if (selectedSubPage === 'subscriptions') {
+ setH1Label('Subscriptions')
+ setH2Label('Manage your subscription plans')
+ }
+ if (selectedSubPage === 'paid-courses') {
+ setH1Label('Paid Courses')
+ setH2Label('Manage your paid courses and pricing')
+ }
+ if (selectedSubPage === 'customers') {
+ setH1Label('Customers')
+ setH2Label('View and manage your customer information')
+ }
+ }
+
+ return (
+
+
+
+
+
+
+ {H1Label}
+
+
+ {H2Label}{' '}
+
+
+
+
+ }
+ label="General"
+ isActive={selectedSubPage === 'general'}
+ onClick={() => setSelectedSubPage('general')}
+ />
+ }
+ label="Configuration"
+ isActive={selectedSubPage === 'configuration'}
+ onClick={() => setSelectedSubPage('configuration')}
+ />
+ }
+ label="Subscriptions"
+ isActive={selectedSubPage === 'subscriptions'}
+ onClick={() => setSelectedSubPage('subscriptions')}
+ />
+ }
+ label="Paid Courses"
+ isActive={selectedSubPage === 'paid-courses'}
+ onClick={() => setSelectedSubPage('paid-courses')}
+ />
+ }
+ label="Customers"
+ isActive={selectedSubPage === 'customers'}
+ onClick={() => setSelectedSubPage('customers')}
+ />
+
+
+
+
+ {selectedSubPage === 'general' && General
}
+ {selectedSubPage === 'configuration' && }
+ {selectedSubPage === 'subscriptions' && Subscriptions
}
+ {selectedSubPage === 'paid-courses' && Paid Courses
}
+ {selectedSubPage === 'customers' && Customers
}
+
+
+ )
+}
+
+const TabLink = ({ href, icon, label, isActive, onClick }: { href: string, icon: React.ReactNode, label: string, isActive: boolean, onClick: () => void }) => (
+
+
+
+)
+
+export default PaymentsPage
diff --git a/apps/web/components/Dashboard/Payments/PaymentsConfigurationPage.tsx b/apps/web/components/Dashboard/Payments/PaymentsConfigurationPage.tsx
new file mode 100644
index 00000000..82629669
--- /dev/null
+++ b/apps/web/components/Dashboard/Payments/PaymentsConfigurationPage.tsx
@@ -0,0 +1,148 @@
+import React, { useState } from 'react';
+import { useOrg } from '@components/Contexts/OrgContext';
+import { SiStripe } from '@icons-pack/react-simple-icons'
+import { useLHSession } from '@components/Contexts/LHSessionContext';
+import { getPaymentConfigs, createPaymentConfig, updatePaymentConfig } from '@services/payments/payments';
+import FormLayout, { ButtonBlack, Input, Textarea, FormField, FormLabelAndMessage, Flex } from '@components/StyledElements/Form/Form';
+import { Check, Edit } from 'lucide-react';
+import toast from 'react-hot-toast';
+import useSWR, { mutate } from 'swr';
+import Modal from '@components/StyledElements/Modal/Modal';
+
+const PaymentsConfigurationPage: React.FC = () => {
+ const org = useOrg() as any;
+ const session = useLHSession() as any;
+ const access_token = session?.data?.tokens?.access_token;
+ const { data: paymentConfigs, error, isLoading } = useSWR(
+ () => (org && access_token ? [`/payments/${org.id}/config`, access_token] : null),
+ ([url, token]) => getPaymentConfigs(org.id, token)
+ );
+
+ const stripeConfig = paymentConfigs?.find((config: any) => config.provider === 'stripe');
+ const [isModalOpen, setIsModalOpen] = useState(false);
+
+ const enableStripe = async () => {
+ try {
+ const newConfig = { provider: 'stripe', enabled: true };
+ const config = await createPaymentConfig(org.id, newConfig, access_token);
+ toast.success('Stripe enabled successfully');
+ mutate([`/payments/${org.id}/config`, access_token]);
+ } catch (error) {
+ console.error('Error enabling Stripe:', error);
+ toast.error('Failed to enable Stripe');
+ }
+ };
+
+ const editConfig = async () => {
+ setIsModalOpen(true);
+ };
+
+ if (isLoading) {
+ return Loading...
;
+ }
+
+ if (error) {
+ return Error loading payment configuration
;
+ }
+
+ return (
+
+
+
+
Payments Configuration
+ Manage your organization payments configuration
+
+
+ {stripeConfig ? (
+
+
+
+ Stripe is enabled
+
+
+
+ Edit Configuration
+
+
+ ) : (
+
+
+ Enable Stripe
+
+ )}
+
+
+ {stripeConfig && (
+
setIsModalOpen(false)}
+ />
+ )}
+
+ );
+};
+
+interface EditStripeConfigModalProps {
+ orgId: number;
+ configId: string;
+ accessToken: string;
+ isOpen: boolean;
+ onClose: () => void;
+}
+
+const EditStripeConfigModal: React.FC = ({ orgId, configId, accessToken, isOpen, onClose }) => {
+ const [stripeKey, setStripeKey] = useState('');
+ const [stripeSecretKey, setStripeSecretKey] = useState('');
+ const [stripeWebhookSecret, setStripeWebhookSecret] = useState('');
+
+ const handleSubmit = async () => {
+ try {
+ const stripe_config = {
+ stripe_key: stripeKey,
+ stripe_secret_key: stripeSecretKey,
+ stripe_webhook_secret: stripeWebhookSecret,
+ };
+ const updatedConfig = {
+ provider_config: stripe_config,
+ };
+ await updatePaymentConfig(orgId, configId, updatedConfig, accessToken);
+ toast.success('Configuration updated successfully');
+ mutate([`/payments/${orgId}/config`, accessToken]);
+ onClose();
+ } catch (error) {
+ console.error('Error updating config:', error);
+ toast.error('Failed to update configuration');
+ }
+ };
+
+ return (
+
+
+
+ setStripeKey(e.target.value)} />
+
+
+
+ setStripeSecretKey(e.target.value)} />
+
+
+
+ setStripeWebhookSecret(e.target.value)} />
+
+
+
+ Save
+
+
+
+ }
+ />
+ );
+};
+
+export default PaymentsConfigurationPage;
diff --git a/apps/web/components/Dashboard/UI/BreadCrumbs.tsx b/apps/web/components/Dashboard/UI/BreadCrumbs.tsx
index f242f315..9e0d9dde 100644
--- a/apps/web/components/Dashboard/UI/BreadCrumbs.tsx
+++ b/apps/web/components/Dashboard/UI/BreadCrumbs.tsx
@@ -1,11 +1,11 @@
'use client';
import { useOrg } from '@components/Contexts/OrgContext';
-import { Backpack, Book, ChevronRight, School, User, Users } from 'lucide-react'
+import { Backpack, Book, ChevronRight, CreditCard, School, User, Users } from 'lucide-react'
import Link from 'next/link'
import React from 'react'
type BreadCrumbsProps = {
- type: 'courses' | 'user' | 'users' | 'org' | 'orgusers' | 'assignments'
+ type: 'courses' | 'user' | 'users' | 'org' | 'orgusers' | 'assignments' | 'payments'
last_breadcrumb?: string
}
@@ -65,6 +65,15 @@ function BreadCrumbs(props: BreadCrumbsProps) {
) : (
''
)}
+ {props.type == 'payments' ? (
+
+ {' '}
+
+ Payments
+
+ ) : (
+ ''
+ )}
{props.last_breadcrumb ?
: ''}
diff --git a/apps/web/components/Objects/Menus/DashMenu.tsx b/apps/web/components/Objects/Menus/DashMenu.tsx
new file mode 100644
index 00000000..4db6af81
--- /dev/null
+++ b/apps/web/components/Objects/Menus/DashMenu.tsx
@@ -0,0 +1,195 @@
+'use client'
+import { useOrg } from '@components/Contexts/OrgContext'
+import { signOut } from 'next-auth/react'
+import ToolTip from '@components/StyledElements/Tooltip/Tooltip'
+import LearnHouseDashboardLogo from '@public/dashLogo.png'
+import { Backpack, BookCopy, CreditCard, Home, LogOut, School, Settings, Users } from 'lucide-react'
+import Image from 'next/image'
+import Link from 'next/link'
+import React, { useEffect } from 'react'
+import UserAvatar from '../UserAvatar'
+import AdminAuthorization from '@components/Security/AdminAuthorization'
+import { useLHSession } from '@components/Contexts/LHSessionContext'
+import { getUriWithOrg, getUriWithoutOrg } from '@services/config/config'
+
+
+function DashLeftMenu() {
+
+ const org = useOrg() as any
+ const session = useLHSession() as any
+ const [loading, setLoading] = React.useState(true)
+
+ function waitForEverythingToLoad() {
+ if (org && session) {
+ return true
+ }
+ return false
+ }
+
+ async function logOutUI() {
+ const res = await signOut({ redirect: true, callbackUrl: getUriWithoutOrg('/login?orgslug=' + org.slug) })
+ if (res) {
+ getUriWithOrg(org.slug, '/')
+ }
+ }
+
+ useEffect(() => {
+ if (waitForEverythingToLoad()) {
+ setLoading(false)
+ }
+ }, [loading])
+
+ return (
+
+
+
+
+
+
+
+
+
+ {org?.name}
+
+
+
+
+
+ {/*
+
+ */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ logOutUI()}
+ className="mx-auto text-neutral-400 cursor-pointer"
+ size={14}
+ />
+
+
+
+
+
+
+ )
+}
+
+export default DashLeftMenu
+
diff --git a/apps/web/components/Objects/Menu/Menu.tsx b/apps/web/components/Objects/Menus/OrgMenu/OrgMenu.tsx
similarity index 99%
rename from apps/web/components/Objects/Menu/Menu.tsx
rename to apps/web/components/Objects/Menus/OrgMenu/OrgMenu.tsx
index ec33ddd8..22eef5c2 100644
--- a/apps/web/components/Objects/Menu/Menu.tsx
+++ b/apps/web/components/Objects/Menus/OrgMenu/OrgMenu.tsx
@@ -3,12 +3,12 @@ import React from 'react'
import Link from 'next/link'
import { getUriWithOrg } from '@services/config/config'
import { HeaderProfileBox } from '@components/Security/HeaderProfileBox'
-import MenuLinks from './MenuLinks'
+import MenuLinks from './OrgMenuLinks'
import { getOrgLogoMediaDirectory } from '@services/media/media'
import { useLHSession } from '@components/Contexts/LHSessionContext'
import { useOrg } from '@components/Contexts/OrgContext'
-export const Menu = (props: any) => {
+export const OrgMenu = (props: any) => {
const orgslug = props.orgslug
const session = useLHSession() as any;
const access_token = session?.data?.tokens?.access_token;
diff --git a/apps/web/components/Objects/Menu/MenuLinks.tsx b/apps/web/components/Objects/Menus/OrgMenu/OrgMenuLinks.tsx
similarity index 100%
rename from apps/web/components/Objects/Menu/MenuLinks.tsx
rename to apps/web/components/Objects/Menus/OrgMenu/OrgMenuLinks.tsx
diff --git a/apps/web/package.json b/apps/web/package.json
index 65a17163..9ba7cb3a 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -12,6 +12,7 @@
},
"dependencies": {
"@hocuspocus/provider": "^2.13.6",
+ "@icons-pack/react-simple-icons": "^10.0.0",
"@radix-ui/colors": "^0.1.9",
"@radix-ui/react-aspect-ratio": "^1.1.0",
"@radix-ui/react-dialog": "^1.1.1",
diff --git a/apps/web/pnpm-lock.yaml b/apps/web/pnpm-lock.yaml
index dac54b9a..727d0a9d 100644
--- a/apps/web/pnpm-lock.yaml
+++ b/apps/web/pnpm-lock.yaml
@@ -8425,8 +8425,8 @@ snapshots:
'@typescript-eslint/parser': 8.8.1(eslint@8.57.1)(typescript@5.4.4)
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
- eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.4.4))(eslint@8.57.1))(eslint@8.57.1)
- eslint-plugin-import: 2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.4.4))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.4.4))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
+ eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1)
+ eslint-plugin-import: 2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.4.4))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.1)
eslint-plugin-react: 7.36.1(eslint@8.57.1)
eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1)
@@ -8451,13 +8451,13 @@ snapshots:
debug: 4.3.7
enhanced-resolve: 5.17.1
eslint: 8.57.1
- eslint-module-utils: 2.11.1(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.4.4))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
+ eslint-module-utils: 2.11.1(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1)
fast-glob: 3.3.2
get-tsconfig: 4.8.1
is-bun-module: 1.2.1
is-glob: 4.0.3
optionalDependencies:
- eslint-plugin-import: 2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.4.4))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.4.4))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
+ eslint-plugin-import: 2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.4.4))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
transitivePeerDependencies:
- "@typescript-eslint/parser"
- eslint-import-resolver-node
@@ -8471,7 +8471,7 @@ snapshots:
'@typescript-eslint/parser': 8.8.1(eslint@8.57.1)(typescript@5.4.4)
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
- eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.4.4))(eslint@8.57.1))(eslint@8.57.1)
+ eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1)
transitivePeerDependencies:
- supports-color
@@ -8486,7 +8486,7 @@ snapshots:
doctrine: 2.1.0
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.11.1(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.4.4))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
+ eslint-module-utils: 2.11.1(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.4.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1))(eslint@8.57.1)
hasown: 2.0.2
is-core-module: 2.15.1
is-glob: 4.0.3
diff --git a/apps/web/services/payments/payments.ts b/apps/web/services/payments/payments.ts
new file mode 100644
index 00000000..8861e331
--- /dev/null
+++ b/apps/web/services/payments/payments.ts
@@ -0,0 +1,38 @@
+import { getAPIUrl } from '@services/config/config';
+import { RequestBodyWithAuthHeader, errorHandling } from '@services/utils/ts/requests';
+
+export async function getPaymentConfigs(orgId: number, access_token: string) {
+ const result = await fetch(
+ `${getAPIUrl()}payments/${orgId}/config`,
+ RequestBodyWithAuthHeader('GET', null, null, access_token)
+ );
+ const res = await errorHandling(result);
+ return res;
+}
+
+export async function createPaymentConfig(orgId: number, data: any, access_token: string) {
+ const result = await fetch(
+ `${getAPIUrl()}payments/${orgId}/config`,
+ RequestBodyWithAuthHeader('POST', data, null, access_token)
+ );
+ const res = await errorHandling(result);
+ return res;
+}
+
+export async function updatePaymentConfig(orgId: number, id: string, data: any, access_token: string) {
+ const result = await fetch(
+ `${getAPIUrl()}payments/${orgId}/config`,
+ RequestBodyWithAuthHeader('PUT', data, null, access_token)
+ );
+ const res = await errorHandling(result);
+ return res;
+}
+
+export async function deletePaymentConfig(orgId: number, id: string, access_token: string) {
+ const result = await fetch(
+ `${getAPIUrl()}payments/${orgId}/config/${id}`,
+ RequestBodyWithAuthHeader('DELETE', null, null, access_token)
+ );
+ const res = await errorHandling(result);
+ return res;
+}