mirror of
https://github.com/thecyberlearn/quantum-ai-v3.git
synced 2026-08-18 17:32:57 +00:00
- Created /wallet/demo/ page for testing Stripe payment integration - Real-time balance monitoring and transaction logging - Interactive payment testing with amount selection - Live webhook status monitoring and debugging logs - AJAX-powered balance refresh and payment creation - Visual indicators for webhook connectivity status - Detailed session ID and payment link display This demo page will help debug webhook delivery issues and test the complete payment flow from session creation to balance updates. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
502 lines
15 KiB
HTML
502 lines
15 KiB
HTML
{% extends 'base.html' %}
|
|
{% load static %}
|
|
|
|
{% block title %}Wallet Demo - Stripe Webhook Testing{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.demo-page {
|
|
background: var(--gradient-hero);
|
|
min-height: calc(100vh - 80px);
|
|
padding: clamp(20px, 5vw, 40px);
|
|
}
|
|
|
|
.demo-container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 24px;
|
|
align-items: start;
|
|
}
|
|
|
|
.demo-card {
|
|
background: rgba(255, 255, 255, 0.95);
|
|
border-radius: 16px;
|
|
padding: 24px;
|
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
backdrop-filter: blur(20px);
|
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.demo-title {
|
|
font-size: 24px;
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
margin-bottom: 20px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.status-indicator {
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 50%;
|
|
background: #e74c3c;
|
|
animation: pulse 2s infinite;
|
|
}
|
|
|
|
.status-indicator.connected {
|
|
background: #27ae60;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0% { opacity: 1; }
|
|
50% { opacity: 0.5; }
|
|
100% { opacity: 1; }
|
|
}
|
|
|
|
.balance-display {
|
|
font-size: 36px;
|
|
font-weight: 800;
|
|
color: var(--primary-blue);
|
|
text-align: center;
|
|
margin: 20px 0;
|
|
padding: 20px;
|
|
background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
|
|
border-radius: 12px;
|
|
border: 2px solid var(--border-light);
|
|
}
|
|
|
|
.user-info {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 15px;
|
|
margin: 20px 0;
|
|
padding: 15px;
|
|
background: #f8fafc;
|
|
border-radius: 8px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.user-info strong {
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.test-amounts {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 12px;
|
|
margin: 20px 0;
|
|
}
|
|
|
|
.amount-btn {
|
|
padding: 12px 20px;
|
|
border: 2px solid var(--primary-blue);
|
|
background: transparent;
|
|
color: var(--primary-blue);
|
|
border-radius: 8px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
text-align: center;
|
|
}
|
|
|
|
.amount-btn:hover {
|
|
background: var(--primary-blue);
|
|
color: white;
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.amount-btn:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.control-buttons {
|
|
display: flex;
|
|
gap: 12px;
|
|
margin: 20px 0;
|
|
}
|
|
|
|
.btn-primary {
|
|
flex: 1;
|
|
padding: 12px 24px;
|
|
background: var(--primary-blue);
|
|
color: white;
|
|
border: none;
|
|
border-radius: 8px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
background: var(--primary-dark);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.btn-secondary {
|
|
flex: 1;
|
|
padding: 12px 24px;
|
|
background: transparent;
|
|
color: var(--primary-blue);
|
|
border: 2px solid var(--primary-blue);
|
|
border-radius: 8px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background: var(--primary-blue);
|
|
color: white;
|
|
}
|
|
|
|
.transaction-log {
|
|
max-height: 300px;
|
|
overflow-y: auto;
|
|
background: #f8fafc;
|
|
border-radius: 8px;
|
|
padding: 15px;
|
|
margin: 20px 0;
|
|
}
|
|
|
|
.transaction-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 8px 0;
|
|
border-bottom: 1px solid #e2e8f0;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.transaction-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.transaction-amount {
|
|
font-weight: 600;
|
|
}
|
|
|
|
.transaction-amount.positive {
|
|
color: #27ae60;
|
|
}
|
|
|
|
.transaction-amount.negative {
|
|
color: #e74c3c;
|
|
}
|
|
|
|
.webhook-logs {
|
|
max-height: 400px;
|
|
overflow-y: auto;
|
|
background: #1a1a1a;
|
|
color: #00ff00;
|
|
border-radius: 8px;
|
|
padding: 15px;
|
|
font-family: 'Courier New', monospace;
|
|
font-size: 12px;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.log-entry {
|
|
margin: 2px 0;
|
|
padding: 2px 0;
|
|
}
|
|
|
|
.log-timestamp {
|
|
color: #888;
|
|
margin-right: 8px;
|
|
}
|
|
|
|
.log-success {
|
|
color: #00ff00;
|
|
}
|
|
|
|
.log-error {
|
|
color: #ff6b6b;
|
|
}
|
|
|
|
.log-info {
|
|
color: #74c0fc;
|
|
}
|
|
|
|
.selected-amount {
|
|
background: var(--primary-blue) !important;
|
|
color: white !important;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.demo-container {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.test-amounts {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
|
|
.control-buttons {
|
|
flex-direction: column;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="demo-page">
|
|
<div class="demo-container">
|
|
<!-- Payment Testing Panel -->
|
|
<div class="demo-card">
|
|
<h2 class="demo-title">
|
|
🧪 Payment Testing
|
|
<div class="status-indicator" id="paymentStatus"></div>
|
|
</h2>
|
|
|
|
<div class="balance-display" id="currentBalance">
|
|
{{ current_balance|floatformat:2 }} AED
|
|
</div>
|
|
|
|
<div class="user-info">
|
|
<div><strong>User ID:</strong> {{ user_id }}</div>
|
|
<div><strong>Email:</strong> {{ user_email }}</div>
|
|
<div><strong>Environment:</strong> Railway</div>
|
|
<div><strong>Webhook:</strong> Active</div>
|
|
</div>
|
|
|
|
<h3>Test Amounts</h3>
|
|
<div class="test-amounts">
|
|
<button class="amount-btn" data-amount="10">10 AED</button>
|
|
<button class="amount-btn" data-amount="50">50 AED</button>
|
|
<button class="amount-btn" data-amount="100">100 AED</button>
|
|
<button class="amount-btn" data-amount="500">500 AED</button>
|
|
</div>
|
|
|
|
<div class="control-buttons">
|
|
<button class="btn-primary" id="createPayment" disabled>
|
|
💳 Create Test Payment
|
|
</button>
|
|
<button class="btn-secondary" id="refreshBalance">
|
|
🔄 Refresh Balance
|
|
</button>
|
|
</div>
|
|
|
|
<div id="paymentInfo" style="display: none; margin-top: 15px; padding: 15px; background: #e3f2fd; border-radius: 8px; font-size: 14px;">
|
|
<strong>Payment Created:</strong><br>
|
|
<span id="sessionId"></span><br>
|
|
<a href="#" id="paymentLink" target="_blank" style="color: var(--primary-blue); font-weight: 600;">🔗 Complete Payment</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Webhook Monitoring Panel -->
|
|
<div class="demo-card">
|
|
<h2 class="demo-title">
|
|
📡 Webhook Monitor
|
|
<div class="status-indicator" id="webhookStatus"></div>
|
|
</h2>
|
|
|
|
<h3>Recent Transactions</h3>
|
|
<div class="transaction-log" id="transactionLog">
|
|
{% for transaction in recent_transactions %}
|
|
<div class="transaction-item">
|
|
<div>
|
|
<div>{{ transaction.description|default:"Wallet Transaction" }}</div>
|
|
<div style="font-size: 12px; color: #666;">{{ transaction.created_at|date:"M d, H:i" }}</div>
|
|
</div>
|
|
<div class="transaction-amount {% if transaction.amount >= 0 %}positive{% else %}negative{% endif %}">
|
|
{% if transaction.amount >= 0 %}+{% endif %}{{ transaction.amount|floatformat:2 }} AED
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div style="text-align: center; color: #666; padding: 20px;">
|
|
No transactions yet. Create a test payment to see webhook activity.
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<h3>Webhook Logs</h3>
|
|
<div class="webhook-logs" id="webhookLogs">
|
|
<div class="log-entry log-info">
|
|
<span class="log-timestamp">[INIT]</span> Webhook monitoring started...
|
|
</div>
|
|
<div class="log-entry log-info">
|
|
<span class="log-timestamp">[INFO]</span> Endpoint: https://netcop.up.railway.app/stripe/webhook/
|
|
</div>
|
|
<div class="log-entry log-info">
|
|
<span class="log-timestamp">[INFO]</span> Waiting for webhook events...
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Include CSRF token for AJAX requests -->
|
|
{% csrf_token %}
|
|
|
|
<script>
|
|
let selectedAmount = null;
|
|
let pollingInterval = null;
|
|
|
|
// Get CSRF token
|
|
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
|
|
|
|
// Initialize page
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
setupEventListeners();
|
|
startPolling();
|
|
updateStatus('webhook', true);
|
|
logMessage('System initialized successfully', 'success');
|
|
});
|
|
|
|
function setupEventListeners() {
|
|
// Amount button selection
|
|
document.querySelectorAll('.amount-btn').forEach(btn => {
|
|
btn.addEventListener('click', function() {
|
|
selectedAmount = parseFloat(this.dataset.amount);
|
|
|
|
// Update UI
|
|
document.querySelectorAll('.amount-btn').forEach(b => b.classList.remove('selected-amount'));
|
|
this.classList.add('selected-amount');
|
|
document.getElementById('createPayment').disabled = false;
|
|
|
|
logMessage(`Selected amount: ${selectedAmount} AED`, 'info');
|
|
});
|
|
});
|
|
|
|
// Create payment button
|
|
document.getElementById('createPayment').addEventListener('click', createTestPayment);
|
|
|
|
// Refresh balance button
|
|
document.getElementById('refreshBalance').addEventListener('click', refreshBalance);
|
|
}
|
|
|
|
function createTestPayment() {
|
|
if (!selectedAmount) {
|
|
logMessage('Please select an amount first', 'error');
|
|
return;
|
|
}
|
|
|
|
const btn = document.getElementById('createPayment');
|
|
btn.disabled = true;
|
|
btn.textContent = '⏳ Creating Payment...';
|
|
|
|
logMessage(`Creating payment session for ${selectedAmount} AED...`, 'info');
|
|
|
|
fetch('/wallet/demo/test-payment/', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': csrfToken
|
|
},
|
|
body: JSON.stringify({ amount: selectedAmount })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
document.getElementById('sessionId').textContent = data.session_id;
|
|
document.getElementById('paymentLink').href = data.payment_url;
|
|
document.getElementById('paymentInfo').style.display = 'block';
|
|
|
|
logMessage(`Payment session created: ${data.session_id}`, 'success');
|
|
logMessage(`User ID in session: ${data.user_id}`, 'info');
|
|
logMessage('Complete payment to test webhook...', 'info');
|
|
|
|
updateStatus('payment', true);
|
|
} else {
|
|
logMessage(`Payment creation failed: ${data.error}`, 'error');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
logMessage(`Error: ${error.message}`, 'error');
|
|
})
|
|
.finally(() => {
|
|
btn.disabled = false;
|
|
btn.textContent = '💳 Create Test Payment';
|
|
});
|
|
}
|
|
|
|
function refreshBalance() {
|
|
logMessage('Refreshing balance...', 'info');
|
|
|
|
fetch('/wallet/demo/check-balance/')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
document.getElementById('currentBalance').textContent = `${data.balance.toFixed(2)} AED`;
|
|
updateTransactionLog(data.recent_transactions);
|
|
logMessage(`Balance updated: ${data.balance.toFixed(2)} AED`, 'success');
|
|
})
|
|
.catch(error => {
|
|
logMessage(`Error refreshing balance: ${error.message}`, 'error');
|
|
});
|
|
}
|
|
|
|
function updateTransactionLog(transactions) {
|
|
const log = document.getElementById('transactionLog');
|
|
log.innerHTML = '';
|
|
|
|
if (transactions.length === 0) {
|
|
log.innerHTML = '<div style="text-align: center; color: #666; padding: 20px;">No transactions yet.</div>';
|
|
return;
|
|
}
|
|
|
|
transactions.forEach(transaction => {
|
|
const item = document.createElement('div');
|
|
item.className = 'transaction-item';
|
|
|
|
const date = new Date(transaction.created_at).toLocaleString();
|
|
const amountClass = transaction.amount >= 0 ? 'positive' : 'negative';
|
|
const amountPrefix = transaction.amount >= 0 ? '+' : '';
|
|
|
|
item.innerHTML = `
|
|
<div>
|
|
<div>${transaction.description || 'Wallet Transaction'}</div>
|
|
<div style="font-size: 12px; color: #666;">${date}</div>
|
|
</div>
|
|
<div class="transaction-amount ${amountClass}">
|
|
${amountPrefix}${transaction.amount.toFixed(2)} AED
|
|
</div>
|
|
`;
|
|
|
|
log.appendChild(item);
|
|
});
|
|
}
|
|
|
|
function startPolling() {
|
|
// Poll for balance changes every 5 seconds
|
|
pollingInterval = setInterval(refreshBalance, 5000);
|
|
}
|
|
|
|
function updateStatus(type, connected) {
|
|
const indicator = document.getElementById(type + 'Status');
|
|
if (connected) {
|
|
indicator.classList.add('connected');
|
|
} else {
|
|
indicator.classList.remove('connected');
|
|
}
|
|
}
|
|
|
|
function logMessage(message, type = 'info') {
|
|
const logs = document.getElementById('webhookLogs');
|
|
const timestamp = new Date().toLocaleTimeString();
|
|
|
|
const entry = document.createElement('div');
|
|
entry.className = `log-entry log-${type}`;
|
|
entry.innerHTML = `<span class="log-timestamp">[${timestamp}]</span> ${message}`;
|
|
|
|
logs.appendChild(entry);
|
|
logs.scrollTop = logs.scrollHeight;
|
|
|
|
// Keep only last 50 log entries
|
|
while (logs.children.length > 50) {
|
|
logs.removeChild(logs.firstChild);
|
|
}
|
|
}
|
|
|
|
// Cleanup on page unload
|
|
window.addEventListener('beforeunload', function() {
|
|
if (pollingInterval) {
|
|
clearInterval(pollingInterval);
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %} |