File size: 13,155 Bytes
912124c 0a2b45f 01ccbf3 a041ea2 01ccbf3 912124c 0a2b45f fb71f9d 9686665 912124c 9686665 703a507 9686665 01ccbf3 912124c 7cf3f41 3ac3303 01ccbf3 3ac3303 5d7e429 732810d 680dbcb 427eb72 680dbcb ad67519 680dbcb 427eb72 680dbcb 6b2c78a 427eb72 ad67519 6b2c78a 680dbcb ad67519 680dbcb 6b2c78a 2ccdcce 73c5143 2ccdcce 5644e04 2ccdcce ad67519 5644e04 2ccdcce ad67519 6b2c78a 680dbcb ad67519 427eb72 ad67519 427eb72 ad67519 427eb72 ad67519 6b2c78a ed0d9dd 7d06ed4 912124c 7d06ed4 01ccbf3 ef840ba 01ccbf3 f70e965 01ccbf3 427eb72 7d06ed4 59615d0 427eb72 58b816c 01ccbf3 7d06ed4 c9000d6 7d06ed4 d80190a c9000d6 d80190a c9000d6 427eb72 7d06ed4 c9000d6 427eb72 c9000d6 7d06ed4 912124c 24c2864 912124c 7ef17d7 427eb72 24c2864 7ef17d7 7d06ed4 24c2864 0b08e96 7d06ed4 0b08e96 ab80e0b 912124c 24c2864 01ccbf3 912124c 24c2864 d80190a 0ea8eb8 912124c 0a2b45f d06f095 c1043bc d06f095 dba3e83 ab80e0b d06f095 c1043bc d06f095 912124c c1043bc dba3e83 851ecc4 dba3e83 851ecc4 dba3e83 0ea8eb8 851ecc4 dba3e83 ab80e0b 851ecc4 dba3e83 0ea8eb8 ed0d9dd 851ecc4 dba3e83 851ecc4 d06f095 851ecc4 83253ea 0ea8eb8 a664e20 912124c ed0d9dd 912124c 9b46162 7cf3f41 9b46162 7cf3f41 9b46162 7cf3f41 9b46162 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 |
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
class CreatePriceRequest(BaseModel):
amount: int # Valor em centavos (ex: 2500 para R$25,00)
user_id: str # ID do usuário que está criando o preço
from fastapi import Header # Import para pegar headers
@router.post("/create_price")
async def create_price(
data: CreatePriceRequest,
user_token: str = Header(None, alias="User-key")
):
try:
if not user_token:
raise HTTPException(status_code=401, detail="Missing User-key header")
amount = data.amount
user_id = data.user_id
if not amount or not user_id:
raise HTTPException(status_code=400, detail="Amount and user_id are required")
# 🔹 Buscar `price_id` do usuário no Supabase
supabase_url = f"{SUPABASE_URL}/rest/v1/User?id=eq.{user_id}"
supabase_headers = {
"apikey": SUPABASE_KEY,
"Authorization": f"Bearer {user_token}",
"Content-Type": "application/json"
}
response = requests.get(supabase_url, headers=supabase_headers)
user_data = response.json()
if not user_data:
raise HTTPException(status_code=404, detail="User not found")
user = user_data[0]
existing_price_id = user.get("price_id")
# 🔹 Se já existir um price_id, tentar atualizar
if existing_price_id:
try:
stripe.Price.modify(
existing_price_id,
unit_amount=amount,
metadata={"updated_at": "now"} # Apenas para gerar um evento de atualização
)
logger.info(f"✅ Price {existing_price_id} updated successfully.")
updated_price_id = existing_price_id
except stripe.error.InvalidRequestError:
logger.warning(f"⚠️ Failed to update price {existing_price_id}, creating a new one instead.")
updated_price_id = None
else:
updated_price_id = None
# 🔹 Se não conseguiu atualizar, criar um novo preço
if not updated_price_id:
price = stripe.Price.create(
unit_amount=amount,
currency="brl",
recurring={"interval": "month"},
product_data={"name": "Custom Subscription Price"}
)
updated_price_id = price.id
logger.info(f"✅ New price created: {updated_price_id}")
# 🔹 Pausar todas as assinaturas associadas ao preço antigo
if existing_price_id:
subscriptions = stripe.Subscription.list(status="active")
for sub in subscriptions.auto_paging_iter():
if sub["items"]["data"][0]["price"]["id"] == existing_price_id:
stripe.Subscription.modify(sub.id, pause_collection={"behavior": "void"})
logger.info(f"🔹 Subscription {sub.id} paused.")
# 🔹 Desativar o preço antigo
stripe.Price.modify(existing_price_id, active=False)
logger.info(f"🚫 Price {existing_price_id} deactivated.")
# 🔹 Atualizar Supabase com o novo `price_id`
update_response = requests.patch(
supabase_url,
headers=supabase_headers,
json={"price_id": updated_price_id}
)
if update_response.status_code not in [200, 204]:
raise HTTPException(status_code=500, detail=f"Failed to update Supabase: {update_response.text}")
return {"message": "Price created or updated successfully!", "price_id": updated_price_id}
except Exception as e:
logger.error(f"❌ Error creating/updating price: {e}")
raise HTTPException(status_code=500, detail="Error creating/updating price.")
@router.post("/create_checkout_session")
def create_checkout_session(
data: SubscriptionRequest,
user_token: str = Header(None, alias="User-key")
):
try:
if not user_token:
raise HTTPException(status_code=401, detail="Missing User-key header")
# 🔹 1. Decodificar o token JWT para obter o ID do usuário
try:
payload = jwt.decode(user_token, JWT_SECRET, algorithms=["HS256"]) # Substitua JWT_SECRET pela sua chave real
user_id = payload.get("id")
if not user_id:
raise HTTPException(status_code=400, detail="Invalid token: User ID not found")
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail="Token expired")
except jwt.InvalidTokenError:
raise HTTPException(status_code=401, detail="Invalid token")
# 🔹 2. 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_stripe_id = stylist.get("stripe_id")
consultations = stylist.get("consultations")
if not consultations or not stylist_stripe_id:
raise HTTPException(status_code=400, detail="Stylist profile is incomplete")
# 🔹 3. Buscar o stripe_id e price_id do usuário autenticado
response_user = requests.get(
f"{SUPABASE_URL}/rest/v1/User?id=eq.{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")
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")
# 🔹 4. Criar Checkout Session no Stripe
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,
"quantity": 1,
"description": "Assinatura personalizada para usuário"
}
],
metadata={
"stylist_id": stylist_stripe_id,
"user_id": user_id, # 🔹 Continua no metadata para rastreamento interno
"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.") |