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 }) => ( + +
+
+ {icon} +
{label}
+
+
+ +) + +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 ( +
+
+
+ + + Learnhouse logo + + +
+ {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; +}