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.
457 lines
13 KiB
457 lines
13 KiB
{% extends "base.html" %}
|
|
|
|
{% block title %}Payment Search - 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;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.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);
|
|
}
|
|
|
|
.search-container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.search-box {
|
|
background: white;
|
|
border-radius: 8px;
|
|
padding: 2rem;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.search-input {
|
|
font-size: 1.2rem;
|
|
padding: 1rem;
|
|
border: 2px solid #dee2e6;
|
|
border-radius: 6px;
|
|
transition: border-color 0.3s;
|
|
}
|
|
|
|
.search-input:focus {
|
|
border-color: #0d6efd;
|
|
outline: none;
|
|
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
|
|
}
|
|
|
|
.search-results {
|
|
background: white;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.result-item {
|
|
padding: 1rem;
|
|
border-bottom: 1px solid #f5f5f5;
|
|
transition: background-color 0.3s;
|
|
}
|
|
|
|
.result-item:hover {
|
|
background-color: #fafafa;
|
|
}
|
|
|
|
.result-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.result-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.result-type {
|
|
display: inline-block;
|
|
padding: 0.25rem 0.5rem;
|
|
border-radius: 4px;
|
|
font-size: 0.8rem;
|
|
font-weight: bold;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.result-type.single {
|
|
background-color: #48c774;
|
|
color: white;
|
|
}
|
|
|
|
.result-type.batch {
|
|
background-color: #0d6efd;
|
|
color: white;
|
|
}
|
|
|
|
.result-details {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 1rem;
|
|
margin: 0.5rem 0;
|
|
}
|
|
|
|
.result-field {
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.result-field strong {
|
|
color: #363636;
|
|
}
|
|
|
|
.result-actions {
|
|
margin-top: 1rem;
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.loading {
|
|
text-align: center;
|
|
padding: 2rem;
|
|
}
|
|
|
|
.no-results {
|
|
text-align: center;
|
|
padding: 3rem;
|
|
color: #666;
|
|
}
|
|
|
|
.search-tips {
|
|
background-color: #f5f5f5;
|
|
border-radius: 4px;
|
|
padding: 1rem;
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
.search-tips ul {
|
|
margin: 0;
|
|
padding-left: 1.5rem;
|
|
}
|
|
|
|
.search-error {
|
|
background-color: #f8d7da;
|
|
border: 1px solid #f5c6cb;
|
|
border-radius: 4px;
|
|
padding: 1rem;
|
|
margin: 1rem 0;
|
|
color: #721c24;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="search-container">
|
|
<div class="search-box">
|
|
<h1 class="h2 mb-2">🔍 Payment Search</h1>
|
|
<p class="text-muted mb-4">Search across all payment records by Splynx ID or Payment Intent</p>
|
|
|
|
<div class="input-group input-group-lg mb-3">
|
|
<input
|
|
class="form-control search-input"
|
|
type="text"
|
|
id="searchQuery"
|
|
placeholder="Enter Splynx ID (e.g. 123456) or Payment Intent (e.g. pi_1234567890)"
|
|
autocomplete="off"
|
|
>
|
|
<button class="btn btn-primary" type="button" onclick="performSearch()">
|
|
<i class="fas fa-search"></i> Search
|
|
</button>
|
|
</div>
|
|
|
|
<div class="row g-3 mb-3">
|
|
<div class="col-md-4">
|
|
<label class="form-label form-label-sm">Search Type:</label>
|
|
<select class="form-select form-select-sm" id="searchType">
|
|
<option value="all">Auto-detect</option>
|
|
<option value="splynx_id">Splynx ID</option>
|
|
<option value="payment_intent">Payment Intent</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label form-label-sm">Results Limit:</label>
|
|
<select class="form-select form-select-sm" id="resultsLimit">
|
|
<option value="25">25 results</option>
|
|
<option value="50" selected>50 results</option>
|
|
<option value="100">100 results</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-4 d-flex align-items-end">
|
|
<button class="btn btn-sm btn-outline-info" onclick="clearSearch()">
|
|
<i class="fas fa-times"></i> Clear
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="search-tips">
|
|
<h5 class="h6 mb-2">💡 Search Tips:</h5>
|
|
<ul class="mb-0">
|
|
<li><strong>Splynx ID:</strong> Enter customer ID number (e.g., 123456)</li>
|
|
<li><strong>Payment Intent:</strong> Enter full Stripe Payment Intent ID (e.g., pi_1234567890)</li>
|
|
<li><strong>Auto-detect:</strong> System automatically detects search type based on format</li>
|
|
<li><strong>Results:</strong> Searches both Single Payments and Batch Payments simultaneously</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="searchResults" style="display: none;"></div>
|
|
</div>
|
|
|
|
<script>
|
|
let searchInProgress = false;
|
|
|
|
// Perform search on Enter key
|
|
document.getElementById('searchQuery').addEventListener('keypress', function(e) {
|
|
if (e.key === 'Enter') {
|
|
performSearch();
|
|
}
|
|
});
|
|
|
|
// Auto-focus search input
|
|
document.getElementById('searchQuery').focus();
|
|
|
|
function performSearch() {
|
|
const query = document.getElementById('searchQuery').value.trim();
|
|
const searchType = document.getElementById('searchType').value;
|
|
const limit = document.getElementById('resultsLimit').value;
|
|
|
|
if (!query) {
|
|
showError('Please enter a search query');
|
|
return;
|
|
}
|
|
|
|
if (searchInProgress) {
|
|
return; // Prevent multiple simultaneous searches
|
|
}
|
|
|
|
searchInProgress = true;
|
|
showLoading();
|
|
|
|
const params = new URLSearchParams({
|
|
q: query,
|
|
type: searchType,
|
|
limit: limit
|
|
});
|
|
|
|
fetch(`/search/api?${params}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
searchInProgress = false;
|
|
if (data.success) {
|
|
displayResults(data);
|
|
} else {
|
|
showError(data.error || 'Search failed');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
searchInProgress = false;
|
|
console.error('Search error:', error);
|
|
showError('Network error occurred while searching');
|
|
});
|
|
}
|
|
|
|
function showLoading() {
|
|
const resultsDiv = document.getElementById('searchResults');
|
|
resultsDiv.style.display = 'block';
|
|
resultsDiv.innerHTML = `
|
|
<div class="search-results">
|
|
<div class="loading">
|
|
<div class="spinner-border text-primary" role="status">
|
|
<span class="visually-hidden">Loading...</span>
|
|
</div>
|
|
<p class="mt-3">Searching payments...</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function showError(message) {
|
|
const resultsDiv = document.getElementById('searchResults');
|
|
resultsDiv.style.display = 'block';
|
|
resultsDiv.innerHTML = `
|
|
<div class="alert alert-danger" role="alert">
|
|
<strong>Search Error:</strong> ${message}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function displayResults(data) {
|
|
const resultsDiv = document.getElementById('searchResults');
|
|
|
|
if (data.results.length === 0) {
|
|
resultsDiv.innerHTML = `
|
|
<div class="search-results">
|
|
<div class="no-results">
|
|
<h3 class="h4 mb-3">No Results Found</h3>
|
|
<p>No payments found matching "${data.search_query}"</p>
|
|
<p class="text-muted">Try searching with a different Splynx ID or Payment Intent</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
let resultsHtml = `
|
|
<div class="search-results">
|
|
<div class="p-3 border-bottom bg-light">
|
|
<h3 class="h4 mb-2">Search Results</h3>
|
|
<p class="mb-0">Found ${data.total_found} payment(s) for "${data.search_query}" (${data.search_type})</p>
|
|
</div>
|
|
`;
|
|
|
|
data.results.forEach(result => {
|
|
resultsHtml += createResultItem(result);
|
|
});
|
|
|
|
resultsHtml += '</div>';
|
|
resultsDiv.innerHTML = resultsHtml;
|
|
}
|
|
|
|
function createResultItem(result) {
|
|
const statusClass = result.success === true ? 'bg-success bg-opacity-10' :
|
|
result.success === false ? 'bg-danger bg-opacity-10' :
|
|
'bg-warning bg-opacity-10';
|
|
|
|
const statusText = result.success === true ? 'Success' :
|
|
result.success === false ? 'Failed' :
|
|
'Pending';
|
|
|
|
const statusIcon = result.success === true ? 'fa-check' :
|
|
result.success === false ? 'fa-times' :
|
|
'fa-clock';
|
|
|
|
return `
|
|
<div class="result-item ${statusClass}">
|
|
<div class="result-header">
|
|
<div>
|
|
<span class="result-type ${result.type}">${result.type}</span>
|
|
<strong class="ms-2">Payment #${result.id}</strong>
|
|
${result.batch_id ? `<span class="badge bg-info ms-2">Batch #${result.batch_id}</span>` : ''}
|
|
</div>
|
|
<div>
|
|
<span class="badge ${result.success === true ? 'bg-success' : result.success === false ? 'bg-danger' : 'bg-warning'}">
|
|
<i class="fas ${statusIcon}"></i> ${statusText}
|
|
</span>
|
|
${result.refund ? '<span class="badge bg-purple ms-1">Refunded</span>' : ''}
|
|
${result.pi_followup ? '<span class="badge bg-warning ms-1">PI Follow-up</span>' : ''}
|
|
${result.refund_followup ? '<span class="badge bg-info ms-1">Refund Follow-up</span>' : ''}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="result-details">
|
|
<div class="result-field">
|
|
<strong>Splynx ID:</strong>
|
|
${result.splynx_id ? `<a href="${result.splynx_url}" target="_blank">${result.splynx_id}</a>` : 'N/A'}
|
|
</div>
|
|
<div class="result-field">
|
|
<strong>Amount:</strong> $${Math.abs(result.amount).toFixed(2)} AUD
|
|
</div>
|
|
<div class="result-field">
|
|
<strong>Payment Method:</strong> ${result.payment_method || 'N/A'}
|
|
</div>
|
|
<div class="result-field">
|
|
<strong>Payment Intent:</strong>
|
|
<code class="small">${result.payment_intent || 'N/A'}</code>
|
|
</div>
|
|
<div class="result-field">
|
|
<strong>Created:</strong> ${new Date(result.created).toLocaleDateString()} ${new Date(result.created).toLocaleTimeString()}
|
|
</div>
|
|
<div class="result-field">
|
|
<strong>Processed By:</strong> ${result.processed_by}
|
|
</div>
|
|
</div>
|
|
|
|
${result.error ? `
|
|
<div class="alert alert-danger alert-sm mt-2">
|
|
<strong>Error:</strong> ${result.error.substring(0, 200)}${result.error.length > 200 ? '...' : ''}
|
|
</div>
|
|
` : ''}
|
|
|
|
<div class="result-actions">
|
|
<a href="${result.detail_url}" class="btn btn-sm btn-primary">
|
|
<i class="fas fa-eye"></i> View Details
|
|
</a>
|
|
${result.batch_url ? `
|
|
<a href="${result.batch_url}" class="btn btn-sm btn-info">
|
|
<i class="fas fa-layer-group"></i> View Batch
|
|
</a>
|
|
` : ''}
|
|
${result.splynx_url ? `
|
|
<a href="${result.splynx_url}" target="_blank" class="btn btn-sm btn-link">
|
|
<i class="fas fa-external-link-alt"></i> Splynx Customer
|
|
</a>
|
|
` : ''}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function clearSearch() {
|
|
document.getElementById('searchQuery').value = '';
|
|
document.getElementById('searchType').value = 'all';
|
|
document.getElementById('resultsLimit').value = '50';
|
|
document.getElementById('searchResults').style.display = 'none';
|
|
document.getElementById('searchQuery').focus();
|
|
}
|
|
|
|
// Purple badge custom style
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
.bg-purple {
|
|
background-color: #9370db !important;
|
|
color: white;
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
</script>
|
|
{% endblock %}
|
|
|