Update routes/subscription.py
Browse files- routes/subscription.py +67 -38
routes/subscription.py
CHANGED
@@ -60,6 +60,48 @@ class CreatePriceRequest(BaseModel):
|
|
60 |
emergency_price: int # Valor de emergência (ex: 500 para R$5,00)
|
61 |
consultations: int # Número de consultas (ex: 3)
|
62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
63 |
def get_subscription_from_db(subscription_id: str):
|
64 |
"""
|
65 |
Busca a assinatura na tabela 'Subscriptions' com base no subscription_id.
|
@@ -837,35 +879,33 @@ async def create_price(data: CreatePriceRequest, user_token: str = Header(None,
|
|
837 |
if not user_token:
|
838 |
raise HTTPException(status_code=401, detail="Missing User-key header")
|
839 |
|
|
|
840 |
user_id = verify_token(user_token)
|
841 |
logger.info(f"🔹 User verified. user_id: {user_id}")
|
842 |
|
843 |
amount = data.amount
|
844 |
emergency_price = data.emergency_price
|
845 |
-
consultations = data.consultations
|
846 |
|
847 |
if not (500 <= amount <= 99900):
|
848 |
raise HTTPException(status_code=400, detail="Amount must be between $5 and $999")
|
849 |
if not (500 <= emergency_price <= 99900):
|
850 |
raise HTTPException(status_code=400, detail="Emergency price must be between $5 and $999")
|
|
|
|
|
851 |
|
852 |
-
|
853 |
-
consultations = 0
|
854 |
-
elif consultations < 0:
|
855 |
-
raise HTTPException(status_code=400, detail="Consultations must be greater than or equal to 0")
|
856 |
-
|
857 |
-
logger.info(f"🔹 Validated amounts: amount = {amount}, emergency_price = {emergency_price}, consultations = {consultations}")
|
858 |
|
|
|
859 |
supabase_url = f"{SUPABASE_URL}/rest/v1/User?id=eq.{user_id}"
|
860 |
headers = {
|
861 |
"Authorization": f"Bearer {user_token}",
|
862 |
**SUPABASE_HEADERS
|
863 |
}
|
864 |
response = requests.get(supabase_url, headers=headers)
|
865 |
-
logger.info(f"🔹 Supabase GET response: {response.status_code} - {response.text}")
|
866 |
|
867 |
if response.status_code != 200:
|
868 |
-
raise HTTPException(status_code=500, detail=f"Failed to fetch user
|
869 |
|
870 |
user_data = response.json()
|
871 |
if not user_data:
|
@@ -873,28 +913,21 @@ async def create_price(data: CreatePriceRequest, user_token: str = Header(None,
|
|
873 |
|
874 |
user = user_data[0]
|
875 |
existing_price_id = user.get("price_id")
|
876 |
-
user_full_name = user.get("name", "Unknown
|
877 |
-
user_avatar = user.get("avatar"
|
878 |
|
879 |
name_parts = user_full_name.split()
|
880 |
-
if len(name_parts) > 1
|
881 |
-
abbreviated_name = f"{name_parts[0]} {name_parts[1][0]}."
|
882 |
-
else:
|
883 |
-
abbreviated_name = name_parts[0]
|
884 |
-
|
885 |
product_name = f"{abbreviated_name} Subscription"
|
886 |
-
logger.info(f"🔹 Existing price_id: {existing_price_id}, user_name: {abbreviated_name}")
|
887 |
|
888 |
-
|
889 |
-
"name": product_name,
|
890 |
-
}
|
891 |
|
|
|
892 |
if user_avatar:
|
893 |
product_data["images"] = [user_avatar]
|
894 |
|
895 |
product = stripe.Product.create(**product_data)
|
896 |
product_id = product.id
|
897 |
-
logger.info(f"✅ New product created: {product_id}")
|
898 |
|
899 |
price = stripe.Price.create(
|
900 |
unit_amount=amount,
|
@@ -903,19 +936,14 @@ async def create_price(data: CreatePriceRequest, user_token: str = Header(None,
|
|
903 |
product=product_id
|
904 |
)
|
905 |
new_price_id = price.id
|
906 |
-
logger.info(f"✅
|
907 |
|
908 |
-
|
909 |
-
affected_customers = []
|
910 |
|
911 |
if existing_price_id:
|
912 |
subscriptions = stripe.Subscription.list(status="active")
|
913 |
for sub in subscriptions.auto_paging_iter():
|
914 |
if sub["items"]["data"][0]["price"]["id"] == existing_price_id:
|
915 |
-
customer_id = sub["customer"]
|
916 |
-
affected_customers.append(customer_id)
|
917 |
-
|
918 |
-
# Cancelar no final do período e atualizar para novo preço
|
919 |
stripe.Subscription.modify(sub.id, cancel_at_period_end=True)
|
920 |
stripe.Subscription.modify(
|
921 |
sub.id,
|
@@ -925,10 +953,11 @@ async def create_price(data: CreatePriceRequest, user_token: str = Header(None,
|
|
925 |
}],
|
926 |
proration_behavior="none"
|
927 |
)
|
928 |
-
|
929 |
-
|
930 |
-
|
931 |
|
|
|
932 |
update_data = {
|
933 |
"price_id": new_price_id,
|
934 |
"price": amount,
|
@@ -945,14 +974,14 @@ async def create_price(data: CreatePriceRequest, user_token: str = Header(None,
|
|
945 |
update_response = requests.patch(supabase_url, headers=update_headers, json=update_data)
|
946 |
|
947 |
if update_response.status_code not in [200, 204]:
|
948 |
-
raise HTTPException(status_code=500, detail=f"Failed to update
|
949 |
|
950 |
-
|
951 |
-
|
952 |
-
|
953 |
-
|
954 |
-
|
955 |
-
}
|
956 |
|
957 |
except Exception as e:
|
958 |
logger.error(f"❌ Error creating price: {e}")
|
|
|
60 |
emergency_price: int # Valor de emergência (ex: 500 para R$5,00)
|
61 |
consultations: int # Número de consultas (ex: 3)
|
62 |
|
63 |
+
def get_active_subscribers_by_price_id(price_id: str) -> list:
|
64 |
+
"""
|
65 |
+
Retorna uma lista de user_ids que têm uma assinatura ativa com o price_id fornecido.
|
66 |
+
"""
|
67 |
+
response = requests.get(
|
68 |
+
f"{SUPABASE_URL}/rest/v1/Subscriptions?price_id=eq.{price_id}&select=user_id",
|
69 |
+
headers=SUPABASE_ROLE_HEADERS
|
70 |
+
)
|
71 |
+
if response.status_code == 200:
|
72 |
+
data = response.json()
|
73 |
+
return [entry["user_id"] for entry in data if "user_id" in entry]
|
74 |
+
logger.warning(f"⚠️ Failed to fetch subscribers: {response.status_code} - {response.text}")
|
75 |
+
return []
|
76 |
+
|
77 |
+
def create_notifications_for_price_change(user_ids: list, stylist_id: str):
|
78 |
+
"""
|
79 |
+
Cria registros de notificações na tabela 'Notifications' para todos os usuários afetados.
|
80 |
+
"""
|
81 |
+
if not user_ids:
|
82 |
+
return
|
83 |
+
|
84 |
+
now = datetime.now(pytz.timezone("America/Sao_Paulo")).isoformat()
|
85 |
+
payload = [{
|
86 |
+
"type": "changeprice",
|
87 |
+
"target_user": uid,
|
88 |
+
"created_at": now,
|
89 |
+
"user_reference": stylist_id,
|
90 |
+
"read": False,
|
91 |
+
"post_reference": None
|
92 |
+
} for uid in user_ids]
|
93 |
+
|
94 |
+
response = requests.post(
|
95 |
+
f"{SUPABASE_URL}/rest/v1/Notifications",
|
96 |
+
headers=SUPABASE_ROLE_HEADERS,
|
97 |
+
json=payload
|
98 |
+
)
|
99 |
+
|
100 |
+
if response.status_code not in [200, 201]:
|
101 |
+
logger.warning(f"⚠️ Failed to create notifications: {response.status_code} - {response.text}")
|
102 |
+
else:
|
103 |
+
logger.info(f"✅ Created {len(payload)} notifications for affected users.")
|
104 |
+
|
105 |
def get_subscription_from_db(subscription_id: str):
|
106 |
"""
|
107 |
Busca a assinatura na tabela 'Subscriptions' com base no subscription_id.
|
|
|
879 |
if not user_token:
|
880 |
raise HTTPException(status_code=401, detail="Missing User-key header")
|
881 |
|
882 |
+
# 1. Validar o token e obter user_id
|
883 |
user_id = verify_token(user_token)
|
884 |
logger.info(f"🔹 User verified. user_id: {user_id}")
|
885 |
|
886 |
amount = data.amount
|
887 |
emergency_price = data.emergency_price
|
888 |
+
consultations = data.consultations or 0
|
889 |
|
890 |
if not (500 <= amount <= 99900):
|
891 |
raise HTTPException(status_code=400, detail="Amount must be between $5 and $999")
|
892 |
if not (500 <= emergency_price <= 99900):
|
893 |
raise HTTPException(status_code=400, detail="Emergency price must be between $5 and $999")
|
894 |
+
if consultations < 0:
|
895 |
+
raise HTTPException(status_code=400, detail="Consultations must be >= 0")
|
896 |
|
897 |
+
logger.info(f"🔹 Validated inputs")
|
|
|
|
|
|
|
|
|
|
|
898 |
|
899 |
+
# 4. Buscar dados do usuário
|
900 |
supabase_url = f"{SUPABASE_URL}/rest/v1/User?id=eq.{user_id}"
|
901 |
headers = {
|
902 |
"Authorization": f"Bearer {user_token}",
|
903 |
**SUPABASE_HEADERS
|
904 |
}
|
905 |
response = requests.get(supabase_url, headers=headers)
|
|
|
906 |
|
907 |
if response.status_code != 200:
|
908 |
+
raise HTTPException(status_code=500, detail=f"Failed to fetch user: {response.text}")
|
909 |
|
910 |
user_data = response.json()
|
911 |
if not user_data:
|
|
|
913 |
|
914 |
user = user_data[0]
|
915 |
existing_price_id = user.get("price_id")
|
916 |
+
user_full_name = user.get("name", "Unknown")
|
917 |
+
user_avatar = user.get("avatar")
|
918 |
|
919 |
name_parts = user_full_name.split()
|
920 |
+
abbreviated_name = f"{name_parts[0]} {name_parts[1][0]}." if len(name_parts) > 1 else name_parts[0]
|
|
|
|
|
|
|
|
|
921 |
product_name = f"{abbreviated_name} Subscription"
|
|
|
922 |
|
923 |
+
logger.info(f"🔹 Creating Stripe product")
|
|
|
|
|
924 |
|
925 |
+
product_data = {"name": product_name}
|
926 |
if user_avatar:
|
927 |
product_data["images"] = [user_avatar]
|
928 |
|
929 |
product = stripe.Product.create(**product_data)
|
930 |
product_id = product.id
|
|
|
931 |
|
932 |
price = stripe.Price.create(
|
933 |
unit_amount=amount,
|
|
|
936 |
product=product_id
|
937 |
)
|
938 |
new_price_id = price.id
|
939 |
+
logger.info(f"✅ Stripe product and price created: {new_price_id}")
|
940 |
|
941 |
+
affected_users = []
|
|
|
942 |
|
943 |
if existing_price_id:
|
944 |
subscriptions = stripe.Subscription.list(status="active")
|
945 |
for sub in subscriptions.auto_paging_iter():
|
946 |
if sub["items"]["data"][0]["price"]["id"] == existing_price_id:
|
|
|
|
|
|
|
|
|
947 |
stripe.Subscription.modify(sub.id, cancel_at_period_end=True)
|
948 |
stripe.Subscription.modify(
|
949 |
sub.id,
|
|
|
953 |
}],
|
954 |
proration_behavior="none"
|
955 |
)
|
956 |
+
# 🔥 Buscar todos os usuários afetados no Supabase
|
957 |
+
affected_users = get_active_subscribers_by_price_id(existing_price_id)
|
958 |
+
logger.info(f"🔹 Found {len(affected_users)} affected users.")
|
959 |
|
960 |
+
# Atualiza o usuário no Supabase
|
961 |
update_data = {
|
962 |
"price_id": new_price_id,
|
963 |
"price": amount,
|
|
|
974 |
update_response = requests.patch(supabase_url, headers=update_headers, json=update_data)
|
975 |
|
976 |
if update_response.status_code not in [200, 204]:
|
977 |
+
raise HTTPException(status_code=500, detail=f"Failed to update user: {update_response.text}")
|
978 |
|
979 |
+
# 🔥 Cria notificações para os afetados
|
980 |
+
create_notifications_for_price_change(affected_users, stylist_id=user_id)
|
981 |
+
|
982 |
+
logger.info(f"✅ User updated and notifications sent")
|
983 |
+
|
984 |
+
return {"message": "Price created and user updated successfully!", "price_id": new_price_id}
|
985 |
|
986 |
except Exception as e:
|
987 |
logger.error(f"❌ Error creating price: {e}")
|