diff --git a/.env copy b/.env copy
new file mode 100644
index 00000000..9d3a55ee
--- /dev/null
+++ b/.env copy
@@ -0,0 +1,42 @@
+# Database Configuration
+DB_HOST=mysql-db
+DB_USER=app
+DB_PASS=4wdcsa2025!
+DB_NAME=4wdcsa
+SALT=4wdcsa
+HOST=https://beta.4wdcsa.co.za
+
+ENFORCE_HTTPS=true
+
+# Mailjet Email Service
+MAILJET_API_KEY=1a44f8d5e847537dbb8d3c76fe73a93c
+MAILJET_API_SECRET=ec98b45c53a7694c4f30d09eee9ad280
+MAILJET_FROM_EMAIL=info@4wdcsa.co.za
+MAILJET_FROM_NAME=4WDCSA
+ADMIN_EMAIL=info@4wdcsa.co.za
+FINANCE_EMAIL=louiseb@global.co.za
+POP_NOTIFICATION_EMAILS=chrispintoza@gmail.com,chrispintoza+pop@gmail.com,chrispintoza+4wdcsapop@gmail.com
+NOTIFICATION_ADDR=chrispintoza@gmail.com
+
+# Google OAuth
+GOOGLE_CLIENT_ID=948441222188-8qhboq2urr8o9n35mc70s5h2nhd52v0m.apps.googleusercontent.com
+GOOGLE_CLIENT_SECRET=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
+
+# Instagram (optional)
+INSTAGRAM_ACCESS_TOKEN=your-instagram-token
+
+# Application Settings
+APP_ENV=development
+APP_DEBUG=true
+APP_URL=https://beta.4wdcsa.co.za
+
+IKHOKHA_APP_ID=IKFLESZTKFM4HWWS76131L8HK9BYF96P
+IKHOKHA_APP_SECRET=gfoQTvXRXuzq6ArPHUS2CBFxtHtH1bxM
+IKHOKHA_ENDPOINT=https://api.ikhokha.com/public-api/v1/api/payment
+IKHOKHA_EXTERNAL_ENTITY_ID=4WDCSA
+IKHOKHA_CALLBACK_URL=https://beta.4wdcsa.co.za/src/api/ikhokha_webhook.php
+IKHOKHA_SUCCESS_URL=https://beta.4wdcsa.co.za/success
+IKHOKHA_FAILURE_URL=https://beta.4wdcsa.co.za/failure
+IKHOKHA_CANCEL_URL=https://beta.4wdcsa.co.za/cancel
+IKHOKHA_MODE=live
+IKHOKHA_BYPASS_SIGNATURE=true
diff --git a/.env.example b/.env.example
new file mode 100644
index 00000000..1ae3de1d
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,42 @@
+# Database Configuration
+DB_HOST=mysql-db
+DB_USER=app
+DB_PASS=4wdcsa2025!
+DB_NAME=4wdcsa
+SALT=4wdcsa
+HOST=https://4wdcsa.co.za
+
+ENFORCE_HTTPS=true
+
+# Mailjet Email Service
+MAILJET_API_KEY=1a44f8d5e847537dbb8d3c76fe73a93c
+MAILJET_API_SECRET=ec98b45c53a7694c4f30d09eee9ad280
+MAILJET_FROM_EMAIL=info@4wdcsa.co.za
+MAILJET_FROM_NAME=4WDCSA
+ADMIN_EMAIL=info@4wdcsa.co.za
+FINANCE_EMAIL=louiseb@global.co.za
+POP_NOTIFICATION_EMAILS=chrispintoza@gmail.com,chrispintoza+pop@gmail.com,chrispintoza+4wdcsapop@gmail.com
+NOTIFICATION_ADDR=info@4wdcsa.co.za
+
+# Google OAuth
+GOOGLE_CLIENT_ID=948441222188-8qhboq2urr8o9n35mc70s5h2nhd52v0m.apps.googleusercontent.com
+GOOGLE_CLIENT_SECRET=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
+
+# Instagram (optional)
+INSTAGRAM_ACCESS_TOKEN=your-instagram-token
+
+# Application Settings
+APP_ENV=development
+APP_DEBUG=true
+APP_URL=https://4wdcsa.co.za
+
+IKHOKHA_APP_ID=IKFLESZTKFM4HWWS76131L8HK9BYF96P
+IKHOKHA_APP_SECRET=gfoQTvXRXuzq6ArPHUS2CBFxtHtH1bxM
+IKHOKHA_ENDPOINT=https://api.ikhokha.com/public-api/v1/api/payment
+IKHOKHA_EXTERNAL_ENTITY_ID=4WDCSA
+IKHOKHA_CALLBACK_URL=https://4wdcsa.co.za/src/api/ikhokha_webhook.php
+IKHOKHA_SUCCESS_URL=https://4wdcsa.co.za/success
+IKHOKHA_FAILURE_URL=https://4wdcsa.co.za/failure
+IKHOKHA_CANCEL_URL=https://4wdcsa.co.za/cancel
+IKHOKHA_MODE=live
+IKHOKHA_BYPASS_SIGNATURE=true
diff --git a/.htaccess b/.htaccess
index 89e12b95..70870fd0 100644
--- a/.htaccess
+++ b/.htaccess
@@ -94,7 +94,7 @@ RewriteRule ^admin_course_bookings$ src/admin/admin_course_bookings.php [L]
RewriteRule ^admin_camp_bookings$ src/admin/admin_camp_bookings.php [L]
RewriteRule ^admin_trip_bookings$ src/admin/admin_trip_bookings.php [L]
RewriteRule ^admin_visitors$ src/admin/admin_visitors.php [L]
-RewriteRule ^admin_efts$ src/admin/admin_efts.php [L]
+RewriteRule ^admin_transactions$ src/admin/admin_transactions.php [L]
RewriteRule ^admin_trips$ src/admin/admin_trips.php [L]
RewriteRule ^manage_events$ src/admin/manage_events.php [L]
RewriteRule ^manage_trips$ src/admin/manage_trips.php [L]
diff --git a/header.php b/header.php
index 7d82aa03..17930744 100644
--- a/header.php
+++ b/header.php
@@ -288,8 +288,8 @@ if ($headerStyle === 'light') {
Manage Trips
Trip Bookings
Course Bookings
- EFT Payments
- Process Payments
+ iKhokha Payment History
+
Visitor Log
diff --git a/progress.log b/progress.log
index 975b16ba..fa15d3bd 100644
--- a/progress.log
+++ b/progress.log
@@ -1,48 +1,122 @@
-[2025-12-14 16:27:25] Testing Log Entry at 2025-12-14 16:27:25
-[2025-12-14 16:28:03] Testing Log Entry at 2025-12-14 16:28:03
-[2025-12-14 16:28:18] Testing Log Entry at 2025-12-14 16:28:18
-[2025-12-14 18:30:36] --- iKhokha WEBHOOK DEBUG ---
-[2025-12-14 18:30:36] PATH: /src/api/ikhokha_webhook.php
-[2025-12-14 18:30:36] RAW BODY: {"paylinkID":"cwm225jy497j0qc","status":"SUCCESS","externalTransactionID":"693ee604c8e38","responseCode":"00","text":null}
-[2025-12-14 18:30:36] IK-SIGN: 3a2b8c9da4f1aff63c011adec10552450c5b6605f587e82ab1272914715fd4cf
-[2025-12-14 18:30:36] iKhokha webhook: signature mismatch
-[2025-12-14 18:30:36] EXPECTED SIGN: f8356ee6b609e9bd44221fa71851b06171bbb7523c52dca4bde605e3bd93f654
-[2025-12-14 18:30:37] --- iKhokha WEBHOOK DEBUG ---
-[2025-12-14 18:30:37] PATH: /src/api/ikhokha_webhook.php
-[2025-12-14 18:30:37] RAW BODY: {"paylinkID":"cwm225jy497j0qc","status":"SUCCESS","externalTransactionID":"693ee604c8e38","responseCode":"00","text":null}
-[2025-12-14 18:30:37] IK-SIGN: 3a2b8c9da4f1aff63c011adec10552450c5b6605f587e82ab1272914715fd4cf
-[2025-12-14 18:30:37] iKhokha webhook: signature mismatch
-[2025-12-14 18:30:37] EXPECTED SIGN: f8356ee6b609e9bd44221fa71851b06171bbb7523c52dca4bde605e3bd93f654
-[2025-12-14 18:36:19] --- iKhokha WEBHOOK DEBUG ---
-[2025-12-14 18:36:19] PATH: /api/ikhokha_webhook.php
-[2025-12-14 18:36:19] RAW BODY: {"paylinkID":"xx6225jyhx6x5n1","status":"SUCCESS","externalTransactionID":"693ee75cec963","responseCode":"00","text":null}
-[2025-12-14 18:36:19] IK-SIGN: 8fb11ceb8ea6b2cd6fec1773719927889f02764b0f04ad3c8543edbc9f74cf43
-[2025-12-14 18:36:19] iKhokha webhook: signature mismatch
-[2025-12-14 18:36:19] EXPECTED SIGN: 7a594957e7dc8775a6f9e1f8e3a1292f4dcd10268facaec57735f937b1cd9949
-[2025-12-14 18:36:19] --- iKhokha WEBHOOK DEBUG ---
-[2025-12-14 18:36:19] PATH: /api/ikhokha_webhook.php
-[2025-12-14 18:36:19] RAW BODY: {"paylinkID":"xx6225jyhx6x5n1","status":"SUCCESS","externalTransactionID":"693ee75cec963","responseCode":"00","text":null}
-[2025-12-14 18:36:19] IK-SIGN: 8fb11ceb8ea6b2cd6fec1773719927889f02764b0f04ad3c8543edbc9f74cf43
-[2025-12-14 18:36:19] iKhokha webhook: signature mismatch
-[2025-12-14 18:36:19] EXPECTED SIGN: 7a594957e7dc8775a6f9e1f8e3a1292f4dcd10268facaec57735f937b1cd9949
-[2025-12-14 18:55:49] --- iKhokha WEBHOOK DEBUG ---
-[2025-12-14 18:55:49] RAW BODY: {"paylinkID":"433225jzs9rvk5n","status":"SUCCESS","externalTransactionID":"693eebf1a9f75","responseCode":"00","text":null}
-[2025-12-14 18:55:49] IK-SIGN: 5cb01840f3516c964bdffc34cb1da41130bd668deffc2e4ea5a9f4cbb89b9807
-[2025-12-14 18:55:49] SIGN BASE STRING: | CONTEXT: https://beta.4wdcsa.co.za/src/api/ikhokha_webhook.php{"paylinkID":"433225jzs9rvk5n","status":"SUCCESS","externalTransactionID":"693eebf1a9f75","responseCode":"00","text":null}
-[2025-12-14 18:55:49] iKhokha webhook: signature mismatch
-[2025-12-14 18:55:49] EXPECTED SIGN: 75c491d05ac5006b3ad4fcec20c870e860a66ea03ed5a3e8900d6e29cd601445
-[2025-12-14 18:55:49] RECEIVED SIGN: 5cb01840f3516c964bdffc34cb1da41130bd668deffc2e4ea5a9f4cbb89b9807
-[2025-12-14 18:55:49] --- iKhokha WEBHOOK DEBUG ---
-[2025-12-14 18:55:49] RAW BODY: {"paylinkID":"433225jzs9rvk5n","status":"SUCCESS","externalTransactionID":"693eebf1a9f75","responseCode":"00","text":null}
-[2025-12-14 18:55:49] IK-SIGN: 5cb01840f3516c964bdffc34cb1da41130bd668deffc2e4ea5a9f4cbb89b9807
-[2025-12-14 18:55:49] SIGN BASE STRING: | CONTEXT: https://beta.4wdcsa.co.za/src/api/ikhokha_webhook.php{"paylinkID":"433225jzs9rvk5n","status":"SUCCESS","externalTransactionID":"693eebf1a9f75","responseCode":"00","text":null}
-[2025-12-14 18:55:49] iKhokha webhook: signature mismatch
-[2025-12-14 18:55:49] EXPECTED SIGN: 75c491d05ac5006b3ad4fcec20c870e860a66ea03ed5a3e8900d6e29cd601445
-[2025-12-14 18:55:49] RECEIVED SIGN: 5cb01840f3516c964bdffc34cb1da41130bd668deffc2e4ea5a9f4cbb89b9807
-[2025-12-14 20:15:55] --- iKhokha WEBHOOK DEBUG ---
-[2025-12-14 20:15:55] RAW BODY: {"paylinkID":"ys5225k4z56x0mm","status":"SUCCESS","externalTransactionID":"693efeaca71a9","responseCode":"00","text":null}
-[2025-12-14 20:15:55] IK-SIGN: bb1702d488a40091ebd5414bc6f524e203e2c5e36b24a1b86e243dad440bb557
-[2025-12-14 20:15:55] ⚠️ IKHOKHA SIGNATURE CHECK BYPASSED
-[2025-12-14 20:15:55] Parsed externalTransactionID: 693efeaca71a9
-[2025-12-14 20:15:55] Parsed providerPaymentId: ys5225k4z56x0mm
-[2025-12-14 20:15:55] Parsed providerStatus: SUCCESS
+[2025-12-15 12:32:19] AJAX BLOCK ENTERED
+[2025-12-15 12:32:19] startDate=2025-10-16
+[2025-12-15 12:32:19] endDate=2025-12-15
+[2025-12-15 12:32:19] APP ID present: YES
+[2025-12-15 12:32:19] APP SECRET present: YES
+[2025-12-15 12:32:19] PAYLOAD: https://api.ikhokha.com/public-api/v1/api/payments/history?startDate=2025-10-16&endDate=2025-12-15
+[2025-12-15 12:32:19] IK-SIGN: ced35ab201ad54e8f8b5935d2778c4ec7e75af0102d40d9c4515f8118ca8b5dd
+[2025-12-15 12:32:19] CURL HTTP CODE: 422
+[2025-12-15 12:32:19] CURL ERROR: none
+[2025-12-15 12:32:19] RAW RESPONSE: {"error":"Invalid Signature"}
+[2025-12-15 12:33:31] AJAX BLOCK ENTERED
+[2025-12-15 12:33:31] startDate=2025-10-16
+[2025-12-15 12:33:31] endDate=2025-12-15
+[2025-12-15 12:33:31] APP ID present: YES
+[2025-12-15 12:33:31] APP SECRET present: YES
+[2025-12-15 12:33:31] IKHOKHA PAYLOAD (FULL URL): https://api.ikhokha.com/public-api/v1/api/payments/history?startDate=2025-10-16&endDate=2025-12-15
+[2025-12-15 12:33:31] IKHOKHA IK-SIGN: ced35ab201ad54e8f8b5935d2778c4ec7e75af0102d40d9c4515f8118ca8b5dd
+[2025-12-15 12:33:31] CURL HTTP CODE: 422
+[2025-12-15 12:33:31] CURL ERROR: none
+[2025-12-15 12:33:31] RAW RESPONSE: {"error":"Invalid Signature"}
+[2025-12-15 12:33:59] AJAX BLOCK ENTERED
+[2025-12-15 12:33:59] startDate=2025-10-16
+[2025-12-15 12:33:59] endDate=2025-12-15
+[2025-12-15 12:33:59] APP ID present: YES
+[2025-12-15 12:33:59] APP SECRET present: YES
+[2025-12-15 12:33:59] IKHOKHA PAYLOAD (FULL URL): https://api.ikhokha.com/public-api/v1/api/payments/history?startDate=2025-10-16&endDate=2025-12-15
+[2025-12-15 12:33:59] IKHOKHA IK-SIGN: ced35ab201ad54e8f8b5935d2778c4ec7e75af0102d40d9c4515f8118ca8b5dd
+[2025-12-15 12:34:00] CURL HTTP CODE: 422
+[2025-12-15 12:34:00] CURL ERROR: none
+[2025-12-15 12:34:00] RAW RESPONSE: {"error":"Invalid Signature"}
+[2025-12-15 12:37:06] AJAX BLOCK ENTERED
+[2025-12-15 12:37:06] startDate=2025-10-16
+[2025-12-15 12:37:06] endDate=2025-12-15
+[2025-12-15 12:37:06] APP ID present: YES
+[2025-12-15 12:37:06] APP SECRET present: YES
+[2025-12-15 12:37:06] IKHOKHA ENDPOINT (REQUEST): https://api.ikhokha.com/public-api/v1/api/payments/history?startDate=2025-10-16&endDate=2025-12-15
+[2025-12-15 12:37:06] IKHOKHA PAYLOAD (SIGNED): https://api.ikhokha.com/public-api/v1/payments/history?startDate=2025-10-16&endDate=2025-12-15
+[2025-12-15 12:37:06] IKHOKHA IK-SIGN: 418e48921e566e5804b58f65e1ca4a28dba4d69de3611d1cf7f90f865490f42d
+[2025-12-15 12:37:06] CURL HTTP CODE: 422
+[2025-12-15 12:37:06] CURL ERROR: none
+[2025-12-15 12:37:06] RAW RESPONSE: {"error":"Invalid Signature"}
+[2025-12-15 12:56:21] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign
+[2025-12-15 12:56:21] b3b592829090d2cfd0912ccbdec73db18742088d01a2d2bee9f0eacdf37a6b26 | CONTEXT: IKHOKHA Signature
+[2025-12-15 12:56:37] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign
+[2025-12-15 12:56:37] b3b592829090d2cfd0912ccbdec73db18742088d01a2d2bee9f0eacdf37a6b26 | CONTEXT: IKHOKHA Signature
+[2025-12-15 12:57:04] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign
+[2025-12-15 12:57:04] 13e6e02a7ccad937bc27b31038373d48d8ba2700a7ba8d9a7a2e4f9b07378692 | CONTEXT: IKHOKHA Signature
+[2025-12-15 12:57:30] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign
+[2025-12-15 12:57:30] b3b592829090d2cfd0912ccbdec73db18742088d01a2d2bee9f0eacdf37a6b26 | CONTEXT: IKHOKHA Signature
+[2025-12-15 12:57:32] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign
+[2025-12-15 12:57:32] b3b592829090d2cfd0912ccbdec73db18742088d01a2d2bee9f0eacdf37a6b26 | CONTEXT: IKHOKHA Signature
+[2025-12-15 12:57:34] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign
+[2025-12-15 12:57:34] b3b592829090d2cfd0912ccbdec73db18742088d01a2d2bee9f0eacdf37a6b26 | CONTEXT: IKHOKHA Signature
+[2025-12-15 12:58:00] IKFLESZTKFM4HWWS76131L8HK9BYF96P | CONTEXT: IKHOKHA Endpoint
+[2025-12-15 12:58:00] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign
+[2025-12-15 12:58:00] b3b592829090d2cfd0912ccbdec73db18742088d01a2d2bee9f0eacdf37a6b26 | CONTEXT: IKHOKHA Signature
+[2025-12-15 12:58:04] IKFLESZTKFM4HWWS76131L8HK9BYF96P | CONTEXT: IKHOKHA Endpoint
+[2025-12-15 12:58:04] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign
+[2025-12-15 12:58:04] b3b592829090d2cfd0912ccbdec73db18742088d01a2d2bee9f0eacdf37a6b26 | CONTEXT: IKHOKHA Signature
+[2025-12-15 12:58:17] IKFLESZTKFM4HWWS76131L8HK9BYF96P | CONTEXT: IKHOKHA App ID
+[2025-12-15 12:58:17] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign
+[2025-12-15 12:58:17] b3b592829090d2cfd0912ccbdec73db18742088d01a2d2bee9f0eacdf37a6b26 | CONTEXT: IKHOKHA Signature
+[2025-12-15 12:58:48] IKFLESZTKFM4HWWS76131L8HK9BYF96P | CONTEXT: IKHOKHA App ID
+[2025-12-15 12:58:48] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign
+[2025-12-15 12:58:48] 13e6e02a7ccad937bc27b31038373d48d8ba2700a7ba8d9a7a2e4f9b07378692 | CONTEXT: IKHOKHA Signature
+[2025-12-15 13:00:13] IKFLESZTKFM4HWWS76131L8HK9BYF96P | CONTEXT: IKHOKHA App ID
+[2025-12-15 13:00:13] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign
+[2025-12-15 13:00:13] 13e6e02a7ccad937bc27b31038373d48d8ba2700a7ba8d9a7a2e4f9b07378692 | CONTEXT: IKHOKHA Signature
+[2025-12-15 13:00:29] IKFLESZTKFM4HWWS76131L8HK9BYF96P | CONTEXT: IKHOKHA App ID
+[2025-12-15 13:00:29] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign
+[2025-12-15 13:00:29] b3b592829090d2cfd0912ccbdec73db18742088d01a2d2bee9f0eacdf37a6b26 | CONTEXT: IKHOKHA Signature
+[2025-12-15 13:03:10] IKFLESZTKFM4HWWS76131L8HK9BYF96P | CONTEXT: IKHOKHA App ID
+[2025-12-15 13:03:10] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign
+[2025-12-15 13:03:10] b3b592829090d2cfd0912ccbdec73db18742088d01a2d2bee9f0eacdf37a6b26 | CONTEXT: IKHOKHA Signature
+[2025-12-15 13:03:19] IKFLESZTKFM4HWWS76131L8HK9BYF96P | CONTEXT: IKHOKHA App ID
+[2025-12-15 13:03:19] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign
+[2025-12-15 13:03:19] b3b592829090d2cfd0912ccbdec73db18742088d01a2d2bee9f0eacdf37a6b26 | CONTEXT: IKHOKHA Signature
+[2025-12-15 13:05:51] bb1702d488a40091ebd5414bc6f524e203e2c5e36b24a1b86e243dad440bb557 | CONTEXT: IKHOKHA Signature from Webhook
+[2025-12-15 13:05:51] "{\"paylinkID\":\"ys5225k4z56x0mm\",\"status\":\"SUCCESS\",\"externalTransactionID\":\"693efeaca71a9\",\"responseCode\":\"00\",\"text\":null}" | CONTEXT: IKHOKHA Stringified Body
+[2025-12-15 13:06:29] bb1702d488a40091ebd5414bc6f524e203e2c5e36b24a1b86e243dad440bb557 | CONTEXT: IKHOKHA Signature from Webhook
+[2025-12-15 13:06:29] "{\"paylinkID\":\"ys5225k4z56x0mm\",\"status\":\"SUCCESS\",\"externalTransactionID\":\"693efeaca71a9\",\"responseCode\":\"00\",\"text\":null}" | CONTEXT: IKHOKHA Stringified Body
+[2025-12-15 13:06:29] /src/api/ikhokha_webhook.php\"{\\"paylinkID\\":\\"ys5225k4z56x0mm\\",\\"status\\":\\"SUCCESS\\",\\"externalTransactionID\\":\\"693efeaca71a9\\",\\"responseCode\\":\\"00\\",\\"text\\":null}\" | CONTEXT: IKHOKHA Payload to Sign
+[2025-12-15 13:06:29] 43a6a56af31c276174953e115eb41402f12969fedab5b673dd34327cd7135a75 | CONTEXT: IKHOKHA Generated Signature
+[2025-12-15 13:06:42] bb1702d488a40091ebd5414bc6f524e203e2c5e36b24a1b86e243dad440bb557 | CONTEXT: IKHOKHA Signature from Webhook
+[2025-12-15 13:06:42] {"paylinkID":"ys5225k4z56x0mm","status":"SUCCESS","externalTransactionID":"693efeaca71a9","responseCode":"00","text":null} | CONTEXT: IKHOKHA Stringified Body
+[2025-12-15 13:06:42] /src/api/ikhokha_webhook.php\"{\\"paylinkID\\":\\"ys5225k4z56x0mm\\",\\"status\\":\\"SUCCESS\\",\\"externalTransactionID\\":\\"693efeaca71a9\\",\\"responseCode\\":\\"00\\",\\"text\\":null}\" | CONTEXT: IKHOKHA Payload to Sign
+[2025-12-15 13:06:42] 43a6a56af31c276174953e115eb41402f12969fedab5b673dd34327cd7135a75 | CONTEXT: IKHOKHA Generated Signature
+[2025-12-15 13:07:09] bb1702d488a40091ebd5414bc6f524e203e2c5e36b24a1b86e243dad440bb557 | CONTEXT: IKHOKHA Signature from Webhook
+[2025-12-15 13:07:09] {"paylinkID":"ys5225k4z56x0mm","status":"SUCCESS","externalTransactionID":"693efeaca71a9","responseCode":"00","text":null} | CONTEXT: IKHOKHA Stringified Body
+[2025-12-15 13:07:09] /src/api/ikhokha_webhook.php\"{\\"paylinkID\\":\\"ys5225k4z56x0mm\\",\\"status\\":\\"SUCCESS\\",\\"externalTransactionID\\":\\"693efeaca71a9\\",\\"responseCode\\":\\"00\\",\\"text\\":null}\" | CONTEXT: IKHOKHA Payload to Sign
+[2025-12-15 13:07:09] 43a6a56af31c276174953e115eb41402f12969fedab5b673dd34327cd7135a75 | CONTEXT: IKHOKHA Generated Signature
+[2025-12-15 13:09:39] bb1702d488a40091ebd5414bc6f524e203e2c5e36b24a1b86e243dad440bb557 | CONTEXT: IKHOKHA Signature from Webhook
+[2025-12-15 13:19:12] IKFLESZTKFM4HWWS76131L8HK9BYF96P | CONTEXT: IKHOKHA App ID
+[2025-12-15 13:19:12] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign
+[2025-12-15 13:19:12] b3b592829090d2cfd0912ccbdec73db18742088d01a2d2bee9f0eacdf37a6b26 | CONTEXT: IKHOKHA Signature
+[2025-12-15 13:36:28] IKFLESZTKFM4HWWS76131L8HK9BYF96P | CONTEXT: IKHOKHA App ID
+[2025-12-15 13:36:28] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign
+[2025-12-15 13:36:28] b3b592829090d2cfd0912ccbdec73db18742088d01a2d2bee9f0eacdf37a6b26 | CONTEXT: IKHOKHA Signature
+[2025-12-15 13:36:54] IKFLESZTKFM4HWWS76131L8HK9BYF96P | CONTEXT: IKHOKHA App ID
+[2025-12-15 13:36:54] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign
+[2025-12-15 13:36:54] b3b592829090d2cfd0912ccbdec73db18742088d01a2d2bee9f0eacdf37a6b26 | CONTEXT: IKHOKHA Signature
+[2025-12-15 15:41:25] IKFLESZTKFM4HWWS76131L8HK9BYF96P | CONTEXT: IKHOKHA App ID
+[2025-12-15 15:41:25] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign
+[2025-12-15 15:41:25] b3b592829090d2cfd0912ccbdec73db18742088d01a2d2bee9f0eacdf37a6b26 | CONTEXT: IKHOKHA Signature
+[2025-12-15 15:43:53] IKFLESZTKFM4HWWS76131L8HK9BYF96P | CONTEXT: IKHOKHA App ID
+[2025-12-15 15:43:53] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign
+[2025-12-15 15:43:53] b3b592829090d2cfd0912ccbdec73db18742088d01a2d2bee9f0eacdf37a6b26 | CONTEXT: IKHOKHA Signature
+[2025-12-15 15:44:29] IKFLESZTKFM4HWWS76131L8HK9BYF96P | CONTEXT: IKHOKHA App ID
+[2025-12-15 15:44:29] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign
+[2025-12-15 15:44:29] b3b592829090d2cfd0912ccbdec73db18742088d01a2d2bee9f0eacdf37a6b26 | CONTEXT: IKHOKHA Signature
+[2025-12-15 15:46:02] IKFLESZTKFM4HWWS76131L8HK9BYF96P | CONTEXT: IKHOKHA App ID
+[2025-12-15 15:46:02] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign
+[2025-12-15 15:46:02] b3b592829090d2cfd0912ccbdec73db18742088d01a2d2bee9f0eacdf37a6b26 | CONTEXT: IKHOKHA Signature
+[2025-12-15 15:47:46] IKFLESZTKFM4HWWS76131L8HK9BYF96P | CONTEXT: IKHOKHA App ID
+[2025-12-15 15:47:46] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign
+[2025-12-15 15:47:46] b3b592829090d2cfd0912ccbdec73db18742088d01a2d2bee9f0eacdf37a6b26 | CONTEXT: IKHOKHA Signature
+[2025-12-15 15:47:51] IKFLESZTKFM4HWWS76131L8HK9BYF96P | CONTEXT: IKHOKHA App ID
+[2025-12-15 15:47:51] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign
+[2025-12-15 15:47:51] b3b592829090d2cfd0912ccbdec73db18742088d01a2d2bee9f0eacdf37a6b26 | CONTEXT: IKHOKHA Signature
+[2025-12-15 15:48:43] IKFLESZTKFM4HWWS76131L8HK9BYF96P | CONTEXT: IKHOKHA App ID
+[2025-12-15 15:48:43] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign
+[2025-12-15 15:48:43] b3b592829090d2cfd0912ccbdec73db18742088d01a2d2bee9f0eacdf37a6b26 | CONTEXT: IKHOKHA Signature
diff --git a/src/admin/_admin_tx_debug.log b/src/admin/_admin_tx_debug.log
new file mode 100644
index 00000000..fdcd6b4b
--- /dev/null
+++ b/src/admin/_admin_tx_debug.log
@@ -0,0 +1,56 @@
+[2025-12-15 12:28:42] FILE HIT
+[2025-12-15 12:28:42] AJAX BLOCK ENTERED
+[2025-12-15 12:28:42] startDate=2025-10-16
+[2025-12-15 12:28:42] endDate=2025-12-15
+[2025-12-15 12:28:42] APP ID present: YES
+[2025-12-15 12:28:42] APP SECRET present: YES
+[2025-12-15 12:28:42] PAYLOAD: /public-api/v1/api/payments/history?startDate=2025-10-16&endDate=2025-12-15
+[2025-12-15 12:28:42] IK-SIGN: 3d610c60c8306cd1d5c99b2639f0e810594f8ffb9306a98d703f691173dab47d
+[2025-12-15 12:28:44] CURL HTTP CODE: 422
+[2025-12-15 12:28:44] CURL ERROR: none
+[2025-12-15 12:28:44] RAW RESPONSE: {"error":"Invalid Signature"}
+[2025-12-15 12:28:51] FILE HIT
+[2025-12-15 12:28:51] AJAX BLOCK ENTERED
+[2025-12-15 12:28:51] startDate=2025-10-16
+[2025-12-15 12:28:51] endDate=2025-12-15
+[2025-12-15 12:28:51] APP ID present: YES
+[2025-12-15 12:28:51] APP SECRET present: YES
+[2025-12-15 12:28:51] PAYLOAD: /public-api/v1/api/payments/history?startDate=2025-10-16&endDate=2025-12-15
+[2025-12-15 12:28:51] IK-SIGN: 3d610c60c8306cd1d5c99b2639f0e810594f8ffb9306a98d703f691173dab47d
+[2025-12-15 12:28:51] CURL HTTP CODE: 422
+[2025-12-15 12:28:51] CURL ERROR: none
+[2025-12-15 12:28:51] RAW RESPONSE: {"error":"Invalid Signature"}
+[2025-12-15 12:30:54] FILE HIT
+[2025-12-15 12:30:54] AJAX BLOCK ENTERED
+[2025-12-15 12:30:54] startDate=2025-10-16
+[2025-12-15 12:30:54] endDate=2025-12-15
+[2025-12-15 12:30:54] APP ID present: YES
+[2025-12-15 12:30:54] APP SECRET present: YES
+[2025-12-15 12:30:54] PAYLOAD: https://api.ikhokha.com/public-api/v1/api/payments/history?startDate=2025-10-16&endDate=2025-12-15
+[2025-12-15 12:30:54] IK-SIGN: ced35ab201ad54e8f8b5935d2778c4ec7e75af0102d40d9c4515f8118ca8b5dd
+[2025-12-15 12:30:55] CURL HTTP CODE: 422
+[2025-12-15 12:30:55] CURL ERROR: none
+[2025-12-15 12:30:55] RAW RESPONSE: {"error":"Invalid Signature"}
+[2025-12-15 12:31:13] FILE HIT
+[2025-12-15 12:31:13] AJAX BLOCK ENTERED
+[2025-12-15 12:31:13] startDate=2025-10-16
+[2025-12-15 12:31:13] endDate=2025-12-15
+[2025-12-15 12:31:13] APP ID present: YES
+[2025-12-15 12:31:13] APP SECRET present: YES
+[2025-12-15 12:31:13] PAYLOAD: https://api.ikhokha.com/public-api/v1/api/payments/history?startDate=2025-10-16&endDate=2025-12-15
+[2025-12-15 12:31:13] IK-SIGN: ced35ab201ad54e8f8b5935d2778c4ec7e75af0102d40d9c4515f8118ca8b5dd
+[2025-12-15 12:31:13] CURL HTTP CODE: 422
+[2025-12-15 12:31:13] CURL ERROR: none
+[2025-12-15 12:31:13] RAW RESPONSE: {"error":"Invalid Signature"}
+[2025-12-15 12:31:21] FILE HIT
+[2025-12-15 12:31:47] FILE HIT
+[2025-12-15 12:31:47] FILE HIT
+[2025-12-15 12:31:58] FILE HIT
+[2025-12-15 12:32:18] FILE HIT
+[2025-12-15 12:32:19] FILE HIT
+[2025-12-15 12:33:30] FILE HIT
+[2025-12-15 12:33:31] FILE HIT
+[2025-12-15 12:33:59] FILE HIT
+[2025-12-15 12:33:59] FILE HIT
+[2025-12-15 12:37:05] FILE HIT
+[2025-12-15 12:37:06] FILE HIT
diff --git a/src/admin/admin_transactions.php b/src/admin/admin_transactions.php
new file mode 100644
index 00000000..e0bdc6cd
--- /dev/null
+++ b/src/admin/admin_transactions.php
@@ -0,0 +1,248 @@
+
+
+
+
+
+
+
+
+
+
iKhokha Payments
+
+
+
+
+
+
+
+
+
+
+
+
+ ';
+ echo '
+
+
+ | Date |
+ ID |
+ PaylinkID |
+ Description |
+ Amount |
+ Status |
+
+
+ ';
+
+ $printed = false;
+ foreach ($transactions as $row) {
+ $createdAt = isset($row['createdAt']) ? htmlspecialchars($row['createdAt']) : '';
+ // prefer externalTransactionID when available, fallback to paylinkID
+ $txId = isset($row['externalTransactionID']) ? $row['externalTransactionID'] : (isset($row['paylinkID']) ? $row['paylinkID'] : '');
+ $ikhokhaTxId = isset($row['paylinkID']) ? $row['paylinkID'] : '';
+ $description = isset($row['description']) ? $row['description'] : '';
+ $amount = isset($row['amount']) ? $row['amount'] : '';
+ $status = isset($row['status']) ? $row['status'] : '';
+
+ // Skip unpaid transactions
+ if (strcasecmp($status, 'UNPAID') === 0) {
+ continue;
+ }
+
+ echo "
+ | " . htmlspecialchars($createdAt) . " |
+ " . htmlspecialchars($txId) . " |
+ " . htmlspecialchars($ikhokhaTxId) . " |
+ " . htmlspecialchars($description) . " |
+ R " . htmlspecialchars($amount/100) . ".00 |
+ " . htmlspecialchars($status) . " |
+
";
+
+ $printed = true;
+ }
+
+ if (!$printed) {
+ echo '| No records found |
';
+ }
+ } else {
+ echo '';
+ echo '
+
+
+ | Date |
+ ID |
+ Description |
+ Amount |
+ Status |
+
+
+ ';
+ echo '| No records found |
';
+ } ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/api/ikhokha_webhook.php b/src/api/ikhokha_webhook.php
index 9732ffda..a4157953 100644
--- a/src/api/ikhokha_webhook.php
+++ b/src/api/ikhokha_webhook.php
@@ -7,80 +7,102 @@ require_once($rootPath . "/src/config/functions.php");
/**
* ==========================================================
- * Read raw request and headers (DO NOT MODIFY RAW BODY)
+ * JS-equivalent escaping (matches iKhokha docs exactly)
+ * ==========================================================
+ */
+function jsStringEscape(string $str): string
+{
+ $str = preg_replace('/([\\\\\"\'])/', '\\\\$1', $str);
+ $str = str_replace("\0", "\\0", $str);
+ return $str;
+}
+
+function createPayloadToSign(string $path, string $body): string
+{
+ return jsStringEscape($path . $body);
+}
+
+/**
+ * ==========================================================
+ * Read raw request body (DO NOT MODIFY)
* ==========================================================
*/
$raw = file_get_contents('php://input');
-if ($raw === false) {
+if ($raw === false || $raw === '') {
http_response_code(400);
- progress_log('iKhokha webhook: unable to read raw input');
+ progress_log('iKhokha webhook: empty body');
exit('No body');
}
+/**
+ * ==========================================================
+ * Read headers
+ * ==========================================================
+ */
$headers = function_exists('getallheaders') ? getallheaders() : [];
$headers = array_change_key_case($headers, CASE_LOWER);
$ikSign = $headers['ik-sign'] ?? null;
$ikAppId = $headers['ik-appid'] ?? null;
-/**
- * ==========================================================
- * Basic header presence check
- * ==========================================================
- */
-if (empty($ikSign) || empty($ikAppId)) {
+if (!$ikSign || !$ikAppId) {
http_response_code(400);
- progress_log('iKhokha webhook: missing IK-SIGN or IK-APPID');
+ progress_log('iKhokha webhook: missing headers');
exit('Missing headers');
}
/**
* ==========================================================
- * Signature verification
- * HMAC_SHA256( path + raw_body, app_secret )
+ * Signature verification (JS-equivalent)
* ==========================================================
*/
$secret = $_ENV['IKHOKHA_APP_SECRET'] ?? null;
+$callbackUrl = $_ENV['IKHOKHA_CALLBACK_URL'] ?? null;
+$bypass = ($_ENV['IKHOKHA_BYPASS_SIGNATURE'] ?? 'false') === 'true';
-if (empty($secret)) {
+if (!$secret || !$callbackUrl) {
http_response_code(500);
- progress_log('iKhokha webhook: app secret not configured');
exit('Server misconfigured');
}
-
-
-// Debug logging (disable once stable)
progress_log('--- iKhokha WEBHOOK DEBUG ---');
progress_log('RAW BODY: ' . $raw);
progress_log('IK-SIGN: ' . $ikSign);
-$callbackUrl = $_ENV['IKHOKHA_CALLBACK_URL'] ?? null;
-$bypass = ($_ENV['IKHOKHA_BYPASS_SIGNATURE'] ?? 'false') === 'true';
+// Decode body so we can remove `text`
+$bodyArray = json_decode($raw, true);
+if (!is_array($bodyArray)) {
+ http_response_code(400);
+ exit('Invalid JSON');
+}
+
+// iKhokha JS deletes `text`
+unset($bodyArray['text']);
+
+// JS-style JSON (no escaped slashes)
+$jsonBody = json_encode($bodyArray, JSON_UNESCAPED_SLASHES);
+
+
+// Now sign the SAME payload JS signs
+$payloadToSign = createPayloadToSign($callbackUrl, $jsonBody);
+
+$expected = generateSignature($payloadToSign, $secret);
+
+progress_log('JS PAYLOAD: ' . $payloadToSign);
+progress_log('EXPECTED SIGN: ' . $expected);
+progress_log('RECEIVED SIGN: ' . $ikSign);
if (!$bypass) {
- if (empty($callbackUrl)) {
- http_response_code(500);
- progress_log('iKhokha webhook: callback URL not configured');
- exit('Server misconfigured');
- }
-
- $expected = hash_hmac(
- 'sha256',
- $callbackUrl . $raw,
- $_ENV['IKHOKHA_APP_SECRET']
- );
-
if (!hash_equals($expected, $ikSign)) {
http_response_code(403);
progress_log('iKhokha webhook: signature mismatch');
- progress_log('EXPECTED SIGN: ' . $expected);
- progress_log('RECEIVED SIGN: ' . $ikSign);
- // Audit signature mismatch
if (function_exists('auditLog')) {
- auditLog(null, 'IKHOKHA_SIGNATURE_MISMATCH', 'webhook', null, ['expected' => $expected, 'received' => $ikSign]);
+ auditLog(null, 'IKHOKHA_SIGNATURE_MISMATCH', 'webhook', null, [
+ 'expected' => $expected,
+ 'received' => $ikSign
+ ]);
}
exit('Invalid signature');
}
@@ -95,20 +117,13 @@ if (!$bypass) {
* ==========================================================
*/
$payload = json_decode($raw, true);
-
-if (!is_array($payload)) {
- http_response_code(400);
- progress_log('iKhokha webhook: invalid JSON');
- exit('Invalid JSON');
-}
+$data = $payload['data'] ?? $payload;
/**
* ==========================================================
- * Extract data safely (iKhokha is inconsistent)
+ * Extract fields safely
* ==========================================================
*/
-$data = $payload['data'] ?? $payload;
-
$externalTransactionID =
$data['externalTransactionID']
?? $data['externalTransactionId']
@@ -127,11 +142,11 @@ $providerStatus =
progress_log('Parsed externalTransactionID: ' . $externalTransactionID);
progress_log('Parsed providerPaymentId: ' . $providerPaymentId);
-progress_log('Parsed providerStatus: ' . print_r($providerStatus, true));
+progress_log('Parsed providerStatus: ' . $providerStatus);
/**
* ==========================================================
- * Locate local payment
+ * Locate payment
* ==========================================================
*/
$localPaymentId = null;
@@ -146,16 +161,13 @@ if ($externalTransactionID) {
WHERE payment_id = ?
LIMIT 1"
);
-
if ($stmt) {
$stmt->bind_param('s', $externalTransactionID);
$stmt->execute();
$res = $stmt->get_result();
if ($row = $res->fetch_assoc()) {
+ extract($row);
$localPaymentId = $row['payment_id'];
- $booking_id = $row['booking_id'];
- $user_id = $row['user_id'];
- $description = $row['description'];
}
$stmt->close();
}
@@ -168,16 +180,13 @@ if (!$localPaymentId && $providerPaymentId) {
WHERE provider_payment_id = ?
LIMIT 1"
);
-
if ($stmt) {
$stmt->bind_param('s', $providerPaymentId);
$stmt->execute();
$res = $stmt->get_result();
if ($row = $res->fetch_assoc()) {
+ extract($row);
$localPaymentId = $row['payment_id'];
- $booking_id = $row['booking_id'];
- $user_id = $row['user_id'];
- $description = $row['description'];
}
$stmt->close();
}
@@ -185,11 +194,6 @@ if (!$localPaymentId && $providerPaymentId) {
if (!$localPaymentId) {
http_response_code(404);
- progress_log('iKhokha webhook: payment not found');
- progress_log(json_encode([$externalTransactionID, $providerPaymentId]));
- if (function_exists('auditLog')) {
- auditLog(null, 'IKHOKHA_PAYMENT_NOT_FOUND', 'payment', null, ['externalTransactionID' => $externalTransactionID, 'providerPaymentId' => $providerPaymentId]);
- }
exit('Payment not found');
}
@@ -216,72 +220,58 @@ if ($update) {
);
$update->execute();
$update->close();
- if (function_exists('auditLog')) {
- auditLog($user_id, 'PAYMENT_PROVIDER_RESPONSE_SAVED', 'payment', null, ['payment_id' => $localPaymentId, 'provider_payment_id' => $providerPaymentId, 'provider_status' => $providerStatus]);
- }
}
/**
* ==========================================================
- * Normalize status and apply business logic
+ * Business logic
* ==========================================================
*/
$normalized = strtoupper(trim((string)$providerStatus));
if (in_array($normalized, ['PAID', 'SUCCESS', 'COMPLETED', 'SETTLED'], true)) {
- // Mark payment as PAID
- $setPaid = $conn->prepare(
+ $conn->prepare(
"UPDATE payments SET status = 'PAID' WHERE payment_id = ?"
- );
- if ($setPaid) {
- $setPaid->bind_param('s', $localPaymentId);
- $setPaid->execute();
- $setPaid->close();
- if (function_exists('auditLog')) {
- auditLog($user_id, 'PAYMENT_MARKED_PAID', 'payment', null, ['payment_id' => $localPaymentId]);
- }
- }
+ )->bind_param('s', $localPaymentId)->execute();
- // Booking or membership update
- if (!empty($booking_id)) {
- $upd = $conn->prepare(
+ if ($booking_id) {
+ $conn->prepare(
"UPDATE bookings SET status = 'PAID' WHERE booking_id = ?"
- );
- if ($upd) {
- $upd->bind_param('i', $booking_id);
- $upd->execute();
- $upd->close();
- sendAdminNotification('4WDCSA.co.za - New Booking - '.getFullName($user_id) , 'We have received a payment for a new booking for '.$description.' from '.getFullName($user_id));
- if (function_exists('auditLog')) {
- auditLog($user_id, 'BOOKING_PAYMENT_MARKED_PAID', 'bookings', $booking_id, ['payment_id' => $localPaymentId]);
- }
- }
+ )->bind_param('i', $booking_id)->execute();
} else {
- $upd = $conn->prepare(
- "UPDATE membership_fees
- SET payment_status = 'PAID'
- WHERE payment_id = ?"
- );
- if ($upd) {
- $upd->bind_param('s', $localPaymentId);
- $upd->execute();
- $upd->close();
- sendAdminNotification('4WDCSA.co.za - Membership Payment Received - '.getFullName($user_id) , 'A Membership Payment has been received from '.getFullName($user_id));
- if (function_exists('auditLog')) {
- auditLog($user_id, 'MEMBERSHIP_PAYMENT_MARKED_PAID', 'membership_fees', null, ['payment_id' => $localPaymentId]);
- }
- }
+ $conn->prepare(
+ "UPDATE membership_fees SET payment_status = 'PAID' WHERE payment_id = ?"
+ )->bind_param('s', $localPaymentId)->execute();
}
- // Send confirmation email
- if (!empty($user_id)) {
- sendPaymentConfirmation(
- getEmail($user_id),
- getFullName($user_id),
- $description
- );
- }
+ sendPaymentConfirmation(
+ getEmail($user_id),
+ getFullName($user_id),
+ $description
+ );
+
+ //generate $message for admin payment confirmation with payment details
+ $message = "Payment Confirmation\n\n";
+ $message .= "Payment ID: " . $localPaymentId . "\n";
+ $message .= "Amount: " . getPaymentAmount($localPaymentId) . "\n";
+ $message .= "Status: PAID\n";
+ $message .= "Description: " . $description . "\n";
+ $message .= "Thank you.\n";
+ $subject = "4WDCSA.co.za Payment Confirmation for Payment ID: " . $localPaymentId;
+ progress_log('Payment confirmation sent for payment ID: ' . $localPaymentId);
+
+ sendEmail(
+ $_ENV['FINANCE_EMAIL'],
+ $subject,
+ nl2br($message)
+ );
+ sendEmail(
+ 'chrispintoza@gmail.com',
+ $subject,
+ nl2br($message)
+ );
+ sendAdminNotification($subject, nl2br($message));
}
/**
diff --git a/src/config/functions.php b/src/config/functions.php
index ab95e405..b21d6ac7 100644
--- a/src/config/functions.php
+++ b/src/config/functions.php
@@ -858,6 +858,45 @@ function createIkhokhaPayment($payment_id, $amount, $description, $publicRef)
return $resp;
}
+function getIkhokhaTransactionHistory($startDate, $endDate,)
+{
+
+ // Base requester URL: prefer explicit env var, otherwise build from request
+ $endpoint = "https://api.ikhokha.com/public-api/v1/api/payments/history?startDate=".$startDate."&endDate=".$endDate;
+ // $endpoint = "https://api.ikhokha.com/public-api/v1/api/payments/history?startDate=2024-02-01&endDate=2026-03-07";
+ $appID = $_ENV['IKHOKHA_APP_ID'];
+ progress_log($appID, "IKHOKHA App ID");
+ $appSecret = $_ENV['IKHOKHA_APP_SECRET'];
+
+ // $stringifiedBody = json_encode($requestBody);
+ $payloadToSign = createPayloadToSign($endpoint, null);
+ progress_log($payloadToSign, "IKHOKHA Payload to Sign");
+
+ $ikSign = generateSignature($payloadToSign, $appSecret);
+ progress_log($ikSign, "IKHOKHA Signature");
+
+ // Initialize cURL session
+ $ch = curl_init($endpoint);
+ // Set cURL options
+ curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET");
+ // curl_setopt($ch, CURLOPT_POSTFIELDS, $stringifiedBody);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_HTTPHEADER, [
+ "Content-Type: application/json",
+ "IK-APPID: $appID",
+ "IK-SIGN: $ikSign"
+ ]);
+ // Execute cURL session
+ $response = curl_exec($ch);
+ curl_close($ch);
+
+ // Decode and output the response
+ $resp = json_decode($response, true);
+
+ return $response;
+}
+
+
function escapeString($str) {
$escaped = preg_replace(['/[\\"\'\"]/u', '/\x00/'], ['\\\\$0', '\\0'], (string)$str);
$cleaned = str_replace('\/', '/', $escaped);
@@ -879,6 +918,20 @@ function generateSignature($payloadToSign, $secret) {
return hash_hmac('sha256', $payloadToSign, $secret);
}
+function getPaymentAmount($localPaymentId) {
+ $conn = openDatabaseConnection();
+ $stmt = $conn->prepare("SELECT amount FROM payments WHERE payment_id = ? LIMIT 1");
+ $stmt->bind_param("s", $localPaymentId);
+ $stmt->execute();
+ $result = $stmt->get_result();
+
+ if ($row = $result->fetch_assoc()) {
+ return $row['amount'];
+ } else {
+ return false; // Payment not found
+ }
+}
+
function processMembershipPayment($payment_id, $amount, $description)
{
$conn = openDatabaseConnection();
diff --git a/src/logs/db_errors.log b/src/logs/db_errors.log
index 4a23ee8e..effc7895 100644
--- a/src/logs/db_errors.log
+++ b/src/logs/db_errors.log
@@ -1 +1 @@
-Database Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directory
\ No newline at end of file
+Database Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directory
\ No newline at end of file
diff --git a/test.php b/test.php
index 011cbbf8..f1cb3b10 100644
--- a/test.php
+++ b/test.php
@@ -1,41 +1,56 @@
IK-SIGN FROM WEBHOOK:
";
-echo "bb1702d488a40091ebd5414bc6f524e203e2c5e36b24a1b86e243dad440bb557
";
+// Simulated header signature from iKhokha
+$ikSignFromWebhook = 'bb1702d488a40091ebd5414bc6f524e203e2c5e36b24a1b86e243dad440bb557';
-$payloadToSign = $path . $raw;
+// Simulated raw webhook body (EXACT)
+$raw = '{"paylinkID":"ys5225k4z56x0mm","status":"SUCCESS","externalTransactionID":"693efeaca71a9","responseCode":"00","text":null}';
-// Generate signature using hash_hmac directly on the constructed string
-$expected = hash_hmac('sha256', $payloadToSign, $secret);
+// Simulated header signature from iKhokha
+$ikSignFromWebhook = 'bb1702d488a40091ebd5414bc6f524e203e2c5e36b24a1b86e243dad440bb557';
-// --- Output debug info (UPDATED) ---
-echo "DEBUG INFO
";
-echo "Callback URL: $callbackUrl
";
+// Decode JSON string into array
+$bodyArray = json_decode($raw, true);
-echo "Payload to Sign (Un-escaped):
";
-echo htmlspecialchars($payloadToSign) . "
";
+// Remove `text` key exactly like JS
+unset($bodyArray['text']);
+
+// Re-encode JSON (no double-encoding)
+$cleanBody = json_encode($bodyArray, JSON_UNESCAPED_SLASHES);
+
+// Now sign the SAME payload JS signs
+$payloadToSign = createPayloadToSign($callbackUrl, $cleanBody);
+
+$ikSign = generateSignature($payloadToSign, $secret);
+?>
+
+
+
+
+ iKhokha Signature Debug (JS)
+
+
+
+IK-SIGN FROM WEBHOOK:
+= htmlspecialchars($ikSignFromWebhook) ?>
+
+DEBUG INFO
+Callback URL: = htmlspecialchars($callbackUrl) ?>
+
+Payload to Sign (Un-escaped):
+
+= $payloadToSign ?>
+
+EXPECTED SIGNATURE (JS):
+
+= $ikSign ?>
-echo "EXPECTED SIGNATURE:
";
-echo $expected . "
";
\ No newline at end of file
diff --git a/test2.php b/test2.php
new file mode 100644
index 00000000..7c9dc916
--- /dev/null
+++ b/test2.php
@@ -0,0 +1,5 @@
+