diff --git a/__pycache__/server.cpython-312.pyc b/__pycache__/server.cpython-312.pyc index 8405405..d4011c1 100644 Binary files a/__pycache__/server.cpython-312.pyc and b/__pycache__/server.cpython-312.pyc differ diff --git a/server.py b/server.py index 4fd6df2..00676ec 100644 --- a/server.py +++ b/server.py @@ -1742,6 +1742,75 @@ async def get_my_event_activity( "total_rsvps": len(rsvps) } +# ============================================================================ +# Member Transaction History Endpoint +# ============================================================================ +@api_router.get("/members/transactions") +async def get_member_transactions( + current_user: User = Depends(get_active_member), + db: Session = Depends(get_db) +): + """ + Get current member's transaction history including subscriptions and donations. + Returns both types of transactions sorted by date (newest first). + """ + # Get user's subscriptions with plan details + subscriptions = db.query(Subscription).filter( + Subscription.user_id == current_user.id + ).order_by(Subscription.created_at.desc()).all() + + subscription_list = [] + for sub in subscriptions: + plan = db.query(SubscriptionPlan).filter(SubscriptionPlan.id == sub.plan_id).first() + subscription_list.append({ + "id": str(sub.id), + "type": "subscription", + "description": plan.name if plan else "Subscription", + "amount_cents": sub.amount_paid_cents or (sub.base_subscription_cents + sub.donation_cents), + "base_amount_cents": sub.base_subscription_cents, + "donation_cents": sub.donation_cents, + "status": sub.status.value if sub.status else "unknown", + "payment_method": sub.payment_method, + "card_brand": sub.card_brand, + "card_last4": sub.card_last4, + "stripe_receipt_url": sub.stripe_receipt_url, + "created_at": sub.created_at.isoformat() if sub.created_at else None, + "payment_completed_at": sub.payment_completed_at.isoformat() if sub.payment_completed_at else None, + "start_date": sub.start_date.isoformat() if sub.start_date else None, + "end_date": sub.end_date.isoformat() if sub.end_date else None, + "billing_cycle": plan.billing_cycle if plan else None, + "manual_payment": sub.manual_payment + }) + + # Get user's donations + donations = db.query(Donation).filter( + Donation.user_id == current_user.id + ).order_by(Donation.created_at.desc()).all() + + donation_list = [] + for don in donations: + donation_list.append({ + "id": str(don.id), + "type": "donation", + "description": "Donation", + "amount_cents": don.amount_cents, + "status": don.status.value if don.status else "unknown", + "payment_method": don.payment_method, + "card_brand": don.card_brand, + "card_last4": don.card_last4, + "stripe_receipt_url": don.stripe_receipt_url, + "created_at": don.created_at.isoformat() if don.created_at else None, + "payment_completed_at": don.payment_completed_at.isoformat() if don.payment_completed_at else None, + "notes": don.notes + }) + + return { + "subscriptions": subscription_list, + "donations": donation_list, + "total_subscription_amount_cents": sum(s["amount_cents"] or 0 for s in subscription_list), + "total_donation_amount_cents": sum(d["amount_cents"] or 0 for d in donation_list) + } + # ============================================================================ # Calendar Export Endpoints (Universal iCalendar .ics format) # ============================================================================ @@ -2305,6 +2374,81 @@ async def get_user_by_id( "updated_at": user.updated_at.isoformat() if user.updated_at else None } +@api_router.get("/admin/users/{user_id}/transactions") +async def get_user_transactions( + user_id: str, + db: Session = Depends(get_db), + current_user: User = Depends(require_permission("users.view")) +): + """ + Get a specific user's transaction history (admin only). + Returns subscriptions and donations for the specified user. + """ + # Verify user exists + user = db.query(User).filter(User.id == user_id).first() + if not user: + raise HTTPException(status_code=404, detail="User not found") + + # Get user's subscriptions with plan details + subscriptions = db.query(Subscription).filter( + Subscription.user_id == user_id + ).order_by(Subscription.created_at.desc()).all() + + subscription_list = [] + for sub in subscriptions: + plan = db.query(SubscriptionPlan).filter(SubscriptionPlan.id == sub.plan_id).first() + subscription_list.append({ + "id": str(sub.id), + "type": "subscription", + "description": plan.name if plan else "Subscription", + "amount_cents": sub.amount_paid_cents or (sub.base_subscription_cents + sub.donation_cents), + "base_amount_cents": sub.base_subscription_cents, + "donation_cents": sub.donation_cents, + "status": sub.status.value if sub.status else "unknown", + "payment_method": sub.payment_method, + "card_brand": sub.card_brand, + "card_last4": sub.card_last4, + "stripe_receipt_url": sub.stripe_receipt_url, + "created_at": sub.created_at.isoformat() if sub.created_at else None, + "payment_completed_at": sub.payment_completed_at.isoformat() if sub.payment_completed_at else None, + "start_date": sub.start_date.isoformat() if sub.start_date else None, + "end_date": sub.end_date.isoformat() if sub.end_date else None, + "billing_cycle": plan.billing_cycle if plan else None, + "manual_payment": sub.manual_payment, + "manual_payment_notes": sub.manual_payment_notes + }) + + # Get user's donations + donations = db.query(Donation).filter( + Donation.user_id == user_id + ).order_by(Donation.created_at.desc()).all() + + donation_list = [] + for don in donations: + donation_list.append({ + "id": str(don.id), + "type": "donation", + "description": "Donation", + "amount_cents": don.amount_cents, + "status": don.status.value if don.status else "unknown", + "payment_method": don.payment_method, + "card_brand": don.card_brand, + "card_last4": don.card_last4, + "stripe_receipt_url": don.stripe_receipt_url, + "created_at": don.created_at.isoformat() if don.created_at else None, + "payment_completed_at": don.payment_completed_at.isoformat() if don.payment_completed_at else None, + "notes": don.notes + }) + + return { + "user_id": str(user.id), + "user_name": f"{user.first_name} {user.last_name}", + "subscriptions": subscription_list, + "donations": donation_list, + "total_subscription_amount_cents": sum(s["amount_cents"] or 0 for s in subscription_list), + "total_donation_amount_cents": sum(d["amount_cents"] or 0 for d in donation_list) + } + @api_router.put("/admin/users/{user_id}") async def update_user_profile( user_id: str,