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
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> ${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 %}
|