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.
718 lines
30 KiB
718 lines
30 KiB
{% extends "base.html" %}
|
|
|
|
{% block title %}Payment #{{ payment.id }} - Plutus{% endblock %}
|
|
|
|
{% block content %}
|
|
<nav class="breadcrumb" aria-label="breadcrumbs">
|
|
<ul>
|
|
<li><a href="{{ url_for('main.index') }}">Dashboard</a></li>
|
|
<li><a href="{{ url_for('main.batch_list') }}">Payment Batches</a></li>
|
|
<li><a href="{{ url_for('main.batch_detail', batch_id=payment.PaymentBatch_ID) }}">Batch #{{ payment.PaymentBatch_ID }}</a></li>
|
|
<li class="is-active"><a href="#" aria-current="page">Payment #{{ payment.id }}</a></li>
|
|
</ul>
|
|
</nav>
|
|
|
|
<div class="level">
|
|
<div class="level-left">
|
|
<div>
|
|
<h1 class="title">Batch Payment #{{ payment.id }}</h1>
|
|
<p class="subtitle">Batch #{{ payment.PaymentBatch_ID }} • Processed: {{ payment.Created.strftime('%Y-%m-%d %H:%M:%S') if payment.Created else 'Unknown' }}</p>
|
|
</div>
|
|
</div>
|
|
<div class="level-right">
|
|
<div class="field is-grouped">
|
|
{% if payment.Refund != True and payment.Refund_FollowUp != True and payment.Success == True %}
|
|
<div class="control">
|
|
<button class="button is-warning" id="refundBtn" onclick="showRefundModal()">
|
|
<span class="icon"><i class="fas fa-undo"></i></span>
|
|
<span>Process Refund</span>
|
|
</button>
|
|
</div>
|
|
{% elif payment.Refund_FollowUp == True %}
|
|
<div class="control">
|
|
<button class="button is-warning" id="checkRefundBtn" onclick="checkRefundStatus()">
|
|
<span class="icon"><i class="fas fa-sync"></i></span>
|
|
<span>Check Refund Status</span>
|
|
</button>
|
|
</div>
|
|
{% endif %}
|
|
{% if payment.PI_FollowUp %}
|
|
<div class="control">
|
|
<button class="button is-info" id="checkIntentBtn" onclick="checkPaymentIntent()">
|
|
<span class="icon"><i class="fas fa-sync-alt"></i></span>
|
|
<span>Check Status</span>
|
|
</button>
|
|
</div>
|
|
{% endif %}
|
|
<div class="control">
|
|
<a class="button is-light" href="{{ url_for('main.batch_detail', batch_id=payment.PaymentBatch_ID) }}">
|
|
<span class="icon"><i class="fas fa-arrow-left"></i></span>
|
|
<span>Back to Batch</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Payment Status Banner -->
|
|
<div class="box">
|
|
<div class="level">
|
|
<div class="level-left">
|
|
<div class="level-item">
|
|
{% if payment.Refund == True %}
|
|
<span class="icon is-large" style="color: #9370db;">
|
|
<i class="fas fa-undo fa-2x"></i>
|
|
</span>
|
|
{% elif payment.Refund_FollowUp == True %}
|
|
<span class="icon is-large" style="color: #ff8c00;">
|
|
<i class="fas fa-clock fa-2x"></i>
|
|
</span>
|
|
{% elif payment.Success == True %}
|
|
<span class="icon is-large has-text-success">
|
|
<i class="fas fa-check-circle fa-2x"></i>
|
|
</span>
|
|
{% elif payment.Success == False %}
|
|
<span class="icon is-large has-text-danger">
|
|
<i class="fas fa-times-circle fa-2x"></i>
|
|
</span>
|
|
{% else %}
|
|
<span class="icon is-large has-text-warning">
|
|
<i class="fas fa-clock fa-2x"></i>
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
<div class="level-item">
|
|
<div>
|
|
{% if payment.Refund == True %}
|
|
<h2 class="title is-4 mb-2" style="color: #9370db;">Payment Refunded</h2>
|
|
<p class="has-text-grey">This payment has been refunded to the customer.</p>
|
|
{% if payment.Stripe_Refund_Created %}
|
|
<p class="has-text-grey is-size-7">Refunded: {{ payment.Stripe_Refund_Created.strftime('%Y-%m-%d %H:%M:%S') }}</p>
|
|
{% endif %}
|
|
{% elif payment.Refund_FollowUp == True %}
|
|
<h2 class="title is-4 mb-2" style="color: #ff8c00;">Refund Processing</h2>
|
|
<p class="has-text-grey">A refund is being processed for this payment.</p>
|
|
<p class="has-text-grey is-size-7">BECS Direct Debit refunds can take several business days to complete.</p>
|
|
{% if payment.Stripe_Refund_Created %}
|
|
<p class="has-text-grey is-size-7">Initiated: {{ payment.Stripe_Refund_Created.strftime('%Y-%m-%d %H:%M:%S') }}</p>
|
|
{% endif %}
|
|
{% elif payment.Success == True %}
|
|
<h2 class="title is-4 has-text-success mb-2">Payment Successful</h2>
|
|
<p class="has-text-grey">This payment has been completed successfully.</p>
|
|
{% elif payment.Success == False %}
|
|
<h2 class="title is-4 has-text-danger mb-2">Payment Failed</h2>
|
|
<p class="has-text-grey">This payment could not be completed.</p>
|
|
{% else %}
|
|
<h2 class="title is-4 has-text-warning mb-2">Payment Pending</h2>
|
|
<p class="has-text-grey">This payment is still being processed.</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Payment Details -->
|
|
<div class="columns">
|
|
<div class="column is-half">
|
|
<div class="box">
|
|
<h3 class="title is-5">
|
|
<span class="icon"><i class="fas fa-info-circle"></i></span>
|
|
Payment Information
|
|
</h3>
|
|
|
|
<table class="table is-fullwidth">
|
|
<tbody>
|
|
<tr>
|
|
<td><strong>Payment ID</strong></td>
|
|
<td>#{{ payment.id }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Batch ID</strong></td>
|
|
<td>
|
|
<a href="{{ url_for('main.batch_detail', batch_id=payment.PaymentBatch_ID) }}" class="has-text-weight-semibold">
|
|
#{{ payment.PaymentBatch_ID }}
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Splynx Customer ID</strong></td>
|
|
<td>
|
|
{% if payment.Splynx_ID %}
|
|
<a href="https://billing.interphone.com.au/admin/customers/view?id={{ payment.Splynx_ID }}"
|
|
target="_blank">{{ payment.Splynx_ID }}</a>
|
|
{% else %}
|
|
-
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Stripe Customer ID</strong></td>
|
|
<td><code>{{ payment.Stripe_Customer_ID or '-' }}</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Payment Intent</strong></td>
|
|
<td><code>{{ payment.Payment_Intent or '-' }}</code></td>
|
|
</tr>
|
|
{% if payment.Stripe_Charge_ID %}
|
|
<tr>
|
|
<td><strong>Stripe Charge ID</strong></td>
|
|
<td><code>{{ payment.Stripe_Charge_ID }}</code></td>
|
|
</tr>
|
|
{% endif %}
|
|
{% if payment.Stripe_Refund_ID %}
|
|
<tr>
|
|
<td><strong>Stripe Refund ID</strong></td>
|
|
<td><code>{{ payment.Stripe_Refund_ID }}</code></td>
|
|
</tr>
|
|
{% endif %}
|
|
<tr>
|
|
<td><strong>Payment Method</strong></td>
|
|
<td>
|
|
{% if payment.Payment_Method %}
|
|
<span class="tag is-info">{{ payment.Payment_Method }}</span>
|
|
{% else %}
|
|
-
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Created</strong></td>
|
|
<td>{{ payment.Created.strftime('%Y-%m-%d %H:%M:%S') if payment.Created else '-' }}</td>
|
|
</tr>
|
|
{% if payment.PaymentPlan_ID %}
|
|
<tr>
|
|
<td><strong>Payment Plan</strong></td>
|
|
<td>
|
|
<a href="{{ url_for('main.payment_plans_detail', plan_id=payment.PaymentPlan_ID) }}" class="has-text-weight-semibold">
|
|
Plan #{{ payment.PaymentPlan_ID }}
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
{% endif %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="column is-half">
|
|
<div class="box">
|
|
<h3 class="title is-5">
|
|
<span class="icon"><i class="fas fa-dollar-sign"></i></span>
|
|
Financial Details
|
|
</h3>
|
|
|
|
<table class="table is-fullwidth">
|
|
<tbody>
|
|
<tr>
|
|
<td><strong>Payment Amount</strong></td>
|
|
<td><strong class="has-text-success">{{ payment.Payment_Amount | currency }}</strong></td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Stripe Fee</strong></td>
|
|
<td>{{ payment.Fee_Stripe | currency if payment.Fee_Stripe else '-' }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Tax Fee</strong></td>
|
|
<td>{{ payment.Fee_Tax | currency if payment.Fee_Tax else '-' }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Total Fees</strong></td>
|
|
<td>{{ payment.Fee_Total | currency if payment.Fee_Total else '-' }}</td>
|
|
</tr>
|
|
{% if payment.Fee_Total and payment.Payment_Amount %}
|
|
<tr>
|
|
<td><strong>Net Amount</strong></td>
|
|
<td><strong>{{ (payment.Payment_Amount - payment.Fee_Total) | currency }}</strong></td>
|
|
</tr>
|
|
{% endif %}
|
|
</tbody>
|
|
</table>
|
|
|
|
{% if payment.PI_FollowUp %}
|
|
<div class="notification is-warning is-light">
|
|
<span class="icon"><i class="fas fa-exclamation-triangle"></i></span>
|
|
<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="notification is-light" style="background-color: #f8f4ff; border-color: #9370db;">
|
|
<span class="icon" style="color: #9370db;"><i class="fas fa-undo"></i></span>
|
|
<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>{{ payment.Stripe_Refund_ID }}</code></small>
|
|
{% endif %}
|
|
</div>
|
|
{% elif payment.Refund_FollowUp == True %}
|
|
<div class="notification is-warning is-light">
|
|
<span class="icon" style="color: #ff8c00;"><i class="fas fa-clock"></i></span>
|
|
<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>{{ 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>
|
|
|
|
<!-- Error Information -->
|
|
{% if payment.Error %}
|
|
{% set error_alert = payment | error_alert %}
|
|
<div class="box">
|
|
<h3 class="title is-5 has-text-danger">
|
|
<span class="icon"><i class="fas fa-exclamation-triangle"></i></span>
|
|
Payment Error Details
|
|
</h3>
|
|
|
|
{% if error_alert %}
|
|
<div class="error-alert {{ error_alert.type }}">
|
|
<div class="error-alert-header">
|
|
<span class="icon"><i class="fas {{ error_alert.icon }}"></i></span>
|
|
<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="notification is-danger is-light">
|
|
<h5 class="title is-6">Payment Error</h5>
|
|
<p>An error occurred during payment processing.</p>
|
|
<details class="mt-3">
|
|
<summary class="has-text-grey">Technical Details</summary>
|
|
<pre class="mt-2">{{ payment.Error }}</pre>
|
|
</details>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- JSON Data -->
|
|
<div class="columns">
|
|
{% if payment.PI_JSON %}
|
|
<div class="column">
|
|
<div class="box">
|
|
<h3 class="title is-5">
|
|
<span class="icon"><i class="fas fa-code"></i></span>
|
|
Payment Intent JSON
|
|
</h3>
|
|
|
|
<div class="field is-grouped">
|
|
<div class="control">
|
|
<button class="button is-small is-info" onclick="copyFormattedJSON('pi-json-content')">
|
|
<span class="icon"><i class="fas fa-copy"></i></span>
|
|
<span>Copy JSON</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<pre class="is-size-7"><code>{{ payment.PI_JSON | format_json }}</code></pre>
|
|
<div id="pi-json-content" style="display: none;">{{ payment.PI_JSON | format_json }}</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if payment.PI_FollowUp_JSON %}
|
|
<div class="column">
|
|
<div class="box">
|
|
<h3 class="title is-5">
|
|
<span class="icon"><i class="fas fa-redo"></i></span>
|
|
Follow-up JSON
|
|
</h3>
|
|
|
|
<div class="field is-grouped">
|
|
<div class="control">
|
|
<button class="button is-small is-primary" onclick="copyFormattedJSON('followup-json-content')">
|
|
<span class="icon"><i class="fas fa-copy"></i></span>
|
|
<span>Copy JSON</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<pre class="is-size-7"><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>
|
|
{% endif %}
|
|
|
|
{% if payment.Refund_JSON %}
|
|
<div class="column">
|
|
<div class="box">
|
|
<h3 class="title is-5">
|
|
<span class="icon" style="color: #9370db;"><i class="fas fa-undo"></i></span>
|
|
Refund JSON
|
|
</h3>
|
|
|
|
<div class="field is-grouped">
|
|
<div class="control">
|
|
<button class="button is-small" style="border-color: #9370db; color: #9370db;" onclick="copyFormattedJSON('refund-json-content')">
|
|
<span class="icon"><i class="fas fa-copy"></i></span>
|
|
<span>Copy JSON</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<pre class="is-size-7"><code>{{ payment.Refund_JSON | format_json }}</code></pre>
|
|
<div id="refund-json-content" style="display: none;">{{ payment.Refund_JSON | format_json }}</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Refund Modal -->
|
|
<div class="modal" id="refundModal">
|
|
<div class="modal-background"></div>
|
|
<div class="modal-card">
|
|
<header class="modal-card-head" style="background-color: #9370db;">
|
|
<p class="modal-card-title has-text-white">
|
|
<span class="icon"><i class="fas fa-undo"></i></span>
|
|
Process Refund
|
|
</p>
|
|
<button class="delete" aria-label="close" onclick="hideModal('refundModal')"></button>
|
|
</header>
|
|
<section class="modal-card-body">
|
|
<div class="has-text-centered py-4">
|
|
<span class="icon is-large mb-4" style="color: #9370db;">
|
|
<i class="fas fa-undo fa-3x"></i>
|
|
</span>
|
|
<div class="content">
|
|
<h4 class="title is-5">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="field">
|
|
<label class="label">Refund Reason</label>
|
|
<div class="control">
|
|
<div class="select is-fullwidth">
|
|
<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>
|
|
</div>
|
|
</section>
|
|
<footer class="modal-card-foot is-justify-content-center">
|
|
<button class="button" style="border-color: #9370db; color: #9370db;" onclick="processRefund()" id="processRefundBtn">
|
|
<span class="icon"><i class="fas fa-undo"></i></span>
|
|
<span>Process Refund</span>
|
|
</button>
|
|
<button class="button" onclick="hideModal('refundModal')">Cancel</button>
|
|
</footer>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Success Modal -->
|
|
<div class="modal" id="successModal">
|
|
<div class="modal-background"></div>
|
|
<div class="modal-card">
|
|
<header class="modal-card-head has-background-success">
|
|
<p class="modal-card-title has-text-white">
|
|
<span class="icon"><i class="fas fa-check-circle"></i></span>
|
|
Success
|
|
</p>
|
|
</header>
|
|
<section class="modal-card-body">
|
|
<div class="has-text-centered py-4">
|
|
<span class="icon is-large has-text-success mb-4">
|
|
<i class="fas fa-check-circle fa-3x"></i>
|
|
</span>
|
|
<div id="successMessage" class="content">
|
|
<!-- Success details will be populated here -->
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<footer class="modal-card-foot is-justify-content-center">
|
|
<button class="button is-success" onclick="closeSuccessModal()">
|
|
<span class="icon"><i class="fas fa-check"></i></span>
|
|
<span>Refresh Page</span>
|
|
</button>
|
|
</footer>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Error Modal -->
|
|
<div class="modal" id="errorModal">
|
|
<div class="modal-background" onclick="hideModal('errorModal')"></div>
|
|
<div class="modal-card">
|
|
<header class="modal-card-head has-background-danger">
|
|
<p class="modal-card-title has-text-white">
|
|
<span class="icon"><i class="fas fa-exclamation-circle"></i></span>
|
|
Error
|
|
</p>
|
|
<button class="delete" aria-label="close" onclick="hideModal('errorModal')"></button>
|
|
</header>
|
|
<section class="modal-card-body">
|
|
<div class="has-text-centered py-4">
|
|
<span class="icon is-large has-text-danger mb-4">
|
|
<i class="fas fa-exclamation-circle fa-3x"></i>
|
|
</span>
|
|
<div id="errorDetails" class="content">
|
|
<!-- Error details will be populated here -->
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<footer class="modal-card-foot is-justify-content-center">
|
|
<button class="button is-danger" onclick="hideModal('errorModal')">
|
|
<span class="icon"><i class="fas fa-times"></i></span>
|
|
<span>Close</span>
|
|
</button>
|
|
</footer>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function checkPaymentIntent() {
|
|
const btn = document.getElementById('checkIntentBtn');
|
|
const originalText = btn.innerHTML;
|
|
|
|
// Disable button and show loading
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<span class="icon"><i class="fas fa-spinner fa-spin"></i></span><span>Checking...</span>';
|
|
|
|
// Make API call to check payment intent
|
|
fetch(`/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 class="title is-5">Payment Completed Successfully!</h4>
|
|
<p>The payment has been processed and completed.</p>
|
|
<p><strong>Status:</strong> ${data.status}</p>
|
|
`);
|
|
} else {
|
|
showSuccessModal(`
|
|
<h4 class="title is-5">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 showRefundModal() {
|
|
document.getElementById('refundModal').classList.add('is-active');
|
|
}
|
|
|
|
function checkRefundStatus() {
|
|
const btn = document.getElementById('checkRefundBtn');
|
|
const originalText = btn.innerHTML;
|
|
|
|
// Disable button and show loading
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<span class="icon"><i class="fas fa-spinner fa-spin"></i></span><span>Checking...</span>';
|
|
|
|
// Make API call to check refund status
|
|
fetch(`/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 class="title is-5">Refund Completed Successfully!</h4>
|
|
<p>The refund has been processed and completed by the bank.</p>
|
|
<p><strong>Status:</strong> <span class="tag is-success">${data.status}</span></p>
|
|
<p><strong>Refund ID:</strong> ${data.refund_id}</p>
|
|
`);
|
|
} else {
|
|
showSuccessModal(`
|
|
<h4 class="title is-5">Refund Status Updated</h4>
|
|
<p>Refund status has been checked and updated.</p>
|
|
<p><strong>Current Status:</strong> <span class="tag is-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 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="icon"><i class="fas fa-spinner fa-spin"></i></span><span>Processing...</span>';
|
|
|
|
// Make API call to process refund
|
|
fetch(`/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 class="title is-5">Refund Processing!</h4>
|
|
<p>The refund has been initiated and is currently being processed by the bank.</p>
|
|
<p><strong>Status:</strong> <span class="tag is-warning">Pending</span></p>
|
|
<p><strong>Refund ID:</strong> ${data.refund_id}</p>
|
|
<p><strong>Amount:</strong> ${data.amount_refunded}</p>
|
|
<div class="notification is-info is-light mt-3">
|
|
<span class="icon"><i class="fas fa-info-circle"></i></span>
|
|
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 class="title is-5">Refund Processed Successfully!</h4>
|
|
<p>The refund has been completed and processed by Stripe.</p>
|
|
<p><strong>Status:</strong> <span class="tag is-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 showSuccessModal(message) {
|
|
document.getElementById('successMessage').innerHTML = message;
|
|
document.getElementById('successModal').classList.add('is-active');
|
|
}
|
|
|
|
function showErrorModal(errorMessage) {
|
|
document.getElementById('errorDetails').innerHTML = `<p>${errorMessage}</p>`;
|
|
document.getElementById('errorModal').classList.add('is-active');
|
|
}
|
|
|
|
function hideModal(modalId) {
|
|
document.getElementById(modalId).classList.remove('is-active');
|
|
}
|
|
|
|
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 = '<span class="icon"><i class="fas fa-check"></i></span><span>Copied!</span>';
|
|
button.classList.add('is-success');
|
|
|
|
setTimeout(function() {
|
|
button.innerHTML = originalText;
|
|
button.classList.remove('is-success');
|
|
}, 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 = '<span class="icon"><i class="fas fa-check"></i></span><span>Copied!</span>';
|
|
button.classList.add('is-success');
|
|
|
|
setTimeout(function() {
|
|
button.innerHTML = originalText;
|
|
button.classList.remove('is-success');
|
|
}, 2000);
|
|
} catch (fallbackErr) {
|
|
console.error('Fallback copy failed: ', fallbackErr);
|
|
}
|
|
document.body.removeChild(textArea);
|
|
});
|
|
}
|
|
|
|
// Close modal on Escape key
|
|
document.addEventListener('keydown', function(event) {
|
|
if (event.key === 'Escape') {
|
|
const activeModals = document.querySelectorAll('.modal.is-active');
|
|
activeModals.forEach(modal => modal.classList.remove('is-active'));
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %}
|