From 4e9959f9a5fb7f6a4bef97b43fc883929ec456c0 Mon Sep 17 00:00:00 2001 From: nzozaya Date: Mon, 19 Aug 2024 00:51:40 -0500 Subject: [PATCH] pause membership --- client/src/components/Api/Api.js | 26 ++++++- client/src/pages/Account.js | 63 ++++++++++++---- client/types/User.d.ts | 3 +- server/app/database/supabase_user_selector.py | 31 ++++---- server/app/mapper/mapper.py | 11 ++- server/app/routes.py | 71 ++++++++++++++++++- 6 files changed, 171 insertions(+), 34 deletions(-) diff --git a/client/src/components/Api/Api.js b/client/src/components/Api/Api.js index 8a516c9..ed3911f 100644 --- a/client/src/components/Api/Api.js +++ b/client/src/components/Api/Api.js @@ -49,7 +49,7 @@ api.interceptors.response.use( async (response) => { // Check if the response contains authentication error information if (response.data && response.data.type === "AuthenticationError") { - if (response.data.error.toLowerCase() === "session not found") { + if (response.data.error.toLowerCase().includes("session")) { window.location.href = "/"; return Promise.reject(response.data); } @@ -325,11 +325,18 @@ export const deleteAllActivations = async () => { return { ...response.data, statusCode: response.status }; }; +/** + * @returns {Promise} + */ export const createPaymentIntent = async () => { const response = await api.post("/create-payment-intent"); return { ...response.data, statusCode: response.status }; }; +/** + * @param {string} userEmail + * @returns {Promise} + */ export const startStripePaymentSchedule = async (userEmail) => { const response = await api.post("/start_stripe_payment_schedule", { userEmail, @@ -337,9 +344,26 @@ export const startStripePaymentSchedule = async (userEmail) => { return { ...response.data, statusCode: response.status }; }; +/** + * @param {string} userId + * @returns {Promise} + */ export const setSupabaseUserStatusToPaid = async (userId) => { const response = await api.post("/set_supabase_user_status_to_paid", { userId, }); return { ...response.data, statusCode: response.status }; }; + +/** + * @param {string} userId + * @param {string} email + * @returns {Promise} + */ +export const pauseStripePaymentSchedule = async (userId, email) => { + const response = await api.post("/pause_stripe_payment_schedule", { + userId, + email, + }); + return { ...response.data, statusCode: response.status }; +}; diff --git a/client/src/pages/Account.js b/client/src/pages/Account.js index 0d75738..69f5d5e 100644 --- a/client/src/pages/Account.js +++ b/client/src/pages/Account.js @@ -3,6 +3,7 @@ import { getInstanceUrl, getLoggedInUser, createPaymentIntent, + pauseStripePaymentSchedule, startStripePaymentSchedule, setSupabaseUserStatusToPaid, } from "./../components/Api/Api"; @@ -42,7 +43,7 @@ const CheckoutForm = ({ onSubscriptionComplete, userEmail }) => { return; } setProcessing(true); - + try { const { error: submitError } = await elements.submit(); if (submitError) { @@ -50,19 +51,22 @@ const CheckoutForm = ({ onSubscriptionComplete, userEmail }) => { setProcessing(false); return; } - + const response = await startStripePaymentSchedule(userEmail); if (response.success) { const { clientSecret } = response.data[0]; const result = await stripe.confirmPayment({ elements, clientSecret, - redirect: 'if_required', + redirect: "if_required", }); - + if (result.error) { setError(result.error.message); - } else if (result.paymentIntent && result.paymentIntent.status === 'succeeded') { + } else if ( + result.paymentIntent && + result.paymentIntent.status === "succeeded" + ) { await onSubscriptionComplete(); // Handle successful payment here without redirecting // For example, update UI, show a success message, etc. @@ -105,6 +109,7 @@ const Account = () => { username: "", photoUrl: "", }); + const [userStatus, setUserStatus] = useState("not paid"); const [instanceUrl, setInstanceUrl] = useState(""); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -122,6 +127,7 @@ const Account = () => { if (userResponse.success && instanceUrlResponse.success) { setUser(userResponse.data[0]); setInstanceUrl(instanceUrlResponse.data[0]); + setUserStatus(userResponse.data[0].status); } else { setError("Failed to fetch user data or instance URL"); } @@ -151,6 +157,24 @@ const Account = () => { fetchData(); }, []); + /** + * @returns {Promise} + */ + const handlePauseMembership = async () => { + try { + const response = await pauseStripePaymentSchedule(user.id, user.email); + if (response.success) { + setSnackbarOpen(true); + setUserStatus("paused"); + } else { + setError("Failed to pause membership"); + } + } catch (error) { + setError("An error occurred while pausing membership"); + console.error("Error pausing membership:", error); + } + }; + const handleUpgradeClick = () => { setOpenUpgradeDialog(true); }; @@ -196,14 +220,25 @@ const Account = () => { Email: {user.email} Username: {user.username} - + {userStatus === "paid" ? ( + + ) : ( + + )} Upgrade to Paid Plan @@ -211,7 +246,7 @@ const Account = () => { Subscribe to our premium plan for $20/month - {clientSecret && ( + {clientSecret && user.email && ( UserModel: try: service_supabase = get_supabase_admin_client() - # Fetch all users - response = service_supabase.auth.admin.list_users() - supabase_user = None - # Find the user with matching salesforce_id in user metadata - for user in response: - if ( - user.user_metadata - and user.user_metadata.get("salesforce_id") == salesforce_id - ): - supabase_user = user + # Query the User table for the specific salesforce_id + response = ( + service_supabase.table("User") + .select("*") + .eq("salesforce_id", salesforce_id) + .execute() + ) + + if response.data and len(response.data) > 0: + supabase_user = response.data[0] + return supabase_user_to_python_user(supabase_user) + else: + return None - return supabase_user except Exception as e: - raise AuthenticationError(f"Failed to fetch Supabase user ID: {str(e)}") + raise AuthenticationError(f"Failed to fetch Supabase user: {str(e)}") diff --git a/server/app/mapper/mapper.py b/server/app/mapper/mapper.py index 3efcdbe..7581af2 100644 --- a/server/app/mapper/mapper.py +++ b/server/app/mapper/mapper.py @@ -351,4 +351,13 @@ def python_user_to_supabase_dict(user: UserModel) -> Dict: } # Remove None values - return {k: v for k, v in supabase_user.items() if v is not None} \ No newline at end of file + return {k: v for k, v in supabase_user.items() if v is not None} + +def supabase_user_to_python_user(row: Dict) -> UserModel: + return UserModel( + id=row["salesforce_id"], + email=row["email"], + orgId=row["org_id"], + photoUrl=row["photo_url"], + status=row["status"] + ) \ No newline at end of file diff --git a/server/app/routes.py b/server/app/routes.py index 021d5bd..d3ff91b 100644 --- a/server/app/routes.py +++ b/server/app/routes.py @@ -7,6 +7,7 @@ load_active_activations_order_by_first_prospecting_activity_asc, ) from app.database.settings_selector import load_settings +from app.database.supabase_user_selector import fetch_supabase_user from app.database.dml import ( save_settings, save_session, @@ -474,11 +475,14 @@ def get_salesforce_events_by_user_ids(): @bp.route("/get_salesforce_user", methods=["GET"]) @authenticate def get_salesforce_user(): - from app.data_models import ApiResponse + from app.data_models import ApiResponse, UserModel response = ApiResponse(data=[], message="", success=False) try: - response.data = [fetch_logged_in_salesforce_user().data] + user: UserModel = fetch_logged_in_salesforce_user().data + supabase_user: UserModel = fetch_supabase_user(user.id) + user.status = supabase_user.status + response.data = [user.to_dict()] response.success = True except Exception as e: log_error(e) @@ -489,6 +493,67 @@ def get_salesforce_user(): return jsonify(response.to_dict()), get_status_code(response) +@bp.route("/pause_stripe_payment_schedule", methods=["POST"]) +@authenticate +def pause_stripe_payment_schedule(): + from app.data_models import ApiResponse, UserModel + + api_response = ApiResponse(data=[], message="", success=False) + + try: + user_email = request.json.get("email") + user_id = request.json.get("userId") + + if not user_email: + api_response.message = "User email is required" + return jsonify(api_response.to_dict()), 400 + + # Find the Stripe customer by email + customers = stripe.Customer.list(email=user_email, limit=1) + + if not customers.data: + api_response.message = "Stripe customer not found" + return jsonify(api_response.to_dict()), 404 + + stripe_customer = customers.data[0] + + # Fetch the customer's subscriptions + subscriptions = stripe.Subscription.list(customer=stripe_customer.id) + + if not subscriptions.data: + api_response.message = "No active subscriptions found" + return jsonify(api_response.to_dict()), 404 + + # Pause the first active subscription + subscription = subscriptions.data[0] + updated_subscription = stripe.Subscription.modify( + subscription.id, + pause_collection={ + "behavior": "void", + }, + ) + + # Update the user's status in your database + upsert_supabase_user( + UserModel(id=user_id, status="paused"), + is_sandbox=get_session_state()["is_sandbox"], + ) + + api_response.success = True + api_response.message = "Subscription paused successfully" + api_response.data = [{"subscription_id": updated_subscription.id}] + return jsonify(api_response.to_dict()), 200 + + except stripe.error.StripeError as e: + log_error(e) + api_response.message = f"Stripe error: {str(e)}" + return jsonify(api_response.to_dict()), 400 + except Exception as e: + log_error(e) + api_response.message = f"An error occurred: {format_error_message(e)}" + return jsonify(api_response.to_dict()), 500 + + @bp.route("/get_task_fields", methods=["GET"]) @authenticate def get_task_fields(): @@ -574,7 +639,7 @@ def start_stripe_payment_schedule(): try: # Get the current user's email - user_email = request.get_json().get("email") + user_email = request.get_json().get("userEmail") # Check if customer already exists existing_customers = stripe.Customer.list(email=user_email, limit=1)