Spaces:
Sleeping
Sleeping
payment
Browse files- routers/blink.py +63 -2
- templates/index.html +78 -5
routers/blink.py
CHANGED
|
@@ -198,7 +198,7 @@ async def get_gemini_jobs(
|
|
| 198 |
"created_at": item.created_at.isoformat() if item.created_at else None,
|
| 199 |
"completed_at": item.completed_at.isoformat() if item.completed_at else None
|
| 200 |
}
|
| 201 |
-
|
| 202 |
],
|
| 203 |
"total": total,
|
| 204 |
"page": page,
|
|
@@ -212,7 +212,68 @@ async def get_gemini_jobs(
|
|
| 212 |
)
|
| 213 |
|
| 214 |
|
| 215 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
|
| 217 |
@router.get("/blink")
|
| 218 |
async def blink(
|
|
|
|
| 198 |
"created_at": item.created_at.isoformat() if item.created_at else None,
|
| 199 |
"completed_at": item.completed_at.isoformat() if item.completed_at else None
|
| 200 |
}
|
| 201 |
+
for item in items
|
| 202 |
],
|
| 203 |
"total": total,
|
| 204 |
"page": page,
|
|
|
|
| 212 |
)
|
| 213 |
|
| 214 |
|
| 215 |
+
@router.get("/api/payment-transactions")
|
| 216 |
+
async def get_payment_transactions(
|
| 217 |
+
page: int = Query(1, ge=1, description="Page number"),
|
| 218 |
+
limit: int = Query(50, ge=1, le=500, description="Items per page"),
|
| 219 |
+
db: AsyncSession = Depends(get_db)
|
| 220 |
+
):
|
| 221 |
+
"""
|
| 222 |
+
Get paginated payment transactions.
|
| 223 |
+
"""
|
| 224 |
+
from core.models import PaymentTransaction
|
| 225 |
+
|
| 226 |
+
try:
|
| 227 |
+
offset = (page - 1) * limit
|
| 228 |
+
|
| 229 |
+
# Get total count
|
| 230 |
+
total_result = await db.execute(select(func.count(PaymentTransaction.id)))
|
| 231 |
+
total = total_result.scalar() or 0
|
| 232 |
+
|
| 233 |
+
# Get total revenue (paid transactions only)
|
| 234 |
+
revenue_result = await db.execute(
|
| 235 |
+
select(func.sum(PaymentTransaction.amount_paise)).where(
|
| 236 |
+
PaymentTransaction.status == "paid"
|
| 237 |
+
)
|
| 238 |
+
)
|
| 239 |
+
total_revenue_paise = revenue_result.scalar() or 0
|
| 240 |
+
|
| 241 |
+
# Get paginated items
|
| 242 |
+
query = select(PaymentTransaction).order_by(PaymentTransaction.id.desc()).offset(offset).limit(limit)
|
| 243 |
+
result = await db.execute(query)
|
| 244 |
+
items = result.scalars().all()
|
| 245 |
+
|
| 246 |
+
return {
|
| 247 |
+
"items": [
|
| 248 |
+
{
|
| 249 |
+
"id": item.id,
|
| 250 |
+
"transaction_id": item.transaction_id,
|
| 251 |
+
"user_id": item.user_id,
|
| 252 |
+
"gateway": item.gateway,
|
| 253 |
+
"package_id": item.package_id,
|
| 254 |
+
"credits_amount": item.credits_amount,
|
| 255 |
+
"amount_paise": item.amount_paise,
|
| 256 |
+
"amount_rupees": item.amount_paise / 100,
|
| 257 |
+
"currency": item.currency,
|
| 258 |
+
"status": item.status,
|
| 259 |
+
"error_message": item.error_message,
|
| 260 |
+
"created_at": item.created_at.isoformat() if item.created_at else None,
|
| 261 |
+
"paid_at": item.paid_at.isoformat() if item.paid_at else None
|
| 262 |
+
}
|
| 263 |
+
for item in items
|
| 264 |
+
],
|
| 265 |
+
"total": total,
|
| 266 |
+
"total_revenue_paise": total_revenue_paise,
|
| 267 |
+
"total_revenue_rupees": total_revenue_paise / 100,
|
| 268 |
+
"page": page,
|
| 269 |
+
"limit": limit
|
| 270 |
+
}
|
| 271 |
+
except Exception as e:
|
| 272 |
+
logger.error(f"Error fetching payment transactions: {e}")
|
| 273 |
+
raise HTTPException(
|
| 274 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 275 |
+
detail="Error fetching payment transactions"
|
| 276 |
+
)
|
| 277 |
|
| 278 |
@router.get("/blink")
|
| 279 |
async def blink(
|
templates/index.html
CHANGED
|
@@ -299,6 +299,7 @@
|
|
| 299 |
<button class="tab-btn" onclick="switchTab('users')">π₯ Users</button>
|
| 300 |
<button class="tab-btn" onclick="switchTab('audit')">π Audit Logs</button>
|
| 301 |
<button class="tab-btn" onclick="switchTab('jobs')">β‘ Gemini Jobs</button>
|
|
|
|
| 302 |
<button class="tab-btn" onclick="switchTab('keys')">π API Keys</button>
|
| 303 |
</div>
|
| 304 |
|
|
@@ -452,6 +453,43 @@
|
|
| 452 |
</div>
|
| 453 |
</div>
|
| 454 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 455 |
<!-- API Keys Usage Table -->
|
| 456 |
<div class="table-container hidden" id="keysTable">
|
| 457 |
<div class="table-wrapper">
|
|
@@ -490,7 +528,8 @@
|
|
| 490 |
blink: { page: 1, total: 0 },
|
| 491 |
users: { page: 1, total: 0 },
|
| 492 |
audit: { page: 1, total: 0 },
|
| 493 |
-
jobs: { page: 1, total: 0 }
|
|
|
|
| 494 |
};
|
| 495 |
let currentTab = 'blink';
|
| 496 |
|
|
@@ -498,7 +537,8 @@
|
|
| 498 |
blink: '/api/data',
|
| 499 |
users: '/api/users',
|
| 500 |
audit: '/api/audit-logs',
|
| 501 |
-
jobs: '/api/gemini-jobs'
|
|
|
|
| 502 |
};
|
| 503 |
|
| 504 |
function switchTab(tab) {
|
|
@@ -513,10 +553,11 @@
|
|
| 513 |
document.getElementById('usersTable').classList.toggle('hidden', tab !== 'users');
|
| 514 |
document.getElementById('auditTable').classList.toggle('hidden', tab !== 'audit');
|
| 515 |
document.getElementById('jobsTable').classList.toggle('hidden', tab !== 'jobs');
|
|
|
|
| 516 |
document.getElementById('keysTable').classList.toggle('hidden', tab !== 'keys');
|
| 517 |
|
| 518 |
-
// Show/hide unique users stat (
|
| 519 |
-
document.getElementById('uniqueUsersCard').classList.toggle('hidden', tab !== 'blink');
|
| 520 |
|
| 521 |
// Load data
|
| 522 |
if (tab === 'keys') {
|
|
@@ -615,11 +656,34 @@
|
|
| 615 |
`).join('');
|
| 616 |
}
|
| 617 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 618 |
const renderers = {
|
| 619 |
blink: renderBlinkTable,
|
| 620 |
users: renderUsersTable,
|
| 621 |
audit: renderAuditTable,
|
| 622 |
-
jobs: renderJobsTable
|
|
|
|
| 623 |
};
|
| 624 |
|
| 625 |
function updatePagination(tab) {
|
|
@@ -640,6 +704,15 @@
|
|
| 640 |
document.getElementById('uniqueUsers').textContent = data.unique_users.toLocaleString();
|
| 641 |
}
|
| 642 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 643 |
renderers[tab](data.items);
|
| 644 |
updatePagination(tab);
|
| 645 |
}
|
|
|
|
| 299 |
<button class="tab-btn" onclick="switchTab('users')">π₯ Users</button>
|
| 300 |
<button class="tab-btn" onclick="switchTab('audit')">π Audit Logs</button>
|
| 301 |
<button class="tab-btn" onclick="switchTab('jobs')">β‘ Gemini Jobs</button>
|
| 302 |
+
<button class="tab-btn" onclick="switchTab('payments')">π³ Payments</button>
|
| 303 |
<button class="tab-btn" onclick="switchTab('keys')">π API Keys</button>
|
| 304 |
</div>
|
| 305 |
|
|
|
|
| 453 |
</div>
|
| 454 |
</div>
|
| 455 |
|
| 456 |
+
<!-- Payment Transactions Table -->
|
| 457 |
+
<div class="table-container hidden" id="paymentsTable">
|
| 458 |
+
<div class="table-wrapper">
|
| 459 |
+
<table>
|
| 460 |
+
<thead>
|
| 461 |
+
<tr>
|
| 462 |
+
<th>ID</th>
|
| 463 |
+
<th>Transaction ID</th>
|
| 464 |
+
<th>User ID</th>
|
| 465 |
+
<th>Gateway</th>
|
| 466 |
+
<th>Package</th>
|
| 467 |
+
<th>Credits</th>
|
| 468 |
+
<th>Amount</th>
|
| 469 |
+
<th>Status</th>
|
| 470 |
+
<th>Created At</th>
|
| 471 |
+
<th>Paid At</th>
|
| 472 |
+
</tr>
|
| 473 |
+
</thead>
|
| 474 |
+
<tbody id="paymentsBody">
|
| 475 |
+
<tr>
|
| 476 |
+
<td colspan="10">
|
| 477 |
+
<div class="loading">
|
| 478 |
+
<div class="spinner"></div>Loading...
|
| 479 |
+
</div>
|
| 480 |
+
</td>
|
| 481 |
+
</tr>
|
| 482 |
+
</tbody>
|
| 483 |
+
</table>
|
| 484 |
+
</div>
|
| 485 |
+
<div class="pagination">
|
| 486 |
+
<button onclick="prevPage('payments')" id="paymentsPrev" disabled>β Previous</button>
|
| 487 |
+
<span class="page-info">Page <span id="paymentsCurrentPage">1</span> of <span
|
| 488 |
+
id="paymentsTotalPages">1</span></span>
|
| 489 |
+
<button onclick="nextPage('payments')" id="paymentsNext">Next β</button>
|
| 490 |
+
</div>
|
| 491 |
+
</div>
|
| 492 |
+
|
| 493 |
<!-- API Keys Usage Table -->
|
| 494 |
<div class="table-container hidden" id="keysTable">
|
| 495 |
<div class="table-wrapper">
|
|
|
|
| 528 |
blink: { page: 1, total: 0 },
|
| 529 |
users: { page: 1, total: 0 },
|
| 530 |
audit: { page: 1, total: 0 },
|
| 531 |
+
jobs: { page: 1, total: 0 },
|
| 532 |
+
payments: { page: 1, total: 0, revenue: 0 }
|
| 533 |
};
|
| 534 |
let currentTab = 'blink';
|
| 535 |
|
|
|
|
| 537 |
blink: '/api/data',
|
| 538 |
users: '/api/users',
|
| 539 |
audit: '/api/audit-logs',
|
| 540 |
+
jobs: '/api/gemini-jobs',
|
| 541 |
+
payments: '/api/payment-transactions'
|
| 542 |
};
|
| 543 |
|
| 544 |
function switchTab(tab) {
|
|
|
|
| 553 |
document.getElementById('usersTable').classList.toggle('hidden', tab !== 'users');
|
| 554 |
document.getElementById('auditTable').classList.toggle('hidden', tab !== 'audit');
|
| 555 |
document.getElementById('jobsTable').classList.toggle('hidden', tab !== 'jobs');
|
| 556 |
+
document.getElementById('paymentsTable').classList.toggle('hidden', tab !== 'payments');
|
| 557 |
document.getElementById('keysTable').classList.toggle('hidden', tab !== 'keys');
|
| 558 |
|
| 559 |
+
// Show/hide unique users stat (for blink and payments)
|
| 560 |
+
document.getElementById('uniqueUsersCard').classList.toggle('hidden', tab !== 'blink' && tab !== 'payments');
|
| 561 |
|
| 562 |
// Load data
|
| 563 |
if (tab === 'keys') {
|
|
|
|
| 656 |
`).join('');
|
| 657 |
}
|
| 658 |
|
| 659 |
+
function renderPaymentsTable(items) {
|
| 660 |
+
const tbody = document.getElementById('paymentsBody');
|
| 661 |
+
if (items.length === 0) {
|
| 662 |
+
tbody.innerHTML = '<tr><td colspan="10"><div class="empty-state">No payment transactions found</div></td></tr>';
|
| 663 |
+
return;
|
| 664 |
+
}
|
| 665 |
+
tbody.innerHTML = items.map(item => `
|
| 666 |
+
<tr>
|
| 667 |
+
<td>${item.id}</td>
|
| 668 |
+
<td class="user-id">${item.transaction_id}</td>
|
| 669 |
+
<td class="user-id">${item.user_id}</td>
|
| 670 |
+
<td><span class="status-badge" style="background: rgba(123, 44, 191, 0.2); color: #a855f7;">${item.gateway}</span></td>
|
| 671 |
+
<td>${item.package_id}</td>
|
| 672 |
+
<td><span class="credits-badge">${item.credits_amount}</span></td>
|
| 673 |
+
<td>βΉ${item.amount_rupees}</td>
|
| 674 |
+
<td><span class="status-badge status-${item.status === 'paid' ? 'success' : item.status === 'failed' ? 'failed' : 'queued'}">${item.status}</span></td>
|
| 675 |
+
<td class="timestamp">${item.created_at ? new Date(item.created_at).toLocaleString() : '-'}</td>
|
| 676 |
+
<td class="timestamp">${item.paid_at ? new Date(item.paid_at).toLocaleString() : '-'}</td>
|
| 677 |
+
</tr>
|
| 678 |
+
`).join('');
|
| 679 |
+
}
|
| 680 |
+
|
| 681 |
const renderers = {
|
| 682 |
blink: renderBlinkTable,
|
| 683 |
users: renderUsersTable,
|
| 684 |
audit: renderAuditTable,
|
| 685 |
+
jobs: renderJobsTable,
|
| 686 |
+
payments: renderPaymentsTable
|
| 687 |
};
|
| 688 |
|
| 689 |
function updatePagination(tab) {
|
|
|
|
| 704 |
document.getElementById('uniqueUsers').textContent = data.unique_users.toLocaleString();
|
| 705 |
}
|
| 706 |
|
| 707 |
+
// Show revenue for payments tab
|
| 708 |
+
if (tab === 'payments' && data.total_revenue_rupees !== undefined) {
|
| 709 |
+
state.payments.revenue = data.total_revenue_rupees;
|
| 710 |
+
document.getElementById('uniqueUsers').textContent = 'βΉ' + data.total_revenue_rupees.toLocaleString();
|
| 711 |
+
document.getElementById('uniqueUsersCard').querySelector('.stat-label').textContent = 'Total Revenue';
|
| 712 |
+
} else if (tab === 'blink') {
|
| 713 |
+
document.getElementById('uniqueUsersCard').querySelector('.stat-label').textContent = 'Unique Users';
|
| 714 |
+
}
|
| 715 |
+
|
| 716 |
renderers[tab](data.items);
|
| 717 |
updatePagination(tab);
|
| 718 |
}
|