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.
213 lines
7.4 KiB
213 lines
7.4 KiB
"""
|
|
Search blueprint for Plutus payment processing application.
|
|
|
|
Provides unified search functionality across SinglePayments and Payments tables.
|
|
Supports search by Splynx_ID and Payment Intent.
|
|
"""
|
|
|
|
from flask import Blueprint, render_template, request, jsonify
|
|
from flask_login import login_required
|
|
from sqlalchemy import or_, and_
|
|
from models import SinglePayments, Payments, Users
|
|
from app import db
|
|
from permissions import helpdesk_required
|
|
import re
|
|
|
|
search_bp = Blueprint('search', __name__, url_prefix='/search')
|
|
|
|
@search_bp.route('/')
|
|
@helpdesk_required
|
|
def search_page():
|
|
"""Display the unified payment search page."""
|
|
return render_template('search/search.html')
|
|
|
|
@search_bp.route('/api', methods=['GET'])
|
|
@helpdesk_required
|
|
def search_payments():
|
|
"""
|
|
API endpoint for searching payments.
|
|
|
|
Query parameters:
|
|
- q: Search query (Splynx_ID or Payment Intent)
|
|
- type: Search type ('all', 'splynx_id', 'payment_intent')
|
|
- limit: Maximum results to return (default: 50)
|
|
"""
|
|
try:
|
|
query = request.args.get('q', '').strip()
|
|
search_type = request.args.get('type', 'all')
|
|
limit = min(int(request.args.get('limit', 50)), 100) # Max 100 results
|
|
|
|
if not query:
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'Search query is required',
|
|
'results': []
|
|
})
|
|
|
|
# Determine search strategy based on query content
|
|
if search_type == 'all':
|
|
search_type = detect_search_type(query)
|
|
|
|
# Build search queries for both tables
|
|
single_payments_query = build_single_payments_query(query, search_type, limit)
|
|
payments_query = build_payments_query(query, search_type, limit)
|
|
|
|
# Execute searches
|
|
single_payments_results = single_payments_query.all()
|
|
payments_results = payments_query.all()
|
|
|
|
# Format results
|
|
formatted_results = []
|
|
|
|
# Add single payments results
|
|
for payment in single_payments_results:
|
|
formatted_results.append(format_single_payment_result(payment))
|
|
|
|
# Add batch payments results
|
|
for payment in payments_results:
|
|
formatted_results.append(format_batch_payment_result(payment))
|
|
|
|
# Sort by creation date (newest first)
|
|
formatted_results.sort(key=lambda x: x['created'], reverse=True)
|
|
|
|
# Apply limit to combined results
|
|
formatted_results = formatted_results[:limit]
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'results': formatted_results,
|
|
'total_found': len(formatted_results),
|
|
'search_query': query,
|
|
'search_type': search_type
|
|
})
|
|
|
|
except Exception as e:
|
|
return jsonify({
|
|
'success': False,
|
|
'error': str(e),
|
|
'results': []
|
|
}), 500
|
|
|
|
def detect_search_type(query: str) -> str:
|
|
"""
|
|
Detect the type of search based on query content.
|
|
|
|
Args:
|
|
query: Search query string
|
|
|
|
Returns:
|
|
str: 'payment_intent' if looks like PI, 'splynx_id' if numeric, 'all' otherwise
|
|
"""
|
|
# Check if it looks like a payment intent ID
|
|
if re.match(r'^pi_[a-zA-Z0-9]+$', query):
|
|
return 'payment_intent'
|
|
|
|
# Check if it's purely numeric (likely Splynx ID)
|
|
if query.isdigit():
|
|
return 'splynx_id'
|
|
|
|
# Default to searching all fields
|
|
return 'all'
|
|
|
|
def build_single_payments_query(query: str, search_type: str, limit: int):
|
|
"""Build SQLAlchemy query for SinglePayments table."""
|
|
base_query = db.session.query(SinglePayments, Users.FullName).outerjoin(
|
|
Users, SinglePayments.Who == Users.id
|
|
)
|
|
|
|
if search_type == 'splynx_id':
|
|
try:
|
|
splynx_id = int(query)
|
|
return base_query.filter(SinglePayments.Splynx_ID == splynx_id).limit(limit)
|
|
except ValueError:
|
|
return base_query.filter(False) # No results for invalid numeric
|
|
|
|
elif search_type == 'payment_intent':
|
|
return base_query.filter(SinglePayments.Payment_Intent == query).limit(limit)
|
|
|
|
else: # search_type == 'all'
|
|
# Try to convert to int for Splynx_ID search
|
|
conditions = []
|
|
|
|
if query.isdigit():
|
|
conditions.append(SinglePayments.Splynx_ID == int(query))
|
|
|
|
conditions.extend([
|
|
SinglePayments.Payment_Intent == query,
|
|
SinglePayments.Stripe_Customer_ID.like(f'%{query}%')
|
|
])
|
|
|
|
return base_query.filter(or_(*conditions)).limit(limit)
|
|
|
|
def build_payments_query(query: str, search_type: str, limit: int):
|
|
"""Build SQLAlchemy query for Payments table."""
|
|
base_query = db.session.query(Payments)
|
|
|
|
if search_type == 'splynx_id':
|
|
try:
|
|
splynx_id = int(query)
|
|
return base_query.filter(Payments.Splynx_ID == splynx_id).limit(limit)
|
|
except ValueError:
|
|
return base_query.filter(False) # No results for invalid numeric
|
|
|
|
elif search_type == 'payment_intent':
|
|
return base_query.filter(Payments.Payment_Intent == query).limit(limit)
|
|
|
|
else: # search_type == 'all'
|
|
conditions = []
|
|
|
|
if query.isdigit():
|
|
conditions.append(Payments.Splynx_ID == int(query))
|
|
|
|
conditions.extend([
|
|
Payments.Payment_Intent == query,
|
|
Payments.Stripe_Customer_ID.like(f'%{query}%')
|
|
])
|
|
|
|
return base_query.filter(or_(*conditions)).limit(limit)
|
|
|
|
def format_single_payment_result(payment_data) -> dict:
|
|
"""Format a SinglePayment result for API response."""
|
|
payment, user_name = payment_data
|
|
|
|
return {
|
|
'id': payment.id,
|
|
'type': 'single',
|
|
'splynx_id': payment.Splynx_ID,
|
|
'stripe_customer_id': payment.Stripe_Customer_ID,
|
|
'payment_intent': payment.Payment_Intent,
|
|
'payment_method': payment.Payment_Method,
|
|
'amount': payment.Payment_Amount,
|
|
'success': payment.Success,
|
|
'error': payment.Error,
|
|
'refund': payment.Refund,
|
|
'refund_followup': payment.Refund_FollowUp,
|
|
'pi_followup': payment.PI_FollowUp,
|
|
'created': payment.Created.isoformat(),
|
|
'processed_by': user_name or 'Unknown',
|
|
'detail_url': f'/single-payment/detail/{payment.id}',
|
|
'splynx_url': f'https://billing.interphone.com.au/admin/customers/view?id={payment.Splynx_ID}' if payment.Splynx_ID else None
|
|
}
|
|
|
|
def format_batch_payment_result(payment) -> dict:
|
|
"""Format a batch Payment result for API response."""
|
|
return {
|
|
'id': payment.id,
|
|
'type': 'batch',
|
|
'batch_id': payment.PaymentBatch_ID,
|
|
'splynx_id': payment.Splynx_ID,
|
|
'stripe_customer_id': payment.Stripe_Customer_ID,
|
|
'payment_intent': payment.Payment_Intent,
|
|
'payment_method': payment.Payment_Method,
|
|
'amount': payment.Payment_Amount,
|
|
'success': payment.Success,
|
|
'error': payment.Error,
|
|
'refund': payment.Refund,
|
|
'refund_followup': payment.Refund_FollowUp,
|
|
'pi_followup': payment.PI_FollowUp,
|
|
'created': payment.Created.isoformat(),
|
|
'processed_by': 'Batch Process',
|
|
'detail_url': f'/payment/detail/{payment.id}',
|
|
'batch_url': f'/batch/{payment.PaymentBatch_ID}',
|
|
'splynx_url': f'https://billing.interphone.com.au/admin/customers/view?id={payment.Splynx_ID}' if payment.Splynx_ID else None
|
|
}
|