connect / routes /subscription.py
habulaj's picture
Update routes/subscription.py
427eb72 verified
raw
history blame
10.1 kB
import stripe
import logging
import json
import os
import requests
from fastapi import APIRouter, HTTPException, Request
from pydantic import BaseModel
router = APIRouter()
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
# 🔥 Pegando as chaves do ambiente
stripe.api_key = os.getenv("STRIPE_KEY") # Lendo do ambiente
stripe.api_version = "2023-10-16"
# 🔥 Supabase Configuração com Secrets
SUPABASE_URL = "https://ussxqnifefkgkaumjann.supabase.co"
SUPABASE_KEY = os.getenv("SUPA_KEY") # Lendo do ambiente
if not stripe.api_key or not SUPABASE_KEY:
raise ValueError("❌ STRIPE_KEY ou SUPA_KEY não foram definidos no ambiente!")
SUPABASE_HEADERS = {
"apikey": SUPABASE_KEY,
"Authorization": f"Bearer {SUPABASE_KEY}",
"Content-Type": "application/json"
}
class CheckSubscriptionRequest(BaseModel):
user_id: str
stylist_id: str
# 📌 Agora recebemos `user_id` (ID do cliente que está comprando)
class SubscriptionRequest(BaseModel):
id: str # ID do estilista
user_id: str # ID do usuário que está comprando a assinatura
@router.post("/create_price")
def create_price(request: Request):
try:
data = await request.json()
amount = data.get("amount")
user_id = data.get("user_id")
if not amount or not user_id:
raise HTTPException(status_code=400, detail="Amount and user_id are required")
# 🔹 Criar o preço no Stripe
price = stripe.Price.create(
unit_amount=int(amount * 100), # Convertendo para centavos
currency="brl",
recurring={"interval": "month"}, # Preço recorrente mensal
product_data={
"name": "Custom Subscription Price",
"description": "Custom price for user subscription"
}
)
# 🔹 Atualizar o usuário no Supabase com o price_id
response = requests.patch(
f"{SUPABASE_URL}/rest/v1/User?id=eq.{user_id}",
headers=SUPABASE_HEADERS,
json={"price_id": price.id}
)
if response.status_code not in [200, 204]:
raise HTTPException(status_code=500, detail="Failed to update Supabase with price_id")
return {"message": "Price created successfully!", "price_id": price.id}
except Exception as e:
logger.error(f"Error creating price: {e}")
raise HTTPException(status_code=500, detail="Error creating price.")
@router.post("/create_checkout_session")
def create_checkout_session(data: SubscriptionRequest):
try:
# 🔹 1. Buscar estilista no Supabase
response = requests.get(
f"{SUPABASE_URL}/rest/v1/User?id=eq.{data.id}",
headers=SUPABASE_HEADERS
)
stylist_data = response.json()
if not stylist_data:
raise HTTPException(status_code=404, detail="Stylist not found")
stylist = stylist_data[0]
stylist_name = stylist["name"]
stylist_avatar = stylist["avatar"]
consultations = stylist["consultations"]
stylist_stripe_id = stylist["stripe_id"]
if not consultations or not stylist_stripe_id:
raise HTTPException(status_code=400, detail="Stylist profile is incomplete")
# 🔹 2. Buscar o stripe_id e price_id do usuário no banco de dados (Supabase)
response_user = requests.get(
f"{SUPABASE_URL}/rest/v1/User?id=eq.{data.user_id}",
headers=SUPABASE_HEADERS
)
user_data = response_user.json()
if not user_data:
raise HTTPException(status_code=404, detail="User not found")
user = user_data[0]
user_stripe_id = user.get("stripe_id")
price_id = user.get("price_id") # Pegamos o price_id salvo no Supabase
if not user_stripe_id:
raise HTTPException(status_code=400, detail="User does not have a Stripe ID")
if not price_id:
raise HTTPException(status_code=400, detail="User does not have a valid price ID")
# 🔹 3. Criar Checkout Session no Stripe com o price_id salvo
session = stripe.checkout.Session.create(
success_url="https://yourdomain.com/success",
cancel_url="https://yourdomain.com/cancel",
payment_method_types=["card"],
mode="subscription",
customer=user_stripe_id,
line_items=[
{
"price": price_id, # Agora usamos o price_id salvo no banco
"quantity": 1
}
],
metadata={
"stylist_id": stylist_stripe_id,
"user_id": data.user_id,
"consultations_per_month": consultations
}
)
return {
"message": "Checkout session created successfully!",
"checkout_url": session.url
}
except Exception as e:
logger.error(f"Error creating checkout session: {e}")
raise HTTPException(status_code=500, detail="Error creating checkout session.")
### **WEBHOOK PARA PROCESSAR PAGAMENTOS**
@router.post("/webhook")
async def stripe_webhook(request: Request):
payload = await request.body()
headers = dict(request.headers)
event = None
try:
# Tenta construir o evento Stripe a partir do payload recebido
event = stripe.Event.construct_from(json.loads(payload), stripe.api_key)
except Exception as e:
logger.error(f"⚠️ Error processing webhook: {e}")
raise HTTPException(status_code=400, detail="Webhook error")
logger.info(f"🔹 Event received: {event['type']}")
# Verifica se o evento é de uma sessão de checkout completada
if event["type"] == "checkout.session.completed":
session = event["data"]["object"]
payment_intent_id = session.get("payment_intent")
try:
# Recupera o PaymentIntent associado à sessão
payment_intent = stripe.PaymentIntent.retrieve(payment_intent_id)
except Exception as e:
logger.error(f"⚠️ Error retrieving PaymentIntent: {e}")
raise HTTPException(status_code=400, detail="Error retrieving PaymentIntent")
# Extrai os dados do metadata
user_id = session.metadata.get("user_id") # ID do cliente
stylist_id = session.metadata.get("stylist_id") # ID do estilista no Stripe
consultations_per_month = session.metadata.get("consultations_per_month") # Consultas por mês
# Verifica o valor pago
amount = payment_intent["amount_received"]
# Define a divisão do pagamento entre a plataforma e o estilista
stylist_share = int(amount * 0.8) # 80% para o estilista
platform_share = amount - stylist_share # 20% para a plataforma
# Aqui, o pagamento já foi transferido para o estilista, então não há mais necessidade de fazer uma transferência manual
logger.info(f"💸 Payment of R${amount / 100:.2f} BRL received for stylist {stylist_id}.")
logger.info(f"💸 Stylist share: R${stylist_share / 100:.2f} BRL, Platform share: R${platform_share / 100:.2f} BRL.")
# Aqui você pode atualizar seu banco de dados com as informações do pagamento
# Exemplo de atualização do banco com o valor e o status do pagamento
# update_payment_status(user_id, stylist_id, amount, stylist_share, platform_share)
return {
"status": "Payment processed successfully!",
"user_id": user_id, # Retorna o ID do cliente
"stylist_id": stylist_id, # Retorna o ID do estilista
"consultations_per_month": consultations_per_month,
"total_paid": amount / 100,
"stylist_share": stylist_share / 100,
"platform_share": platform_share / 100
}
return {"status": "Event received, no action needed."}
### **CANCELAMENTO DE ASSINATURA**
class CancelSubscriptionRequest(BaseModel):
subscription_id: str
@router.post("/cancel_subscription")
def cancel_subscription(data: CancelSubscriptionRequest):
try:
subscription = stripe.Subscription.modify(
data.subscription_id,
cancel_at_period_end=True,
)
return {"status": "Subscription will be canceled at period end"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# 🚀 Verificação de Assinatura
@router.post("/check_subscription")
def check_subscription(data: CheckSubscriptionRequest):
try:
# Buscar todas as assinaturas ativas para o usuário (cliente)
subscriptions = stripe.Subscription.list(
customer=data.user_id, # ID do cliente
status="active", # Assinaturas ativas
expand=["data.items"] # Expande os dados para pegar os itens e o metadata
)
# Verificar se existe uma assinatura ativa para o estilista
for subscription in subscriptions["data"]:
# Verifica se o stylist_id está presente no metadata da assinatura
if subscription.metadata.get("stylist_id") == data.stylist_id:
return {
"status": "active",
"subscription_id": subscription.id,
"consultations_per_month": subscription.metadata.get("consultations_per_month")
}
# Caso não tenha encontrado assinatura ativa para o estilista
return {
"status": "inactive",
"message": "No active subscription found for this stylist."
}
except stripe.error.StripeError as e:
# Captura erros específicos do Stripe
raise HTTPException(status_code=500, detail=f"Stripe error: {str(e)}")
except Exception as e:
# Captura outros erros genéricos
logger.error(f"Error checking subscription: {e}")
raise HTTPException(status_code=500, detail="Error checking subscription.")