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.
 
 
 

428 lines
13 KiB

{% extends "base.html" %}
{% block title %}Payment Search - Plutus{% endblock %}
{% block head %}
<style>
.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 #dbdbdb;
border-radius: 6px;
transition: border-color 0.3s;
}
.search-input:focus {
border-color: #3273dc;
outline: none;
box-shadow: 0 0 0 3px rgba(50, 115, 220, 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: 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: #3273dc;
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="title is-2">🔍 Payment Search</h1>
<p class="subtitle">Search across all payment records by Splynx ID or Payment Intent</p>
<div class="field has-addons">
<div class="control is-expanded">
<input
class="input search-input"
type="text"
id="searchQuery"
placeholder="Enter Splynx ID (e.g. 123456) or Payment Intent (e.g. pi_1234567890)"
autocomplete="off"
>
</div>
<div class="control">
<button class="button is-primary is-large" onclick="performSearch()">
<span class="icon">
<i class="fas fa-search"></i>
</span>
<span>Search</span>
</button>
</div>
</div>
<div class="field is-grouped is-grouped-multiline" style="margin-top: 1rem;">
<div class="control">
<label class="label is-small">Search Type:</label>
<div class="select is-small">
<select 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>
<div class="control">
<label class="label is-small">Results Limit:</label>
<div class="select is-small">
<select id="resultsLimit">
<option value="25">25 results</option>
<option value="50" selected>50 results</option>
<option value="100">100 results</option>
</select>
</div>
</div>
<div class="control">
<button class="button is-small is-info is-outlined" onclick="clearSearch()">
<span class="icon"><i class="fas fa-times"></i></span>
<span>Clear</span>
</button>
</div>
</div>
<div class="search-tips">
<h5 class="title is-6">💡 Search Tips:</h5>
<ul>
<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="loader"></div>
<p>Searching payments...</p>
</div>
</div>
`;
}
function showError(message) {
const resultsDiv = document.getElementById('searchResults');
resultsDiv.style.display = 'block';
resultsDiv.innerHTML = `
<div class="search-error">
<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="title is-4">No Results Found</h3>
<p>No payments found matching "${data.search_query}"</p>
<p class="has-text-grey">Try searching with a different Splynx ID or Payment Intent</p>
</div>
</div>
`;
return;
}
let resultsHtml = `
<div class="search-results">
<div style="padding: 1rem; border-bottom: 1px solid #f5f5f5; background-color: #f9f9f9;">
<h3 class="title is-4">Search Results</h3>
<p>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 ? 'has-background-success-light' :
result.success === false ? 'has-background-danger-light' :
'has-background-warning-light';
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 style="margin-left: 0.5rem;">Payment #${result.id}</strong>
${result.batch_id ? `<span class="tag is-info is-light">Batch #${result.batch_id}</span>` : ''}
</div>
<div class="tags">
<span class="tag ${result.success === true ? 'is-success' : result.success === false ? 'is-danger' : 'is-warning'}">
<i class="fas ${statusIcon}"></i>&nbsp;${statusText}
</span>
${result.refund ? '<span class="tag is-purple">Refunded</span>' : ''}
${result.pi_followup ? '<span class="tag is-warning">PI Follow-up</span>' : ''}
${result.refund_followup ? '<span class="tag is-info">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 style="font-size: 0.8rem;">${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="notification is-danger is-light" style="margin-top: 0.5rem;">
<strong>Error:</strong> ${result.error.substring(0, 200)}${result.error.length > 200 ? '...' : ''}
</div>
` : ''}
<div class="result-actions">
<a href="${result.detail_url}" class="button is-small is-primary">
<span class="icon"><i class="fas fa-eye"></i></span>
<span>View Details</span>
</a>
${result.batch_url ? `
<a href="${result.batch_url}" class="button is-small is-info">
<span class="icon"><i class="fas fa-layer-group"></i></span>
<span>View Batch</span>
</a>
` : ''}
${result.splynx_url ? `
<a href="${result.splynx_url}" target="_blank" class="button is-small is-link">
<span class="icon"><i class="fas fa-external-link-alt"></i></span>
<span>Splynx Customer</span>
</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();
}
// CSS for loader
const style = document.createElement('style');
style.textContent = `
.loader {
border: 3px solid #f3f3f3;
border-top: 3px solid #3498db;
border-radius: 50%;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
margin: 0 auto 1rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.tag.is-purple {
background-color: #9370db;
color: white;
}
`;
document.head.appendChild(style);
</script>
{% endblock %}