jebin2 commited on
Commit
9869f13
·
1 Parent(s): 02d7aa0

payment id

Browse files
Files changed (4) hide show
  1. app.py +2 -1
  2. core/models.py +19 -0
  3. routers/contact.py +98 -0
  4. routers/payments.py +27 -6
app.py CHANGED
@@ -12,7 +12,7 @@ from fastapi.middleware.cors import CORSMiddleware
12
  from fastapi.responses import JSONResponse
13
 
14
  from core.database import init_db
15
- from routers import auth, blink, credits, general, gemini, payments
16
  from services.drive_service import DriveService
17
 
18
  # Configure logging
@@ -88,6 +88,7 @@ app.include_router(blink.router)
88
  app.include_router(gemini.router)
89
  app.include_router(credits.router)
90
  app.include_router(payments.router)
 
91
 
92
 
93
  @app.exception_handler(Exception)
 
12
  from fastapi.responses import JSONResponse
13
 
14
  from core.database import init_db
15
+ from routers import auth, blink, contact, credits, general, gemini, payments
16
  from services.drive_service import DriveService
17
 
18
  # Configure logging
 
88
  app.include_router(gemini.router)
89
  app.include_router(credits.router)
90
  app.include_router(payments.router)
91
+ app.include_router(contact.router)
92
 
93
 
94
  @app.exception_handler(Exception)
core/models.py CHANGED
@@ -190,3 +190,22 @@ class PaymentTransaction(Base):
190
 
191
  def __repr__(self):
192
  return f"<PaymentTransaction(id={self.transaction_id}, status={self.status}, amount={self.amount_paise})>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
 
191
  def __repr__(self):
192
  return f"<PaymentTransaction(id={self.transaction_id}, status={self.status}, amount={self.amount_paise})>"
193
+
194
+
195
+ class Contact(Base):
196
+ """
197
+ Contact form submissions from authenticated users.
198
+ For customer support inquiries.
199
+ """
200
+ __tablename__ = "contacts"
201
+
202
+ id = Column(Integer, primary_key=True, autoincrement=True, index=True)
203
+ user_id = Column(String(50), index=True, nullable=False) # Links to User.user_id
204
+ email = Column(String(255), nullable=False, index=True) # User's email
205
+ subject = Column(String(500), nullable=True)
206
+ message = Column(Text, nullable=False)
207
+ ip_address = Column(String(45), nullable=True) # IPv6 can be up to 45 chars
208
+ created_at = Column(DateTime(timezone=True), server_default=func.now())
209
+
210
+ def __repr__(self):
211
+ return f"<Contact(id={self.id}, user_id={self.user_id}, email={self.email})>"
routers/contact.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Contact Router - API endpoint for customer support contact form.
3
+
4
+ Endpoints:
5
+ - POST /contact - Submit a contact form (requires authentication)
6
+ """
7
+
8
+ import logging
9
+ from typing import Optional
10
+
11
+ from fastapi import APIRouter, Depends, HTTPException, Request, status
12
+ from pydantic import BaseModel, EmailStr
13
+ from sqlalchemy.ext.asyncio import AsyncSession
14
+
15
+ from core.database import get_db
16
+ from core.models import User, Contact
17
+ from dependencies import get_current_user
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ router = APIRouter(prefix="/contact", tags=["contact"])
22
+
23
+
24
+ # =============================================================================
25
+ # Request/Response Models
26
+ # =============================================================================
27
+
28
+ class ContactRequest(BaseModel):
29
+ """Request to submit a contact form."""
30
+ subject: Optional[str] = None
31
+ message: str
32
+
33
+
34
+ class ContactResponse(BaseModel):
35
+ """Response after contact form submission."""
36
+ success: bool
37
+ message: str
38
+ email: str # Return user's email for confirmation
39
+
40
+
41
+ # =============================================================================
42
+ # Endpoints
43
+ # =============================================================================
44
+
45
+ @router.post("", response_model=ContactResponse)
46
+ async def submit_contact(
47
+ request_body: ContactRequest,
48
+ request: Request,
49
+ user: User = Depends(get_current_user),
50
+ db: AsyncSession = Depends(get_db)
51
+ ):
52
+ """
53
+ Submit a contact form for customer support.
54
+
55
+ Requires authentication - user must be logged in.
56
+ """
57
+ # Validate message
58
+ if not request_body.message or not request_body.message.strip():
59
+ raise HTTPException(
60
+ status_code=status.HTTP_400_BAD_REQUEST,
61
+ detail="Message cannot be empty"
62
+ )
63
+
64
+ # Get client IP
65
+ client_ip = request.headers.get("X-Forwarded-For", request.client.host if request.client else None)
66
+ if client_ip:
67
+ client_ip = client_ip.split(",")[0].strip()
68
+
69
+ try:
70
+ # Create contact record
71
+ contact = Contact(
72
+ user_id=user.user_id,
73
+ email=user.email,
74
+ subject=request_body.subject.strip() if request_body.subject else None,
75
+ message=request_body.message.strip(),
76
+ ip_address=client_ip
77
+ )
78
+ db.add(contact)
79
+ await db.commit()
80
+
81
+ logger.info(
82
+ f"Contact form submitted: user={user.user_id}, email={user.email}, "
83
+ f"subject={request_body.subject}"
84
+ )
85
+
86
+ return ContactResponse(
87
+ success=True,
88
+ message="Your message has been received. We will get back to you shortly.",
89
+ email=user.email
90
+ )
91
+
92
+ except Exception as e:
93
+ logger.error(f"Error saving contact form: {e}")
94
+ await db.rollback()
95
+ raise HTTPException(
96
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
97
+ detail="Failed to submit contact form. Please try again."
98
+ )
routers/payments.py CHANGED
@@ -14,9 +14,9 @@ import uuid
14
  from datetime import datetime
15
  from typing import List, Optional
16
 
17
- from fastapi import APIRouter, Depends, HTTPException, Request, status
18
  from pydantic import BaseModel
19
- from sqlalchemy import select, desc
20
  from sqlalchemy.ext.asyncio import AsyncSession
21
 
22
  from core.database import get_db
@@ -93,6 +93,7 @@ class VerifyPaymentResponse(BaseModel):
93
  class PaymentHistoryItem(BaseModel):
94
  """Single payment in history."""
95
  transaction_id: str
 
96
  package_id: str
97
  credits_amount: int
98
  amount_paise: int
@@ -105,9 +106,11 @@ class PaymentHistoryItem(BaseModel):
105
 
106
 
107
  class PaymentHistoryResponse(BaseModel):
108
- """Payment history response."""
109
  transactions: List[PaymentHistoryItem]
110
  total_count: int
 
 
111
 
112
 
113
  # =============================================================================
@@ -454,18 +457,33 @@ async def razorpay_webhook(
454
 
455
  @router.get("/history", response_model=PaymentHistoryResponse)
456
  async def get_payment_history(
 
 
457
  user: User = Depends(get_current_user),
458
  db: AsyncSession = Depends(get_db)
459
  ):
460
  """
