kingkay000 commited on
Commit
320782b
·
verified ·
1 Parent(s): e9256f8

Delete easypay

Browse files
easypay/DUPLICATE_PAYMENT_ENHANCEMENT.md DELETED
@@ -1,115 +0,0 @@
1
- # Payment Processing Enhancement - Duplicate Payment Handling
2
-
3
- ## Overview
4
- Enhanced the payment processing system to intelligently handle duplicate payments on the same date for the same student.
5
-
6
- ## Changes Made
7
-
8
- ### 1. **process_payment.php - STEP 1 Enhancement** (Lines 92-147)
9
-
10
- #### Previous Behavior:
11
- - Simply threw an exception if any payment existed for the student on the same date
12
- - No differentiation between same-term and different-term payments
13
-
14
- #### New Behavior:
15
- - **Checks for existing payment** and retrieves full details (id, session, term, total_paid)
16
- - **Different Term Detection**: If existing payment is for a different term/session, throws exception:
17
- ```
18
- "A payment for this student has already been registered on this date in a different term (Session: X, Term: Y)"
19
- ```
20
- - **Same Term Handling**: If existing payment is for the same term:
21
- - Returns JSON response with `status: 'confirmation_required'`
22
- - Includes detailed message showing existing payment amount and new amount to be added
23
- - Waits for user confirmation via `confirm_update` POST parameter
24
- - If user confirms (`confirm_update === 'yes'`), sets `$isUpdateMode = true` and stores existing record details
25
-
26
- ### 2. **process_payment.php - STEP 3b Enhancement** (Lines 202-244)
27
-
28
- #### Previous Behavior:
29
- - Always inserted a new record into `tb_account_school_fee_sum_payments`
30
-
31
- #### New Behavior:
32
- - **Update Mode** (when `$isUpdateMode === true`):
33
- ```sql
34
- UPDATE tb_account_school_fee_sum_payments
35
- SET total_paid = total_paid + :additional_amount,
36
- registered_on = NOW()
37
- WHERE id = :existing_record_id
38
- ```
39
- - Adds new payment amount to existing total
40
- - Updates `registered_on` timestamp to current time
41
-
42
- - **Insert Mode** (when `$isUpdateMode === false`):
43
- - Proceeds with normal INSERT operation as before
44
-
45
- ### 3. **index.php - Form Submission Handler** (Lines 708-786)
46
-
47
- #### New AJAX-Based Submission:
48
- - Intercepts form submission to handle confirmation flow
49
- - Detects JSON responses with `status: 'confirmation_required'`
50
- - Shows browser confirmation dialog with payment details
51
- - **If User Confirms**:
52
- - Appends `confirm_update=yes` to form data
53
- - Resubmits request to process_payment.php
54
- - Displays final success/error page
55
- - **If User Cancels**:
56
- - Shows "Payment cancelled by user" message
57
- - Re-enables form for new attempt
58
-
59
- ## User Flow
60
-
61
- ### Scenario 1: Different Term Payment Exists
62
- 1. User attempts to process payment for Student X on Date Y
63
- 2. System finds existing payment for different term
64
- 3. **Exception thrown immediately**: "A payment for this student has already been registered on this date in a different term (Session: 2024, Term: 2)"
65
- 4. Payment process stops
66
-
67
- ### Scenario 2: Same Term Payment Exists
68
- 1. User attempts to process payment for Student X on Date Y
69
- 2. System finds existing payment for same term/session
70
- 3. **Popup appears**: "A payment for this student already exists on 2026-01-06 for Session 2024, Term 1. Current total: ₦50,000.00. Do you want to update this record by adding ₦25,000.00 to the existing amount?"
71
- 4. **User clicks OK**:
72
- - System updates existing record
73
- - `total_paid` becomes ₦75,000.00
74
- - `registered_on` updated to current timestamp
75
- - All other STEP 3 operations (3a, 3c, 3d, 3e) proceed normally
76
- 5. **User clicks Cancel**:
77
- - Alert: "Payment cancelled by user"
78
- - No database changes
79
- - Form remains open for retry
80
-
81
- ### Scenario 3: No Existing Payment
82
- 1. User processes payment normally
83
- 2. No duplicate detected
84
- 3. System proceeds with standard INSERT operation
85
- 4. Success page displayed
86
-
87
- ## Database Impact
88
-
89
- ### Tables Modified:
90
- - **tb_account_school_fee_sum_payments**:
91
- - UPDATE operation added for duplicate same-term scenarios
92
- - `total_paid` incremented
93
- - `registered_on` timestamp updated
94
-
95
- ### Tables Unaffected by Update Mode:
96
- - **tb_account_school_fee_payments**: Always receives new records (3a)
97
- - **tb_account_student_payments**: Always receives new records (3c)
98
- - **tb_account_payment_registers**: Always receives new records (3d)
99
- - **tb_student_logistics**: Always updated with new payment amounts (3e)
100
-
101
- ## Technical Notes
102
-
103
- 1. **Transaction Safety**: All operations remain within the existing database transaction, ensuring atomicity
104
- 2. **Backward Compatibility**: Normal payment flows (no duplicates) work exactly as before
105
- 3. **Error Handling**: Maintains existing exception handling and rollback mechanisms
106
- 4. **User Experience**: Confirmation dialog provides clear information about existing payment and proposed update
107
-
108
- ## Testing Recommendations
109
-
110
- 1. Test different-term duplicate detection
111
- 2. Test same-term duplicate with user confirmation
112
- 3. Test same-term duplicate with user cancellation
113
- 4. Verify database updates are correct when user confirms
114
- 5. Verify no database changes when user cancels
115
- 6. Test normal payment flow (no duplicates) still works
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
easypay/README.md DELETED
@@ -1,204 +0,0 @@
1
- # Student Fee Payment Registration System
2
-
3
- A streamlined 2-page PHP application that simplifies the school fee payment process by automatically allocating payments across outstanding fees from oldest to newest.
4
-
5
- ## Features
6
-
7
- - **AJAX-powered student search** - Find students quickly by name or student code
8
- - **Outstanding fees display** - View all unpaid fees sorted from oldest to newest
9
- - **Automatic payment allocation** - Payments are automatically distributed across selected fees in chronological order
10
- - **Teller validation** - Automatic lookup of bank statements with unreconciled amount calculation
11
- - **Duplicate prevention** - Prevents multiple payments for the same student on the same date
12
- - **Transactional integrity** - All database operations wrapped in transactions with automatic rollback on error
13
- - **Comprehensive receipts** - Detailed payment confirmation with all settled fees
14
-
15
- ## Database Tables Used
16
-
17
- The application interacts with the following tables in the `one_arps_aci` database:
18
-
19
- - `tb_student_registrations` - Student information
20
- - `tb_academic_levels` - Academic level details
21
- - `tb_account_school_fees` - Fee definitions
22
- - `tb_account_receivables` - Billed fees (invoices)
23
- - `tb_account_bank_statements` - Bank transaction data
24
- - `tb_account_school_fee_payments` - Individual fee payment records
25
- - `tb_account_school_fee_sum_payments` - Aggregated payment records
26
- - `tb_account_student_payments` - Cumulative payment tracking
27
- - `tb_account_payment_registers` - Receipt records
28
- - `tb_student_logistics` - Student outstanding balance tracking
29
-
30
- ## Installation
31
-
32
- ### Prerequisites
33
-
34
- - PHP 7.4 or higher
35
- - MySQL 5.7 or higher
36
- - Web server (Apache, Nginx, or PHP built-in server)
37
- - Existing `one_arps_aci` database with required tables
38
-
39
- ### Setup Steps
40
-
41
- 1. **Copy files to your web directory**
42
- ```
43
- c:\ESD\ARPS EasyPayments\
44
- ├── db_config.php
45
- ├── index.php
46
- ├── process_payment.php
47
- └── ajax_handlers.php
48
- ```
49
-
50
- 2. **Configure database connection**
51
-
52
- Edit `db_config.php` and update the following constants:
53
- ```php
54
- define('DB_HOST', 'localhost'); // Your MySQL host
55
- define('DB_USER', 'root'); // Your MySQL username
56
- define('DB_PASS', ''); // Your MySQL password
57
- ```
58
-
59
- 3. **Set proper permissions**
60
-
61
- Ensure the web server has read access to all PHP files.
62
-
63
- 4. **Access the application**
64
-
65
- Navigate to `http://localhost/ARPS%20EasyPayments/index.php` in your browser.
66
-
67
- ## Usage Guide
68
-
69
- ### Step 1: Search for a Student
70
-
71
- 1. Type the student's name or student code in the search box
72
- 2. Select the student from the dropdown results
73
- 3. The page will reload showing student details and outstanding fees
74
-
75
- ### Step 2: Review Outstanding Fees
76
-
77
- - All outstanding fees are displayed in a table, sorted from oldest to newest
78
- - Fees are pre-checked by default
79
- - You can uncheck any fees you don't want to settle
80
- - The table shows:
81
- - Fee description
82
- - Academic session and term
83
- - Billed amount
84
- - Amount already paid
85
- - Outstanding balance
86
-
87
- ### Step 3: Process Payment
88
-
89
- 1. Click the **"Process Payment"** button
90
- 2. Enter the **Teller Number** from the bank statement
91
- 3. The system will automatically:
92
- - Look up the bank statement
93
- - Fill in the bank narration
94
- - Calculate the unreconciled amount on the teller
95
- 4. Enter the **Amount to Use for Fees** (must not exceed unreconciled amount)
96
- 5. Click **"OK PROCEED!"**
97
-
98
- ### Step 4: View Confirmation
99
-
100
- - On success, you'll see a detailed receipt showing:
101
- - Student name and receipt number
102
- - Payment date and teller information
103
- - All fees that were settled
104
- - Remaining unreconciled amount on the teller
105
- - On failure, you'll see an error message (no database changes are made)
106
-
107
- ## Payment Allocation Logic
108
-
109
- The system automatically allocates payments using the following rules:
110
-
111
- 1. **Oldest First** - Fees are sorted by academic session (ASC), then term (ASC)
112
- 2. **Full Settlement Priority** - Each fee is fully settled before moving to the next
113
- 3. **Partial Settlement** - If the payment amount runs out, the last fee is partially settled
114
- 4. **Automatic Calculation** - No manual allocation required
115
-
116
- ### Example
117
-
118
- If a student has these outstanding fees:
119
- - 2024 Term 1: ₦10,000
120
- - 2024 Term 2: ₦15,000
121
- - 2024 Term 3: ₦12,000
122
-
123
- And you process a payment of ₦30,000:
124
- - 2024 Term 1: Fully settled (₦10,000)
125
- - 2024 Term 2: Fully settled (₦15,000)
126
- - 2024 Term 3: Partially settled (₦5,000)
127
-
128
- ## ID Generation Rules
129
-
130
- The application follows strict ID generation rules as per the database schema:
131
-
132
- - **transaction_id**: `student_id + academic_session + payment_date`
133
- - **school_fee_payment_id**: `student_code + fee_id + payment_date`
134
- - **receipt_no**: `student_code + payment_date`
135
- - **Date format**: Always `YYYY-MM-DD`
136
-
137
- ## Error Handling
138
-
139
- The application includes comprehensive error handling:
140
-
141
- - **Database connection failures** - Clear error message displayed
142
- - **Invalid teller numbers** - Validation before processing
143
- - **Duplicate payments** - Prevented at database level
144
- - **Insufficient unreconciled amount** - Client and server-side validation
145
- - **Transaction failures** - Automatic rollback with error details
146
-
147
- ## Security Features
148
-
149
- - **PDO with prepared statements** - Protection against SQL injection
150
- - **Input validation** - Server-side validation of all inputs
151
- - **Output sanitization** - All user data is escaped before display
152
- - **Transaction integrity** - All-or-nothing database operations
153
-
154
- ## Browser Compatibility
155
-
156
- - Chrome (recommended)
157
- - Firefox
158
- - Edge
159
- - Safari
160
-
161
- ## Troubleshooting
162
-
163
- ### "Database connection failed"
164
- - Check your database credentials in `db_config.php`
165
- - Ensure MySQL server is running
166
- - Verify the database name is `one_arps_aci`
167
-
168
- ### "Teller number not found"
169
- - Verify the teller number exists in `tb_account_bank_statements`
170
- - Check that the teller number is the last token in the description field
171
- - Ensure there's unreconciled amount available
172
-
173
- ### "A payment for this student has already been registered on this date"
174
- - This is a duplicate prevention feature
175
- - You cannot process multiple payments for the same student on the same date
176
- - Use a different payment date or check existing records
177
-
178
- ## Technical Notes
179
-
180
- ### Teller Number Extraction
181
-
182
- The system extracts teller information from `tb_account_bank_statements.description` using this logic:
183
- - **Teller Number**: Last token (rightmost word) after splitting by spaces
184
- - **Teller Name**: All text before the last space
185
-
186
- Example: `"SCHOOL FEES PAYMENT 1234567890"`
187
- - Teller Number: `1234567890`
188
- - Teller Name: `SCHOOL FEES PAYMENT`
189
-
190
- ### Unreconciled Amount Calculation
191
-
192
- ```sql
193
- unreconciled_amount = bank_statement.amount_paid - SUM(school_fee_payments.amount_paid)
194
- ```
195
-
196
- This shows how much of the bank deposit has not yet been allocated to student fees.
197
-
198
- ## Support
199
-
200
- For issues or questions, please contact the system administrator.
201
-
202
- ## License
203
-
204
- Internal use only - ARPS School Management System
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
easypay/ajax_handlers.php DELETED
@@ -1,122 +0,0 @@
1
- <?php
2
- /**
3
- * AJAX Handlers
4
- * Endpoints for student search and teller lookup
5
- */
6
-
7
- require_once 'db_config.php';
8
-
9
- header('Content-Type: application/json');
10
-
11
- $action = $_GET['action'] ?? '';
12
-
13
- try {
14
- switch ($action) {
15
- case 'search_students':
16
- searchStudents($pdo);
17
- break;
18
-
19
- case 'lookup_teller':
20
- lookupTeller($pdo);
21
- break;
22
-
23
- default:
24
- echo json_encode(['error' => 'Invalid action']);
25
- }
26
- } catch (Exception $e) {
27
- echo json_encode(['error' => $e->getMessage()]);
28
- }
29
-
30
- /**
31
- * Search for students by name or student code
32
- */
33
- function searchStudents($pdo)
34
- {
35
- $search = $_GET['search'] ?? '';
36
-
37
- if (strlen($search) < 2) {
38
- echo json_encode([]);
39
- return;
40
- }
41
-
42
- $sql = "SELECT
43
- id,
44
- student_code,
45
- CONCAT(last_name, ' ', first_name, ' ', COALESCE(other_name, '')) AS full_name
46
- FROM tb_student_registrations
47
- WHERE
48
- admission_status = 'Active' -- New condition added here
49
- AND (
50
- student_code LIKE :search1
51
- OR last_name LIKE :search2
52
- OR first_name LIKE :search3
53
- OR other_name LIKE :search4
54
- OR CONCAT(last_name, ' ', first_name, ' ', COALESCE(other_name, '')) LIKE :search5
55
- )
56
- ORDER BY last_name, first_name
57
- LIMIT 20";
58
-
59
- $stmt = $pdo->prepare($sql);
60
- $searchParam = '%' . $search . '%';
61
- $stmt->execute([
62
- 'search1' => $searchParam,
63
- 'search2' => $searchParam,
64
- 'search3' => $searchParam,
65
- 'search4' => $searchParam,
66
- 'search5' => $searchParam
67
- ]);
68
-
69
- $results = $stmt->fetchAll();
70
- echo json_encode($results);
71
- }
72
-
73
- /**
74
- * Lookup bank statement by teller number
75
- */
76
- function lookupTeller($pdo)
77
- {
78
- $tellerNumber = $_GET['teller_number'] ?? '';
79
-
80
- if (empty($tellerNumber)) {
81
- echo json_encode(['error' => 'Teller number is required']);
82
- return;
83
- }
84
-
85
- // Query to find bank statement and calculate unreconciled amount
86
- $sql = "SELECT
87
- bs.id,
88
- bs.description,
89
- bs.amount_paid,
90
- bs.payment_date,
91
- COALESCE(fp.total_registered_fee, 0.00) AS registered_amount,
92
- (bs.amount_paid - COALESCE(fp.total_registered_fee, 0.00)) AS unreconciled_amount
93
- FROM tb_account_bank_statements bs
94
- LEFT JOIN (
95
- SELECT teller_no, SUM(amount_paid) AS total_registered_fee
96
- FROM tb_account_school_fee_payments
97
- GROUP BY teller_no
98
- ) fp ON SUBSTRING_INDEX(bs.description, ' ', -1) = fp.teller_no
99
- WHERE SUBSTRING_INDEX(bs.description, ' ', -1) = :teller_number
100
- HAVING unreconciled_amount >= 0
101
- LIMIT 1";
102
-
103
- $stmt = $pdo->prepare($sql);
104
- $stmt->execute(['teller_number' => $tellerNumber]);
105
-
106
- $result = $stmt->fetch();
107
-
108
- if (!$result) {
109
- echo json_encode(['error' => 'Teller number not found or fully reconciled']);
110
- return;
111
- }
112
-
113
- // Extract teller name (description without last token)
114
- $descParts = explode(' ', $result['description']);
115
- array_pop($descParts); // Remove teller number
116
- $tellerName = implode(' ', $descParts);
117
-
118
- $result['teller_name'] = $tellerName;
119
- $result['teller_no'] = $tellerNumber;
120
-
121
- echo json_encode($result);
122
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
easypay/db_config.php DELETED
@@ -1,27 +0,0 @@
1
- <?php
2
- /**
3
- * Database Configuration
4
- * PDO connection for one_arps_aci database
5
- */
6
-
7
- // Database credentials
8
- define('DB_HOST', 'o2.service.oyster.cloud76.cc:3306');
9
- define('DB_NAME', 'one_arps_aci');
10
- define('DB_USER', 'cs_admin');
11
- define('DB_PASS', 'D55-system-2-beasts-jungle-sky-birth');
12
- define('DB_CHARSET', 'utf8mb4');
13
-
14
- // PDO options
15
- $options = [
16
- PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
17
- PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
18
- PDO::ATTR_EMULATE_PREPARES => false,
19
- ];
20
-
21
- // Create PDO instance
22
- try {
23
- $dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET;
24
- $pdo = new PDO($dsn, DB_USER, DB_PASS, $options);
25
- } catch (PDOException $e) {
26
- die("Database connection failed: " . $e->getMessage());
27
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
easypay/index.php DELETED
@@ -1,789 +0,0 @@
1
- <?php
2
- /**
3
- * Student Fee Payment Registration - Main Page
4
- * Search for students and display outstanding fees
5
- */
6
-
7
- require_once 'db_config.php';
8
-
9
- $studentData = null;
10
- $outstandingFees = [];
11
- $studentId = $_GET['student_id'] ?? '';
12
-
13
- // If student is selected, fetch their data and outstanding fees
14
- if (!empty($studentId)) {
15
- try {
16
- // Fetch student details
17
- $sql = "SELECT
18
- sr.id,
19
- sr.student_code,
20
- CONCAT(sr.last_name, ' ', sr.first_name, ' ', COALESCE(sr.other_name, '')) AS full_name,
21
- al.level_name
22
- FROM tb_student_registrations sr
23
- LEFT JOIN tb_academic_levels al ON al.id = sr.level_id
24
- WHERE sr.id = :student_id";
25
-
26
- $stmt = $pdo->prepare($sql);
27
- $stmt->execute(['student_id' => $studentId]);
28
- $studentData = $stmt->fetch();
29
-
30
- if ($studentData) {
31
- // Fetch outstanding fees per fee_id
32
- $sql = "SELECT
33
- ar.id AS receivable_id,
34
- ar.fee_id,
35
- asf.description AS fee_description,
36
- ar.academic_session,
37
- ar.term_of_session,
38
- ar.actual_value AS billed_amount,
39
- COALESCE(asp.total_paid_for_period, 0) AS total_paid, -- Renamed column for clarity
40
- (ar.actual_value - COALESCE(asp.total_paid_for_period, 0)) AS outstanding_amount,
41
- ar.created_on
42
- FROM
43
- tb_account_receivables ar
44
- JOIN
45
- tb_account_school_fees asf ON asf.id = ar.fee_id
46
- LEFT JOIN (
47
- -- Subquery now calculates total payments specific to a session, term, and fee
48
- SELECT
49
- fee_id,
50
- student_id,
51
- academic_session,
52
- term_of_session,
53
- SUM(payment_to_date) AS total_paid_for_period
54
- FROM
55
- tb_account_student_payments
56
- GROUP BY
57
- fee_id,
58
- student_id,
59
- academic_session,
60
- term_of_session
61
- ) asp ON asp.fee_id = ar.fee_id
62
- AND asp.student_id = ar.student_id
63
- AND asp.academic_session = ar.academic_session
64
- AND asp.term_of_session = ar.term_of_session
65
- WHERE
66
- ar.student_id = :student_id
67
- AND ar.academic_session > 2023
68
- -- Only show records where the calculated outstanding amount is greater than zero
69
- AND (ar.actual_value - COALESCE(asp.total_paid_for_period, 0)) > 0
70
- ORDER BY
71
- ar.academic_session ASC, ar.term_of_session ASC, ar.created_on ASC";
72
-
73
- $stmt = $pdo->prepare($sql);
74
- $stmt->execute(['student_id' => $studentId]);
75
- $outstandingFees = $stmt->fetchAll();
76
- }
77
- } catch (PDOException $e) {
78
- $error = "Error fetching student data: " . $e->getMessage();
79
- }
80
- }
81
- ?>
82
- <!DOCTYPE html>
83
- <html lang="en">
84
-
85
- <head>
86
- <meta charset="UTF-8">
87
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
88
- <title>Student Fee Payment Registration</title>
89
- <style>
90
- * {
91
- margin: 0;
92
- padding: 0;
93
- box-sizing: border-box;
94
- }
95
-
96
- body {
97
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
98
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
99
- min-height: 100vh;
100
- padding: 20px;
101
- }
102
-
103
- .container {
104
- max-width: 1200px;
105
- margin: 0 auto;
106
- background: white;
107
- border-radius: 12px;
108
- box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
109
- padding: 30px;
110
- }
111
-
112
- h1 {
113
- color: #333;
114
- margin-bottom: 30px;
115
- text-align: center;
116
- font-size: 28px;
117
- }
118
-
119
- .search-section {
120
- margin-bottom: 30px;
121
- padding: 20px;
122
- background: #f8f9fa;
123
- border-radius: 8px;
124
- }
125
-
126
- .search-box {
127
- position: relative;
128
- }
129
-
130
- .search-box label {
131
- display: block;
132
- margin-bottom: 8px;
133
- font-weight: 600;
134
- color: #555;
135
- }
136
-
137
- .search-box input {
138
- width: 100%;
139
- padding: 12px 15px;
140
- border: 2px solid #ddd;
141
- border-radius: 6px;
142
- font-size: 16px;
143
- transition: border-color 0.3s;
144
- }
145
-
146
- .search-box input:focus {
147
- outline: none;
148
- border-color: #667eea;
149
- }
150
-
151
- .search-results {
152
- position: absolute;
153
- top: 100%;
154
- left: 0;
155
- right: 0;
156
- background: white;
157
- border: 2px solid #667eea;
158
- border-top: none;
159
- border-radius: 0 0 6px 6px;
160
- max-height: 300px;
161
- overflow-y: auto;
162
- display: none;
163
- z-index: 1000;
164
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
165
- }
166
-
167
- .search-results.active {
168
- display: block;
169
- }
170
-
171
- .search-result-item {
172
- padding: 12px 15px;
173
- cursor: pointer;
174
- border-bottom: 1px solid #eee;
175
- transition: background-color 0.2s;
176
- }
177
-
178
- .search-result-item:hover {
179
- background-color: #f0f0f0;
180
- }
181
-
182
- .search-result-item:last-child {
183
- border-bottom: none;
184
- }
185
-
186
- .student-code {
187
- color: #667eea;
188
- font-weight: 600;
189
- margin-right: 10px;
190
- }
191
-
192
- .student-details {
193
- margin-bottom: 30px;
194
- padding: 20px;
195
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
196
- color: white;
197
- border-radius: 8px;
198
- }
199
-
200
- .student-details h2 {
201
- margin-bottom: 10px;
202
- font-size: 24px;
203
- }
204
-
205
- .student-details p {
206
- font-size: 16px;
207
- margin-bottom: 5px;
208
- }
209
-
210
- .fees-section {
211
- margin-bottom: 30px;
212
- }
213
-
214
- .fees-section h3 {
215
- margin-bottom: 15px;
216
- color: #333;
217
- font-size: 20px;
218
- }
219
-
220
- table {
221
- width: 100%;
222
- border-collapse: collapse;
223
- margin-bottom: 20px;
224
- }
225
-
226
- th,
227
- td {
228
- padding: 12px;
229
- text-align: left;
230
- border-bottom: 1px solid #ddd;
231
- }
232
-
233
- th {
234
- background-color: #667eea;
235
- color: white;
236
- font-weight: 600;
237
- }
238
-
239
- tr:hover {
240
- background-color: #f8f9fa;
241
- }
242
-
243
- .amount {
244
- text-align: right;
245
- font-family: 'Courier New', monospace;
246
- }
247
-
248
- .btn {
249
- padding: 12px 30px;
250
- border: none;
251
- border-radius: 6px;
252
- font-size: 16px;
253
- font-weight: 600;
254
- cursor: pointer;
255
- transition: all 0.3s;
256
- }
257
-
258
- .btn-primary {
259
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
260
- color: white;
261
- }
262
-
263
- .btn-primary:hover {
264
- transform: translateY(-2px);
265
- box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
266
- }
267
-
268
- .btn-primary:disabled {
269
- background: #ccc;
270
- cursor: not-allowed;
271
- transform: none;
272
- }
273
-
274
- .modal {
275
- display: none;
276
- position: fixed;
277
- top: 0;
278
- left: 0;
279
- width: 100%;
280
- height: 100%;
281
- background: rgba(0, 0, 0, 0.5);
282
- z-index: 2000;
283
- align-items: center;
284
- justify-content: center;
285
- }
286
-
287
- .modal.active {
288
- display: flex;
289
- }
290
-
291
- .modal-content {
292
- background: white;
293
- padding: 30px;
294
- border-radius: 12px;
295
- max-width: 600px;
296
- width: 90%;
297
- max-height: 90vh;
298
- overflow-y: auto;
299
- }
300
-
301
- .modal-header {
302
- margin-bottom: 20px;
303
- }
304
-
305
- .modal-header h2 {
306
- color: #333;
307
- font-size: 22px;
308
- }
309
-
310
- .form-group {
311
- margin-bottom: 20px;
312
- }
313
-
314
- .form-group label {
315
- display: block;
316
- margin-bottom: 8px;
317
- font-weight: 600;
318
- color: #555;
319
- }
320
-
321
- .form-group input,
322
- .form-group textarea {
323
- width: 100%;
324
- padding: 10px 12px;
325
- border: 2px solid #ddd;
326
- border-radius: 6px;
327
- font-size: 14px;
328
- font-family: inherit;
329
- }
330
-
331
- .form-group input:focus,
332
- .form-group textarea:focus {
333
- outline: none;
334
- border-color: #667eea;
335
- }
336
-
337
- .form-group input[readonly] {
338
- background-color: #f0f0f0;
339
- cursor: not-allowed;
340
- }
341
-
342
- .modal-actions {
343
- display: flex;
344
- gap: 10px;
345
- justify-content: flex-end;
346
- margin-top: 25px;
347
- }
348
-
349
- .btn-secondary {
350
- background: #6c757d;
351
- color: white;
352
- }
353
-
354
- .btn-secondary:hover {
355
- background: #5a6268;
356
- }
357
-
358
- .error {
359
- color: #dc3545;
360
- font-size: 14px;
361
- margin-top: 5px;
362
- }
363
-
364
- .success {
365
- color: #28a745;
366
- font-size: 14px;
367
- margin-top: 5px;
368
- }
369
-
370
- .alert {
371
- padding: 15px;
372
- border-radius: 6px;
373
- margin-bottom: 20px;
374
- }
375
-
376
- .alert-error {
377
- background-color: #f8d7da;
378
- color: #721c24;
379
- border: 1px solid #f5c6cb;
380
- }
381
-
382
- .total-row {
383
- font-weight: bold;
384
- background-color: #f0f0f0;
385
- }
386
-
387
- .loading {
388
- display: inline-block;
389
- width: 16px;
390
- height: 16px;
391
- border: 3px solid #f3f3f3;
392
- border-top: 3px solid #667eea;
393
- border-radius: 50%;
394
- animation: spin 1s linear infinite;
395
- margin-left: 10px;
396
- }
397
-
398
- @keyframes spin {
399
- 0% {
400
- transform: rotate(0deg);
401
- }
402
-
403
- 100% {
404
- transform: rotate(360deg);
405
- }
406
- }
407
- </style>
408
- </head>
409
-
410
- <body>
411
- <div class="container">
412
- <h1>Student Fee Payment Registration</h1>
413
-
414
- <?php if (isset($error)): ?>
415
- <div class="alert alert-error"><?php echo htmlspecialchars($error); ?></div>
416
- <?php endif; ?>
417
-
418
- <!-- Student Search Section -->
419
- <div class="search-section">
420
- <div class="search-box">
421
- <label for="studentSearch">Search Student (by name or student code)</label>
422
- <input type="text" id="studentSearch" placeholder="Type to search..." autocomplete="off">
423
- <div class="search-results" id="searchResults"></div>
424
- </div>
425
- </div>
426
-
427
- <?php if ($studentData): ?>
428
- <!-- Student Details -->
429
- <div class="student-details">
430
- <h2><?php echo htmlspecialchars($studentData['full_name']); ?></h2>
431
- <p><strong>Student Code:</strong> <?php echo htmlspecialchars($studentData['student_code']); ?></p>
432
- <p><strong>Academic Level:</strong> <?php echo htmlspecialchars($studentData['level_name'] ?? 'N/A'); ?></p>
433
- </div>
434
-
435
- <?php if (count($outstandingFees) > 0): ?>
436
- <!-- Outstanding Fees Section -->
437
- <div class="fees-section">
438
- <h3>Outstanding Fees</h3>
439
- <form id="paymentForm">
440
- <input type="hidden" name="student_id" value="<?php echo htmlspecialchars($studentData['id']); ?>">
441
- <input type="hidden" name="student_code"
442
- value="<?php echo htmlspecialchars($studentData['student_code']); ?>">
443
-
444
- <table>
445
- <thead>
446
- <tr>
447
- <th width="50">Select</th>
448
- <th>Fee Description</th>
449
- <th width="100">Session</th>
450
- <th width="80">Term</th>
451
- <th width="120" class="amount">Billed</th>
452
- <th width="120" class="amount">Paid</th>
453
- <th width="120" class="amount">Outstanding</th>
454
- </tr>
455
- </thead>
456
- <tbody>
457
- <?php
458
- $totalOutstanding = 0;
459
- foreach ($outstandingFees as $fee):
460
- $totalOutstanding += $fee['outstanding_amount'];
461
- ?>
462
- <tr>
463
- <td>
464
- <input type="checkbox" class="fee-checkbox" name="selected_fees[]" value="<?php echo htmlspecialchars(json_encode([
465
- 'receivable_id' => $fee['receivable_id'],
466
- 'fee_id' => $fee['fee_id'],
467
- 'academic_session' => $fee['academic_session'],
468
- 'term_of_session' => $fee['term_of_session'],
469
- 'outstanding_amount' => $fee['outstanding_amount']
470
- ])); ?>" checked>
471
- </td>
472
- <td><?php echo htmlspecialchars($fee['fee_description']); ?></td>
473
- <td><?php echo htmlspecialchars($fee['academic_session']); ?></td>
474
- <td><?php echo htmlspecialchars($fee['term_of_session']); ?></td>
475
- <td class="amount">₦<?php echo number_format($fee['billed_amount'], 2); ?></td>
476
- <td class="amount">₦<?php echo number_format($fee['total_paid'], 2); ?></td>
477
- <td class="amount">₦<?php echo number_format($fee['outstanding_amount'], 2); ?></td>
478
- </tr>
479
- <?php endforeach; ?>
480
- <tr class="total-row">
481
- <td colspan="6" style="text-align: right;">Total Outstanding:</td>
482
- <td class="amount">₦<?php echo number_format($totalOutstanding, 2); ?></td>
483
- </tr>
484
- </tbody>
485
- </table>
486
-
487
- <button type="button" class="btn btn-primary" id="processPaymentBtn">Process Payment</button>
488
- </form>
489
- </div>
490
- <?php else: ?>
491
- <div class="alert alert-error">No outstanding fees found for this student.</div>
492
- <?php endif; ?>
493
- <?php endif; ?>
494
- </div>
495
-
496
- <!-- Payment Modal -->
497
- <div class="modal" id="paymentModal">
498
- <div class="modal-content">
499
- <div class="modal-header">
500
- <h2>Process Payment</h2>
501
- </div>
502
-
503
- <form id="paymentDetailsForm" method="POST" action="process_payment.php">
504
- <input type="hidden" name="student_id" id="modal_student_id">
505
- <input type="hidden" name="student_code" id="modal_student_code">
506
- <input type="hidden" name="selected_fees" id="modal_selected_fees">
507
- <input type="hidden" name="payment_date" id="modal_payment_date">
508
- <input type="hidden" name="bank_description" id="modal_bank_description">
509
-
510
- <div class="form-group">
511
- <label for="teller_number">Teller Number *</label>
512
- <input type="text" id="teller_number" name="teller_number" required>
513
- <span class="loading" id="tellerLoading" style="display:none;"></span>
514
- <div class="error" id="tellerError"></div>
515
- </div>
516
-
517
- <div class="form-group">
518
- <label for="bank_narration">Bank Narration</label>
519
- <textarea id="bank_narration" name="bank_narration" rows="3" readonly></textarea>
520
- </div>
521
-
522
- <div class="form-group">
523
- <label for="unreconciled_amount">Unreconciled Amount on Teller</label>
524
- <input type="text" id="unreconciled_amount" readonly>
525
- </div>
526
-
527
- <div class="form-group">
528
- <label for="amount_to_use">Amount to Use for Fees *</label>
529
- <input type="number" id="amount_to_use" name="amount_to_use" step="0.01" min="0" required>
530
- <div class="error" id="amountError"></div>
531
- </div>
532
-
533
- <div class="modal-actions">
534
- <button type="button" class="btn btn-secondary" id="cancelBtn">Cancel</button>
535
- <button type="submit" class="btn btn-primary" id="proceedBtn" disabled>OK PROCEED!</button>
536
- </div>
537
- </form>
538
- </div>
539
- </div>
540
-
541
- <script>
542
- // Student search functionality
543
- const searchInput = document.getElementById('studentSearch');
544
- const searchResults = document.getElementById('searchResults');
545
- let searchTimeout;
546
-
547
- searchInput.addEventListener('input', function () {
548
- clearTimeout(searchTimeout);
549
- const searchTerm = this.value.trim();
550
-
551
- if (searchTerm.length < 2) {
552
- searchResults.classList.remove('active');
553
- searchResults.innerHTML = '';
554
- return;
555
- }
556
-
557
- searchTimeout = setTimeout(() => {
558
- fetch(`ajax_handlers.php?action=search_students&search=${encodeURIComponent(searchTerm)}`)
559
- .then(response => response.json())
560
- .then(data => {
561
- if (data.error) {
562
- searchResults.innerHTML = `<div class="search-result-item">${data.error}</div>`;
563
- } else if (data.length === 0) {
564
- searchResults.innerHTML = '<div class="search-result-item">No students found</div>';
565
- } else {
566
- searchResults.innerHTML = data.map(student =>
567
- `<div class="search-result-item" data-id="${student.id}">
568
- <span class="student-code">${student.student_code}</span>
569
- <span>${student.full_name}</span>
570
- </div>`
571
- ).join('');
572
-
573
- // Add click handlers
574
- document.querySelectorAll('.search-result-item').forEach(item => {
575
- item.addEventListener('click', function () {
576
- const studentId = this.dataset.id;
577
- window.location.href = `?student_id=${studentId}`;
578
- });
579
- });
580
- }
581
- searchResults.classList.add('active');
582
- })
583
- .catch(error => {
584
- console.error('Search error:', error);
585
- searchResults.innerHTML = '<div class="search-result-item">Error searching students</div>';
586
- searchResults.classList.add('active');
587
- });
588
- }, 300);
589
- });
590
-
591
- // Close search results when clicking outside
592
- document.addEventListener('click', function (e) {
593
- if (!searchInput.contains(e.target) && !searchResults.contains(e.target)) {
594
- searchResults.classList.remove('active');
595
- }
596
- });
597
-
598
- // Payment modal functionality
599
- const modal = document.getElementById('paymentModal');
600
- const processPaymentBtn = document.getElementById('processPaymentBtn');
601
- const cancelBtn = document.getElementById('cancelBtn');
602
- const tellerInput = document.getElementById('teller_number');
603
- const tellerLoading = document.getElementById('tellerLoading');
604
- const tellerError = document.getElementById('tellerError');
605
- const amountInput = document.getElementById('amount_to_use');
606
- const amountError = document.getElementById('amountError');
607
- const proceedBtn = document.getElementById('proceedBtn');
608
-
609
- let unreconciledAmount = 0;
610
-
611
- processPaymentBtn?.addEventListener('click', function () {
612
- const checkedFees = document.querySelectorAll('.fee-checkbox:checked');
613
-
614
- if (checkedFees.length === 0) {
615
- alert('Please select at least one fee to process payment.');
616
- return;
617
- }
618
-
619
- // Collect selected fees
620
- const selectedFees = Array.from(checkedFees).map(cb => JSON.parse(cb.value));
621
-
622
- // Populate modal
623
- document.getElementById('modal_student_id').value = document.querySelector('input[name="student_id"]').value;
624
- document.getElementById('modal_student_code').value = document.querySelector('input[name="student_code"]').value;
625
- document.getElementById('modal_selected_fees').value = JSON.stringify(selectedFees);
626
-
627
- // Reset form
628
- document.getElementById('paymentDetailsForm').reset();
629
- tellerError.textContent = '';
630
- amountError.textContent = '';
631
- proceedBtn.disabled = true;
632
- unreconciledAmount = 0;
633
-
634
- modal.classList.add('active');
635
- });
636
-
637
- cancelBtn.addEventListener('click', function () {
638
- modal.classList.remove('active');
639
- });
640
-
641
- // Teller lookup on blur
642
- tellerInput.addEventListener('blur', function () {
643
- const tellerNumber = this.value.trim();
644
-
645
- if (!tellerNumber) {
646
- return;
647
- }
648
-
649
- tellerLoading.style.display = 'inline-block';
650
- tellerError.textContent = '';
651
-
652
- fetch(`ajax_handlers.php?action=lookup_teller&teller_number=${encodeURIComponent(tellerNumber)}`)
653
- .then(response => response.json())
654
- .then(data => {
655
- tellerLoading.style.display = 'none';
656
-
657
- if (data.error) {
658
- tellerError.textContent = data.error;
659
- document.getElementById('bank_narration').value = '';
660
- document.getElementById('unreconciled_amount').value = '';
661
- unreconciledAmount = 0;
662
- proceedBtn.disabled = true;
663
- } else {
664
- document.getElementById('bank_narration').value = data.teller_name;
665
- document.getElementById('unreconciled_amount').value = '₦' + parseFloat(data.unreconciled_amount).toFixed(2);
666
- document.getElementById('modal_payment_date').value = data.payment_date;
667
- document.getElementById('modal_bank_description').value = data.description;
668
- unreconciledAmount = parseFloat(data.unreconciled_amount);
669
-
670
- // Enable proceed button if amount is valid
671
- validateAmount();
672
- }
673
- })
674
- .catch(error => {
675
- tellerLoading.style.display = 'none';
676
- tellerError.textContent = 'Error looking up teller number';
677
- console.error('Teller lookup error:', error);
678
- });
679
- });
680
-
681
- // Amount validation
682
- amountInput.addEventListener('input', validateAmount);
683
-
684
- function validateAmount() {
685
- const amount = parseFloat(amountInput.value);
686
-
687
- if (isNaN(amount) || amount <= 0) {
688
- amountError.textContent = 'Amount must be greater than zero';
689
- proceedBtn.disabled = true;
690
- return;
691
- }
692
-
693
- if (unreconciledAmount === 0) {
694
- amountError.textContent = 'Please enter a valid teller number first';
695
- proceedBtn.disabled = true;
696
- return;
697
- }
698
-
699
- if (amount > unreconciledAmount) {
700
- amountError.textContent = `Amount cannot exceed unreconciled amount (₦${unreconciledAmount.toFixed(2)})`;
701
- proceedBtn.disabled = true;
702
- return;
703
- }
704
-
705
- amountError.textContent = '';
706
- proceedBtn.disabled = false;
707
- }
708
-
709
- // Handle form submission with AJAX to intercept confirmation requests
710
- const paymentDetailsForm = document.getElementById('paymentDetailsForm');
711
- paymentDetailsForm.addEventListener('submit', function (e) {
712
- e.preventDefault();
713
-
714
- // Disable submit button to prevent double submission
715
- proceedBtn.disabled = true;
716
- proceedBtn.textContent = 'Processing...';
717
-
718
- // Get form data
719
- const formData = new FormData(this);
720
-
721
- // Submit via AJAX
722
- fetch('process_payment.php', {
723
- method: 'POST',
724
- body: formData
725
- })
726
- .then(response => {
727
- const contentType = response.headers.get('content-type');
728
- if (contentType && contentType.includes('application/json')) {
729
- return response.json();
730
- }
731
- // If not JSON, it's the HTML success/error page
732
- return response.text().then(html => ({ html: html }));
733
- })
734
- .then(data => {
735
- if (data.status === 'confirmation_required') {
736
- // Show confirmation dialog
737
- const confirmed = confirm(data.message);
738
-
739
- if (confirmed) {
740
- // User confirmed - resubmit with confirmation flag
741
- formData.append('confirm_update', 'yes');
742
-
743
- // Resubmit the form
744
- fetch('process_payment.php', {
745
- method: 'POST',
746
- body: formData
747
- })
748
- .then(response => response.text())
749
- .then(html => {
750
- // Write the response HTML to the page
751
- document.open();
752
- document.write(html);
753
- document.close();
754
- })
755
- .catch(error => {
756
- console.error('Error:', error);
757
- alert('An error occurred while processing the payment.');
758
- proceedBtn.disabled = false;
759
- proceedBtn.textContent = 'OK PROCEED!';
760
- });
761
- } else {
762
- // User cancelled
763
- alert('Payment cancelled by user.');
764
- proceedBtn.disabled = false;
765
- proceedBtn.textContent = 'OK PROCEED!';
766
- }
767
- } else if (data.html) {
768
- // Normal HTML response - display it
769
- document.open();
770
- document.write(data.html);
771
- document.close();
772
- } else {
773
- // Unexpected response
774
- alert('Unexpected response from server.');
775
- proceedBtn.disabled = false;
776
- proceedBtn.textContent = 'OK PROCEED!';
777
- }
778
- })
779
- .catch(error => {
780
- console.error('Error:', error);
781
- alert('An error occurred while processing the payment.');
782
- proceedBtn.disabled = false;
783
- proceedBtn.textContent = 'OK PROCEED!';
784
- });
785
- });
786
- </script>
787
- </body>
788
-
789
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
easypay/process_payment.php DELETED
@@ -1,720 +0,0 @@
1
- <?php
2
- /**
3
- * Process Payment
4
- * Handle payment allocation and database transactions
5
- */
6
-
7
- require_once 'db_config.php';
8
-
9
- // Initialize response
10
- $success = false;
11
- $message = '';
12
- $paymentDetails = [];
13
-
14
- try {
15
- // Validate POST data
16
- if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
17
- throw new Exception('Invalid request method');
18
- }
19
-
20
- $studentId = $_POST['student_id'] ?? '';
21
- $studentCode = $_POST['student_code'] ?? '';
22
- $selectedFeesJson = $_POST['selected_fees'] ?? '';
23
- $tellerNumber = $_POST['teller_number'] ?? '';
24
- $bankDescription = $_POST['bank_description'] ?? '';
25
- $paymentDate = $_POST['payment_date'] ?? '';
26
- $amountToUse = floatval($_POST['amount_to_use'] ?? 0);
27
-
28
- // Validate required fields
29
- if (
30
- empty($studentId) || empty($studentCode) || empty($selectedFeesJson) ||
31
- empty($tellerNumber) || empty($paymentDate) || $amountToUse <= 0
32
- ) {
33
- throw new Exception('Missing required fields');
34
- }
35
-
36
- // Parse selected fees
37
- $selectedFees = json_decode($selectedFeesJson, true);
38
- if (!is_array($selectedFees) || count($selectedFees) === 0) {
39
- throw new Exception('No fees selected');
40
- }
41
-
42
- // Sort fees by academic_session ASC, term_of_session ASC (oldest to newest)
43
- usort($selectedFees, function ($a, $b) {
44
- if ($a['academic_session'] != $b['academic_session']) {
45
- return $a['academic_session'] - $b['academic_session'];
46
- }
47
- return $a['term_of_session'] - $b['term_of_session'];
48
- });
49
-
50
- // Re-fetch bank statement to verify
51
- $sql = "SELECT
52
- bs.id,
53
- bs.description,
54
- bs.amount_paid,
55
- bs.payment_date,
56
- COALESCE(fp.total_registered_fee, 0.00) AS registered_amount,
57
- (bs.amount_paid - COALESCE(fp.total_registered_fee, 0.00)) AS unreconciled_amount
58
- FROM tb_account_bank_statements bs
59
- LEFT JOIN (
60
- SELECT teller_no, SUM(amount_paid) AS total_registered_fee
61
- FROM tb_account_school_fee_payments
62
- GROUP BY teller_no
63
- ) fp ON SUBSTRING_INDEX(bs.description, ' ', -1) = fp.teller_no
64
- WHERE SUBSTRING_INDEX(bs.description, ' ', -1) = :teller_number
65
- LIMIT 1";
66
-
67
- $stmt = $pdo->prepare($sql);
68
- $stmt->execute(['teller_number' => $tellerNumber]);
69
- $bankStatement = $stmt->fetch();
70
-
71
- if (!$bankStatement) {
72
- throw new Exception('Bank statement not found for teller number: ' . $tellerNumber);
73
- }
74
-
75
- // Verify unreconciled amount
76
- if ($amountToUse > $bankStatement['unreconciled_amount']) {
77
- throw new Exception('Amount exceeds unreconciled amount on teller');
78
- }
79
-
80
- // Extract teller name and number from description
81
- $descParts = explode(' ', $bankStatement['description']);
82
- $tellerNo = array_pop($descParts);
83
- $tellerName = implode(' ', $descParts);
84
-
85
- // Use the oldest fee's session/term for transaction_id
86
- $dominantSession = $selectedFees[0]['academic_session'];
87
- $dominantTerm = $selectedFees[0]['term_of_session'];
88
-
89
- // Generate transaction_id
90
- $transactionId = $studentId . $dominantSession . $paymentDate;
91
-
92
- // STEP 1: Guard check - prevent duplicate payments on same date
93
- $sql = "SELECT id, academic_session, term_of_session, total_paid
94
- FROM tb_account_school_fee_sum_payments
95
- WHERE student_id = :student_id
96
- AND payment_date = :payment_date
97
- LIMIT 1";
98
-
99
- $stmt = $pdo->prepare($sql);
100
- $stmt->execute([
101
- 'student_id' => $studentId,
102
- 'payment_date' => $paymentDate
103
- ]);
104
-
105
- $existingPayment = $stmt->fetch();
106
- $isUpdateMode = false;
107
- $existingRecordId = null;
108
- $existingTotalPaid = 0;
109
-
110
- if ($existingPayment) {
111
- // Check if the existing payment is for the same term
112
- if (
113
- $existingPayment['term_of_session'] != $dominantTerm ||
114
- $existingPayment['academic_session'] != $dominantSession
115
- ) {
116
- throw new Exception('A payment for this student has already been registered on this date in a different term (Session: ' .
117
- $existingPayment['academic_session'] . ', Term: ' . $existingPayment['term_of_session'] . ')');
118
- }
119
-
120
- // Same term - need user confirmation to update
121
- // This will be handled by JavaScript popup on the client side
122
- // For now, check if user has confirmed via POST parameter
123
- $userConfirmed = isset($_POST['confirm_update']) && $_POST['confirm_update'] === 'yes';
124
-
125
- if (!$userConfirmed) {
126
- // Return a special response that triggers the popup
127
- header('Content-Type: application/json');
128
- echo json_encode([
129
- 'status' => 'confirmation_required',
130
- 'message' => 'A payment for this student already exists on ' . $paymentDate . ' for Session ' .
131
- $existingPayment['academic_session'] . ', Term ' . $existingPayment['term_of_session'] .
132
- '. Current total: ₦' . number_format($existingPayment['total_paid'], 2) .
133
- '. Do you want to update this record by adding ₦' . number_format($totalPaid, 2) . ' to the existing amount?',
134
- 'existing_payment' => [
135
- 'id' => $existingPayment['id'],
136
- 'session' => $existingPayment['academic_session'],
137
- 'term' => $existingPayment['term_of_session'],
138
- 'total_paid' => $existingPayment['total_paid']
139
- ]
140
- ]);
141
- exit;
142
- }
143
-
144
- // User confirmed - set update mode
145
- $isUpdateMode = true;
146
- $existingRecordId = $existingPayment['id'];
147
- $existingTotalPaid = floatval($existingPayment['total_paid']);
148
- }
149
-
150
- // STEP 2: Allocate payment across fees (oldest to newest)
151
- $feeAllocations = [];
152
- $remainingAmount = $amountToUse;
153
-
154
- foreach ($selectedFees as $fee) {
155
- if ($remainingAmount <= 0) {
156
- break;
157
- }
158
-
159
- $outstandingAmount = floatval($fee['outstanding_amount']);
160
-
161
- if ($remainingAmount >= $outstandingAmount) {
162
- // Fully settle this fee
163
- $amountForThisFee = $outstandingAmount;
164
- $remainingAmount -= $outstandingAmount;
165
- } else {
166
- // Partially settle this fee
167
- $amountForThisFee = $remainingAmount;
168
- $remainingAmount = 0;
169
- }
170
-
171
- $feeAllocations[] = [
172
- 'fee_id' => $fee['fee_id'],
173
- 'academic_session' => $fee['academic_session'],
174
- 'term_of_session' => $fee['term_of_session'],
175
- 'amount' => $amountForThisFee
176
- ];
177
- }
178
-
179
- if (count($feeAllocations) === 0) {
180
- throw new Exception('No fees could be allocated');
181
- }
182
-
183
- // Calculate total paid
184
- $totalPaid = array_sum(array_column($feeAllocations, 'amount'));
185
-
186
- // STEP 3: Begin database transaction
187
- $pdo->beginTransaction();
188
-
189
- try {
190
- // Constants
191
- $creditBankId = '000001373634585148';
192
- $debitBankId = '514297805530965017';
193
- $paymodeId = '000001373901891416';
194
- $recipientId = 'SS0011441283890434';
195
- $paymentBy = 'SS0011441283890434';
196
- $createdBy = 'SS0011441283890434';
197
- $createdAs = 'school';
198
- $paymentStatus = 'Approved';
199
- $paymodeCategory = 'BANK';
200
-
201
- // Generate receipt_no (used across all fee records)
202
- $receiptNo = $studentCode . $paymentDate;
203
-
204
- // 3a) INSERT into tb_account_school_fee_payments (per fee)
205
- foreach ($feeAllocations as $allocation) {
206
- $schoolFeePaymentId = $studentCode . $allocation['fee_id'] . $paymentDate; //original code
207
-
208
- //$schoolFeePaymentId = $allocation['fee_id'] . $paymentDate; //Is used to reduce length of id to solve duplicate entry error.
209
-
210
- $sql = "INSERT INTO tb_account_school_fee_payments (
211
- id, fee_id, student_id, transaction_id, amount_paid,
212
- teller_no, teller_name, credit_bank_id, debit_bank_id,
213
- paymode_id, payment_status, payment_date, recipient_id,
214
- academic_session, term_of_session, payment_by, payment_on
215
- ) VALUES (
216
- :id, :fee_id, :student_id, :transaction_id, :amount_paid,
217
- :teller_no, :teller_name, :credit_bank_id, :debit_bank_id,
218
- :paymode_id, :payment_status, :payment_date, :recipient_id,
219
- :academic_session, :term_of_session, :payment_by, NOW()
220
- )";
221
-
222
- $stmt = $pdo->prepare($sql);
223
- $stmt->execute([
224
- 'id' => $schoolFeePaymentId,
225
- 'fee_id' => $allocation['fee_id'],
226
- 'student_id' => $studentId,
227
- 'transaction_id' => $transactionId,
228
- 'amount_paid' => $allocation['amount'],
229
- 'teller_no' => $tellerNo,
230
- 'teller_name' => $tellerName,
231
- 'credit_bank_id' => $creditBankId,
232
- 'debit_bank_id' => $debitBankId,
233
- 'paymode_id' => $paymodeId,
234
- 'payment_status' => $paymentStatus,
235
- 'payment_date' => $paymentDate,
236
- 'recipient_id' => $recipientId,
237
- 'academic_session' => $allocation['academic_session'],
238
- 'term_of_session' => $allocation['term_of_session'],
239
- 'payment_by' => $paymentBy
240
- ]);
241
- }
242
-
243
- // 3b) INSERT or UPDATE tb_account_school_fee_sum_payments (single record)
244
- if ($isUpdateMode) {
245
- // Update existing record
246
- $sql = "UPDATE tb_account_school_fee_sum_payments
247
- SET total_paid = total_paid + :additional_amount,
248
- registered_on = NOW()
249
- WHERE id = :id";
250
-
251
- $stmt = $pdo->prepare($sql);
252
- $stmt->execute([
253
- 'additional_amount' => $totalPaid,
254
- 'id' => $existingRecordId
255
- ]);
256
- } else {
257
- // Insert new record
258
- $sumPaymentsId = $studentId . $dominantSession . $paymentDate;
259
-
260
- $sql = "INSERT INTO tb_account_school_fee_sum_payments (
261
- id, student_id, total_paid, paymode_id, payment_date,
262
- credit_bank_id, debit_bank_id, transaction_id, status,
263
- academic_session, term_of_session, registered_by, registered_on
264
- ) VALUES (
265
- :id, :student_id, :total_paid, :paymode_id, :payment_date,
266
- :credit_bank_id, :debit_bank_id, :transaction_id, :status,
267
- :academic_session, :term_of_session, :registered_by, NOW()
268
- )";
269
-
270
- $stmt = $pdo->prepare($sql);
271
- $stmt->execute([
272
- 'id' => $sumPaymentsId,
273
- 'student_id' => $studentId,
274
- 'total_paid' => $totalPaid,
275
- 'paymode_id' => $paymodeId,
276
- 'payment_date' => $paymentDate,
277
- 'credit_bank_id' => $creditBankId,
278
- 'debit_bank_id' => $debitBankId,
279
- 'transaction_id' => $transactionId,
280
- 'status' => $paymentStatus,
281
- 'academic_session' => $dominantSession,
282
- 'term_of_session' => $dominantTerm,
283
- 'registered_by' => $createdBy
284
- ]);
285
- }
286
-
287
- // 3c) INSERT into tb_account_student_payments (per fee)
288
- foreach ($feeAllocations as $allocation) {
289
- // Get current payment_to_date (sum of all previous payments for this fee/session/term)
290
- $sql = "SELECT SUM(payment_to_date) AS current_total
291
- FROM tb_account_student_payments
292
- WHERE student_id = :student_id
293
- AND fee_id = :fee_id
294
- AND academic_session = :academic_session
295
- AND term_of_session = :term_of_session";
296
-
297
- $stmt = $pdo->prepare($sql);
298
- $stmt->execute([
299
- 'student_id' => $studentId,
300
- 'fee_id' => $allocation['fee_id'],
301
- 'academic_session' => $allocation['academic_session'],
302
- 'term_of_session' => $allocation['term_of_session']
303
- ]);
304
-
305
- $currentPayment = $stmt->fetch();
306
- $currentTotal = floatval($currentPayment['current_total'] ?? 0);
307
- $newPaymentToDate = $allocation['amount'];
308
- //$newPaymentToDate = $currentTotal + $allocation['amount']; --Old code that added new payment amount to current total (total before payment)
309
-
310
- $studentPaymentId = $studentCode . $allocation['fee_id'] . $paymentDate;
311
-
312
- $sql = "INSERT INTO tb_account_student_payments (
313
- id, fee_id, student_id, payment_to_date, transaction_id,
314
- academic_session, term_of_session, created_by, created_as, created_on
315
- ) VALUES (
316
- :id, :fee_id, :student_id, :payment_to_date, :transaction_id,
317
- :academic_session, :term_of_session, :created_by, :created_as, NOW()
318
- )";
319
-
320
- $stmt = $pdo->prepare($sql);
321
- $stmt->execute([
322
- 'id' => $studentPaymentId,
323
- 'fee_id' => $allocation['fee_id'],
324
- 'student_id' => $studentId,
325
- 'payment_to_date' => $newPaymentToDate,
326
- 'transaction_id' => $transactionId,
327
- 'academic_session' => $allocation['academic_session'],
328
- 'term_of_session' => $allocation['term_of_session'],
329
- 'created_by' => $createdBy,
330
- 'created_as' => $createdAs
331
- ]);
332
- }
333
-
334
- // 3d) INSERT into tb_account_payment_registers (per fee)
335
- foreach ($feeAllocations as $allocation) {
336
- $paymentRegisterId = $studentCode . $allocation['fee_id'] . $paymentDate;
337
-
338
- $sql = "INSERT INTO tb_account_payment_registers (
339
- id, fee_id, student_id, amount_paid, amount_due,
340
- receipt_no, recipient_id, payment_date, paymode_category,
341
- transaction_id, academic_session, term_of_session,
342
- created_by, created_as, created_on
343
- ) VALUES (
344
- :id, :fee_id, :student_id, :amount_paid, :amount_due,
345
- :receipt_no, :recipient_id, :payment_date, :paymode_category,
346
- :transaction_id, :academic_session, :term_of_session,
347
- :created_by, :created_as, NOW()
348
- )";
349
-
350
- $stmt = $pdo->prepare($sql);
351
- $stmt->execute([
352
- 'id' => $paymentRegisterId,
353
- 'fee_id' => $allocation['fee_id'],
354
- 'student_id' => $studentId,
355
- 'amount_paid' => $allocation['amount'],
356
- 'amount_due' => $allocation['amount'],
357
- 'receipt_no' => $receiptNo,
358
- 'recipient_id' => $recipientId,
359
- 'payment_date' => $paymentDate,
360
- 'paymode_category' => $paymodeCategory,
361
- 'transaction_id' => $transactionId,
362
- 'academic_session' => $allocation['academic_session'],
363
- 'term_of_session' => $allocation['term_of_session'],
364
- 'created_by' => $createdBy,
365
- 'created_as' => $createdAs
366
- ]);
367
- }
368
-
369
- // 3e) UPDATE tb_student_logistics
370
- // Group allocations by session/term to update specific records
371
- $sessionTermTotals = [];
372
- foreach ($feeAllocations as $allocation) {
373
- $key = $allocation['academic_session'] . '-' . $allocation['term_of_session'];
374
- if (!isset($sessionTermTotals[$key])) {
375
- $sessionTermTotals[$key] = [
376
- 'academic_session' => $allocation['academic_session'],
377
- 'term_of_session' => $allocation['term_of_session'],
378
- 'total' => 0
379
- ];
380
- }
381
- $sessionTermTotals[$key]['total'] += $allocation['amount'];
382
- }
383
-
384
- // Update each session/term record
385
- foreach ($sessionTermTotals as $st) {
386
- $sql = "UPDATE tb_student_logistics
387
- SET fees_outstanding = GREATEST(0, fees_outstanding - :total_paid)
388
- WHERE student_id = :student_id
389
- AND academic_session = :academic_session
390
- AND term_of_session = :term_of_session";
391
-
392
- $stmt = $pdo->prepare($sql);
393
- $stmt->execute([
394
- 'total_paid' => $st['total'],
395
- 'student_id' => $studentId,
396
- 'academic_session' => $st['academic_session'],
397
- 'term_of_session' => $st['term_of_session']
398
- ]);
399
- }
400
-
401
- // Commit transaction
402
- $pdo->commit();
403
-
404
- $success = true;
405
- $message = 'Payment processed successfully!';
406
-
407
- // Fetch fee descriptions for display
408
- $feeIds = array_column($feeAllocations, 'fee_id');
409
- $placeholders = implode(',', array_fill(0, count($feeIds), '?'));
410
-
411
- $sql = "SELECT id, description FROM tb_account_school_fees WHERE id IN ($placeholders)";
412
- $stmt = $pdo->prepare($sql);
413
- $stmt->execute($feeIds);
414
- $feeDescriptions = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
415
-
416
- // Prepare payment details for display
417
- foreach ($feeAllocations as $key => $allocation) {
418
- $feeAllocations[$key]['description'] = $feeDescriptions[$allocation['fee_id']] ?? 'Unknown Fee';
419
- }
420
- $paymentDetails = [
421
- 'student_code' => $studentCode,
422
- 'payment_date' => $paymentDate,
423
- 'teller_no' => $tellerNo,
424
- 'teller_name' => $tellerName,
425
- 'total_paid' => $totalPaid,
426
- 'receipt_no' => $receiptNo,
427
- 'transaction_id' => $transactionId,
428
- 'allocations' => $feeAllocations,
429
- 'remaining_unreconciled' => $bankStatement['unreconciled_amount'] - $totalPaid
430
- ];
431
-
432
- } catch (Exception $e) {
433
- $pdo->rollBack();
434
- throw new Exception('Transaction failed: ' . $e->getMessage());
435
- }
436
-
437
- } catch (Exception $e) {
438
- $success = false;
439
- $message = $e->getMessage();
440
- }
441
-
442
- // Fetch student name for display
443
- $studentName = '';
444
- if (!empty($studentId)) {
445
- try {
446
- $sql = "SELECT CONCAT(last_name, ' ', first_name, ' ', COALESCE(other_name, '')) AS full_name
447
- FROM tb_student_registrations
448
- WHERE id = :student_id";
449
- $stmt = $pdo->prepare($sql);
450
- $stmt->execute(['student_id' => $studentId]);
451
- $result = $stmt->fetch();
452
- $studentName = $result['full_name'] ?? 'Unknown Student';
453
- } catch (Exception $e) {
454
- $studentName = 'Unknown Student';
455
- }
456
- }
457
- ?>
458
- <!DOCTYPE html>
459
- <html lang="en">
460
-
461
- <head>
462
- <meta charset="UTF-8">
463
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
464
- <title>Payment Processing Result</title>
465
- <style>
466
- * {
467
- margin: 0;
468
- padding: 0;
469
- box-sizing: border-box;
470
- }
471
-
472
- body {
473
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
474
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
475
- min-height: 100vh;
476
- padding: 20px;
477
- display: flex;
478
- align-items: center;
479
- justify-content: center;
480
- }
481
-
482
- .container {
483
- max-width: 800px;
484
- width: 100%;
485
- background: white;
486
- border-radius: 12px;
487
- box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
488
- padding: 40px;
489
- }
490
-
491
- .success-icon {
492
- width: 80px;
493
- height: 80px;
494
- margin: 0 auto 20px;
495
- background: #28a745;
496
- border-radius: 50%;
497
- display: flex;
498
- align-items: center;
499
- justify-content: center;
500
- color: white;
501
- font-size: 48px;
502
- }
503
-
504
- .error-icon {
505
- width: 80px;
506
- height: 80px;
507
- margin: 0 auto 20px;
508
- background: #dc3545;
509
- border-radius: 50%;
510
- display: flex;
511
- align-items: center;
512
- justify-content: center;
513
- color: white;
514
- font-size: 48px;
515
- }
516
-
517
- h1 {
518
- text-align: center;
519
- color: #333;
520
- margin-bottom: 10px;
521
- font-size: 28px;
522
- }
523
-
524
- .message {
525
- text-align: center;
526
- color: #666;
527
- margin-bottom: 30px;
528
- font-size: 16px;
529
- }
530
-
531
- .details-section {
532
- background: #f8f9fa;
533
- padding: 20px;
534
- border-radius: 8px;
535
- margin-bottom: 20px;
536
- }
537
-
538
- .details-section h2 {
539
- color: #333;
540
- margin-bottom: 15px;
541
- font-size: 20px;
542
- }
543
-
544
- .detail-row {
545
- display: flex;
546
- justify-content: space-between;
547
- padding: 10px 0;
548
- border-bottom: 1px solid #ddd;
549
- }
550
-
551
- .detail-row:last-child {
552
- border-bottom: none;
553
- }
554
-
555
- .detail-label {
556
- font-weight: 600;
557
- color: #555;
558
- }
559
-
560
- .detail-value {
561
- color: #333;
562
- text-align: right;
563
- }
564
-
565
- table {
566
- width: 100%;
567
- border-collapse: collapse;
568
- margin-top: 15px;
569
- }
570
-
571
- th,
572
- td {
573
- padding: 12px;
574
- text-align: left;
575
- border-bottom: 1px solid #ddd;
576
- }
577
-
578
- th {
579
- background-color: #667eea;
580
- color: white;
581
- font-weight: 600;
582
- }
583
-
584
- .amount {
585
- text-align: right;
586
- font-family: 'Courier New', monospace;
587
- }
588
-
589
- .total-row {
590
- font-weight: bold;
591
- background-color: #f0f0f0;
592
- }
593
-
594
- .btn {
595
- display: inline-block;
596
- padding: 12px 30px;
597
- border: none;
598
- border-radius: 6px;
599
- font-size: 16px;
600
- font-weight: 600;
601
- cursor: pointer;
602
- text-decoration: none;
603
- transition: all 0.3s;
604
- text-align: center;
605
- }
606
-
607
- .btn-primary {
608
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
609
- color: white;
610
- }
611
-
612
- .btn-primary:hover {
613
- transform: translateY(-2px);
614
- box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
615
- }
616
-
617
- .actions {
618
- text-align: center;
619
- margin-top: 30px;
620
- }
621
-
622
- .highlight {
623
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
624
- color: white;
625
- padding: 15px;
626
- border-radius: 8px;
627
- margin-bottom: 20px;
628
- }
629
-
630
- .highlight p {
631
- margin: 5px 0;
632
- }
633
- </style>
634
- </head>
635
-
636
- <body>
637
- <div class="container">
638
- <?php if ($success): ?>
639
- <div class="success-icon">✓</div>
640
- <h1>Payment Processed Successfully!</h1>
641
- <p class="message"><?php echo htmlspecialchars($message); ?></p>
642
-
643
- <div class="highlight">
644
- <p><strong>Student:</strong> <?php echo htmlspecialchars($studentName); ?></p>
645
- <p><strong>Receipt No:</strong> <?php echo htmlspecialchars($paymentDetails['receipt_no']); ?></p>
646
- <p><strong>Transaction ID:</strong> <?php echo htmlspecialchars($paymentDetails['transaction_id']); ?></p>
647
- </div>
648
-
649
- <div class="details-section">
650
- <h2>Payment Details</h2>
651
- <div class="detail-row">
652
- <span class="detail-label">Payment Date:</span>
653
- <span class="detail-value"><?php echo htmlspecialchars($paymentDetails['payment_date']); ?></span>
654
- </div>
655
- <div class="detail-row">
656
- <span class="detail-label">Teller Number:</span>
657
- <span class="detail-value"><?php echo htmlspecialchars($paymentDetails['teller_no']); ?></span>
658
- </div>
659
- <div class="detail-row">
660
- <span class="detail-label">Bank Narration:</span>
661
- <span class="detail-value"><?php echo htmlspecialchars($paymentDetails['teller_name']); ?></span>
662
- </div>
663
- <div class="detail-row">
664
- <span class="detail-label">Total Amount Used:</span>
665
- <span class="detail-value">₦<?php echo number_format($paymentDetails['total_paid'], 2); ?></span>
666
- </div>
667
- <div class="detail-row">
668
- <span class="detail-label">Remaining Unreconciled on Teller:</span>
669
- <span
670
- class="detail-value">₦<?php echo number_format($paymentDetails['remaining_unreconciled'], 2); ?></span>
671
- </div>
672
- </div>
673
-
674
- <div class="details-section">
675
- <h2>Fees Settled</h2>
676
- <table>
677
- <thead>
678
- <tr>
679
- <th>Fee Description</th>
680
- <th width="100">Session</th>
681
- <th width="80">Term</th>
682
- <th width="120" class="amount">Amount Paid</th>
683
- </tr>
684
- </thead>
685
- <tbody>
686
- <?php foreach ($paymentDetails['allocations'] as $allocation): ?>
687
- <tr>
688
- <td><?php echo htmlspecialchars($allocation['description']); ?></td>
689
- <td><?php echo htmlspecialchars($allocation['academic_session']); ?></td>
690
- <td><?php echo htmlspecialchars($allocation['term_of_session']); ?></td>
691
- <td class="amount">₦<?php echo number_format($allocation['amount'], 2); ?></td>
692
- </tr>
693
- <?php endforeach; ?>
694
- <tr class="total-row">
695
- <td colspan="3" style="text-align: right;">Total:</td>
696
- <td class="amount">₦<?php echo number_format($paymentDetails['total_paid'], 2); ?></td>
697
- </tr>
698
- </tbody>
699
- </table>
700
- </div>
701
-
702
- <?php else: ?>
703
- <div class="error-icon">✗</div>
704
- <h1>Payment Processing Failed</h1>
705
- <p class="message" style="color: #dc3545;"><?php echo htmlspecialchars($message); ?></p>
706
-
707
- <div class="details-section">
708
- <h2>Error Details</h2>
709
- <p>The payment could not be processed. No changes were made to the database.</p>
710
- <p style="margin-top: 10px;"><strong>Error:</strong> <?php echo htmlspecialchars($message); ?></p>
711
- </div>
712
- <?php endif; ?>
713
-
714
- <div class="actions">
715
- <a href="index.php" class="btn btn-primary">Return to Main Page</a>
716
- </div>
717
- </div>
718
- </body>
719
-
720
- </html>