Skip to content

Commit

Permalink
pause membership
Browse files Browse the repository at this point in the history
  • Loading branch information
NickTracker committed Aug 19, 2024
1 parent 5e5baec commit 4e9959f
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 34 deletions.
26 changes: 25 additions & 1 deletion client/src/components/Api/Api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -325,21 +325,45 @@ export const deleteAllActivations = async () => {
return { ...response.data, statusCode: response.status };
};

/**
* @returns {Promise<ApiResponse>}
*/
export const createPaymentIntent = async () => {
const response = await api.post("/create-payment-intent");
return { ...response.data, statusCode: response.status };
};

/**
* @param {string} userEmail
* @returns {Promise<ApiResponse>}
*/
export const startStripePaymentSchedule = async (userEmail) => {
const response = await api.post("/start_stripe_payment_schedule", {
userEmail,
});
return { ...response.data, statusCode: response.status };
};

/**
* @param {string} userId
* @returns {Promise<ApiResponse>}
*/
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<ApiResponse>}
*/
export const pauseStripePaymentSchedule = async (userId, email) => {
const response = await api.post("/pause_stripe_payment_schedule", {
userId,
email,
});
return { ...response.data, statusCode: response.status };
};
63 changes: 49 additions & 14 deletions client/src/pages/Account.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
getInstanceUrl,
getLoggedInUser,
createPaymentIntent,
pauseStripePaymentSchedule,
startStripePaymentSchedule,
setSupabaseUserStatusToPaid,
} from "./../components/Api/Api";
Expand Down Expand Up @@ -42,27 +43,30 @@ const CheckoutForm = ({ onSubscriptionComplete, userEmail }) => {
return;
}
setProcessing(true);

try {
const { error: submitError } = await elements.submit();
if (submitError) {
setError(submitError.message);
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.
Expand Down Expand Up @@ -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);
Expand All @@ -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");
}
Expand Down Expand Up @@ -151,6 +157,24 @@ const Account = () => {
fetchData();
}, []);

/**
* @returns {Promise<void>}
*/
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);
};
Expand Down Expand Up @@ -196,22 +220,33 @@ const Account = () => {
<Typography variant="body1">Email: {user.email}</Typography>
<Typography variant="body1">Username: {user.username}</Typography>

<Button
variant="contained"
color="primary"
onClick={handleUpgradeClick}
sx={{ marginTop: 2 }}
>
Upgrade to Paid
</Button>
{userStatus === "paid" ? (
<Button
variant="contained"
color="info"
onClick={handlePauseMembership}
sx={{ marginTop: 2, backgroundColor: "grey.500", color: "white" }}
>
Pause Membership
</Button>
) : (
<Button
variant="contained"
color="primary"
onClick={handleUpgradeClick}
sx={{ marginTop: 2 }}
>
Upgrade to Paid
</Button>
)}

<Dialog open={openUpgradeDialog} onClose={handleCloseUpgradeDialog}>
<DialogTitle>Upgrade to Paid Plan</DialogTitle>
<DialogContent>
<Typography variant="body1" gutterBottom>
Subscribe to our premium plan for $20/month
</Typography>
{clientSecret && (
{clientSecret && user.email && (
<StripeWrapper options={{ clientSecret }}>
<CheckoutForm
onSubscriptionComplete={handleSubscriptionComplete}
Expand Down
3 changes: 2 additions & 1 deletion client/types/User.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
export interface User {
id: number;
id: string;
firstName: string;
lastName: string;
photoUrl: string;
username: string;
email: string;
status: string;
}
31 changes: 17 additions & 14 deletions server/app/database/supabase_user_selector.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
from app.database.supabase_connection import get_supabase_admin_client
from app.data_models import AuthenticationError
from app.data_models import AuthenticationError, UserModel
from app.mapper.mapper import supabase_user_to_python_user


def fetch_supabase_user(salesforce_id: str):
def fetch_supabase_user(salesforce_id: str) -> 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)}")
11 changes: 10 additions & 1 deletion server/app/mapper/mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}
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"]
)
71 changes: 68 additions & 3 deletions server/app/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand All @@ -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():
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 4e9959f

Please sign in to comment.