From cdd893ca6ff4befd5ff38bf4fd862c796e1a8ffd Mon Sep 17 00:00:00 2001 From: swve Date: Sun, 3 Nov 2024 00:56:52 +0100 Subject: [PATCH] feat: prevent access removal if user has paid for a product --- .../src/services/payments/payments_products.py | 13 +++++++++++++ .../api/src/services/payments/payments_users.py | 10 +++++++--- apps/api/src/services/payments/stripe.py | 2 +- .../[orgslug]/dash/user-account/owned/page.tsx | 13 +++++++++---- .../Dashboard/Payments/PaymentsProductPage.tsx | 17 ++++++++--------- apps/web/services/payments/payments.ts | 1 + apps/web/services/payments/products.ts | 1 + 7 files changed, 40 insertions(+), 17 deletions(-) diff --git a/apps/api/src/services/payments/payments_products.py b/apps/api/src/services/payments/payments_products.py index d0963fe0..ce2dede1 100644 --- a/apps/api/src/services/payments/payments_products.py +++ b/apps/api/src/services/payments/payments_products.py @@ -9,6 +9,7 @@ from src.db.payments.payments_products import ( PaymentsProductUpdate, PaymentsProductRead, ) +from src.db.payments.payments_users import PaymentStatusEnum, PaymentsUser from src.db.users import PublicUser, AnonymousUser from src.db.organizations import Organization from src.services.orgs.orgs import rbac_check @@ -138,6 +139,18 @@ async def delete_payments_product( if not product: raise HTTPException(status_code=404, detail="Payments product not found") + # Check if there are any payment users linked to this product + statement = select(PaymentsUser).where( + PaymentsUser.payment_product_id == product_id, + PaymentsUser.status.in_([PaymentStatusEnum.ACTIVE, PaymentStatusEnum.COMPLETED]) # type: ignore + ) + payment_users = db_session.exec(statement).all() + if payment_users: + raise HTTPException( + status_code=400, + detail="Cannot delete product because users have paid access to it." + ) + # Archive product in Stripe await archive_stripe_product(request, org_id, product.provider_product_id, current_user, db_session) diff --git a/apps/api/src/services/payments/payments_users.py b/apps/api/src/services/payments/payments_users.py index 077ab713..16af55a6 100644 --- a/apps/api/src/services/payments/payments_users.py +++ b/apps/api/src/services/payments/payments_users.py @@ -43,7 +43,7 @@ async def create_payment_user( stripe_customer=provider_data if provider_data else None, ) - # Check if user already has a payment user + # Check if user already has a payment user for this product statement = select(PaymentsUser).where( PaymentsUser.user_id == user_id, PaymentsUser.org_id == org_id, @@ -52,8 +52,12 @@ async def create_payment_user( existing_payment_user = db_session.exec(statement).first() if existing_payment_user: - if existing_payment_user.status == PaymentStatusEnum.PENDING: - # Delete existing pending payment + # If status is PENDING, CANCELLED, or FAILED, delete the existing record + if existing_payment_user.status in [ + PaymentStatusEnum.PENDING, + PaymentStatusEnum.CANCELLED, + PaymentStatusEnum.FAILED + ]: db_session.delete(existing_payment_user) db_session.commit() else: diff --git a/apps/api/src/services/payments/stripe.py b/apps/api/src/services/payments/stripe.py index 6154b9a0..b7c8643d 100644 --- a/apps/api/src/services/payments/stripe.py +++ b/apps/api/src/services/payments/stripe.py @@ -208,7 +208,7 @@ async def create_checkout_session( product_id=product_id, status=PaymentStatusEnum.PENDING, provider_data=customer, - current_user=current_user, + current_user=InternalUser(), db_session=db_session ) diff --git a/apps/web/app/orgs/[orgslug]/dash/user-account/owned/page.tsx b/apps/web/app/orgs/[orgslug]/dash/user-account/owned/page.tsx index f2b83c14..767db3cf 100644 --- a/apps/web/app/orgs/[orgslug]/dash/user-account/owned/page.tsx +++ b/apps/web/app/orgs/[orgslug]/dash/user-account/owned/page.tsx @@ -7,7 +7,7 @@ import useSWR from 'swr' import { getOwnedCourses } from '@services/payments/payments' import CourseThumbnail from '@components/Objects/Thumbnails/CourseThumbnail' import PageLoading from '@components/Objects/Loaders/PageLoading' -import { BookOpen } from 'lucide-react' +import { BookOpen, Package2 } from 'lucide-react' function OwnedCoursesPage() { const org = useOrg() as any @@ -24,9 +24,14 @@ function OwnedCoursesPage() { return (
-
-

My Courses

-

Courses you have purchased or subscribed to

+
+
+ +
+

My Courses

+

Courses you have purchased or subscribed to

+
+
diff --git a/apps/web/components/Dashboard/Payments/PaymentsProductPage.tsx b/apps/web/components/Dashboard/Payments/PaymentsProductPage.tsx index 240fe73f..0feb9a15 100644 --- a/apps/web/components/Dashboard/Payments/PaymentsProductPage.tsx +++ b/apps/web/components/Dashboard/Payments/PaymentsProductPage.tsx @@ -55,12 +55,12 @@ function PaymentsProductPage() { }, [paymentConfigs]); const handleArchiveProduct = async (productId: string) => { - try { - await archiveProduct(org.id, productId, session.data?.tokens?.access_token); - mutate([`/payments/${org.id}/products`, session.data?.tokens?.access_token]); + const res = await archiveProduct(org.id, productId, session.data?.tokens?.access_token); + mutate([`/payments/${org.id}/products`, session.data?.tokens?.access_token]); + if (res.status === 200) { toast.success('Product archived successfully'); - } catch (error) { - toast.error('Failed to archive product'); + } else { + toast.error(res.data.detail); } } @@ -77,7 +77,7 @@ function PaymentsProductPage() { return (
- +