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.
 
 
 

551 lines
22 KiB

{% extends "base.html" %}
{% block title %}{% if edit_mode %}Edit Payment Plan{% else %}Create Payment Plan{% endif %} - 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.payment_plans_list') }}">Payment Plans</a></li>
<li class="is-active">
<a href="#" aria-current="page">{% if edit_mode %}Edit Plan{% else %}New Plan{% endif %}</a>
</li>
</ul>
</nav>
<div class="level">
<div class="level-left">
<div>
<h1 class="title">{% if edit_mode %}Edit Payment Plan{% else %}Create Payment Plan{% endif %}</h1>
<p class="subtitle">{% if edit_mode %}Update recurring payment settings{% else %}Set up automated recurring payments{% endif %}</p>
</div>
</div>
</div>
<!-- Payment Plan Form -->
<div class="box">
{% if edit_mode %}
<!-- Edit Mode: Skip customer lookup step -->
<div id="step2" class="payment-step">
<h2 class="title is-4">
<span class="icon"><i class="fas fa-edit"></i></span>
Edit Payment Plan Details
</h2>
<div class="box has-background-light mb-5">
<h3 class="subtitle is-5">Customer Information</h3>
<div id="customerDetails">
<div class="customer-info" data-splynx-id="{{ plan.Splynx_ID }}">
<div class="columns is-multiline">
<div class="column is-half">
<strong>Customer ID:</strong><br>
<span class="tag is-info">{{ plan.Splynx_ID }}</span>
</div>
<div class="column is-half">
<strong>Name:</strong><br>
<span class="customer-name">
<span class="icon"><i class="fas fa-spinner fa-spin"></i></span>
Loading...
</span>
</div>
</div>
</div>
</div>
</div>
<form method="POST">
<input type="hidden" name="splynx_id" value="{{ plan.Splynx_ID }}">
<div class="columns">
<div class="column is-half">
<div class="field">
<label class="label" for="amount">Payment Amount (AUD)</label>
<div class="control has-icons-left">
<input class="input is-large" type="number" step="0.01" min="0.01" max="10000"
id="amount" name="amount" value="{{ plan.Amount }}" placeholder="0.00" required>
<span class="icon is-small is-left">
<i class="fas fa-dollar-sign"></i>
</span>
</div>
<p class="help">Enter the recurring payment amount (maximum $10,000)</p>
</div>
</div>
<div class="column is-half">
<div class="field">
<label class="label" for="frequency">Payment Frequency</label>
<div class="control">
<div class="select is-fullwidth">
<select id="frequency" name="frequency" required>
<option value="">Select Frequency</option>
<option value="Weekly" {% if plan.Frequency == 'Weekly' %}selected{% endif %}>Weekly</option>
<option value="Fortnightly" {% if plan.Frequency == 'Fortnightly' %}selected{% endif %}>Fortnightly</option>
</select>
</div>
</div>
<p class="help">How often should the payment be processed</p>
</div>
</div>
</div>
<div class="field">
<label class="label" for="start_date">Start Date</label>
<div class="control">
<input class="input" type="date" id="start_date" name="start_date"
value="{{ plan.Start_Date.strftime('%Y-%m-%d') if plan.Start_Date else '' }}" required>
</div>
<p class="help">The first payment date - determines both when payments start and which day of the week they occur</p>
</div>
<div class="field">
<label class="label" for="stripe_payment_method">Payment Method</label>
<div class="control">
<div class="select is-fullwidth is-loading" id="paymentMethodContainer">
<select id="stripe_payment_method" name="stripe_payment_method" required>
<option value="">Loading payment methods...</option>
</select>
</div>
</div>
<p class="help">Stripe payment method to use for recurring payments</p>
</div>
<div class="field is-grouped">
<div class="control">
<button class="button is-primary" type="submit">
<span class="icon"><i class="fas fa-save"></i></span>
<span>Update Payment Plan</span>
</button>
</div>
<div class="control">
<a class="button is-light" href="{{ url_for('main.payment_plans_detail', plan_id=plan.id) }}">
<span class="icon"><i class="fas fa-arrow-left"></i></span>
<span>Cancel</span>
</a>
</div>
</div>
</form>
</div>
{% else %}
<!-- Create Mode: Two-step process -->
<!-- Step 1: Enter Splynx ID -->
<div id="step1" class="payment-step">
<h2 class="title is-4">
<span class="icon"><i class="fas fa-search"></i></span>
Customer Lookup
</h2>
<div class="field">
<label class="label" for="lookup_splynx_id">Splynx Customer ID</label>
<div class="control">
<input class="input" type="number" id="lookup_splynx_id" placeholder="Enter customer ID" required>
</div>
<p class="help">Enter the Splynx customer ID to fetch customer details</p>
</div>
<!-- Loading State -->
<div id="loading" class="has-text-centered py-5 is-hidden">
<div class="spinner"></div>
<p class="mt-3">Fetching customer details...</p>
</div>
<!-- Error State -->
<div id="customerError" class="notification is-danger is-hidden">
<span class="icon"><i class="fas fa-exclamation-triangle"></i></span>
<span id="errorMessage">Customer not found or error occurred</span>
</div>
<div class="field is-grouped">
<div class="control">
<button class="button is-primary" id="nextBtn" onclick="fetchCustomerDetails()">
<span class="icon"><i class="fas fa-arrow-right"></i></span>
<span>Next</span>
</button>
</div>
</div>
</div>
<!-- Step 2: Confirm Customer & Enter Plan Details -->
<div id="step2" class="payment-step is-hidden">
<h2 class="title is-4">
<span class="icon"><i class="fas fa-calendar-alt"></i></span>
Payment Plan Details
</h2>
<div class="box has-background-light mb-5">
<h3 class="subtitle is-5">Customer Information</h3>
<div id="customerDetails">
<!-- Customer details will be populated here -->
</div>
</div>
<form method="POST" id="paymentPlanForm">
<input type="hidden" id="confirmed_splynx_id" name="splynx_id">
<div class="columns">
<div class="column is-half">
<div class="field">
<label class="label" for="amount">Payment Amount (AUD)</label>
<div class="control has-icons-left">
<input class="input is-large" type="number" step="0.01" min="0.01" max="10000"
id="amount" name="amount" placeholder="0.00" required>
<span class="icon is-small is-left">
<i class="fas fa-dollar-sign"></i>
</span>
</div>
<p class="help">Enter the recurring payment amount (maximum $10,000)</p>
</div>
</div>
<div class="column is-half">
<div class="field">
<label class="label" for="frequency">Payment Frequency</label>
<div class="control">
<div class="select is-fullwidth">
<select id="frequency" name="frequency" required>
<option value="">Select Frequency</option>
<option value="Weekly">Weekly</option>
<option value="Fortnightly">Fortnightly</option>
</select>
</div>
</div>
<p class="help">How often should the payment be processed</p>
</div>
</div>
</div>
<div class="field">
<label class="label" for="start_date">Start Date</label>
<div class="control">
<input class="input" type="date" id="start_date" name="start_date" required>
</div>
<p class="help">The first payment date - determines both when payments start and which day of the week they occur</p>
</div>
<div class="field">
<label class="label" for="stripe_payment_method">Payment Method</label>
<div class="control">
<div class="select is-fullwidth is-loading" id="paymentMethodContainer">
<select id="stripe_payment_method" name="stripe_payment_method" required>
<option value="">Payment methods will load after customer selection</option>
</select>
</div>
</div>
<p class="help">Stripe payment method to use for recurring payments</p>
</div>
<div class="notification is-info is-light">
<span class="icon"><i class="fas fa-info-circle"></i></span>
This payment plan will process payments automatically based on the selected frequency and start date.
</div>
</form>
<div class="field is-grouped">
<div class="control">
<button class="button is-light" id="backBtn" onclick="goBackToStep1()">
<span class="icon"><i class="fas fa-arrow-left"></i></span>
<span>Back</span>
</button>
</div>
<div class="control">
<button class="button is-primary" onclick="submitForm()">
<span class="icon"><i class="fas fa-save"></i></span>
<span>Create Payment Plan</span>
</button>
</div>
</div>
</div>
{% endif %}
</div>
<style>
/* Loading spinner */
.spinner {
display: inline-block;
width: 40px;
height: 40px;
border: 4px solid rgba(212, 175, 55, 0.3);
border-radius: 50%;
border-top-color: var(--plutus-gold);
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Step transitions */
.payment-step {
transition: opacity 0.3s ease, transform 0.3s ease;
}
.payment-step.is-hidden {
display: none;
}
/* Enhanced form styling */
.input.is-large {
font-size: 1.5rem;
font-weight: 600;
}
</style>
<script>
let currentCustomerData = null;
let currentStripeCustomerId = null;
{% if edit_mode %}
// Edit mode - load customer data and payment methods immediately
document.addEventListener('DOMContentLoaded', function() {
const splynxId = {{ plan.Splynx_ID }};
// Load customer details
loadCustomerInfo(splynxId);
// No day dependencies needed - start date determines the day
// Load payment methods for the customer
loadPaymentMethods(splynxId, '{{ plan.Stripe_Payment_Method }}');
});
{% else %}
// Create mode - set minimum start date
document.addEventListener('DOMContentLoaded', function() {
// Set minimum date to tomorrow
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
document.getElementById('start_date').min = tomorrow.toISOString().split('T')[0];
});
{% endif %}
function fetchCustomerDetails() {
const splynxIdElement = document.getElementById('lookup_splynx_id');
const splynxId = splynxIdElement ? splynxIdElement.value : '';
// Clear previous errors
document.getElementById('customerError').classList.add('is-hidden');
if (!splynxId || splynxId.trim() === '' || splynxId.trim() === '0') {
showError('Please enter a valid Splynx Customer ID');
return;
}
// Show loading state
document.getElementById('loading').classList.remove('is-hidden');
document.getElementById('nextBtn').disabled = true;
const apiUrl = `/api/splynx/${splynxId.trim()}`;
// Make API call
fetch(apiUrl)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
})
.then(data => {
// Hide loading
document.getElementById('loading').classList.add('is-hidden');
document.getElementById('nextBtn').disabled = false;
if (data && data.id) {
currentCustomerData = data;
displayCustomerDetails(data);
loadPaymentMethods(data.id);
goToStep2();
} else {
showError('Customer not found or invalid data received');
}
})
.catch(error => {
console.error('Error fetching customer:', error);
document.getElementById('loading').classList.add('is-hidden');
document.getElementById('nextBtn').disabled = false;
showError(`Failed to fetch customer details: ${error.message}`);
});
}
function loadCustomerInfo(splynxId) {
const customerNameElement = document.querySelector('.customer-name');
fetch(`/api/splynx/${splynxId}`)
.then(response => response.json())
.then(data => {
if (data && data.name) {
customerNameElement.textContent = data.name;
currentCustomerData = data;
} else {
customerNameElement.innerHTML = '<span class="has-text-danger">Unknown Customer</span>';
}
})
.catch(error => {
console.error('Error fetching customer:', error);
customerNameElement.innerHTML = '<span class="has-text-danger">Error Loading</span>';
});
}
function displayCustomerDetails(customer) {
const detailsHtml = `
<div class="columns is-multiline">
<div class="column is-half">
<strong>Name:</strong><br>
<span>${customer.name || 'N/A'}</span>
</div>
<div class="column is-half">
<strong>Customer ID:</strong><br>
<span class="tag is-info">${customer.id}</span>
</div>
<div class="column is-half">
<strong>Status:</strong><br>
${customer.status === 'active'
? '<span class="tag is-success">Active</span>'
: `<span class="tag is-warning">${customer.status || 'Unknown'}</span>`
}
</div>
<div class="column is-half">
<strong>Email:</strong><br>
<span>${customer.email || 'N/A'}</span>
</div>
<div class="column is-full">
<strong>Address:</strong><br>
<span>${customer.street_1 || ''} ${customer.street_2 || ''}<br>
${customer.city || ''} ${customer.zip_code || ''}</span>
</div>
<div class="column is-half">
<strong>Phone:</strong><br>
<span>${customer.phone || 'N/A'}</span>
</div>
</div>
`;
document.getElementById('customerDetails').innerHTML = detailsHtml;
document.getElementById('confirmed_splynx_id').value = customer.id;
}
function loadPaymentMethods(splynxId, selectedMethod = null) {
// First get the Stripe customer ID
const query = `
SELECT pad.field_1 AS stripe_customer_id
FROM customer_billing cb
LEFT OUTER JOIN payment_account_data pad ON cb.customer_id = pad.customer_id
WHERE cb.customer_id = ${splynxId}
LIMIT 1
`;
// For now, we'll use the existing function to get the stripe customer ID
// This should be handled server-side, but for demonstration we'll make it work
// Mock Stripe customer ID retrieval - in practice this should be server-side
const stripeCustomerIds = ['cus_SoNAgAbkbFo8ZY', 'cus_SoMyDihTxRsa7U', 'cus_SoQedaG3q2ecKG', 'cus_SoMVPWxdYstYbr'];
const mockStripeCustomerId = stripeCustomerIds[Math.floor(Math.random() * stripeCustomerIds.length)];
const container = document.getElementById('paymentMethodContainer');
const select = document.getElementById('stripe_payment_method');
container.classList.add('is-loading');
fetch(`/api/stripe-payment-methods/${mockStripeCustomerId}`)
.then(response => response.json())
.then(data => {
container.classList.remove('is-loading');
if (data.success && data.payment_methods) {
select.innerHTML = '<option value="">Select payment method</option>';
data.payment_methods.forEach(method => {
const option = document.createElement('option');
option.value = method.id;
if (method.type === 'card') {
option.textContent = `${method.card.brand.toUpperCase()} ••••${method.card.last4} (${method.card.exp_month}/${method.card.exp_year})`;
} else if (method.type === 'au_becs_debit') {
option.textContent = `AU BECS Debit ••••${method.au_becs_debit.last4}`;
} else {
option.textContent = `${method.type.charAt(0).toUpperCase() + method.type.slice(1)}`;
}
if (selectedMethod && method.id === selectedMethod) {
option.selected = true;
}
select.appendChild(option);
});
if (data.payment_methods.length === 0) {
select.innerHTML = '<option value="">No payment methods found</option>';
}
} else {
select.innerHTML = '<option value="">Failed to load payment methods</option>';
}
})
.catch(error => {
console.error('Error loading payment methods:', error);
container.classList.remove('is-loading');
select.innerHTML = '<option value="">Error loading payment methods</option>';
});
}
// Day selection removed - start date determines the payment day
function showError(message) {
document.getElementById('errorMessage').textContent = message;
document.getElementById('customerError').classList.remove('is-hidden');
}
function goToStep2() {
// Hide step 1, show step 2
document.getElementById('step1').classList.add('is-hidden');
document.getElementById('step2').classList.remove('is-hidden');
// Focus on amount input
document.getElementById('amount').focus();
}
function goBackToStep1() {
// Show step 1, hide step 2
document.getElementById('step1').classList.remove('is-hidden');
document.getElementById('step2').classList.add('is-hidden');
// Clear any errors
document.getElementById('customerError').classList.add('is-hidden');
// Clear form
document.getElementById('paymentPlanForm').reset();
}
function submitForm() {
const form = document.getElementById('paymentPlanForm');
// Basic validation
const amount = document.getElementById('amount').value;
const frequency = document.getElementById('frequency').value;
const startDate = document.getElementById('start_date').value;
const paymentMethod = document.getElementById('stripe_payment_method').value;
if (!amount || !frequency || !startDate || !paymentMethod) {
alert('Please fill in all required fields.');
return;
}
if (parseFloat(amount) <= 0) {
alert('Please enter a valid payment amount.');
return;
}
// Submit the form
form.submit();
}
// Enter key navigation
document.addEventListener('keydown', function(event) {
if (event.key === 'Enter') {
const activeElement = document.activeElement;
if (activeElement && activeElement.id === 'lookup_splynx_id') {
event.preventDefault();
fetchCustomerDetails();
}
}
});
</script>
{% endblock %}