diff --git a/efile_app/efile/api/filing_views.py b/efile_app/efile/api/filing_views.py index 48f50ea..a62a72e 100644 --- a/efile_app/efile/api/filing_views.py +++ b/efile_app/efile/api/filing_views.py @@ -12,7 +12,7 @@ from django.views.decorators.http import require_http_methods from ..utils.case_data_utils import get_case_data -from ..utils.proxy_connection import get_party_type_code_from_api +from ..utils.proxy_connection import get_headers, get_party_type_code_from_api from .base import APIResponseMixin logger = logging.getLogger(__name__) @@ -84,6 +84,85 @@ def create_filing(request): except Exception as e: return FilingAPIViews.error_response(f"Error: {str(e)}") + @staticmethod + @require_http_methods(["POST"]) + @csrf_exempt + def payment_fees(request): + try: + logger.info("Test in payment fees!") + data = json.loads(request.body) + efile_data = data.get("efile_data", {}) + if not efile_data: + return JsonResponse({"success": False, "error": "No efile data provided in request"}, status=400) + + jurisdiction_id = request.session.get("jurisdiction") + auth_tokens = request.session.get("auth_tokens", {}) + case_data = request.session.get("case_data", {}) + court_id = case_data.get("court", "") + url = f"{settings.EFSP_URL}/jurisdictions/{jurisdiction_id}/filingreview/courts/{court_id}/filing/fees" + + headers = get_headers() + tyler_token = ( + auth_tokens.get(f"TYLER-TOKEN-{jurisdiction_id.upper()}") + or auth_tokens.get(f"tyler_token_{jurisdiction_id}") + or auth_tokens.get(f"tyler-token-{jurisdiction_id}") + ) + + # Add Tyler token if available (following auth_views.py pattern) + if tyler_token: + headers[f"TYLER-TOKEN-{jurisdiction_id.upper()}"] = tyler_token + else: + logger.warning(f"No Tyler token found for jurisdiction '{jurisdiction_id}' in filing submission") + + logger.info(f"Making request!: {url}") + response = requests.post(url, json=efile_data, headers=headers) + logger.info(f"Made request: {response.status_code}") + + if response.status_code == 200 or response.status_code == 201: + response_data = response.json() + + logger.info(f"Sending back: {response_data}") + return JsonResponse( + { + "success": True, + "message": "Payment fees submitted successfully", + "api_response": response_data, + } + ) + else: + try: + error_data = response.json() + error_message = error_data.get("error", f"API returned status {response.status_code}") + logger.info(f"Sending back: {error_data}, {error_message}") + + # For 400 errors, include more details + if response.status_code == 400: + validation_errors = error_data.get("validation_errors", error_data.get("errors", [])) + if validation_errors: + error_message += f" - Validation errors: {validation_errors}" + + except json.JSONDecodeError: + error_message = f"API returned status {response.status_code} - Response: {response.text}" + except Exception as parse_error: + error_message = ( + f"API returned status {response.status_code} - Could not parse response: {str(parse_error)}" + ) + + return JsonResponse( + { + "success": False, + "error": f"Filing submission failed: {error_message}", + "api_status_code": response.status_code, + "api_response": response.text[:500] if response.text else "No response body", + }, + status=response.status_code, + ) + + except json.JSONDecodeError: + return FilingAPIViews.error_response("Invalid JSON data") + except Exception as e: + return FilingAPIViews.error_response(f"Error: {str(e)}") + @staticmethod @require_http_methods(["GET"]) def get_filing_detail(request, filing_id): @@ -449,6 +528,7 @@ def determine_party_type_for_existing_case(case_data): # Individual view functions for URL mapping get_filings = FilingAPIViews.get_filings create_filing = FilingAPIViews.create_filing +payment_fees = FilingAPIViews.payment_fees get_filing_detail = FilingAPIViews.get_filing_detail update_filing = FilingAPIViews.update_filing delete_filing = FilingAPIViews.delete_filing diff --git a/efile_app/efile/api/urls.py b/efile_app/efile/api/urls.py index 0aeaca0..1213d19 100644 --- a/efile_app/efile/api/urls.py +++ b/efile_app/efile/api/urls.py @@ -23,7 +23,7 @@ get_optional_services, get_party_types, ) -from .filing_views import create_filing, delete_filing, get_filing_detail, get_filings, update_filing +from .filing_views import create_filing, delete_filing, get_filing_detail, get_filings, payment_fees, update_filing from .s3_upload import ( mock_s3_upload, simple_s3_upload, @@ -60,6 +60,7 @@ path("auth/tyler-token/", tyler_token, name="tyler_token"), # Payment API endpoints path("payment-accounts/", payment_accounts, name="payment_accounts"), + path("payment-fees/", payment_fees, name="payment_fees"), # Filing API endpoints path("filings/", get_filings, name="filings_list"), path("filings/create/", create_filing, name="create_filing"), diff --git a/efile_app/efile/static/js/api-utils.js b/efile_app/efile/static/js/api-utils.js index faf62cd..a8c2247 100644 --- a/efile_app/efile/static/js/api-utils.js +++ b/efile_app/efile/static/js/api-utils.js @@ -70,7 +70,6 @@ class ApiUtils { const cacheEntry = this.cache[cacheKey]; if (this.isCacheValid(cacheEntry)) { - const age = Date.now() - cacheEntry.timestamp; return cacheEntry.data; } @@ -273,6 +272,32 @@ class ApiUtils { return response; } + async cachedPost(endpoint, body) { + // Check cache first + const cachedResponse = this.getCachedResponse(endpoint, body); + if (cachedResponse !== null) { + return cachedResponse; + } + + let headers = { + "Content-Type": "application/json", + "X-CSRFToken": apiUtils.getCSRFToken(), + }; + + // Make API request if not cached + const response = await this.makeRequest(endpoint, { + method: "POST", + headers, + data: body, + }); + + // Cache the response + this.setCachedResponse(endpoint, body, response); + + return response; + + } + async post(endpoint, data = {}, params = {}) { return this.makeRequest(endpoint, { method: 'POST', diff --git a/efile_app/efile/static/js/payment.js b/efile_app/efile/static/js/payment.js new file mode 100644 index 0000000..1c120d3 --- /dev/null +++ b/efile_app/efile/static/js/payment.js @@ -0,0 +1,626 @@ +/** + * Review Page JavaScript - Optimized Version + * Handles review page functionality with improved organization and performance + */ + +// Configuration constants +const CONFIG = { + URLS: { + PROFILE: '/api/auth/profile/', + PAYMENT_ACCOUNTS: '/api/payment-accounts/', + TYLER_TOKEN: '/api/auth/tyler-token/', + SUBMIT_FILING: '/api/submit-final-filing/', + QUERY_FEES: '/api/payment-fees/', + } +}; + + +// Utility functions +const Utils = { + getElement(id) { + return document.getElementById(id); + }, + + getElements(selector) { + return document.querySelectorAll(selector); + }, + + parseJSON(elementId) { + const element = this.getElement(elementId); + return element ? JSON.parse(element.textContent) : {}; + }, + + showElement(element) { + if (element) element.style.display = "block"; + }, + + hideElement(element) { + if (element) element.style.display = "none"; + }, + + // URL parameter helper + getURLParam(param) { + return new URLSearchParams(window.location.search).get(param); + }, + + cleanURL() { + window.history.replaceState({}, document.title, window.location.pathname); + } + +}; + +// Message handling +const Messages = { + show(type, message) { + const messageDiv = Utils.getElement(type === 'error' ? 'errorMessage' : 'successMessage'); + const textElement = Utils.getElement(type === 'error' ? 'errorText' : 'successText'); + + if (messageDiv && textElement) { + textElement.textContent = message; + Utils.showElement(messageDiv); + messageDiv.scrollIntoView({ + behavior: "smooth", + block: "center" + }); + } + }, + + showError(message) { + this.show('error', message); + }, + + showSuccess(message) { + this.show('success', message); + }, + + hide() { + Utils.hideElement(Utils.getElement('errorMessage')); + Utils.hideElement(Utils.getElement('successMessage')); + } +}; + +// UI Field management +const FieldManager = { + // Consolidated field setting logic + setFieldValue(fieldPrefix, value, displayValue = value) { + const input = Utils.getElement(`${fieldPrefix}Input`); + const text = Utils.getElement(`${fieldPrefix}Text`); + + if (input) input.value = value || ""; + if (text) text.textContent = displayValue || ""; + }, + + getFieldValue(fieldPrefix) { + const input = Utils.getElement(`${fieldPrefix}Input`); + const text = Utils.getElement(`${fieldPrefix}Text`); + const inputVal = input?.value?.trim(); + const textVal = text?.textContent?.trim(); + return (inputVal && inputVal.length > 0) ? inputVal : textVal; + }, + + startEditing(input, text, button, inputId) { + Utils.hideElement(text); + Utils.showElement(input); + input.focus(); + input.select(); + button.textContent = "Save"; + button.onclick = () => this.saveField(input, text, button, inputId); + }, + + saveField(input, text, button, inputId) { + text.textContent = input.value.trim(); + Utils.hideElement(input); + Utils.showElement(text); + button.textContent = "Edit"; + button.onclick = () => this.toggleEdit(inputId, button); + } +}; + +// API handlers +const APIHandlers = { + async loadPaymentAccounts() { + const params = { + jurisdiction: apiUtils.getCurrentJurisdiction() + }; + const result = await apiUtils.get(CONFIG.URLS.PAYMENT_ACCOUNTS, params); + + if (result?.success && result.data) { + UIUpdater.updatePaymentMethodsSection(result.data); + let elems = document.querySelectorAll('input[name="paymentMethod"]'); + elems.forEach(e => e.addEventListener("change", () => { + window.queryFees(); + })); + window.queryFees(); + } else { + UIUpdater.showAddNewPaymentMethod(); + } + } +}; + +// UI updaters +const UIUpdater = { + updatePaymentMethodsSection(paymentAccounts) { + const container = Utils.getElement('paymentMethodsContainer'); + if (!container || !paymentAccounts?.length) { + return this.showAddNewPaymentMethod(); + } + + let html = '
'; + + let hasMultipleWaivers = paymentAccounts.filter((account) => account.paymentAccountTypeCode === "WV").length > 1; + paymentAccounts.forEach((account, index) => { + const isDefault = index === 0; + const cardType = account.cardType?.value || "Card"; + const cardLast4 = account.cardLast4 || "****"; + let paymentText = `${cardType} ending in ${cardLast4}`; + + if (account.paymentAccountTypeCode === "WV") { + if (hasMultipleWaivers) { + paymentText = `Payment Waiver (named "${account.accountName}")`; + } else { + paymentText = 'Payment Waiver'; + } + } + + html += `
+
+ + +
+
`; + }); + + html += `
+
+ +
`; + + container.innerHTML = html; + }, + + showAddNewPaymentMethod() { + const container = Utils.getElement('paymentMethodsContainer'); + if (!container) return; + + container.innerHTML = `
+
+ + No payment methods found. Please add a payment method to continue. +
+ +
`; + } +}; + +// Payment handling +const PaymentHandler = { + async addNewPaymentMethod() { + try { + const params = new URLSearchParams({ + jurisdiction: apiUtils.getCurrentJurisdiction(), + }); + const authData = await apiUtils.get(CONFIG.URLS.TYLER_TOKEN, params); + + if (!authData?.success || !authData.data?.tyler_token) { + Messages.showError("Authentication failed. Please try again."); + return; + } + + this.redirectToPaymentForm(authData.data); + } catch (error) { + console.warn("Create payment error: %o", error); + Messages.showError("Failed to create payment method. Please try again."); + } + }, + + redirectToPaymentForm(authData) { + const form = document.createElement("form"); + form.method = "post"; + + const jurisdiction = authData.state || apiUtils.getCurrentJurisdiction(); + form.action = Utils.parseJSON('new-toga-url'); + + let dateStr = new Date().toDateString(); + const fields = [ + ['account_name', `Payment Account made on ${dateStr}`], + ['global', 'false'], + ['type_code', 'CC'], + ['tyler_info', authData.tyler_token], + ['original_url', `${window.location.origin}/jurisdiction/${jurisdiction}/payment/?payment_status=success`], + ['error_url', `${window.location.origin}/jurisdiction/${jurisdiction}/payment/?payment_status=failure`] + ]; + + fields.forEach(([name, value]) => { + const input = document.createElement("input"); + input.type = "hidden"; + input.name = name; + input.value = value; + form.appendChild(input); + }); + + document.body.appendChild(form); + form.submit(); + }, + + handleCallback() { + const status = Utils.getURLParam('payment_status'); + + if (status === 'success') { + Messages.showSuccess(gettext("Payment method added successfully!")); + setTimeout(() => APIHandlers.loadPaymentAccounts(), 1000); + Utils.cleanURL(); + } else if (status === 'failure') { + Messages.showError(gettext("Failed to add payment method. Please try again.")); + Utils.cleanURL(); + } + }, + + calcPaymentCosts() { + + } +}; + +// Navigation +const Navigation = { + goBack() { + window.location.href = `/jurisdiction/${apiUtils.getCurrentJurisdiction()}/upload`; + }, + + async toReview() { + const selectedPaymentMethod = document.querySelector('input[name="paymentMethod"]:checked'); + await apiUtils.saveCaseData({ + "selected_payment_account": selectedPaymentMethod.value, + "selected_payment_account_name": selectedPaymentMethod.getAttribute("fullName") + }); + + window.location.href = `/jurisdiction/${apiUtils.getCurrentJurisdiction()}/review`; + } +}; + +// Filing submission +const FilingHandler = { + async queryFees() { + document.getElementById("paymentSection").setAttribute("hidden", true); + this.setFeesState(true); + + const userData = await this.collectUserData(); + const selectedPaymentMethod = document.querySelector('input[name="paymentMethod"]:checked'); + if (!selectedPaymentMethod) { + this.setFeesState(false); + return; + } + + + try { + const result = await this.processFees(userData, selectedPaymentMethod.value); + this.handleFeesResponse(result); + } catch (error) { + console.warn("Error on submission: %o", error) + Messages.showError(gettext("An unexpected error occurred. Please try again.")); + this.setFeesState(false); + } + }, + + async collectUserData() { + const params = { + jurisdiction: apiUtils.getCurrentJurisdiction() + }; + const data = await apiUtils.get(CONFIG.URLS.PROFILE, params, true); + + if (data?.success && data.data) { + const profile = data.data; + const fullName = [profile.first_name, profile.last_name].filter(n => n).join(" "); + + // Set all user fields + return { + fullName: fullName, + address: profile.address, + addressLine2: profile.address_line2, + city: profile.city, + state: profile.state, + zip: profile.zip, + email: profile.email, + phone: profile.phone + }; + } + return { + fullName: "", + address: "", + addressLine2: "", + city: "Citytown", + state: "IL", + zip: "", + email: "test@example.com", + phone: "", + }; + }, + + setFeesState(isQueryingFees) { + const submitButton = Utils.getElement('submitButton'); + const loadingSpinner = Utils.getElement('loadingSpinner'); + + if (submitButton) submitButton.disabled = isQueryingFees; + if (loadingSpinner) loadingSpinner.style.display = isQueryingFees ? "block" : "none"; + }, + + async processFees(userData, paymentAccountID) { + let [caseData, uploadData] = await Promise.all([ + apiUtils.getCaseData(), + apiUtils.getUploadData() + ]); + + caseData = caseData.data.case_data; + + const efilingData = this.buildEFilingData(userData, caseData, uploadData, paymentAccountID); + + return await apiUtils.cachedPost(CONFIG.URLS.QUERY_FEES, { + efile_data: efilingData, + confirm_submission: true, + payment_account_id: paymentAccountID + }); + }, + + + buildEFilingData(userData, caseData, uploadData, paymentAccountID) { + const nameParts = userData.fullName.split(" "); + const firstName = nameParts[0] || ""; + const lastName = nameParts.length > 1 ? nameParts[nameParts.length - 1] : ""; + const middleName = nameParts.length > 2 ? nameParts.slice(1, -1).join(" ") : ""; + + const partyType = caseData.determined_party_type || caseData.petitioner_party_type || caseData.party_type; + + if (!partyType) { + throw new Error('Party type could not be determined. This is required for eFiling.'); + } + + // Build user object + const mainUser = { + mobile_number: userData.phone, + phone_number: userData.phone, + address: { + address: userData.address, + unit: userData.addressLine2, + city: userData.city, + state: userData.state, + zip: userData.zip, + country: "US" + }, + email: userData.email, + party_type: partyType, + date_of_birth: "", + is_form_filler: true, + name: { + first: firstName, + middle: middleName, + last: lastName, + suffix: "" + }, + is_new: true + }; + + const users = [mainUser]; + + // Add second user if needed for name changes + if (caseData.new_name_party_type) { + users.push({ + ...mainUser, + party_type: caseData.new_name_party_type, + name: { + first: caseData.new_first_name || firstName, + middle: caseData.new_middle_name || middleName, + last: caseData.new_last_name || lastName, + suffix: caseData.new_suffix || "" + } + }); + } + + // Add second user if needed for name changes + if (caseData.respondent_name_party_type) { + users.push({ + party_type: caseData.respondent_name_party_type, + name: { + first: caseData.respondent_first_name || "", + middle: caseData.respondent_middle_name || "", + last: caseData.respondent_last_name || "", + suffix: caseData.respondent_suffix || "" + }, + is_new: true, + }); + } + + let other_parties = []; + + if (caseData.other_first_name && caseData.other_party_type) { + other_parties.push({ + party_type: caseData.other_party_type, + name: { + first: caseData.other_first_name, + last: caseData.other_last_name + }, + address: { + address: caseData.other_address_line_1, + unit: caseData.other_address_line_2, + city: caseData.other_address_city, + state: caseData.other_address_state, + zip: caseData.other_address_zip, + country: "US" + }, + email: caseData.other_email, + phone_number: caseData.other_phone_number, + is_new: true, + }); + } + + const efilingData = { + efile_case_category: caseData.case_category, + efile_case_type: caseData.case_type, + efile_case_subtype: caseData.case_subtype, + previous_case_id: caseData?.previous_case_id, + docket_number: caseData?.docket_number, + users, + other_parties, + user_started_case: !caseData?.previous_case_id, + al_court_bundle: [], + cross_references: "", + comments_to_clerk: "", + tyler_payment_id: paymentAccountID, + lead_contact: { + name: { + first: firstName, + middle: middleName, + last: lastName + }, + email: userData.email + }, + return_date: "" + }; + + // Add court bundles for documents + this.addCourtBundles(efilingData, uploadData, caseData, users); + + return efilingData; + }, + + addCourtBundles(efilingData, uploadData, caseData, users) { + const courtName = caseData.court_name || caseData.court || ""; + if (courtName.toLowerCase().includes("cook") || courtName.toLowerCase().includes("dupage")) { + efilingData.cross_references = { + 254500: "254500" + }; + } + + // Add lead document + if (uploadData?.files?.lead) { + const leadBundle = this.createDocumentBundle( + uploadData.files.lead, + uploadData.lead_filing_type || caseData.filing_type, + uploadData.lead_document_type || caseData.document_type, + uploadData.lead_filing_component || caseData.filing_component, + users, + uploadData.lead_filing_type_name || caseData.case_type_name, + uploadData.lead_document_type_name || "", + uploadData.lead_cc_email + ); + efilingData.al_court_bundle.push(leadBundle); + } + + // Add supporting documents + if (uploadData?.files?.supporting?.length > 0) { + uploadData.files.supporting.forEach((doc, index) => { + const config = uploadData.supporting_documents?.[index] || {}; + const bundle = this.createDocumentBundle( + doc, + config.filing_type || caseData.filing_type_id, + config.document_type || caseData.document_type, + config.filing_component || "supporting", + users, + config.filing_type_name || `Supporting Document ${index + 1}`, + config.document_type_name || "", + config.cc_email + ); + efilingData.al_court_bundle.push(bundle); + }); + } + }, + + createDocumentBundle(doc, filingType, documentType, filingComponent, users, description, docDescription, cc_email) { + if (cc_email) { + courtesy_copies = [cc_email] + } else { + courtesy_copies = [] + } + return { + proxy_enabled: true, + filing_type: filingType, + optional_services: [], + due_date: null, + filing_description: description, + reference_number: "", + filing_attorney: "", + filing_comment: "", + courtesy_copies: courtesy_copies, + preliminary_copies: [], + filing_parties: users.length === 1 ? ["users[0]"] : ["users[0]", "users[1]"], + filing_action: "efile", + tyler_merge_attachments: false, + document_type: documentType, + filing_component: filingComponent, + filename: doc.name, + document_description: docDescription, + data_url: doc.url || doc.s3_url || doc.file_url || doc.download_url + }; + }, + + handleSubmissionResult(result) { + if (result?.success) { + Messages.showSuccess(gettext("Filing submitted successfully! You will be redirected to the confirmation page.")); + setTimeout(() => { + const jurisdiction = apiUtils.getCurrentJurisdiction(); + window.location.href = result.redirect_url || `/jurisdiction/${jurisdiction}/filing-confirmation/`; + }, 2000); + } else { + Messages.showError(result?.error || "An error occurred during submission."); + this.setSubmissionState(false); + } + }, + + handleFeesResponse(result) { + if (result?.success) { + let htmlStr = ` + Total: $${result.api_response.feesCalculationAmount.value} + + "; + + let infoElem = document.getElementById("paymentInfo"); + infoElem.innerHTML = htmlStr; + document.getElementById("paymentSection").removeAttribute("hidden"); + } else { + Messages.showError(result?.error || "An error occurred when calculating fees."); + } + this.setFeesState(false); + } +}; + +// Main application initialization +const ReviewApp = { + async init() { + await this.loadAllData(); + PaymentHandler.handleCallback(); + }, + + async loadAllData() { + // Load other data in parallel + await Promise.all([ + APIHandlers.loadPaymentAccounts() + ]); + } +}; + +// Global function exports for HTML onclick handlers +window.goBack = Navigation.goBack; +window.toReview = Navigation.toReview; +window.queryFees = FilingHandler.queryFees.bind(FilingHandler); +window.PaymentHandler = PaymentHandler; +window.Navigation = Navigation; + +// Initialize app when DOM is ready +document.addEventListener("DOMContentLoaded", () => ReviewApp.init()); \ No newline at end of file diff --git a/efile_app/efile/static/js/review.js b/efile_app/efile/static/js/review.js index 8bb525f..18fca25 100644 --- a/efile_app/efile/static/js/review.js +++ b/efile_app/efile/static/js/review.js @@ -17,6 +17,7 @@ const CONFIG = { TYLER_TOKEN: '/api/auth/tyler-token/', SUBMIT_FILING: '/api/submit-final-filing/', CASE_DATA: '/api/get-case-data/', + QUERY_FEES: '/api/payment-fees/', } }; @@ -58,15 +59,6 @@ const Utils = { const cleaned = phone.replace(/[\s()-\.]/g, ""); return CONFIG.VALIDATION.PHONE_REGEX.test(cleaned); }, - - // URL parameter helper - getURLParam(param) { - return new URLSearchParams(window.location.search).get(param); - }, - - cleanURL() { - window.history.replaceState({}, document.title, window.location.pathname); - } }; // Message handling @@ -263,10 +255,10 @@ const APIHandlers = { }, async loadUserInfo() { - const params = new URLSearchParams({ - jurisdiction: apiUtils.getCurrentJurisdiction(), - }); - const data = await DataManager.fetchJSON(`${CONFIG.URLS.PROFILE}?${params}`); + const params = { + jurisdiction: apiUtils.getCurrentJurisdiction() + }; + const data = await apiUtils.get(CONFIG.URLS.PROFILE, params); if (data?.success && data.data) { const profile = data.data; @@ -300,14 +292,12 @@ const APIHandlers = { }, async loadPaymentAccounts() { - const params = new URLSearchParams({ - jurisdiction: apiUtils.getCurrentJurisdiction(), - }); - const result = await DataManager.fetchJSON(`${CONFIG.URLS.PAYMENT_ACCOUNTS}?${params}`); - - if (result?.success && result.data) { - UIUpdater.updatePaymentMethodsSection(result.data); - } else { + try { + const caseData = await DataManager.getCaseData(); + UIUpdater.updatePaymentMethodsSection(caseData.selected_payment_account, caseData.selected_payment_account_name || "Your payment"); + await window.queryFees(); + } catch (ex) { + console.log(ex); UIUpdater.showAddNewPaymentMethod(); } } @@ -373,51 +363,31 @@ const UIUpdater = { `; }, - updatePaymentMethodsSection(paymentAccounts) { + updatePaymentMethodsSection(account, account_name) { + console.log(account); const container = Utils.getElement('paymentMethodsContainer'); - if (!container || !paymentAccounts?.length) { - return this.showAddNewPaymentMethod(); - } - let html = '
'; - let hasMultipleWaivers = paymentAccounts.filter((account) => account.paymentAccountTypeCode === "WV").length > 1; - paymentAccounts.forEach((account, index) => { - const isDefault = index === 0; - const cardType = account.cardType?.value || "Card"; - const cardLast4 = account.cardLast4 || "****"; - let paymentText = `${cardType} ending in ${cardLast4}`; - - if (account.paymentAccountTypeCode === "WV") { - if (hasMultipleWaivers) { - paymentText = `Payment Waiver (named "${account.accountName}")`; - } else { - paymentText = 'Payment Waiver'; - } - } + //const cardType = account.cardType?.value || "Card"; + //const cardLast4 = account.cardLast4 || "****"; + //let paymentText = `${cardType} ending in ${cardLast4}`; - html += `
+ html += `
- -
`; - }); - html += `
-
- -
`; + html += `
`; container.innerHTML = html; }, @@ -429,84 +399,16 @@ const UIUpdater = { container.innerHTML = `
- No payment methods found. Please add a payment method to continue. + No payment methods found. Please go back to add a payment method.
-
`; } }; -// Payment handling -const PaymentHandler = { - async addNewPaymentMethod() { - try { - const params = new URLSearchParams({ - jurisdiction: apiUtils.getCurrentJurisdiction(), - }); - const authData = await DataManager.fetchJSON(`${CONFIG.URLS.TYLER_TOKEN}?${params}`); - - if (!authData?.success || !authData.data?.tyler_token) { - Messages.showError("Authentication failed. Please try again."); - return; - } - - this.redirectToPaymentForm(authData.data); - } catch (error) { - console.log("Create payment error: %o", error); - Messages.showError("Failed to create payment method. Please try again."); - } - }, - - redirectToPaymentForm(authData) { - const form = document.createElement("form"); - form.method = "post"; - - const jurisdiction = authData.state || apiUtils.getCurrentJurisdiction(); - // TODO: we should revisit this hardcoded value in the future too - form.action = Utils.parseJSON('new-toga-url'); - - let dateStr = new Date().toDateString(); - const fields = [ - ['account_name', `Payment Account made on ${dateStr}`], - ['global', 'false'], - ['type_code', 'CC'], - ['tyler_info', authData.tyler_token], - ['original_url', `${window.location.origin}/jurisdiction/${jurisdiction}/review/?payment_status=success`], - ['error_url', `${window.location.origin}/jurisdiction/${jurisdiction}/review/?payment_status=failure`] - ]; - - fields.forEach(([name, value]) => { - const input = document.createElement("input"); - input.type = "hidden"; - input.name = name; - input.value = value; - form.appendChild(input); - }); - - document.body.appendChild(form); - form.submit(); - }, - - handleCallback() { - const status = Utils.getURLParam('payment_status'); - - if (status === 'success') { - Messages.showSuccess(gettext("Payment method added successfully!")); - setTimeout(() => APIHandlers.loadPaymentAccounts(), 1000); - Utils.cleanURL(); - } else if (status === 'failure') { - Messages.showError(gettext("Failed to add payment method. Please try again.")); - Utils.cleanURL(); - } - } -}; - // Navigation const Navigation = { goBack() { - window.location.href = `/jurisdiction/${apiUtils.getCurrentJurisdiction()}/upload`; + window.location.href = `/jurisdiction/${apiUtils.getCurrentJurisdiction()}/payment`; }, changeDocument(type) { @@ -531,7 +433,7 @@ const FilingHandler = { return; } - const selectedPaymentMethod = document.querySelector('input[name="paymentMethod"]:checked'); + const selectedPaymentMethod = document.getElementById('paymentAccountID'); if (!selectedPaymentMethod) { Messages.showError(gettext("Please select a payment method to continue.")); return; @@ -541,15 +443,48 @@ const FilingHandler = { Messages.hide(); try { - const result = await this.processSubmission(userData, selectedPaymentMethod.value); + const result = await this.processSubmission(userData, selectedPaymentMethod.getAttribute("value")); this.handleSubmissionResult(result); } catch (error) { console.log("Error on submission: %o", error) - Messages.showError(gexttext("An unexpected error occurred. Please try again.")); + Messages.showError(gettext("An unexpected error occurred. Please try again.")); this.setSubmissionState(false); } }, + async queryFees() { + const userData = await this.collectUserData(); + const selectedPaymentMethod = document.getElementById('paymentAccountID'); + + this.setFeesState(true); + + try { + const result = await this.processFees(userData, selectedPaymentMethod.getAttribute("value")); + this.handleFeesResponse(result); + } catch (error) { + console.log("Error on submission: %o", error) + Messages.showError(gettext("An unexpected error occurred. Please try again.")); + this.setFeesState(false); + } + }, + + async processFees(userData, paymentAccountID) { + let [caseData, uploadData] = await Promise.all([ + apiUtils.getCaseData(), + apiUtils.getUploadData() + ]); + + caseData = caseData.data.case_data; + + const efilingData = this.buildEFilingData(userData, caseData, uploadData, paymentAccountID); + + return await apiUtils.cachedPost(CONFIG.URLS.QUERY_FEES, { + efile_data: efilingData, + confirm_submission: true, + payment_account_id: paymentAccountID + }); + }, + collectUserData() { return { fullName: FieldManager.getFieldValue('userName'), @@ -563,6 +498,14 @@ const FilingHandler = { }; }, + setFeesState(isQueryingFees) { + const submitButton = Utils.getElement('submitButton'); + const loadingSpinner = Utils.getElement('loadingSpinner'); + + if (submitButton) submitButton.disabled = isQueryingFees; + if (loadingSpinner) loadingSpinner.style.display = isQueryingFees ? "block" : "none"; + }, + setSubmissionState(isSubmitting) { const submitButton = Utils.getElement('submitButton'); const loadingSpinner = Utils.getElement('loadingSpinner'); @@ -573,8 +516,8 @@ const FilingHandler = { async processSubmission(userData, paymentAccountID) { let [caseData, uploadData] = await Promise.all([ - DataManager.fetchJSON(CONFIG.URLS.CASE_DATA), - DataManager.fetchJSON(CONFIG.URLS.UPLOAD_DATA) + apiUtils.getCaseData(), + apiUtils.getUploadData() ]); caseData = caseData.data.case_data; @@ -792,6 +735,29 @@ const FilingHandler = { Messages.showError(result?.error || "An error occurred during submission."); this.setSubmissionState(false); } + }, + + handleFeesResponse(result) { + if (result?.success) { + let htmlStr = ` + Total: $${result.api_response.feesCalculationAmount.value} + + "; + + let infoElem = document.getElementById("paymentInfo"); + infoElem.innerHTML = htmlStr; + document.getElementById("paymentSection").removeAttribute("hidden"); + } else { + Messages.showError(result?.error || "An error occurred when calculating fees."); + } + this.setFeesState(false); } }; @@ -799,7 +765,6 @@ const FilingHandler = { const ReviewApp = { async init() { await this.loadAllData(); - PaymentHandler.handleCallback(); APIHandlers.fetchPartyType(); }, @@ -816,8 +781,8 @@ const ReviewApp = { await Promise.all([ APIHandlers.loadUserInfo(), APIHandlers.loadUploadData(), - APIHandlers.loadPaymentAccounts() ]); + await APIHandlers.loadPaymentAccounts() } }; @@ -825,7 +790,7 @@ const ReviewApp = { window.toggleEdit = FieldManager.toggleEdit.bind(FieldManager); window.goBack = Navigation.goBack; window.submitFiling = FilingHandler.submitFiling.bind(FilingHandler); -window.PaymentHandler = PaymentHandler; +window.queryFees = FilingHandler.queryFees.bind(FilingHandler); window.Navigation = Navigation; // Initialize app when DOM is ready diff --git a/efile_app/efile/static/js/upload-handler.js b/efile_app/efile/static/js/upload-handler.js index 11c4340..4973487 100644 --- a/efile_app/efile/static/js/upload-handler.js +++ b/efile_app/efile/static/js/upload-handler.js @@ -785,8 +785,8 @@ class UploadHandler { await this.saveUploadDataToSession(uploadDataWithUrls); - // Redirect to review page - window.location.href = `/jurisdiction/${this.jurisdiction}/review/`; + // Redirect to payments page + window.location.href = `/jurisdiction/${this.jurisdiction}/payment/`; } catch (error) { console.error('Form submission error:', error); diff --git a/efile_app/efile/templates/efile/payment.html b/efile_app/efile/templates/efile/payment.html new file mode 100644 index 0000000..d4e3c47 --- /dev/null +++ b/efile_app/efile/templates/efile/payment.html @@ -0,0 +1,78 @@ +{% load static %} +{% load i18n %} + + + + + + Review Case Details + + + + + + + {% csrf_token %} + {% include "efile/components/profile_header.html" %} +
+
+

{% translate "Payment information" %}

+

{% translate "Choose how you want to pay the filing fees to the court." %}

+
+

{% translate "Choose your payment method" %}

+ + +
+

{% translate "Existing" %}

+
+ +
+
+ + + +
+ + +
+
+ + +
+ +
+
+ Loading... +
+

{% translate "Calculating cost of filing..." %}

+
+ +
+ + +
+
+
+
+ + + + {{ case_data|json_script:"case-data" }} + {{ new_toga_url|json_script:"new-toga-url" }} + + + + + + diff --git a/efile_app/efile/templates/efile/review.html b/efile_app/efile/templates/efile/review.html index da1fcd7..2704050 100644 --- a/efile_app/efile/templates/efile/review.html +++ b/efile_app/efile/templates/efile/review.html @@ -179,13 +179,12 @@

{% translate "Your documents for filing" %}

{% translate "Payment method" %}

-

{% translate "Existing" %}

-