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.
 
 
 

522 lines
20 KiB

{% extends "base.html" %}
{% block title %}Batch 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;
}
</style>
<script>
// 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');
} else if (elementId.includes('error')) {
button.classList.add('btn-info');
}
}, 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.batch_list') }}">Payment Batches</a></li>
<li class="breadcrumb-item"><a href="{{ url_for('main.batch_detail', batch_id=payment.PaymentBatch_ID) }}">Batch #{{ payment.PaymentBatch_ID }}</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">Batch 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.batch_detail', batch_id=payment.PaymentBatch_ID) }}">
<i class="fas fa-arrow-left me-2"></i>Back to Batch #{{ payment.PaymentBatch_ID }}
</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>
</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>
{% if payment.PI_Last_Check %}
<p class="status-text small">Last checked: {{ payment.PI_Last_Check.strftime('%Y-%m-%d %H:%M:%S') }}</p>
{% endif %}
</div>
</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>
</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><strong>#{{ payment.id }}</strong></td>
</tr>
<tr>
<td>Batch ID</td>
<td>
<a href="{{ url_for('main.batch_detail', batch_id=payment.PaymentBatch_ID) }}"
class="fw-semibold">#{{ payment.PaymentBatch_ID }}</a>
</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">${{ "%.2f"|format(payment.Payment_Amount|abs) if payment.Payment_Amount else '0.00' }} AUD</strong></td>
</tr>
<tr>
<td>Stripe Fee</td>
<td>${{ "%.2f"|format(payment.Fee_Stripe|abs) if payment.Fee_Stripe else '0.00' }}</td>
</tr>
<tr>
<td>Tax Fee</td>
<td>${{ "%.2f"|format(payment.Fee_Tax|abs) if payment.Fee_Tax else '0.00' }}</td>
</tr>
<tr>
<td>Total Fees</td>
<td>${{ "%.2f"|format(payment.Fee_Total|abs) if payment.Fee_Total else '0.00' }}</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 %}
<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">
<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>
</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>
{% endblock %}