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