habulaj commited on
Commit
59dfb23
·
verified ·
1 Parent(s): 98fa8b4

Update routes/users.py

Browse files
Files changed (1) hide show
  1. routes/users.py +92 -66
routes/users.py CHANGED
@@ -203,20 +203,18 @@ async def get_recent_users_endpoint(
203
  logger.error(f"❌ Erro ao obter usuários: {str(e)}")
204
  raise HTTPException(status_code=500, detail=str(e))
205
 
206
- from datetime import datetime
207
-
208
- from datetime import datetime
209
-
210
  @router.get("/admin/user-transfers")
211
  async def get_user_transfer_history(
212
  user_token: str = Header(None, alias="User-key"),
213
  user_id: str = Query(..., description="ID of the user in Supabase")
214
  ):
215
  """
216
- Returns the last 10 payments of the user with metadata, stylist info and formatted status.
 
 
217
  """
218
  try:
219
- # Verify permissions
220
  await verify_token_with_permissions(user_token, "view_users")
221
 
222
  # Get user's stripe_id
@@ -230,77 +228,105 @@ async def get_user_transfer_history(
230
  raise HTTPException(status_code=404, detail="Stripe ID not found for this user")
231
  stripe_id = data[0]["stripe_id"]
232
 
233
- # Fetch charges from Stripe
234
- all_charges = stripe.Charge.list(customer=stripe_id, limit=100)
235
- total_count = len(all_charges["data"])
236
- recent_charges = sorted(all_charges["data"], key=lambda c: c["created"], reverse=True)[:10]
 
237
 
238
- # Status mapping (id → label + color)
239
- status_map = {
240
- "succeeded": {"label": "Succeeded", "color": "#15803d"},
241
- "pending": {"label": "Pending", "color": "#92400e"},
242
- "failed": {"label": "Failed", "color": "#991b1b"},
243
- "canceled": {"label": "Canceled", "color": "#374151"},
244
- }
245
 
246
- # Helper to fetch stylist info
247
- async def fetch_stylist(stylist_id: str) -> Optional[Dict[str, Any]]:
248
- url = f"{SUPABASE_URL}/rest/v1/User?id=eq.{stylist_id}&select=name,email,avatar"
249
- async with aiohttp.ClientSession() as session:
250
- async with session.get(url, headers=SUPABASE_HEADERS) as response:
251
- if response.status == 200:
252
- data = await response.json()
253
- if data:
254
- return data[0]
255
- return None
 
 
256
 
257
- # Collect stylist tasks
258
- transfers = []
259
- tasks = []
 
 
260
 
261
- for charge in recent_charges:
262
- metadata = charge.get("metadata", {})
263
- stylist_id = metadata.get("stylist_id")
264
- task = fetch_stylist(stylist_id) if stylist_id else None
265
- tasks.append(task)
 
 
266
 
267
- stylist_data_list = await asyncio.gather(*[t if t else asyncio.sleep(0) for t in tasks])
 
 
 
 
 
 
 
 
268
 
269
- for i, charge in enumerate(recent_charges):
270
- metadata = charge.get("metadata", {})
271
- stylist_info = stylist_data_list[i] if i < len(stylist_data_list) else None
 
 
 
 
 
272
 
273
- status_id = charge["status"]
274
- status_info = status_map.get(status_id, {
275
- "label": "Unknown",
276
- "color": "#9ca3af"
277
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
 
279
- transfers.append({
280
- "id": charge["id"],
281
- "amount": charge["amount"] / 100,
282
- "currency": charge["currency"].upper(),
283
- "status_info": {
284
- "id": status_id,
285
- "label": status_info["label"],
286
- "color": status_info["color"]
287
- },
288
- "created_at": datetime.fromtimestamp(charge["created"]).isoformat(),
289
- "description": charge.get("description"),
290
- "payment_method": charge.get("payment_method_details", {}).get("type"),
291
- "receipt_url": charge.get("receipt_url"),
292
- "metadata": metadata,
293
- "stylist": stylist_info
294
- })
295
 
296
- return {
297
- "stripe_id": stripe_id,
298
- "transfers": transfers,
299
- "count": total_count
300
- }
301
 
302
  except HTTPException as he:
303
  raise he
304
  except Exception as e:
305
  logger.error(f"❌ Error retrieving transfers: {str(e)}")
306
- raise HTTPException(status_code=500, detail="Internal error while fetching transfer history.")
 
203
  logger.error(f"❌ Erro ao obter usuários: {str(e)}")
204
  raise HTTPException(status_code=500, detail=str(e))
205
 
 
 
 
 
206
  @router.get("/admin/user-transfers")
207
  async def get_user_transfer_history(
208
  user_token: str = Header(None, alias="User-key"),
209
  user_id: str = Query(..., description="ID of the user in Supabase")
210
  ):
211
  """
212
+ Returns financial information for a user:
213
+ - If the user is a customer (stripe_id starts with 'cus_'), returns last 10 charges.
214
+ - If the user is a seller (stripe_id starts with 'acct_'), returns total revenue and upcoming payout info.
215
  """
216
  try:
217
+ # Verify admin permission
218
  await verify_token_with_permissions(user_token, "view_users")
219
 
220
  # Get user's stripe_id
 
228
  raise HTTPException(status_code=404, detail="Stripe ID not found for this user")
229
  stripe_id = data[0]["stripe_id"]
230
 
231
+ # Se for estilista (Stripe Account)
232
+ if stripe_id.startswith("acct_"):
233
+ # List all transfers to this connected account
234
+ transfers = stripe.Transfer.list(destination=stripe_id, limit=100)
235
+ total_revenue = sum([t["amount"] for t in transfers["data"]]) / 100
236
 
237
+ # Próximo pagamento (caso exista)
238
+ upcoming_payouts = stripe.Payout.list(
239
+ destination=None, # Para connected accounts, isso vem automaticamente
240
+ stripe_account=stripe_id,
241
+ limit=1,
242
+ arrival_date={"gte": int(datetime.now().timestamp())}
243
+ )
244
 
245
+ upcoming = upcoming_payouts["data"][0] if upcoming_payouts["data"] else None
246
+
247
+ return {
248
+ "stripe_id": stripe_id,
249
+ "role": "stylist",
250
+ "total_revenue": total_revenue,
251
+ "upcoming_payout": {
252
+ "amount": upcoming["amount"] / 100 if upcoming else None,
253
+ "arrival_date": datetime.fromtimestamp(upcoming["arrival_date"]).isoformat() if upcoming else None,
254
+ },
255
+ "transfers_count": len(transfers["data"])
256
+ }
257
 
258
+ # Se for cliente (Stripe Customer)
259
+ elif stripe_id.startswith("cus_"):
260
+ all_charges = stripe.Charge.list(customer=stripe_id, limit=100)
261
+ total_count = len(all_charges["data"])
262
+ recent_charges = sorted(all_charges["data"], key=lambda c: c["created"], reverse=True)[:10]
263
 
264
+ # Status mapping (id → label + dark color)
265
+ status_map = {
266
+ "succeeded": {"label": "Succeeded", "color": "#15803d"},
267
+ "pending": {"label": "Pending", "color": "#92400e"},
268
+ "failed": {"label": "Failed", "color": "#991b1b"},
269
+ "canceled": {"label": "Canceled", "color": "#374151"},
270
+ }
271
 
272
+ async def fetch_stylist(stylist_id: str) -> Optional[Dict[str, Any]]:
273
+ url = f"{SUPABASE_URL}/rest/v1/User?id=eq.{stylist_id}&select=name,email,avatar"
274
+ async with aiohttp.ClientSession() as session:
275
+ async with session.get(url, headers=SUPABASE_HEADERS) as response:
276
+ if response.status == 200:
277
+ data = await response.json()
278
+ if data:
279
+ return data[0]
280
+ return None
281
 
282
+ # Buscar stylists
283
+ tasks = []
284
+ for charge in recent_charges:
285
+ metadata = charge.get("metadata", {})
286
+ stylist_id = metadata.get("stylist_id")
287
+ task = fetch_stylist(stylist_id) if stylist_id else None
288
+ tasks.append(task)
289
+ stylist_data_list = await asyncio.gather(*[t if t else asyncio.sleep(0) for t in tasks])
290
 
291
+ # Formatando
292
+ transfers = []
293
+ for i, charge in enumerate(recent_charges):
294
+ metadata = charge.get("metadata", {})
295
+ stylist_info = stylist_data_list[i] if i < len(stylist_data_list) else None
296
+ status_id = charge["status"]
297
+ status_info = status_map.get(status_id, {
298
+ "label": "Unknown",
299
+ "color": "#1f2937"
300
+ })
301
+ transfers.append({
302
+ "id": charge["id"],
303
+ "amount": charge["amount"] / 100,
304
+ "currency": charge["currency"].upper(),
305
+ "status_info": {
306
+ "id": status_id,
307
+ "label": status_info["label"],
308
+ "color": status_info["color"]
309
+ },
310
+ "created_at": datetime.fromtimestamp(charge["created"]).isoformat(),
311
+ "description": charge.get("description"),
312
+ "payment_method": charge.get("payment_method_details", {}).get("type"),
313
+ "receipt_url": charge.get("receipt_url"),
314
+ "metadata": metadata,
315
+ "stylist": stylist_info
316
+ })
317
 
318
+ return {
319
+ "stripe_id": stripe_id,
320
+ "role": "customer",
321
+ "transfers": transfers,
322
+ "count": total_count
323
+ }
 
 
 
 
 
 
 
 
 
 
324
 
325
+ else:
326
+ raise HTTPException(status_code=400, detail="Invalid stripe_id format.")
 
 
 
327
 
328
  except HTTPException as he:
329
  raise he
330
  except Exception as e:
331
  logger.error(f"❌ Error retrieving transfers: {str(e)}")
332
+ raise HTTPException(status_code=500, detail="Internal error while fetching financial data.")