461
- Get user's payment history.
462
 
463
- Returns all payment transactions ordered by newest first.
464
  """
 
 
 
 
 
 
 
 
 
 
 
465
  result = await db.execute(
466
  select(PaymentTransaction)
467
  .where(PaymentTransaction.user_id == user.user_id)
468
  .order_by(desc(PaymentTransaction.created_at))
 
 
469
  )
470
  transactions = result.scalars().all()
471
 
@@ -473,6 +491,7 @@ async def get_payment_history(
473
  for txn in transactions:
474
  history.append(PaymentHistoryItem(
475
  transaction_id=txn.transaction_id,
 
476
  package_id=txn.package_id,
477
  credits_amount=txn.credits_amount,
478
  amount_paise=txn.amount_paise,
@@ -486,5 +505,7 @@ async def get_payment_history(
486
 
487
  return PaymentHistoryResponse(
488
  transactions=history,
489
- total_count=len(history)
 
 
490
  )
 
14
  from datetime import datetime
15
  from typing import List, Optional
16
 
17
+ from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
18
  from pydantic import BaseModel
19
+ from sqlalchemy import select, desc, func
20
  from sqlalchemy.ext.asyncio import AsyncSession
21
 
22
  from core.database import get_db
 
93
  class PaymentHistoryItem(BaseModel):
94
  """Single payment in history."""
95
  transaction_id: str
96
+ razorpay_payment_id: Optional[str] = None # Razorpay payment ID
97
  package_id: str
98
  credits_amount: int
99
  amount_paise: int
 
106
 
107
 
108
  class PaymentHistoryResponse(BaseModel):
109
+ """Payment history response with pagination."""
110
  transactions: List[PaymentHistoryItem]
111
  total_count: int
112
+ page: int
113
+ limit: int
114
 
115
 
116
  # =============================================================================
 
457
 
458
  @router.get("/history", response_model=PaymentHistoryResponse)
459
  async def get_payment_history(
460
+ page: int = Query(1, ge=1, description="Page number"),
461
+ limit: int = Query(20, ge=1, le=100, description="Items per page"),
462
  user: User = Depends(get_current_user),
463
  db: AsyncSession = Depends(get_db)
464
  ):
465
  """
466
+ Get user's payment history with pagination.
467
 
468
+ Returns payment transactions ordered by newest first.
469
  """
470
+ # Get total count
471
+ count_result = await db.execute(
472
+ select(func.count(PaymentTransaction.id))
473
+ .where(PaymentTransaction.user_id == user.user_id)
474
+ )
475
+ total_count = count_result.scalar() or 0
476
+
477
+ # Calculate offset
478
+ offset = (page - 1) * limit
479
+
480
+ # Get paginated transactions
481
  result = await db.execute(
482
  select(PaymentTransaction)
483
  .where(PaymentTransaction.user_id == user.user_id)
484
  .order_by(desc(PaymentTransaction.created_at))
485
+ .offset(offset)
486
+ .limit(limit)
487
  )
488
  transactions = result.scalars().all()
489
 
 
491
  for txn in transactions:
492
  history.append(PaymentHistoryItem(
493
  transaction_id=txn.transaction_id,
494
+ razorpay_payment_id=txn.gateway_payment_id,
495
  package_id=txn.package_id,
496
  credits_amount=txn.credits_amount,
497
  amount_paise=txn.amount_paise,
 
505
 
506
  return PaymentHistoryResponse(
507
  transactions=history,
508
+ total_count=total_count,
509
+ page=page,
510
+ limit=limit
511
  )