You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
928 lines
33 KiB
928 lines
33 KiB
{% extends "base.html" %}
|
|
|
|
{% block title %}Payment #{{ payment.id }} - Plutus{% endblock %}
|
|
|
|
{% block head %}
|
|
<style>
|
|
/* Background styling */
|
|
body {
|
|
background-color: #3a3a3a !important;
|
|
background-image: url("{{ url_for('static', filename='images/plutus3.JPG') }}") !important;
|
|
background-size: cover;
|
|
background-position: center;
|
|
background-repeat: no-repeat;
|
|
background-attachment: fixed;
|
|
position: relative;
|
|
}
|
|
|
|
body::before {
|
|
content: '';
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-color: rgba(58, 58, 58, 0.85);
|
|
z-index: -1;
|
|
}
|
|
|
|
/* Ensure content is visible */
|
|
.container-fluid, .container {
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
/* Ensure navbar and footer are above background */
|
|
.navbar, .footer, main {
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
/* Page title and breadcrumb styling */
|
|
h1, h2, h3, h4, h5, h6 {
|
|
color: #faf8f0 !important;
|
|
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
|
|
}
|
|
|
|
.breadcrumb-item a {
|
|
color: #d4af37 !important;
|
|
}
|
|
|
|
.breadcrumb-item.active {
|
|
color: #faf8f0 !important;
|
|
}
|
|
|
|
/* Enhanced visibility */
|
|
.card, .info-card {
|
|
background-color: rgba(250, 248, 240, 0.98) !important;
|
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
.alert {
|
|
background-color: rgba(250, 248, 240, 0.98);
|
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
/* Payment status banner colors with gradients */
|
|
.status-banner {
|
|
border-left: 5px solid;
|
|
border-radius: 0.375rem;
|
|
padding: 1.5rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.status-banner.refund {
|
|
border-color: #9370db;
|
|
background: linear-gradient(135deg, #f8f4ff 0%, #ede7f6 100%);
|
|
}
|
|
|
|
.status-banner.refund-processing {
|
|
border-color: #ff8c00;
|
|
background: linear-gradient(135deg, #fff8f0 0%, #ffe5cc 100%);
|
|
}
|
|
|
|
.status-banner.success {
|
|
border-color: #28a745;
|
|
background: linear-gradient(135deg, #f0fff4 0%, #e8f5e9 100%);
|
|
}
|
|
|
|
.status-banner.failed {
|
|
border-color: #dc3545;
|
|
background: linear-gradient(135deg, #fff5f5 0%, #ffebee 100%);
|
|
}
|
|
|
|
.status-banner.pending {
|
|
border-color: #ffc107;
|
|
background: linear-gradient(135deg, #fffbf0 0%, #fff3cd 100%);
|
|
}
|
|
|
|
.status-banner .status-icon {
|
|
font-size: 2.5rem;
|
|
}
|
|
|
|
.status-banner .status-title {
|
|
font-size: 1.5rem;
|
|
font-weight: 600;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.status-banner .status-text {
|
|
color: #6c757d;
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
/* Card styling with subtle shadows */
|
|
.info-card {
|
|
border-radius: 0.5rem;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
transition: box-shadow 0.3s ease;
|
|
}
|
|
|
|
.info-card:hover {
|
|
box-shadow: 0 4px 16px rgba(0,0,0,0.15);
|
|
}
|
|
|
|
.card-header-custom {
|
|
background: linear-gradient(135deg, #d4af37 0%, #c5a028 100%);
|
|
color: #000;
|
|
font-weight: 600;
|
|
padding: 1rem 1.25rem;
|
|
border-radius: 0.5rem 0.5rem 0 0;
|
|
}
|
|
|
|
.card-header-error {
|
|
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
|
|
color: #fff;
|
|
}
|
|
|
|
/* Table styling enhancements */
|
|
.table-config td:first-child {
|
|
background-color: #f8f9fa;
|
|
font-weight: 600;
|
|
width: 40%;
|
|
}
|
|
|
|
/* JSON code block styling */
|
|
pre code {
|
|
background-color: #1e1e1e;
|
|
color: #d4d4d4;
|
|
padding: 1rem;
|
|
border-radius: 0.375rem;
|
|
display: block;
|
|
overflow-x: auto;
|
|
max-height: 400px;
|
|
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
/* Modal styling to match Hades theme */
|
|
/* Z-index is handled globally in custom.css */
|
|
.modal-content {
|
|
background-color: #2b2b2b;
|
|
border: none;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.modal-header {
|
|
background-color: #d4af37;
|
|
color: #000;
|
|
border-bottom: none;
|
|
border-top-left-radius: 8px;
|
|
border-top-right-radius: 8px;
|
|
padding: 1rem 1.5rem;
|
|
}
|
|
|
|
.modal-header.bg-refund {
|
|
background-color: #9370db !important;
|
|
color: #fff !important;
|
|
}
|
|
|
|
.modal-header.bg-refund .modal-title {
|
|
color: #fff;
|
|
}
|
|
|
|
.modal-header.bg-success {
|
|
background-color: #28a745 !important;
|
|
color: #fff !important;
|
|
}
|
|
|
|
.modal-header.bg-success .modal-title,
|
|
.modal-header.bg-danger .modal-title {
|
|
color: #fff;
|
|
}
|
|
|
|
.modal-header.bg-success .btn-close,
|
|
.modal-header.bg-danger .btn-close,
|
|
.modal-header.bg-refund .btn-close {
|
|
filter: brightness(0) invert(1);
|
|
}
|
|
|
|
.modal-header .modal-title {
|
|
color: #000;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.modal-header .btn-close {
|
|
filter: brightness(0);
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.modal-header .btn-close:hover {
|
|
opacity: 1;
|
|
}
|
|
|
|
.modal-body {
|
|
background-color: #2b2b2b;
|
|
color: #f0f0f0;
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
.modal-footer {
|
|
background-color: #2b2b2b;
|
|
border-top: 1px solid #444;
|
|
border-bottom-left-radius: 8px;
|
|
border-bottom-right-radius: 8px;
|
|
}
|
|
|
|
/* Scrollbar styling for dark modal */
|
|
.modal-body::-webkit-scrollbar {
|
|
width: 8px;
|
|
}
|
|
|
|
.modal-body::-webkit-scrollbar-track {
|
|
background: #1e1e1e;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.modal-body::-webkit-scrollbar-thumb {
|
|
background: #d4af37;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.modal-body::-webkit-scrollbar-thumb:hover {
|
|
background: #c5a028;
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
// Modal functionality (Bootstrap 5)
|
|
function showModal(modalId) {
|
|
console.log('Single Payment Detail: Opening modal ' + modalId);
|
|
const modalElement = document.getElementById(modalId);
|
|
if (!modalElement) {
|
|
console.error('Single Payment Detail: Modal element ' + modalId + ' not found!');
|
|
return;
|
|
}
|
|
|
|
const modal = new bootstrap.Modal(modalElement);
|
|
modal.show();
|
|
|
|
modalElement.addEventListener('shown.bs.modal', function() {
|
|
console.log('Single Payment Detail: Modal ' + modalId + ' is now visible');
|
|
}, { once: true });
|
|
}
|
|
|
|
function hideModal(modalId) {
|
|
const modalElement = document.getElementById(modalId);
|
|
if (!modalElement) return;
|
|
|
|
const modal = bootstrap.Modal.getInstance(modalElement);
|
|
if (modal) {
|
|
modal.hide();
|
|
}
|
|
}
|
|
|
|
function showRefundModal() {
|
|
showModal('refundModal');
|
|
}
|
|
|
|
function processRefund() {
|
|
const btn = document.getElementById('processRefundBtn');
|
|
const originalText = btn.innerHTML;
|
|
const reason = document.getElementById('refundReason').value;
|
|
|
|
// Disable button and show loading
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Processing...';
|
|
|
|
// Make API call to process refund
|
|
fetch(`/single-payment/refund/{{ payment.id }}`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
reason: reason
|
|
})
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
hideModal('refundModal');
|
|
if (data.success) {
|
|
// Handle both successful and pending refunds
|
|
if (data.pending) {
|
|
// BECS Direct Debit refunds are pending and need follow-up
|
|
showSuccessModal(`
|
|
<h4>Refund Processing!</h4>
|
|
<p>The refund has been initiated and is currently being processed by the bank.</p>
|
|
<p><strong>Status:</strong> <span class="badge bg-warning">Pending</span></p>
|
|
<p><strong>Refund ID:</strong> ${data.refund_id}</p>
|
|
<p><strong>Amount:</strong> ${data.amount_refunded}</p>
|
|
<div class="alert alert-info mt-3">
|
|
<i class="fas fa-info-circle me-2"></i>
|
|
BECS Direct Debit refunds can take several business days to complete. The refund will be automatically updated once processed.
|
|
</div>
|
|
`);
|
|
} else {
|
|
// Standard card refunds are processed immediately
|
|
showSuccessModal(`
|
|
<h4>Refund Processed Successfully!</h4>
|
|
<p>The refund has been completed and processed by Stripe.</p>
|
|
<p><strong>Status:</strong> <span class="badge bg-success">Completed</span></p>
|
|
<p><strong>Refund ID:</strong> ${data.refund_id}</p>
|
|
<p><strong>Amount:</strong> ${data.amount_refunded}</p>
|
|
`);
|
|
}
|
|
} else {
|
|
showErrorModal(data.error || 'Failed to process refund');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
hideModal('refundModal');
|
|
console.error('Error processing refund:', error);
|
|
showErrorModal('Failed to process refund. Please try again.');
|
|
})
|
|
.finally(() => {
|
|
// Re-enable button
|
|
btn.disabled = false;
|
|
btn.innerHTML = originalText;
|
|
});
|
|
}
|
|
|
|
function checkPaymentIntent() {
|
|
const btn = document.getElementById('checkIntentBtn');
|
|
const originalText = btn.innerHTML;
|
|
|
|
// Disable button and show loading
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Checking...';
|
|
|
|
// Make API call to check payment intent
|
|
fetch(`/single-payment/check-intent/{{ payment.id }}`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
if (data.payment_succeeded) {
|
|
showSuccessModal(`
|
|
<h4>Payment Completed Successfully!</h4>
|
|
<p>The Direct Debit payment has been processed and completed.</p>
|
|
<p><strong>Status:</strong> ${data.status}</p>
|
|
`);
|
|
} else {
|
|
showSuccessModal(`
|
|
<h4>Status Updated</h4>
|
|
<p>Payment status has been updated.</p>
|
|
<p><strong>Current Status:</strong> ${data.status}</p>
|
|
`);
|
|
}
|
|
} else {
|
|
showErrorModal(data.error || 'Failed to check payment intent status');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error checking payment intent:', error);
|
|
showErrorModal('Failed to check payment intent status. Please try again.');
|
|
})
|
|
.finally(() => {
|
|
// Re-enable button
|
|
btn.disabled = false;
|
|
btn.innerHTML = originalText;
|
|
});
|
|
}
|
|
|
|
function checkRefundStatus() {
|
|
const btn = document.getElementById('checkRefundBtn');
|
|
const originalText = btn.innerHTML;
|
|
|
|
// Disable button and show loading
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Checking...';
|
|
|
|
// Make API call to check refund status
|
|
fetch(`/single-payment/check-refund/{{ payment.id }}`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
if (data.refund_completed) {
|
|
showSuccessModal(`
|
|
<h4>Refund Completed Successfully!</h4>
|
|
<p>The refund has been processed and completed by the bank.</p>
|
|
<p><strong>Status:</strong> <span class="badge bg-success">${data.status}</span></p>
|
|
<p><strong>Refund ID:</strong> ${data.refund_id}</p>
|
|
`);
|
|
} else {
|
|
showSuccessModal(`
|
|
<h4>Refund Status Updated</h4>
|
|
<p>Refund status has been checked and updated.</p>
|
|
<p><strong>Current Status:</strong> <span class="badge bg-warning">${data.status}</span></p>
|
|
<p><strong>Refund ID:</strong> ${data.refund_id}</p>
|
|
<p><em>The refund is still being processed. Please check again later.</em></p>
|
|
`);
|
|
}
|
|
} else {
|
|
showErrorModal(data.error || 'Failed to check refund status');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error checking refund status:', error);
|
|
showErrorModal('Failed to check refund status. Please try again.');
|
|
})
|
|
.finally(() => {
|
|
// Re-enable button
|
|
btn.disabled = false;
|
|
btn.innerHTML = originalText;
|
|
});
|
|
}
|
|
|
|
function showSuccessModal(message) {
|
|
document.getElementById('successMessage').innerHTML = message;
|
|
showModal('successModal');
|
|
}
|
|
|
|
function showErrorModal(errorMessage) {
|
|
document.getElementById('errorDetails').innerHTML = `<p>${errorMessage}</p>`;
|
|
showModal('errorModal');
|
|
}
|
|
|
|
function closeSuccessModal() {
|
|
hideModal('successModal');
|
|
// Refresh the page to show updated data
|
|
window.location.reload();
|
|
}
|
|
|
|
// Copy to clipboard functionality
|
|
function copyFormattedJSON(elementId) {
|
|
const element = document.getElementById(elementId);
|
|
const text = element.textContent || element.innerText;
|
|
|
|
navigator.clipboard.writeText(text).then(function() {
|
|
// Show temporary success message
|
|
const button = event.target.closest('button');
|
|
const originalText = button.innerHTML;
|
|
button.innerHTML = '<i class="fas fa-check me-2"></i>Copied!';
|
|
button.classList.add('btn-success');
|
|
button.classList.remove('btn-info', 'btn-primary', 'btn-outline-secondary');
|
|
|
|
setTimeout(function() {
|
|
button.innerHTML = originalText;
|
|
button.classList.remove('btn-success');
|
|
// Restore original button class based on elementId
|
|
if (elementId.includes('pi-json')) {
|
|
button.classList.add('btn-info');
|
|
} else if (elementId.includes('followup')) {
|
|
button.classList.add('btn-primary');
|
|
} else if (elementId.includes('refund')) {
|
|
button.classList.add('btn-outline-secondary');
|
|
}
|
|
}, 2000);
|
|
}).catch(function(err) {
|
|
console.error('Failed to copy text: ', err);
|
|
// Fallback for older browsers
|
|
const textArea = document.createElement('textarea');
|
|
textArea.value = text;
|
|
document.body.appendChild(textArea);
|
|
textArea.select();
|
|
try {
|
|
document.execCommand('copy');
|
|
const button = event.target.closest('button');
|
|
const originalText = button.innerHTML;
|
|
button.innerHTML = '<i class="fas fa-check me-2"></i>Copied!';
|
|
button.classList.add('btn-success');
|
|
|
|
setTimeout(function() {
|
|
button.innerHTML = originalText;
|
|
button.classList.remove('btn-success');
|
|
}, 2000);
|
|
} catch (fallbackErr) {
|
|
console.error('Fallback copy failed: ', fallbackErr);
|
|
}
|
|
document.body.removeChild(textArea);
|
|
});
|
|
}
|
|
</script>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<nav aria-label="breadcrumb">
|
|
<ol class="breadcrumb">
|
|
<li class="breadcrumb-item"><a href="{{ url_for('main.index') }}">Dashboard</a></li>
|
|
<li class="breadcrumb-item"><a href="{{ url_for('main.single_payments_list') }}">Single Payments</a></li>
|
|
<li class="breadcrumb-item active" aria-current="page">Payment #{{ payment.id }}</li>
|
|
</ol>
|
|
</nav>
|
|
|
|
<div class="d-flex justify-content-between align-items-start mb-4">
|
|
<div>
|
|
<h1 class="h2 mb-1">Single Payment #{{ payment.id }}</h1>
|
|
<p class="text-muted">Processed: {{ payment.Created.strftime('%Y-%m-%d %H:%M:%S') if payment.Created else 'Unknown' }}</p>
|
|
</div>
|
|
<a class="btn btn-light" href="{{ url_for('main.single_payments_list') }}">
|
|
<i class="fas fa-arrow-left me-2"></i>Back to Payments
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Payment Status Banner -->
|
|
{% if payment.Refund == True %}
|
|
<div class="status-banner refund">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div class="d-flex align-items-center">
|
|
<i class="fas fa-undo status-icon me-4" style="color: #9370db;"></i>
|
|
<div>
|
|
<h2 class="status-title" style="color: #9370db;">Payment Refunded</h2>
|
|
<p class="status-text">This payment has been refunded to the customer.</p>
|
|
{% if payment.Stripe_Refund_Created %}
|
|
<p class="status-text small">Refunded: {{ payment.Stripe_Refund_Created.strftime('%Y-%m-%d %H:%M:%S') }}</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% if payment.Refund_FollowUp != True %}
|
|
<span class="badge" style="background-color: #9370db; font-size: 1rem; padding: 0.5rem 1rem;">Completed</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% elif payment.Refund_FollowUp == True %}
|
|
<div class="status-banner refund-processing">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div class="d-flex align-items-center">
|
|
<i class="fas fa-clock status-icon me-4" style="color: #ff8c00;"></i>
|
|
<div>
|
|
<h2 class="status-title" style="color: #ff8c00;">Refund Processing</h2>
|
|
<p class="status-text">A refund is being processed for this payment.</p>
|
|
<p class="status-text small">BECS Direct Debit refunds can take several business days to complete.</p>
|
|
{% if payment.Stripe_Refund_Created %}
|
|
<p class="status-text small">Initiated: {{ payment.Stripe_Refund_Created.strftime('%Y-%m-%d %H:%M:%S') }}</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<button class="btn btn-warning" id="checkRefundBtn" onclick="checkRefundStatus()">
|
|
<i class="fas fa-sync me-2"></i>Check Refund Status
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% elif payment.PI_FollowUp == True %}
|
|
<div class="status-banner pending">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div class="d-flex align-items-center">
|
|
<i class="fas fa-clock status-icon text-warning me-4"></i>
|
|
<div>
|
|
<h2 class="status-title text-warning">Payment Pending</h2>
|
|
<p class="status-text">This payment is still being processed by the bank.</p>
|
|
<p class="status-text small">BECS Direct Debit payments can take several business days to complete.</p>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<button class="btn btn-warning" id="checkIntentBtn" onclick="checkPaymentIntent()">
|
|
<i class="fas fa-sync-alt me-2"></i>Force Check Status
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% elif payment.Success == True %}
|
|
<div class="status-banner success">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div class="d-flex align-items-center">
|
|
<i class="fas fa-check-circle status-icon text-success me-4"></i>
|
|
<div>
|
|
<h2 class="status-title text-success">Payment Successful</h2>
|
|
<p class="status-text">This payment has been completed successfully.</p>
|
|
</div>
|
|
</div>
|
|
{% if current_user.Permissions != "Helpdesk" %}
|
|
<div>
|
|
<button class="btn btn-outline-secondary" style="border-color: #9370db; color: #9370db;" id="refundBtn" onclick="showRefundModal()">
|
|
<i class="fas fa-undo me-2"></i>Process Refund
|
|
</button>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% elif payment.Success == False %}
|
|
<div class="status-banner failed">
|
|
<div class="d-flex align-items-center">
|
|
<i class="fas fa-times-circle status-icon text-danger me-4"></i>
|
|
<div>
|
|
<h2 class="status-title text-danger">Payment Failed</h2>
|
|
<p class="status-text">This payment could not be completed.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="status-banner pending">
|
|
<div class="d-flex align-items-center">
|
|
<i class="fas fa-clock status-icon text-warning me-4"></i>
|
|
<div>
|
|
<h2 class="status-title text-warning">Payment Pending</h2>
|
|
<p class="status-text">This payment is still being processed.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Payment Details -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-6">
|
|
<div class="card info-card">
|
|
<div class="card-header-custom">
|
|
<i class="fas fa-info-circle me-2"></i>Payment Information
|
|
</div>
|
|
<div class="card-body">
|
|
<table class="table table-sm table-config mb-0">
|
|
<tbody>
|
|
<tr>
|
|
<td>Payment ID</td>
|
|
<td>#{{ payment.id }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Splynx Customer ID</td>
|
|
<td>
|
|
{% if payment.Splynx_ID %}
|
|
<a href="https://billing.interphone.com.au/admin/customers/view?id={{ payment.Splynx_ID }}"
|
|
target="_blank" class="fw-semibold">{{ payment.Splynx_ID }}</a>
|
|
{% else %}
|
|
-
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Stripe Customer ID</td>
|
|
<td><code class="small">{{ payment.Stripe_Customer_ID or '-' }}</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td>Payment Intent</td>
|
|
<td><code class="small">{{ payment.Payment_Intent or '-' }}</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td>Payment Method</td>
|
|
<td>
|
|
{% if payment.Payment_Method %}
|
|
<span class="badge bg-info">{{ payment.Payment_Method }}</span>
|
|
{% else %}
|
|
-
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Created</td>
|
|
<td>{{ payment.Created.strftime('%Y-%m-%d %H:%M:%S') if payment.Created else '-' }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Processed By</td>
|
|
<td>{{ payment.processed_by or 'Unknown' }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<div class="card info-card">
|
|
<div class="card-header-custom">
|
|
<i class="fas fa-dollar-sign me-2"></i>Financial Details
|
|
</div>
|
|
<div class="card-body">
|
|
<table class="table table-sm table-config mb-3">
|
|
<tbody>
|
|
<tr>
|
|
<td>Payment Amount</td>
|
|
<td><strong class="text-success">{{ payment.Payment_Amount | currency }}</strong></td>
|
|
</tr>
|
|
<tr>
|
|
<td>Stripe Fee</td>
|
|
<td>{{ payment.Fee_Stripe | currency if payment.Fee_Stripe else '-' }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Tax Fee</td>
|
|
<td>{{ payment.Fee_Tax | currency if payment.Fee_Tax else '-' }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Total Fees</td>
|
|
<td>{{ payment.Fee_Total | currency if payment.Fee_Total else '-' }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
{% if payment.PI_FollowUp %}
|
|
<div class="alert alert-warning">
|
|
<i class="fas fa-exclamation-triangle me-2"></i>
|
|
<strong>Follow-up Required:</strong> This payment requires additional processing.
|
|
{% if payment.PI_Last_Check %}
|
|
<br><small>Last checked: {{ payment.PI_Last_Check.strftime('%Y-%m-%d %H:%M:%S') }}</small>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if payment.Refund == True %}
|
|
<div class="alert" style="background-color: #f8f4ff; border-color: #9370db; color: #5a4570;">
|
|
<i class="fas fa-undo me-2" style="color: #9370db;"></i>
|
|
<strong style="color: #9370db;">Refund Completed:</strong> This payment has been successfully refunded.
|
|
{% if payment.Stripe_Refund_Created %}
|
|
<br><small>Refunded: {{ payment.Stripe_Refund_Created.strftime('%Y-%m-%d %H:%M:%S') }}</small>
|
|
{% endif %}
|
|
{% if payment.Stripe_Refund_ID %}
|
|
<br><small>Refund ID: <code class="small">{{ payment.Stripe_Refund_ID }}</code></small>
|
|
{% endif %}
|
|
</div>
|
|
{% elif payment.Refund_FollowUp == True %}
|
|
<div class="alert alert-warning">
|
|
<i class="fas fa-clock me-2" style="color: #ff8c00;"></i>
|
|
<strong style="color: #ff8c00;">Refund Processing:</strong> A refund for this payment is currently being processed by the bank.
|
|
{% if payment.Stripe_Refund_Created %}
|
|
<br><small>Initiated: {{ payment.Stripe_Refund_Created.strftime('%Y-%m-%d %H:%M:%S') }}</small>
|
|
{% endif %}
|
|
{% if payment.Stripe_Refund_ID %}
|
|
<br><small>Refund ID: <code class="small">{{ payment.Stripe_Refund_ID }}</code></small>
|
|
{% endif %}
|
|
<br><small><em>BECS Direct Debit refunds typically take 3-5 business days to complete.</em></small>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Error Information -->
|
|
{% if payment.Error %}
|
|
{% set error_alert = payment | error_alert %}
|
|
<div class="card info-card mb-4">
|
|
<div class="card-header-error">
|
|
<i class="fas fa-exclamation-triangle me-2"></i>Payment Error Details
|
|
</div>
|
|
<div class="card-body">
|
|
{% if error_alert %}
|
|
<div class="error-alert {{ error_alert.type }}">
|
|
<div class="error-alert-header">
|
|
<i class="fas {{ error_alert.icon }} me-2"></i>
|
|
<span class="error-title">{{ error_alert.title }}</span>
|
|
</div>
|
|
<div class="error-alert-body">
|
|
<p class="error-message">{{ error_alert.message }}</p>
|
|
<p class="error-suggestion"><strong>Suggested Action:</strong> {{ error_alert.suggestion }}</p>
|
|
<details class="error-details">
|
|
<summary>View Technical Details</summary>
|
|
<pre>{{ error_alert.raw_error }}</pre>
|
|
</details>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<!-- Fallback for unclassified errors -->
|
|
<div class="alert alert-danger">
|
|
<h5 class="h6">Payment Error</h5>
|
|
<p>An error occurred during payment processing.</p>
|
|
<details class="mt-3">
|
|
<summary class="text-muted">Technical Details</summary>
|
|
<pre class="mt-2">{{ payment.Error }}</pre>
|
|
</details>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- JSON Data -->
|
|
<div class="row">
|
|
{% if payment.PI_JSON %}
|
|
<div class="col-md-{% if payment.PI_FollowUp_JSON or payment.Refund_JSON %}6{% else %}12{% endif %} mb-4">
|
|
<div class="card info-card">
|
|
<div class="card-header-custom">
|
|
<i class="fas fa-code me-2"></i>Payment Intent JSON
|
|
</div>
|
|
<div class="card-body">
|
|
<button class="btn btn-sm btn-info mb-3" onclick="copyFormattedJSON('pi-json-content')">
|
|
<i class="fas fa-copy me-2"></i>Copy JSON
|
|
</button>
|
|
<pre><code>{{ payment.PI_JSON | format_json }}</code></pre>
|
|
<div id="pi-json-content" style="display: none;">{{ payment.PI_JSON | format_json }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if payment.PI_FollowUp_JSON %}
|
|
<div class="col-md-6 mb-4">
|
|
<div class="card info-card">
|
|
<div class="card-header-custom">
|
|
<i class="fas fa-redo me-2"></i>Follow-up JSON
|
|
</div>
|
|
<div class="card-body">
|
|
<button class="btn btn-sm btn-primary mb-3" onclick="copyFormattedJSON('followup-json-content')">
|
|
<i class="fas fa-copy me-2"></i>Copy JSON
|
|
</button>
|
|
<pre><code>{{ payment.PI_FollowUp_JSON | format_json }}</code></pre>
|
|
<div id="followup-json-content" style="display: none;">{{ payment.PI_FollowUp_JSON | format_json }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if payment.Refund_JSON %}
|
|
<div class="col-md-6 mb-4">
|
|
<div class="card info-card">
|
|
<div class="card-header-custom" style="background: linear-gradient(135deg, #9370db 0%, #7d5ba6 100%); color: #fff;">
|
|
<i class="fas fa-undo me-2"></i>Refund JSON
|
|
</div>
|
|
<div class="card-body">
|
|
<button class="btn btn-sm btn-outline-secondary mb-3" style="border-color: #9370db; color: #9370db;" onclick="copyFormattedJSON('refund-json-content')">
|
|
<i class="fas fa-copy me-2"></i>Copy JSON
|
|
</button>
|
|
<pre><code>{{ payment.Refund_JSON | format_json }}</code></pre>
|
|
<div id="refund-json-content" style="display: none;">{{ payment.Refund_JSON | format_json }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Refund Modal -->
|
|
<div class="modal fade" id="refundModal" tabindex="-1" aria-labelledby="refundModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content">
|
|
<div class="modal-header bg-refund">
|
|
<h5 class="modal-title" id="refundModalLabel">
|
|
<i class="fas fa-undo me-2"></i>Process Refund
|
|
</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="text-center py-4">
|
|
<i class="fas fa-undo fa-3x mb-4" style="color: #9370db;"></i>
|
|
<h4 class="h5 mb-3">Confirm Refund</h4>
|
|
<p>Are you sure you want to process a refund for this payment?</p>
|
|
<p><strong>Payment Amount:</strong> {{ payment.Payment_Amount | currency }}</p>
|
|
<p><strong>Customer:</strong> {{ payment.Splynx_ID }}</p>
|
|
|
|
<div class="mb-3 mt-4">
|
|
<label for="refundReason" class="form-label">Refund Reason</label>
|
|
<select class="form-select" id="refundReason">
|
|
<option value="requested_by_customer">Requested by Customer</option>
|
|
<option value="duplicate">Duplicate Payment</option>
|
|
<option value="fraudulent">Fraudulent</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer justify-content-center">
|
|
<button class="btn" style="background-color: #9370db; border-color: #9370db; color: #fff;" onclick="processRefund()" id="processRefundBtn">
|
|
<i class="fas fa-undo me-2"></i>Process Refund
|
|
</button>
|
|
<button class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Success Modal -->
|
|
<div class="modal fade" id="successModal" tabindex="-1" aria-labelledby="successModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content">
|
|
<div class="modal-header bg-success">
|
|
<h5 class="modal-title" id="successModalLabel">
|
|
<i class="fas fa-check-circle me-2"></i>Payment Status Updated
|
|
</h5>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="text-center py-4">
|
|
<i class="fas fa-check-circle fa-3x text-success mb-4"></i>
|
|
<div id="successMessage">
|
|
<!-- Success details will be populated here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer justify-content-center">
|
|
<button class="btn btn-success" onclick="closeSuccessModal()">
|
|
<i class="fas fa-check me-2"></i>Refresh Page
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Error Modal -->
|
|
<div class="modal fade" id="errorModal" tabindex="-1" aria-labelledby="errorModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content">
|
|
<div class="modal-header bg-danger">
|
|
<h5 class="modal-title" id="errorModalLabel">
|
|
<i class="fas fa-exclamation-circle me-2"></i>Check Failed
|
|
</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="text-center py-4">
|
|
<i class="fas fa-exclamation-circle fa-3x text-danger mb-4"></i>
|
|
<div id="errorDetails">
|
|
<!-- Error details will be populated here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer justify-content-center">
|
|
<button class="btn btn-danger" data-bs-dismiss="modal">
|
|
<i class="fas fa-times me-2"></i>Close
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|