master #1
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/paystack.iml" filepath="$PROJECT_DIR$/.idea/paystack.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/paystack.iml
generated
Normal file
8
.idea/paystack.iml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
19
.idea/php.xml
generated
Normal file
19
.idea/php.xml
generated
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MessDetectorOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PHPCSFixerOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PHPCodeSnifferOptionsConfiguration">
|
||||
<option name="highlightLevel" value="WARNING" />
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PhpStanOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PsalmOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
73
LICENSE
73
LICENSE
@ -1,73 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
|
||||
|
||||
Copyright 2025 sackey
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
4
config/autoload.php
Normal file
4
config/autoload.php
Normal file
@ -0,0 +1,4 @@
|
||||
<?php
|
||||
defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
$autoload['model'] = array('paystack_model');
|
||||
240
controllers/Admin.php
Normal file
240
controllers/Admin.php
Normal file
@ -0,0 +1,240 @@
|
||||
<?php
|
||||
|
||||
defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
class Admin extends AdminController
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->load->model('paystack_model');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display settings page
|
||||
*/
|
||||
public function settings()
|
||||
{
|
||||
if (!has_permission('settings', '', 'view')) {
|
||||
access_denied('settings');
|
||||
}
|
||||
|
||||
if ($this->input->post()) {
|
||||
if (!has_permission('settings', '', 'edit')) {
|
||||
access_denied('settings');
|
||||
}
|
||||
|
||||
$data = $this->input->post();
|
||||
$success = $this->paystack_model->update_settings($data);
|
||||
|
||||
if ($success) {
|
||||
set_alert('success', _l('settings_updated'));
|
||||
}
|
||||
}
|
||||
|
||||
$data['title'] = _l('paystack_settings');
|
||||
$data['tab'] = 'settings';
|
||||
$this->load->view('admin/settings', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display transaction logs
|
||||
*/
|
||||
public function transactions()
|
||||
{
|
||||
if (!has_permission('payments', '', 'view')) {
|
||||
access_denied('payments');
|
||||
}
|
||||
|
||||
$data['title'] = _l('paystack_transactions');
|
||||
$data['tab'] = 'transactions';
|
||||
|
||||
// Get filters
|
||||
$filter = [
|
||||
'start_date' => $this->input->get('start_date'),
|
||||
'end_date' => $this->input->get('end_date'),
|
||||
'status' => $this->input->get('status')
|
||||
];
|
||||
|
||||
$data['transactions'] = $this->paystack_model->get_transactions($filter);
|
||||
$this->load->view('admin/transactions', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display test mode interface
|
||||
*/
|
||||
public function test_mode()
|
||||
{
|
||||
if (!has_permission('settings', '', 'view')) {
|
||||
access_denied('settings');
|
||||
}
|
||||
|
||||
$data['title'] = _l('paystack_test_mode');
|
||||
$data['tab'] = 'test_mode';
|
||||
$data['test_keys'] = $this->paystack_model->get_test_keys();
|
||||
$this->load->view('admin/test_mode', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display payment status dashboard
|
||||
*/
|
||||
public function dashboard()
|
||||
{
|
||||
if (!has_permission('payments', '', 'view')) {
|
||||
access_denied('payments');
|
||||
}
|
||||
|
||||
$data['title'] = _l('paystack_dashboard');
|
||||
$data['tab'] = 'dashboard';
|
||||
|
||||
// Get statistics
|
||||
$data['stats'] = $this->paystack_model->get_payment_stats();
|
||||
$data['recent_transactions'] = $this->paystack_model->get_recent_transactions();
|
||||
$data['monthly_chart'] = $this->paystack_model->get_monthly_chart_data();
|
||||
|
||||
$this->load->view('admin/dashboard', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transaction details (AJAX)
|
||||
*/
|
||||
public function get_transaction_details($reference)
|
||||
{
|
||||
if (!has_permission('payments', '', 'view')) {
|
||||
ajax_access_denied();
|
||||
}
|
||||
|
||||
$transaction = $this->paystack_model->get_transaction($reference);
|
||||
echo json_encode($transaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify test webhook
|
||||
*/
|
||||
// public function test_webhook()
|
||||
// {
|
||||
// if (!has_permission('settings', '', 'view')) {
|
||||
// ajax_access_denied();
|
||||
// }
|
||||
//
|
||||
// $this->load->library('paystack_gateway');
|
||||
// $result = $this->paystack_gateway->test_webhook();
|
||||
//
|
||||
// echo json_encode($result);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Initiate test payment
|
||||
*/
|
||||
public function initiate_test_payment()
|
||||
{
|
||||
if (!has_permission('settings', '', 'view')) {
|
||||
ajax_access_denied();
|
||||
}
|
||||
|
||||
$amount = $this->input->post('amount');
|
||||
$email = $this->input->post('email');
|
||||
|
||||
if (!$amount || !$email) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => _l('invalid_input')
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$reference = 'TEST_' . time() . '_' . mt_rand(1000, 9999);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'reference' => $reference
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test webhook connection
|
||||
*/
|
||||
public function test_webhook()
|
||||
{
|
||||
if (!has_permission('settings', '', 'view')) {
|
||||
ajax_access_denied();
|
||||
}
|
||||
|
||||
$this->load->library('paystack_gateway');
|
||||
|
||||
// Try to send a test webhook
|
||||
$webhook_url = site_url('paystack/webhook');
|
||||
$test_data = [
|
||||
'event' => 'test',
|
||||
'data' => [
|
||||
'reference' => 'TEST_' . time(),
|
||||
'status' => 'success'
|
||||
]
|
||||
];
|
||||
|
||||
$ch = curl_init($webhook_url);
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($test_data));
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/json',
|
||||
'X-Paystack-Signature: ' . hash_hmac('sha512', json_encode($test_data), $this->paystack_gateway->get_webhook_secret())
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($http_code == 200) {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => _l('webhook_received_response')
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => _l('webhook_connection_failed') . ' (HTTP ' . $http_code . ')'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log debug message
|
||||
*/
|
||||
public function log_debug()
|
||||
{
|
||||
if (!has_permission('settings', '', 'view')) {
|
||||
ajax_access_denied();
|
||||
}
|
||||
|
||||
$message = $this->input->post('message');
|
||||
if ($message) {
|
||||
$this->paystack_model->add_debug_log($message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get debug log
|
||||
*/
|
||||
public function get_debug_log()
|
||||
{
|
||||
if (!has_permission('settings', '', 'view')) {
|
||||
ajax_access_denied();
|
||||
}
|
||||
|
||||
$log = $this->paystack_model->get_debug_log();
|
||||
echo $log;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear debug log
|
||||
*/
|
||||
public function clear_debug_log()
|
||||
{
|
||||
if (!has_permission('settings', '', 'view')) {
|
||||
ajax_access_denied();
|
||||
}
|
||||
|
||||
$this->paystack_model->clear_debug_log();
|
||||
}
|
||||
}
|
||||
337
controllers/Paystack.php
Normal file
337
controllers/Paystack.php
Normal file
@ -0,0 +1,337 @@
|
||||
<?php
|
||||
|
||||
defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
class Paystack extends App_Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->load->model('invoices_model');
|
||||
$this->load->model('clients_model');
|
||||
$this->load->library('paystack_gateway');
|
||||
$this->load->model('paystack/paystack_model');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show payment form
|
||||
*/
|
||||
public function make_payment()
|
||||
{
|
||||
$payment_data = $this->session->userdata('paystack_payment_data');
|
||||
|
||||
if (!$payment_data) {
|
||||
set_alert('danger', _l('invalid_payment'));
|
||||
redirect(site_url('invoices'));
|
||||
}
|
||||
|
||||
$invoice = $this->invoices_model->get($payment_data['invoice_id']);
|
||||
if (!$invoice) {
|
||||
set_alert('danger', _l('invoice_not_found'));
|
||||
redirect(site_url('invoices'));
|
||||
}
|
||||
|
||||
// Get client data
|
||||
$client = $this->clients_model->get($invoice->clientid);
|
||||
|
||||
$data = [];
|
||||
$data['invoice'] = $invoice;
|
||||
$data['payment_data'] = $payment_data;
|
||||
$data['client'] = $client;
|
||||
|
||||
$this->load->view('paystack/payment', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify payment callback
|
||||
*/
|
||||
public function verify_payment($reference = null)
|
||||
{
|
||||
if (!$reference) {
|
||||
redirect(site_url('clients/invoices'));
|
||||
}
|
||||
|
||||
// Log verification attempt
|
||||
$this->paystack_model->add_payment_log([
|
||||
'message' => 'Verifying payment: ' . $reference,
|
||||
'log_type' => 'info'
|
||||
]);
|
||||
|
||||
$transaction = $this->paystack_gateway->verify_transaction($reference);
|
||||
|
||||
if ($transaction['success']) {
|
||||
// Get the transaction from our database
|
||||
$local_transaction = $this->paystack_model->get_transaction_by_reference($reference);
|
||||
|
||||
if ($local_transaction) {
|
||||
// Update local transaction status
|
||||
$this->db->where('id', $local_transaction['id']);
|
||||
$this->db->update(db_prefix() . 'paystack_transactions', [
|
||||
'status' => 'success',
|
||||
'transaction_date' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
// Record the payment in Perfex CRM
|
||||
$payment_data = [
|
||||
'amount' => $local_transaction['amount'],
|
||||
'invoiceid' => $local_transaction['invoice_id'],
|
||||
'paymentmode' => 'paystack',
|
||||
'transactionid' => $reference
|
||||
];
|
||||
|
||||
$this->load->model('payments_model');
|
||||
$payment_id = $this->payments_model->add($payment_data);
|
||||
|
||||
if ($payment_id) {
|
||||
set_alert('success', _l('payment_recorded_successfully'));
|
||||
} else {
|
||||
set_alert('danger', _l('payment_record_failed'));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
set_alert('danger', _l('payment_failed'));
|
||||
}
|
||||
|
||||
// Redirect to invoice
|
||||
redirect(site_url('clients/invoices'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Paystack webhook
|
||||
*/
|
||||
// public function webhook()
|
||||
// {
|
||||
// if ((strtoupper($_SERVER['REQUEST_METHOD']) != 'POST') || !array_key_exists('HTTP_X_PAYSTACK_SIGNATURE', $_SERVER)) {
|
||||
// exit();
|
||||
// }
|
||||
//
|
||||
// $input = file_get_contents("php://input");
|
||||
//
|
||||
// // Verify webhook signature
|
||||
// $secret_key = $this->paystack_gateway->decryptSetting('paystack_secret_key');
|
||||
// if ($_SERVER['HTTP_X_PAYSTACK_SIGNATURE'] !== hash_hmac('sha512', $input, $secret_key)) {
|
||||
// exit();
|
||||
// }
|
||||
//
|
||||
// http_response_code(200);
|
||||
//
|
||||
// $event = json_decode($input);
|
||||
//
|
||||
// // Handle the event
|
||||
// switch ($event->event) {
|
||||
// case 'charge.success':
|
||||
// $this->handle_successful_charge($event->data);
|
||||
// break;
|
||||
// case 'transfer.success':
|
||||
// $this->handle_successful_transfer($event->data);
|
||||
// break;
|
||||
// case 'charge.failed':
|
||||
// $this->handle_failed_charge($event->data);
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// exit();
|
||||
// }
|
||||
public function webhook()
|
||||
{
|
||||
$this->load->helper('paystack_security');
|
||||
|
||||
// Get payload and signature
|
||||
$payload = file_get_contents('php://input');
|
||||
$signature = isset($_SERVER['HTTP_X_PAYSTACK_SIGNATURE']) ? $_SERVER['HTTP_X_PAYSTACK_SIGNATURE'] : '';
|
||||
|
||||
// Log webhook request
|
||||
$this->paystack_model->add_webhook_log([
|
||||
'event' => 'webhook_received',
|
||||
'payload' => $payload,
|
||||
'signature' => $signature,
|
||||
'status' => 'received'
|
||||
]);
|
||||
|
||||
// Verify signature
|
||||
if (!verify_paystack_webhook_signature($payload, $signature)) {
|
||||
$this->paystack_model->add_webhook_log([
|
||||
'event' => 'webhook_verification_failed',
|
||||
'payload' => $payload,
|
||||
'status' => 'failed',
|
||||
'message' => 'Invalid signature'
|
||||
]);
|
||||
header('HTTP/1.1 401 Unauthorized');
|
||||
exit();
|
||||
}
|
||||
|
||||
// Parse payload
|
||||
$event = json_decode($payload);
|
||||
|
||||
// Validate payload
|
||||
if (!is_object($event) || !isset($event->event)) {
|
||||
$this->paystack_model->add_webhook_log([
|
||||
'event' => 'webhook_invalid_payload',
|
||||
'payload' => $payload,
|
||||
'status' => 'failed',
|
||||
'message' => 'Invalid payload format'
|
||||
]);
|
||||
header('HTTP/1.1 400 Bad Request');
|
||||
exit();
|
||||
}
|
||||
|
||||
try {
|
||||
// Process different event types
|
||||
switch ($event->event) {
|
||||
case 'charge.success':
|
||||
$this->handle_successful_charge($event->data);
|
||||
break;
|
||||
|
||||
case 'charge.failed':
|
||||
$this->handle_failed_charge($event->data);
|
||||
break;
|
||||
|
||||
case 'transfer.success':
|
||||
$this->handle_successful_transfer($event->data);
|
||||
break;
|
||||
|
||||
case 'transfer.failed':
|
||||
$this->handle_failed_transfer($event->data);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Log unknown event type
|
||||
$this->paystack_model->add_webhook_log([
|
||||
'event' => $event->event,
|
||||
'payload' => $payload,
|
||||
'status' => 'skipped',
|
||||
'message' => 'Unknown event type'
|
||||
]);
|
||||
break;
|
||||
}
|
||||
|
||||
header('HTTP/1.1 200 OK');
|
||||
echo json_encode(['status' => 'success']);
|
||||
} catch (Exception $e) {
|
||||
// Log error
|
||||
$this->paystack_model->add_webhook_log([
|
||||
'event' => $event->event,
|
||||
'payload' => $payload,
|
||||
'status' => 'error',
|
||||
'message' => $e->getMessage()
|
||||
]);
|
||||
|
||||
header('HTTP/1.1 500 Internal Server Error');
|
||||
echo json_encode(['status' => 'error', 'message' => 'Internal processing error']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle successful charge
|
||||
*/
|
||||
private function handle_successful_charge($data)
|
||||
{
|
||||
// Validate the charge data
|
||||
if (!isset($data->reference) || !isset($data->amount)) {
|
||||
throw new Exception('Invalid charge data');
|
||||
}
|
||||
|
||||
// Find the transaction
|
||||
$transaction = $this->paystack_model->get_transaction_by_reference($data->reference);
|
||||
|
||||
if (!$transaction) {
|
||||
throw new Exception('Transaction not found');
|
||||
}
|
||||
|
||||
// Verify amount
|
||||
$expected_amount = $transaction['amount'] * 100; // Convert to kobo
|
||||
if ($data->amount !== $expected_amount) {
|
||||
throw new Exception('Amount mismatch');
|
||||
}
|
||||
|
||||
// Update transaction status
|
||||
$this->paystack_model->update_transaction($transaction['id'], [
|
||||
'status' => 'success',
|
||||
'transaction_date' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
// Add to payment logs
|
||||
$this->paystack_model->add_payment_log([
|
||||
'transaction_id' => $transaction['id'],
|
||||
'invoice_id' => $transaction['invoice_id'],
|
||||
'amount' => $transaction['amount'],
|
||||
'message' => 'Payment successful',
|
||||
'log_type' => 'success'
|
||||
]);
|
||||
|
||||
// Record payment in Perfex CRM
|
||||
$payment_data = [
|
||||
'amount' => $transaction['amount'],
|
||||
'invoiceid' => $transaction['invoice_id'],
|
||||
'paymentmode' => 'paystack',
|
||||
'transactionid' => $data->reference
|
||||
];
|
||||
|
||||
$this->load->model('payments_model');
|
||||
$payment_id = $this->payments_model->add($payment_data);
|
||||
|
||||
if (!$payment_id) {
|
||||
throw new Exception('Failed to record payment');
|
||||
}
|
||||
|
||||
// Send email notification
|
||||
$this->send_payment_notification($transaction['invoice_id'], $payment_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle failed charge
|
||||
*/
|
||||
private function handle_failed_charge($data)
|
||||
{
|
||||
if (!isset($data->reference)) {
|
||||
throw new Exception('Invalid charge data');
|
||||
}
|
||||
|
||||
$transaction = $this->paystack_model->get_transaction_by_reference($data->reference);
|
||||
|
||||
if ($transaction) {
|
||||
// Update transaction status
|
||||
$this->paystack_model->update_transaction($transaction['id'], [
|
||||
'status' => 'failed',
|
||||
'transaction_date' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
// Add to payment logs
|
||||
$this->paystack_model->add_payment_log([
|
||||
'transaction_id' => $transaction['id'],
|
||||
'invoice_id' => $transaction['invoice_id'],
|
||||
'amount' => $transaction['amount'],
|
||||
'message' => 'Payment failed: ' . ($data->gateway_response ?? 'Unknown error'),
|
||||
'log_type' => 'error'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle successful transfer
|
||||
*/
|
||||
private function handle_successful_transfer($data)
|
||||
{
|
||||
log_activity('Paystack Webhook: Successful transfer - Reference: ' . $data->reference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send payment success email
|
||||
*/
|
||||
private function send_payment_success_email($invoice_id, $transaction)
|
||||
{
|
||||
$this->load->model('emails_model');
|
||||
|
||||
$invoice = $this->invoices_model->get($invoice_id);
|
||||
$client = $this->clients_model->get($invoice->clientid);
|
||||
|
||||
$email_template = 'invoice-payment-recorded';
|
||||
$merge_fields = [];
|
||||
$merge_fields = array_merge($merge_fields, get_invoice_merge_fields($invoice_id));
|
||||
$merge_fields = array_merge($merge_fields, get_client_merge_fields($client->userid));
|
||||
$merge_fields['{payment_total}'] = app_format_money($transaction['data']->amount / 100, $invoice->currency_name);
|
||||
$merge_fields['{payment_reference}'] = $transaction['data']->reference;
|
||||
|
||||
$this->emails_model->send_email_template($email_template, $client->email, $merge_fields);
|
||||
}
|
||||
}
|
||||
75
helpers/paystack_security_helper.php
Normal file
75
helpers/paystack_security_helper.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
* Verify Paystack webhook signature
|
||||
*/
|
||||
function verify_paystack_webhook_signature($payload, $signature)
|
||||
{
|
||||
$secret_key = get_option('paystack_webhook_secret');
|
||||
$calculated_signature = hash_hmac('sha512', $payload, $secret_key);
|
||||
|
||||
return hash_equals($calculated_signature, $signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate Paystack API response
|
||||
*/
|
||||
function validate_paystack_api_response($response)
|
||||
{
|
||||
if (!is_object($response)) {
|
||||
return [
|
||||
'valid' => false,
|
||||
'message' => 'Invalid response format'
|
||||
];
|
||||
}
|
||||
|
||||
if (!isset($response->status) || $response->status !== true) {
|
||||
return [
|
||||
'valid' => false,
|
||||
'message' => isset($response->message) ? $response->message : 'Invalid response status'
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'valid' => true,
|
||||
'data' => $response->data
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize API keys
|
||||
*/
|
||||
function sanitize_paystack_keys($key)
|
||||
{
|
||||
return preg_replace('/[^a-zA-Z0-9_]/', '', $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate amount
|
||||
*/
|
||||
function validate_paystack_amount($amount)
|
||||
{
|
||||
return is_numeric($amount) && $amount > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt sensitive data
|
||||
*/
|
||||
function encrypt_paystack_data($data)
|
||||
{
|
||||
$CI = &get_instance();
|
||||
$CI->load->library('encryption');
|
||||
return $CI->encryption->encrypt($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt sensitive data
|
||||
*/
|
||||
function decrypt_paystack_data($data)
|
||||
{
|
||||
$CI = &get_instance();
|
||||
$CI->load->library('encryption');
|
||||
return $CI->encryption->decrypt($data);
|
||||
}
|
||||
71
install.php
Normal file
71
install.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
// Paystack transactions table
|
||||
if (!$CI->db->table_exists(db_prefix() . 'paystack_transactions')) {
|
||||
$CI->db->query('CREATE TABLE `' . db_prefix() . 'paystack_transactions` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`invoice_id` int(11) NOT NULL,
|
||||
`reference` varchar(100) NOT NULL,
|
||||
`email` varchar(100) NOT NULL,
|
||||
`amount` decimal(15,2) NOT NULL,
|
||||
`status` varchar(20) DEFAULT NULL,
|
||||
`transaction_date` datetime NOT NULL,
|
||||
`date_created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `invoice_id` (`invoice_id`),
|
||||
KEY `reference` (`reference`),
|
||||
KEY `status` (`status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=' . $CI->db->char_set . ';');
|
||||
}
|
||||
|
||||
// Paystack payment logs table
|
||||
if (!$CI->db->table_exists(db_prefix() . 'paystack_payment_logs')) {
|
||||
$CI->db->query('CREATE TABLE `' . db_prefix() . 'paystack_payment_logs` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`transaction_id` int(11) DEFAULT NULL,
|
||||
`invoice_id` int(11) DEFAULT NULL,
|
||||
`amount` decimal(15,2) DEFAULT NULL,
|
||||
`message` text,
|
||||
`log_type` varchar(50) DEFAULT NULL,
|
||||
`ip_address` varchar(45) DEFAULT NULL,
|
||||
`user_agent` text,
|
||||
`date_created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `transaction_id` (`transaction_id`),
|
||||
KEY `invoice_id` (`invoice_id`),
|
||||
KEY `log_type` (`log_type`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=' . $CI->db->char_set . ';');
|
||||
}
|
||||
|
||||
// Paystack webhook logs table
|
||||
if (!$CI->db->table_exists(db_prefix() . 'paystack_webhook_logs')) {
|
||||
$CI->db->query('CREATE TABLE `' . db_prefix() . 'paystack_webhook_logs` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`event` varchar(100) NOT NULL,
|
||||
`payload` text,
|
||||
`status` varchar(20) DEFAULT NULL,
|
||||
`ip_address` varchar(45) DEFAULT NULL,
|
||||
`date_created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `event` (`event`),
|
||||
KEY `status` (`status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=' . $CI->db->char_set . ';');
|
||||
}
|
||||
|
||||
// Add payment gateway options if they don't exist
|
||||
$options = [
|
||||
['name' => 'paystack_test_mode', 'value' => '1'],
|
||||
['name' => 'paystack_live_secret_key', 'value' => ''],
|
||||
['name' => 'paystack_live_public_key', 'value' => ''],
|
||||
['name' => 'paystack_test_secret_key', 'value' => ''],
|
||||
['name' => 'paystack_test_public_key', 'value' => ''],
|
||||
['name' => 'paystack_webhook_secret', 'value' => '']
|
||||
];
|
||||
|
||||
foreach ($options as $option) {
|
||||
if (!get_option($option['name'])) {
|
||||
add_option($option['name'], $option['value']);
|
||||
}
|
||||
}
|
||||
127
language/english/paystack_lang.php
Normal file
127
language/english/paystack_lang.php
Normal file
@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
# Gateway Name
|
||||
$lang['paystack'] = 'Paystack';
|
||||
$lang['paystack_gateway_name'] = 'Paystack Payment Gateway';
|
||||
|
||||
# Settings
|
||||
$lang['settings_paystack_secret_key'] = 'Secret Key';
|
||||
$lang['settings_paystack_public_key'] = 'Public Key';
|
||||
$lang['settings_paystack_test_mode_enabled'] = 'Test Mode';
|
||||
$lang['settings_group_paystack'] = 'Paystack Settings';
|
||||
|
||||
# Messages
|
||||
$lang['payment_received'] = 'Payment received successfully';
|
||||
$lang['payment_failed'] = 'Payment failed';
|
||||
$lang['payment_cancelled'] = 'Payment cancelled';
|
||||
|
||||
# Errors
|
||||
$lang['paystack_invalid_transaction'] = 'Invalid transaction';
|
||||
$lang['paystack_transaction_not_found'] = 'Transaction not found';
|
||||
$lang['paystack_invalid_reference'] = 'Invalid reference';
|
||||
$lang['paystack_api_error'] = 'API Error: %s';
|
||||
|
||||
|
||||
# Gateway Settings
|
||||
$lang['paystack'] = 'Paystack';
|
||||
$lang['paystack_gateway_name'] = 'Paystack Payment Gateway';
|
||||
$lang['paystack_live_secret_key'] = 'Live Secret Key';
|
||||
$lang['paystack_live_public_key'] = 'Live Public Key';
|
||||
$lang['paystack_test_secret_key'] = 'Test Secret Key';
|
||||
$lang['paystack_test_public_key'] = 'Test Public Key';
|
||||
$lang['test_mode_enabled'] = 'Test Mode Enabled';
|
||||
$lang['webhook_secret'] = 'Webhook Secret';
|
||||
$lang['max_retry_attempts'] = 'Maximum Retry Attempts';
|
||||
|
||||
# Payment Status
|
||||
$lang['payment_successful'] = 'Payment Successful';
|
||||
$lang['payment_failed'] = 'Payment Failed';
|
||||
$lang['payment_pending'] = 'Payment Pending';
|
||||
$lang['payment_cancelled'] = 'Payment Cancelled';
|
||||
$lang['payment_processing'] = 'Processing Payment';
|
||||
$lang['payment_validation_failed'] = 'Payment Validation Failed';
|
||||
|
||||
# Error Messages
|
||||
$lang['invalid_transaction'] = 'Invalid Transaction';
|
||||
$lang['invalid_amount'] = 'Invalid Amount';
|
||||
$lang['invalid_currency'] = 'Invalid Currency';
|
||||
$lang['invalid_email'] = 'Invalid Email';
|
||||
$lang['invalid_reference'] = 'Invalid Reference';
|
||||
$lang['transaction_failed'] = 'Transaction Failed';
|
||||
$lang['transaction_expired'] = 'Transaction Expired';
|
||||
$lang['rate_limit_exceeded'] = 'Too many attempts. Please try again later.';
|
||||
$lang['api_error'] = 'API Error: %s';
|
||||
$lang['verification_failed'] = 'Transaction Verification Failed';
|
||||
$lang['webhook_verification_failed'] = 'Webhook Verification Failed';
|
||||
|
||||
# Success Messages
|
||||
$lang['payment_recorded'] = 'Payment Recorded Successfully';
|
||||
$lang['webhook_received'] = 'Webhook Received Successfully';
|
||||
$lang['verification_successful'] = 'Verification Successful';
|
||||
|
||||
# Transaction Details
|
||||
$lang['transaction_reference'] = 'Transaction Reference';
|
||||
$lang['total_transactions'] = 'Total Transaction';
|
||||
$lang['transaction_amount'] = 'Amount';
|
||||
$lang['transaction_date'] = 'Transaction Date';
|
||||
$lang['transaction_status'] = 'Status';
|
||||
$lang['transaction_currency'] = 'Currency';
|
||||
$lang['transaction_email'] = 'Email';
|
||||
$lang['recent_transactions'] = 'Recent transactions';
|
||||
$lang['monthly_transactions'] = 'Monthly transactions';
|
||||
$lang['successful_transactions'] = 'Successful Transactions';
|
||||
$lang['failed_transactions'] = 'Failed Transactions';
|
||||
|
||||
# Admin Area
|
||||
$lang['paystack_settings'] = 'Paystack Settings';
|
||||
$lang['test_mode_warning'] = 'Gateway is in Test Mode. Real payments will not be processed.';
|
||||
$lang['settings_updated'] = 'Settings Updated Successfully';
|
||||
$lang['view_transaction'] = 'View Transaction';
|
||||
$lang['retry_payment'] = 'Retry Payment';
|
||||
$lang['gateway_mode'] = 'Gateway Mode';
|
||||
$lang['live_mode'] = 'Live Mode';
|
||||
$lang['test_mode'] = 'Test Mode';
|
||||
|
||||
# Logging
|
||||
$lang['payment_attempt_logged'] = 'Payment Attempt Logged';
|
||||
$lang['api_request_logged'] = 'API Request Logged';
|
||||
$lang['error_logged'] = 'Error Logged';
|
||||
$lang['view_logs'] = 'View Logs';
|
||||
$lang['clear_logs'] = 'Clear Logs';
|
||||
|
||||
# Webhook
|
||||
$lang['webhook_url'] = 'Webhook URL';
|
||||
$lang['webhook_setup_guide'] = 'Webhook Setup Guide';
|
||||
$lang['configure_webhook'] = 'Configure Webhook';
|
||||
$lang['test_webhook'] = 'Test Webhook';
|
||||
|
||||
# Security
|
||||
$lang['security_settings'] = 'Security Settings';
|
||||
$lang['encryption_key'] = 'Encryption Key';
|
||||
$lang['rate_limiting'] = 'Rate Limiting';
|
||||
$lang['attempts_allowed'] = 'Attempts Allowed';
|
||||
$lang['timeframe_minutes'] = 'Timeframe (Minutes)';
|
||||
|
||||
# Documentation
|
||||
$lang['integration_guide'] = 'Integration Guide';
|
||||
$lang['api_documentation'] = 'API Documentation';
|
||||
$lang['troubleshooting_guide'] = 'Troubleshooting Guide';
|
||||
|
||||
# Buttons
|
||||
$lang['pay_with_paystack'] = 'Pay with Paystack';
|
||||
$lang['verify_payment'] = 'Verify Payment';
|
||||
$lang['retry_verification'] = 'Retry Verification';
|
||||
$lang['cancel_payment'] = 'Cancel Payment';
|
||||
|
||||
# Misc
|
||||
$lang['paystack_not_configured'] = 'Paystack Gateway is not properly configured';
|
||||
$lang['contact_support'] = 'Contact Support';
|
||||
$lang['view_transaction_history'] = 'View Transaction History';
|
||||
$lang['export_transactions'] = 'Export Transactions';
|
||||
$lang['sync_transactions'] = 'Sync Transactions';
|
||||
$lang['paystack_transactions'] = 'Paystack Transactions';
|
||||
$lang['paystack_dashboard'] = 'Dashboard';
|
||||
$lang['dashboard'] = 'Dashboard';
|
||||
$lang['transactions'] = 'Transactions';
|
||||
$lang['start_date'] = 'Start Date';
|
||||
$lang['end_date'] = 'End Date';
|
||||
112
libraries/Paystack_error_handler.php
Normal file
112
libraries/Paystack_error_handler.php
Normal file
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
class Paystack_error_handler
|
||||
{
|
||||
protected $CI;
|
||||
protected $errors = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->CI = &get_instance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle API errors
|
||||
*/
|
||||
public function handle_api_error($response, $context = '')
|
||||
{
|
||||
$error_data = [
|
||||
'type' => 'api_error',
|
||||
'context' => $context,
|
||||
'message' => isset($response->message) ? $response->message : 'Unknown API error',
|
||||
'code' => isset($response->code) ? $response->code : null,
|
||||
'timestamp' => date('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
$this->log_error($error_data);
|
||||
return $error_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle validation errors
|
||||
*/
|
||||
public function handle_validation_error($errors, $context = '')
|
||||
{
|
||||
$error_data = [
|
||||
'type' => 'validation_error',
|
||||
'context' => $context,
|
||||
'message' => is_array($errors) ? implode(', ', $errors) : $errors,
|
||||
'timestamp' => date('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
$this->log_error($error_data);
|
||||
return $error_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle system errors
|
||||
*/
|
||||
public function handle_system_error($exception, $context = '')
|
||||
{
|
||||
$error_data = [
|
||||
'type' => 'system_error',
|
||||
'context' => $context,
|
||||
'message' => $exception->getMessage(),
|
||||
'file' => $exception->getFile(),
|
||||
'line' => $exception->getLine(),
|
||||
'trace' => $exception->getTraceAsString(),
|
||||
'timestamp' => date('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
$this->log_error($error_data);
|
||||
return $error_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log error
|
||||
*/
|
||||
protected function log_error($error_data)
|
||||
{
|
||||
// Add to errors array
|
||||
$this->errors[] = $error_data;
|
||||
|
||||
// Log to database
|
||||
$this->CI->load->model('paystack_model');
|
||||
$log_data = [
|
||||
'message' => json_encode($error_data),
|
||||
'log_type' => 'error'
|
||||
];
|
||||
$this->CI->paystack_model->add_payment_log($log_data);
|
||||
|
||||
// Log to system log if serious error
|
||||
if ($error_data['type'] === 'system_error') {
|
||||
log_message('error', 'Paystack Error: ' . $error_data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last error
|
||||
*/
|
||||
public function get_last_error()
|
||||
{
|
||||
return end($this->errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all errors
|
||||
*/
|
||||
public function get_all_errors()
|
||||
{
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear errors
|
||||
*/
|
||||
public function clear_errors()
|
||||
{
|
||||
$this->errors = [];
|
||||
}
|
||||
}
|
||||
378
libraries/Paystack_gateway.php
Normal file
378
libraries/Paystack_gateway.php
Normal file
@ -0,0 +1,378 @@
|
||||
<?php
|
||||
|
||||
defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
class Paystack_gateway extends App_gateway
|
||||
{
|
||||
protected $test_mode;
|
||||
protected $api_url = 'https://api.paystack.co/';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->test_mode = get_option('test_mode_enabled');
|
||||
|
||||
// Load the model using the full module path
|
||||
$this->ci->load->model('paystack/paystack_model');
|
||||
|
||||
$this->setId('paystack');
|
||||
$this->setName('Paystack');
|
||||
|
||||
/**
|
||||
* Enhanced settings with additional security options
|
||||
*/
|
||||
$this->setSettings([
|
||||
[
|
||||
'name' => 'paystack_secret_key',
|
||||
'encrypted' => true,
|
||||
'label' => 'Paystack Secret Key',
|
||||
'type' => 'input'
|
||||
],
|
||||
[
|
||||
'name' => 'paystack_public_key',
|
||||
'label' => 'Paystack Public Key',
|
||||
'type' => 'input'
|
||||
],
|
||||
[
|
||||
'name' => 'test_mode_enabled',
|
||||
'label' => 'Test Mode',
|
||||
'type' => 'yes_no',
|
||||
'default_value' => 1
|
||||
],
|
||||
[
|
||||
'name' => 'currencies',
|
||||
'label' => 'settings_paymentmethod_currencies',
|
||||
'default_value' => 'NGN,USD,GHS,ZAR'
|
||||
],
|
||||
[
|
||||
'name' => 'webhook_secret',
|
||||
'encrypted' => true,
|
||||
'label' => 'Webhook Secret',
|
||||
'type' => 'input'
|
||||
],
|
||||
[
|
||||
'name' => 'max_retry_attempts',
|
||||
'label' => 'Maximum Retry Attempts',
|
||||
'type' => 'input',
|
||||
'default_value' => '3'
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the payment with validation and security checks
|
||||
*/
|
||||
public function process_payment($data)
|
||||
{
|
||||
try {
|
||||
// Log the payment data for debugging
|
||||
$this->ci->paystack_model->add_payment_log([
|
||||
'invoice_id' => $data['invoiceid'],
|
||||
'amount' => $data['amount'],
|
||||
'message' => 'Payment process initiated',
|
||||
'log_type' => 'debug'
|
||||
]);
|
||||
|
||||
// Generate payment reference
|
||||
$reference = 'INV_' . $data['invoiceid'] . '_' . time();
|
||||
|
||||
// Store transaction data
|
||||
$transaction_data = [
|
||||
'invoice_id' => $data['invoiceid'],
|
||||
'reference' => $reference,
|
||||
'email' => $data['client']->email,
|
||||
'amount' => $data['amount'],
|
||||
'status' => 'pending',
|
||||
'transaction_date' => date('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
$this->ci->db->insert(db_prefix() . 'paystack_transactions', $transaction_data);
|
||||
|
||||
// Return HTML for the payment form
|
||||
return $this->generate_payment_form($data, $reference);
|
||||
|
||||
} catch (Exception $e) {
|
||||
// Log any errors
|
||||
$this->ci->paystack_model->add_payment_log([
|
||||
'invoice_id' => $data['invoiceid'],
|
||||
'message' => 'Error: ' . $e->getMessage(),
|
||||
'log_type' => 'error'
|
||||
]);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function generate_payment_form($data, $reference)
|
||||
{
|
||||
$public_key = $this->getSetting('paystack_public_key');
|
||||
|
||||
$form = '
|
||||
<div id="paystack-payment-form">
|
||||
<script src="https://js.paystack.co/v1/inline.js"></script>
|
||||
<button type="button" class="btn btn-success" onclick="payWithPaystack()">Pay with Paystack</button>
|
||||
<script>
|
||||
function payWithPaystack() {
|
||||
let handler = PaystackPop.setup({
|
||||
key: "' . $public_key . '",
|
||||
email: "' . $data['client']->email . '",
|
||||
amount: ' . ($data['amount'] * 100) . ', // Convert to kobo
|
||||
currency: "' . $data['currency'] . '",
|
||||
ref: "' . $reference . '",
|
||||
callback: function(response) {
|
||||
if(response.status == "success") {
|
||||
window.location.href = "' . site_url('paystack/verify_payment/') . '" + response.reference;
|
||||
}
|
||||
},
|
||||
onClose: function() {
|
||||
// Handle popup closed
|
||||
console.log("Payment window closed");
|
||||
}
|
||||
});
|
||||
handler.openIframe();
|
||||
}
|
||||
</script>
|
||||
</div>';
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record payment with additional validation
|
||||
*/
|
||||
public function record_payment($invoice_id, $transaction)
|
||||
{
|
||||
// Validate transaction data
|
||||
if (!$this->validate_transaction_data($transaction)) {
|
||||
$this->log_error('Invalid transaction data for payment recording');
|
||||
return false;
|
||||
}
|
||||
|
||||
$payment_data = [
|
||||
'amount' => $transaction['data']->amount / 100,
|
||||
'invoiceid' => $invoice_id,
|
||||
'paymentmode' => $this->getId(),
|
||||
'transactionid' => $transaction['data']->reference,
|
||||
'note' => 'Paystack Transaction Reference: ' . $transaction['data']->reference
|
||||
];
|
||||
|
||||
// Record payment
|
||||
$this->ci->load->model('payments_model');
|
||||
$payment_id = $this->ci->payments_model->add($payment_data);
|
||||
|
||||
if ($payment_id) {
|
||||
$this->update_invoice_status($invoice_id);
|
||||
$this->log_success($transaction['data']->reference, $payment_data['amount']);
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->log_error('Failed to record payment for invoice #' . $invoice_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make secure API request
|
||||
*/
|
||||
protected function make_api_request($endpoint, $method = 'GET', $data = null)
|
||||
{
|
||||
$url = $this->api_url . $endpoint;
|
||||
$secret_key = $this->get_secret_key();
|
||||
|
||||
$headers = [
|
||||
'Authorization: Bearer ' . $secret_key,
|
||||
'Cache-Control: no-cache'
|
||||
];
|
||||
|
||||
if ($data && in_array($method, ['POST', 'PUT'])) {
|
||||
$headers[] = 'Content-Type: application/json';
|
||||
}
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_CUSTOMREQUEST => $method,
|
||||
CURLOPT_HTTPHEADER => $headers
|
||||
]);
|
||||
|
||||
if ($data && in_array($method, ['POST', 'PUT'])) {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
|
||||
}
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$error = curl_error($ch);
|
||||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
$this->log_api_request($endpoint, $method, $data, $http_code, $error);
|
||||
|
||||
if ($error) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => $error
|
||||
];
|
||||
}
|
||||
|
||||
$result = json_decode($response);
|
||||
if (!$result || !$result->status) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => isset($result->message) ? $result->message : 'Invalid response'
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => $result
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation methods
|
||||
*/
|
||||
protected function validate_payment_data($data)
|
||||
{
|
||||
$required_fields = ['amount', 'invoiceid', 'currency'];
|
||||
foreach ($required_fields as $field) {
|
||||
if (!isset($data[$field]) || empty($data[$field])) {
|
||||
$this->log_error('Missing required field: ' . $field);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function validate_payment_amount($amount)
|
||||
{
|
||||
return is_numeric($amount) && $amount > 0;
|
||||
}
|
||||
|
||||
protected function validate_payment_currency($currency)
|
||||
{
|
||||
$allowed_currencies = explode(',', get_option('currencies'));
|
||||
return in_array(strtoupper($currency), $allowed_currencies);
|
||||
}
|
||||
|
||||
public function verify_transaction($reference)
|
||||
{
|
||||
$secret_key = $this->decryptSetting('paystack_secret_key');
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => "https://api.paystack.co/transaction/verify/" . rawurlencode($reference),
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Authorization: Bearer " . $secret_key,
|
||||
"Cache-Control: no-cache",
|
||||
],
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$err = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($err) {
|
||||
$this->ci->paystack_model->add_payment_log([
|
||||
'message' => 'Verification Error: ' . $err,
|
||||
'log_type' => 'error'
|
||||
]);
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => $err
|
||||
];
|
||||
}
|
||||
|
||||
$result = json_decode($response);
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => $result->data
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Security methods
|
||||
*/
|
||||
protected function encrypt_payment_data($data)
|
||||
{
|
||||
$this->ci->load->library('encryption');
|
||||
return $this->ci->encryption->encrypt(serialize($data));
|
||||
}
|
||||
|
||||
protected function decrypt_payment_data($encrypted_data)
|
||||
{
|
||||
if (!$encrypted_data) return false;
|
||||
|
||||
$this->ci->load->library('encryption');
|
||||
$decrypted = $this->ci->encryption->decrypt($encrypted_data);
|
||||
return $decrypted ? unserialize($decrypted) : false;
|
||||
}
|
||||
|
||||
protected function get_secret_key()
|
||||
{
|
||||
$key_option = $this->test_mode ? 'paystack_test_secret_key' : 'paystack_live_secret_key';
|
||||
return $this->decryptSetting($key_option);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rate limiting
|
||||
*/
|
||||
protected function check_rate_limit($invoice_id)
|
||||
{
|
||||
$max_attempts = get_option('max_retry_attempts');
|
||||
$timeframe = 15; // minutes
|
||||
|
||||
$this->ci->load->model('paystack_model');
|
||||
$attempts = $this->ci->paystack_model->get_recent_failed_attempts($invoice_id, $timeframe);
|
||||
|
||||
return $attempts < $max_attempts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logging methods
|
||||
*/
|
||||
protected function log_payment_attempt($invoice_id, $amount)
|
||||
{
|
||||
$this->ci->load->model('paystack_model');
|
||||
$this->ci->paystack_model->add_payment_log([
|
||||
'invoice_id' => $invoice_id,
|
||||
'amount' => $amount,
|
||||
'log_type' => 'attempt',
|
||||
'message' => 'Payment attempt initiated'
|
||||
]);
|
||||
}
|
||||
|
||||
protected function log_api_request($endpoint, $method, $data, $http_code, $error = null)
|
||||
{
|
||||
$this->ci->load->model('paystack_model');
|
||||
$this->ci->paystack_model->add_payment_log([
|
||||
'log_type' => 'api_request',
|
||||
'message' => json_encode([
|
||||
'endpoint' => $endpoint,
|
||||
'method' => $method,
|
||||
'http_code' => $http_code,
|
||||
'error' => $error
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
protected function log_success($reference, $amount)
|
||||
{
|
||||
$this->ci->load->model('paystack_model');
|
||||
$this->ci->paystack_model->add_payment_log([
|
||||
'log_type' => 'success',
|
||||
'message' => "Payment successful - Reference: $reference, Amount: $amount"
|
||||
]);
|
||||
}
|
||||
|
||||
protected function log_error($message)
|
||||
{
|
||||
$this->ci->load->model('paystack_model');
|
||||
$this->ci->paystack_model->add_payment_log([
|
||||
'log_type' => 'error',
|
||||
'message' => $message
|
||||
]);
|
||||
}
|
||||
}
|
||||
210
models/Paystack_model.php
Normal file
210
models/Paystack_model.php
Normal file
@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
class Paystack_model extends App_Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all transactions
|
||||
*/
|
||||
public function get_transactions($filter = [])
|
||||
{
|
||||
$this->db->select('*');
|
||||
$this->db->from(db_prefix() . 'paystack_transactions');
|
||||
|
||||
if (isset($filter['start_date']) && $filter['start_date']) {
|
||||
$this->db->where('date_created >=', $filter['start_date'] . ' 00:00:00');
|
||||
}
|
||||
|
||||
if (isset($filter['end_date']) && $filter['end_date']) {
|
||||
$this->db->where('date_created <=', $filter['end_date'] . ' 23:59:59');
|
||||
}
|
||||
|
||||
$this->db->order_by('date_created', 'desc');
|
||||
return $this->db->get()->result_array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get payment statistics
|
||||
*/
|
||||
public function get_payment_stats()
|
||||
{
|
||||
$stats = [
|
||||
'total_transactions' => 0,
|
||||
'successful_transactions' => 0,
|
||||
'failed_transactions' => 0,
|
||||
'total_amount' => 0,
|
||||
'success_rate' => 0
|
||||
];
|
||||
|
||||
// Total transactions
|
||||
$this->db->select('COUNT(*) as total');
|
||||
$this->db->from(db_prefix() . 'paystack_transactions');
|
||||
$stats['total_transactions'] = $this->db->get()->row()->total;
|
||||
|
||||
// Successful transactions
|
||||
$this->db->select('COUNT(*) as total, SUM(amount) as amount');
|
||||
$this->db->where('status', 'success');
|
||||
$this->db->from(db_prefix() . 'paystack_transactions');
|
||||
$result = $this->db->get()->row();
|
||||
$stats['successful_transactions'] = $result->total;
|
||||
$stats['total_amount'] = $result->amount;
|
||||
|
||||
// Failed transactions
|
||||
$this->db->select('COUNT(*) as total');
|
||||
$this->db->where('status', 'failed');
|
||||
$this->db->from(db_prefix() . 'paystack_transactions');
|
||||
$stats['failed_transactions'] = $this->db->get()->row()->total;
|
||||
|
||||
// Calculate success rate
|
||||
if ($stats['total_transactions'] > 0) {
|
||||
$stats['success_rate'] = ($stats['successful_transactions'] / $stats['total_transactions']) * 100;
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get monthly chart data
|
||||
*/
|
||||
public function get_monthly_chart_data()
|
||||
{
|
||||
$months = [];
|
||||
for ($m = 11; $m >= 0; $m--) {
|
||||
$months[date('Y-m', strtotime("-$m months"))] = [
|
||||
'successful' => 0,
|
||||
'failed' => 0,
|
||||
'amount' => 0
|
||||
];
|
||||
}
|
||||
|
||||
$this->db->select('DATE_FORMAT(date_created, "%Y-%m") as month, status, COUNT(*) as total, SUM(amount) as amount');
|
||||
$this->db->from(db_prefix() . 'paystack_transactions');
|
||||
$this->db->where('date_created >= DATE_SUB(NOW(), INTERVAL 12 MONTH)');
|
||||
$this->db->group_by('month, status');
|
||||
$results = $this->db->get()->result_array();
|
||||
|
||||
foreach ($results as $result) {
|
||||
if (isset($months[$result['month']])) {
|
||||
if ($result['status'] == 'success') {
|
||||
$months[$result['month']]['successful'] = $result['total'];
|
||||
$months[$result['month']]['amount'] = $result['amount'];
|
||||
} else {
|
||||
$months[$result['month']]['failed'] = $result['total'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $months;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recent transactions
|
||||
*/
|
||||
public function get_recent_transactions($limit = 10)
|
||||
{
|
||||
$this->db->select('t.*, i.number as invoice_number');
|
||||
$this->db->from(db_prefix() . 'paystack_transactions t');
|
||||
$this->db->join(db_prefix() . 'invoices i', 'i.id = t.invoice_id', 'left');
|
||||
$this->db->order_by('t.date_created', 'desc');
|
||||
$this->db->limit($limit);
|
||||
return $this->db->get()->result_array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add transaction log
|
||||
*/
|
||||
public function add_log($data)
|
||||
{
|
||||
$this->db->insert(db_prefix() . 'paystack_payment_logs', $data);
|
||||
return $this->db->insert_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transaction by reference
|
||||
*/
|
||||
public function get_transaction_by_reference($reference)
|
||||
{
|
||||
$this->db->where('reference', $reference);
|
||||
return $this->db->get(db_prefix() . 'paystack_transactions')->row_array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add webhook log
|
||||
*/
|
||||
public function add_webhook_log($event, $payload, $status)
|
||||
{
|
||||
return $this->db->insert(db_prefix() . 'paystack_webhook_logs', [
|
||||
'event' => $event,
|
||||
'payload' => json_encode($payload),
|
||||
'status' => $status
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update settings
|
||||
*/
|
||||
public function update_settings($data)
|
||||
{
|
||||
foreach ($data as $key => $value) {
|
||||
update_option($key, $value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recent failed attempts
|
||||
*/
|
||||
public function get_recent_failed_attempts($invoice_id, $timeframe)
|
||||
{
|
||||
$this->db->where('invoice_id', $invoice_id);
|
||||
$this->db->where('log_type', 'failed_attempt');
|
||||
$this->db->where('date_created >=', date('Y-m-d H:i:s', strtotime("-$timeframe minutes")));
|
||||
return $this->db->count_all_results(db_prefix() . 'paystack_payment_logs');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add payment log with enhanced details
|
||||
*/
|
||||
public function add_payment_log($data)
|
||||
{
|
||||
// Ensure required fields
|
||||
$data['date_created'] = date('Y-m-d H:i:s');
|
||||
|
||||
// Add client IP if available
|
||||
if (!isset($data['ip_address']) && isset($_SERVER['REMOTE_ADDR'])) {
|
||||
$data['ip_address'] = $_SERVER['REMOTE_ADDR'];
|
||||
}
|
||||
|
||||
// Add user agent if available
|
||||
if (!isset($data['user_agent']) && isset($_SERVER['HTTP_USER_AGENT'])) {
|
||||
$data['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
|
||||
}
|
||||
|
||||
return $this->db->insert(db_prefix() . 'paystack_payment_logs', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transaction logs
|
||||
*/
|
||||
public function get_transaction_logs($transaction_id)
|
||||
{
|
||||
$this->db->where('transaction_id', $transaction_id);
|
||||
$this->db->order_by('date_created', 'desc');
|
||||
return $this->db->get(db_prefix() . 'paystack_payment_logs')->result_array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean old logs
|
||||
*/
|
||||
public function clean_old_logs($days = 90)
|
||||
{
|
||||
$this->db->where('date_created <', date('Y-m-d H:i:s', strtotime("-$days days")));
|
||||
return $this->db->delete(db_prefix() . 'paystack_payment_logs');
|
||||
}
|
||||
}
|
||||
84
paystack.php
Normal file
84
paystack.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
defined('BASEPATH') or exit('No direct script access allowed');
|
||||
|
||||
/*
|
||||
Module Name: Paystack Payment Gateway
|
||||
Description: Payment gateway integration for processing payments through Paystack
|
||||
Version: 1.0.0
|
||||
Requires at least: 2.3.4
|
||||
Author: [Your Name]
|
||||
Author URI: [Your Website]
|
||||
*/
|
||||
|
||||
define('PAYSTACK_MODULE_NAME', 'PAYSTACK');
|
||||
|
||||
/**
|
||||
* Activation hook
|
||||
*/
|
||||
function paystack_activation_hook()
|
||||
{
|
||||
$CI = &get_instance();
|
||||
require_once(__DIR__ . '/install.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add menu items for Paystack
|
||||
* @return null
|
||||
*/
|
||||
function paystack_init_menu_items()
|
||||
{
|
||||
$CI = &get_instance();
|
||||
|
||||
// Main Menu Item
|
||||
if (has_permission('payments', '', 'view')) {
|
||||
$CI->app_menu->add_sidebar_menu_item('paystack', [
|
||||
'name' => 'Paystack',
|
||||
'position' => 30,
|
||||
'icon' => 'fa fa-credit-card',
|
||||
]);
|
||||
|
||||
// Sub Menu Items
|
||||
$CI->app_menu->add_sidebar_children_item('paystack', [
|
||||
'slug' => 'paystack-dashboard',
|
||||
'name' => _l('dashboard'),
|
||||
'href' => admin_url('paystack/admin/dashboard'),
|
||||
'position' => 1,
|
||||
]);
|
||||
|
||||
$CI->app_menu->add_sidebar_children_item('paystack', [
|
||||
'slug' => 'paystack-transactions',
|
||||
'name' => _l('transactions'),
|
||||
'href' => admin_url('paystack/admin/transactions'),
|
||||
'position' => 5,
|
||||
]);
|
||||
}
|
||||
|
||||
// Settings Menu Item
|
||||
if (has_permission('settings', '', 'view')) {
|
||||
$CI->app_menu->add_setup_menu_item('paystack-settings', [
|
||||
'name' => _l('paystack_settings'),
|
||||
'href' => admin_url('paystack/admin/settings'),
|
||||
'position' => 65,
|
||||
'icon' => 'fa fa-credit-card',
|
||||
]);
|
||||
|
||||
$CI->app_menu->add_setup_menu_item('paystack-test-mode', [
|
||||
'parent' => 'paystack-settings',
|
||||
'name' => _l('test_mode'),
|
||||
'href' => admin_url('paystack/admin/test_mode'),
|
||||
'position' => 5,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Register activation hook
|
||||
register_activation_hook('paystack', 'paystack_activation_hook');
|
||||
|
||||
// Register payment gateway
|
||||
register_payment_gateway('paystack_gateway', 'paystack');
|
||||
|
||||
// Add menu items
|
||||
hooks()->add_action('admin_init', 'paystack_init_menu_items');
|
||||
|
||||
register_language_files(PAYSTACK_MODULE_NAME, [PAYSTACK_MODULE_NAME]);
|
||||
178
views/admin/dashboard.php
Normal file
178
views/admin/dashboard.php
Normal file
@ -0,0 +1,178 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed'); ?>
|
||||
<?php init_head(); ?>
|
||||
<div id="wrapper">
|
||||
<div class="content">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="panel_s">
|
||||
<div class="panel-body">
|
||||
<h4 class="no-margin"><?php echo _l('paystack_dashboard'); ?></h4>
|
||||
<hr class="hr-panel-heading" />
|
||||
|
||||
<!-- Stats Widgets -->
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div class="widget-box">
|
||||
<div class="widget-header">
|
||||
<h3><?php echo _l('total_transactions'); ?></h3>
|
||||
</div>
|
||||
<div class="widget-content">
|
||||
<h3 class="bold"><?php echo $stats['total_transactions']; ?></h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="widget-box success-bg">
|
||||
<div class="widget-header">
|
||||
<h3><?php echo _l('successful_transactions'); ?></h3>
|
||||
</div>
|
||||
<div class="widget-content">
|
||||
<h3 class="bold"><?php echo $stats['successful_transactions']; ?></h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="widget-box warning-bg">
|
||||
<div class="widget-header">
|
||||
<h3><?php echo _l('failed_transactions'); ?></h3>
|
||||
</div>
|
||||
<div class="widget-content">
|
||||
<h3 class="bold"><?php echo $stats['failed_transactions']; ?></h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="widget-box info-bg">
|
||||
<div class="widget-header">
|
||||
<h3><?php echo _l('total_amount'); ?></h3>
|
||||
</div>
|
||||
<div class="widget-content">
|
||||
<h3 class="bold"><?php echo app_format_money($stats['total_amount'], get_base_currency()); ?></h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mtop20">
|
||||
<!-- Monthly Chart -->
|
||||
<div class="col-md-8">
|
||||
<div class="panel_s">
|
||||
<div class="panel-body">
|
||||
<h4 class="no-margin"><?php echo _l('monthly_transactions'); ?></h4>
|
||||
<hr class="hr-panel-heading" />
|
||||
<canvas id="monthly-chart" height="300"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Success Rate -->
|
||||
<div class="col-md-4">
|
||||
<div class="panel_s">
|
||||
<div class="panel-body">
|
||||
<h4 class="no-margin"><?php echo _l('success_rate'); ?></h4>
|
||||
<hr class="hr-panel-heading" />
|
||||
<canvas id="success-rate-chart" height="300"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Transactions -->
|
||||
<div class="panel_s mtop20">
|
||||
<div class="panel-body">
|
||||
<h4 class="no-margin"><?php echo _l('recent_transactions'); ?></h4>
|
||||
<hr class="hr-panel-heading" />
|
||||
<table class="table dt-table table-recent-transactions">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php echo _l('date'); ?></th>
|
||||
<th><?php echo _l('reference'); ?></th>
|
||||
<th><?php echo _l('invoice_number'); ?></th>
|
||||
<th><?php echo _l('amount'); ?></th>
|
||||
<th><?php echo _l('status'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach($recent_transactions as $transaction) { ?>
|
||||
<tr>
|
||||
<td><?php echo _dt($transaction['date']); ?></td>
|
||||
<td><?php echo $transaction['reference']; ?></td>
|
||||
<td>
|
||||
<a href="<?php echo admin_url('invoices/list_invoices/'.$transaction['invoice_id']); ?>">
|
||||
<?php echo format_invoice_number($transaction['invoice_number']); ?>
|
||||
</a>
|
||||
</td>
|
||||
<td><?php echo app_format_money($transaction['amount'], get_base_currency()); ?></td>
|
||||
<td>
|
||||
<span class="label label-<?php echo $transaction['status'] == 'success' ? 'success' : 'danger'; ?>">
|
||||
<?php echo _l($transaction['status']); ?>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php init_tail(); ?>
|
||||
<script>
|
||||
// Monthly Chart
|
||||
var ctx = document.getElementById('monthly-chart').getContext('2d');
|
||||
var monthlyChart = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: <?php echo json_encode(array_keys($monthly_chart)); ?>,
|
||||
datasets: [{
|
||||
label: '<?php echo _l("successful_transactions"); ?>',
|
||||
data: <?php echo json_encode(array_column($monthly_chart, 'successful')); ?>,
|
||||
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
||||
borderColor: 'rgba(75, 192, 192, 1)',
|
||||
borderWidth: 1
|
||||
},
|
||||
{
|
||||
label: '<?php echo _l("failed_transactions"); ?>',
|
||||
data: <?php echo json_encode(array_column($monthly_chart, 'failed')); ?>,
|
||||
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
||||
borderColor: 'rgba(255, 99, 132, 1)',
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Success Rate Chart
|
||||
var ctx2 = document.getElementById('success-rate-chart').getContext('2d');
|
||||
var successRateChart = new Chart(ctx2, {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: ['<?php echo _l("successful"); ?>', '<?php echo _l("failed"); ?>'],
|
||||
datasets: [{
|
||||
data: [
|
||||
<?php echo $stats['successful_transactions']; ?>,
|
||||
<?php echo $stats['failed_transactions']; ?>
|
||||
],
|
||||
backgroundColor: [
|
||||
'rgba(75, 192, 192, 0.2)',
|
||||
'rgba(255, 99, 132, 0.2)'
|
||||
],
|
||||
borderColor: [
|
||||
'rgba(75, 192, 192, 1)',
|
||||
'rgba(255, 99, 132, 1)'
|
||||
],
|
||||
borderWidth: 1
|
||||
}]
|
||||
}
|
||||
});
|
||||
</script>
|
||||
104
views/admin/settings.php
Normal file
104
views/admin/settings.php
Normal file
@ -0,0 +1,104 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed'); ?>
|
||||
<?php init_head(); ?>
|
||||
<div id="wrapper">
|
||||
<div class="content">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="panel_s">
|
||||
<div class="panel-body">
|
||||
<?php echo form_open(admin_url('paystack/admin/settings')); ?>
|
||||
<div class="tab-content mtop15">
|
||||
<!-- Settings Tab -->
|
||||
<div class="tab-pane<?php if($tab == 'settings'){echo ' active';} ?>" id="settings">
|
||||
<div class="form-group">
|
||||
<label for="paystack_live_public_key"><?php echo _l('live_public_key'); ?></label>
|
||||
<input type="text" class="form-control" id="paystack_live_public_key"
|
||||
name="paystack_live_public_key"
|
||||
value="<?php echo get_option('paystack_live_public_key'); ?>">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="paystack_live_secret_key"><?php echo _l('live_secret_key'); ?></label>
|
||||
<input type="password" class="form-control" id="paystack_live_secret_key"
|
||||
name="paystack_live_secret_key"
|
||||
value="<?php echo get_option('paystack_live_secret_key'); ?>">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="paystack_webhook_secret"><?php echo _l('webhook_secret'); ?></label>
|
||||
<input type="password" class="form-control" id="paystack_webhook_secret"
|
||||
name="paystack_webhook_secret"
|
||||
value="<?php echo get_option('paystack_webhook_secret'); ?>">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="paystack_default_description"><?php echo _l('default_description'); ?></label>
|
||||
<textarea class="form-control" id="paystack_default_description"
|
||||
name="paystack_default_description" rows="3"><?php echo get_option('paystack_default_description'); ?></textarea>
|
||||
<small class="text-muted"><?php echo _l('default_description_help'); ?></small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test Mode Tab -->
|
||||
<div class="tab-pane<?php if($tab == 'test_mode'){echo ' active';} ?>" id="test_mode">
|
||||
<div class="form-group">
|
||||
<label for="paystack_test_public_key"><?php echo _l('test_public_key'); ?></label>
|
||||
<input type="text" class="form-control" id="paystack_test_public_key"
|
||||
name="paystack_test_public_key"
|
||||
value="<?php echo get_option('paystack_test_public_key'); ?>">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="paystack_test_secret_key"><?php echo _l('test_secret_key'); ?></label>
|
||||
<input type="password" class="form-control" id="paystack_test_secret_key"
|
||||
name="paystack_test_secret_key"
|
||||
value="<?php echo get_option('paystack_test_secret_key'); ?>">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">
|
||||
<div class="checkbox checkbox-primary">
|
||||
<input type="checkbox" name="paystack_test_mode" id="paystack_test_mode"
|
||||
<?php if(get_option('paystack_test_mode') == 1){echo 'checked';} ?>>
|
||||
<label for="paystack_test_mode"><?php echo _l('enable_test_mode'); ?></label>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<?php echo _l('test_mode_notice'); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-bottom-toolbar text-right">
|
||||
<button type="submit" class="btn btn-primary"><?php echo _l('save'); ?></button>
|
||||
</div>
|
||||
<?php echo form_close(); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php init_tail(); ?>
|
||||
|
||||
<script>
|
||||
$(function() {
|
||||
// Toggle test mode fields
|
||||
$('#paystack_test_mode').on('change', function() {
|
||||
if($(this).is(':checked')) {
|
||||
$('#test_keys').removeClass('hide');
|
||||
} else {
|
||||
$('#test_keys').addClass('hide');
|
||||
}
|
||||
});
|
||||
|
||||
// Copy webhook URL to clipboard
|
||||
$('#webhook_url').on('click', function() {
|
||||
$(this).select();
|
||||
document.execCommand('copy');
|
||||
alert_float('success', '<?php echo _l("webhook_url_copied"); ?>');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
228
views/admin/test_mode.php
Normal file
228
views/admin/test_mode.php
Normal file
@ -0,0 +1,228 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed'); ?>
|
||||
<?php init_head(); ?>
|
||||
<div id="wrapper">
|
||||
<div class="content">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="panel_s">
|
||||
<div class="panel-body">
|
||||
<h4 class="no-margin">
|
||||
<?php echo _l('paystack_test_mode'); ?>
|
||||
<?php if(get_option('paystack_test_mode')): ?>
|
||||
<span class="label label-warning"><?php echo _l('test_mode_enabled'); ?></span>
|
||||
<?php endif; ?>
|
||||
</h4>
|
||||
<hr class="hr-panel-heading" />
|
||||
|
||||
<!-- Test Mode Warning -->
|
||||
<div class="alert alert-warning">
|
||||
<h4><i class="fa fa-warning"></i> <?php echo _l('test_mode_warning'); ?></h4>
|
||||
<?php echo _l('test_mode_description'); ?>
|
||||
</div>
|
||||
|
||||
<!-- Test Transaction -->
|
||||
<div class="panel_s">
|
||||
<div class="panel-body">
|
||||
<h4 class="no-margin"><?php echo _l('test_transaction'); ?></h4>
|
||||
<hr class="hr-panel-heading" />
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="test_amount"><?php echo _l('amount'); ?></label>
|
||||
<input type="number" class="form-control" id="test_amount" value="1000">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="test_email"><?php echo _l('email'); ?></label>
|
||||
<input type="email" class="form-control" id="test_email" value="test@example.com">
|
||||
</div>
|
||||
<button type="button" class="btn btn-info" onclick="initiate_test_payment()">
|
||||
<?php echo _l('initiate_test_payment'); ?>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="alert alert-info">
|
||||
<h4><?php echo _l('test_cards'); ?></h4>
|
||||
<p><strong><?php echo _l('successful_payment'); ?>:</strong></p>
|
||||
<pre>Card Number: 4084 0840 8408 4081
|
||||
Expiry: 01/25
|
||||
CVV: 408</pre>
|
||||
<p><strong><?php echo _l('failed_payment'); ?>:</strong></p>
|
||||
<pre>Card Number: 4084 0840 8408 4080
|
||||
Expiry: 01/25
|
||||
CVV: 408</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test Webhook -->
|
||||
<div class="panel_s">
|
||||
<div class="panel-body">
|
||||
<h4 class="no-margin"><?php echo _l('test_webhook'); ?></h4>
|
||||
<hr class="hr-panel-heading" />
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p><?php echo _l('webhook_url'); ?>:</p>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="webhook_url" readonly
|
||||
value="<?php echo site_url('paystack/webhook'); ?>">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button" onclick="copy_webhook_url()">
|
||||
<i class="fa fa-copy"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="mtop15">
|
||||
<button type="button" class="btn btn-info" onclick="test_webhook()">
|
||||
<?php echo _l('test_webhook_connection'); ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div id="webhook_test_result"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Debug Log -->
|
||||
<div class="panel_s">
|
||||
<div class="panel-body">
|
||||
<h4 class="no-margin"><?php echo _l('debug_log'); ?></h4>
|
||||
<hr class="hr-panel-heading" />
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<textarea class="form-control" id="debug_log" rows="10" readonly></textarea>
|
||||
<div class="mtop15">
|
||||
<button type="button" class="btn btn-danger" onclick="clear_debug_log()">
|
||||
<?php echo _l('clear_log'); ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php init_tail(); ?>
|
||||
<script>
|
||||
// Initiate test payment
|
||||
function initiate_test_payment() {
|
||||
var amount = $('#test_amount').val();
|
||||
var email = $('#test_email').val();
|
||||
|
||||
$.post(admin_url + 'paystack/admin/initiate_test_payment', {
|
||||
amount: amount,
|
||||
email: email
|
||||
}).done(function(response) {
|
||||
var data = JSON.parse(response);
|
||||
if(data.success) {
|
||||
// Open Paystack popup
|
||||
let handler = PaystackPop.setup({
|
||||
key: '<?php echo get_option("paystack_test_public_key"); ?>',
|
||||
email: email,
|
||||
amount: amount * 100,
|
||||
ref: data.reference,
|
||||
callback: function(response) {
|
||||
log_debug('Test payment completed: ' + response.reference);
|
||||
alert_float('success', '<?php echo _l("test_payment_successful"); ?>');
|
||||
},
|
||||
onClose: function() {
|
||||
log_debug('Test payment cancelled');
|
||||
alert_float('warning', '<?php echo _l("test_payment_cancelled"); ?>');
|
||||
}
|
||||
});
|
||||
handler.openIframe();
|
||||
} else {
|
||||
alert_float('danger', data.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Test webhook
|
||||
function test_webhook() {
|
||||
$('#webhook_test_result').html('<div class="alert alert-info"><?php echo _l("testing_webhook"); ?></div>');
|
||||
$.post(admin_url + 'paystack/admin/test_webhook')
|
||||
.done(function(response) {
|
||||
var data = JSON.parse(response);
|
||||
if(data.success) {
|
||||
$('#webhook_test_result').html(
|
||||
'<div class="alert alert-success">' +
|
||||
'<h4><i class="fa fa-check"></i> <?php echo _l("webhook_test_successful"); ?></h4>' +
|
||||
'<p>' + data.message + '</p>' +
|
||||
'</div>'
|
||||
);
|
||||
log_debug('Webhook test successful: ' + data.message);
|
||||
} else {
|
||||
$('#webhook_test_result').html(
|
||||
'<div class="alert alert-danger">' +
|
||||
'<h4><i class="fa fa-times"></i> <?php echo _l("webhook_test_failed"); ?></h4>' +
|
||||
'<p>' + data.message + '</p>' +
|
||||
'</div>'
|
||||
);
|
||||
log_debug('Webhook test failed: ' + data.message);
|
||||
}
|
||||
})
|
||||
.fail(function(xhr) {
|
||||
$('#webhook_test_result').html(
|
||||
'<div class="alert alert-danger">' +
|
||||
'<h4><i class="fa fa-times"></i> <?php echo _l("webhook_test_failed"); ?></h4>' +
|
||||
'<p><?php echo _l("connection_failed"); ?></p>' +
|
||||
'</div>'
|
||||
);
|
||||
log_debug('Webhook test failed: Connection error');
|
||||
});
|
||||
}
|
||||
|
||||
// Copy webhook URL
|
||||
function copy_webhook_url() {
|
||||
var copyText = document.getElementById("webhook_url");
|
||||
copyText.select();
|
||||
document.execCommand("copy");
|
||||
alert_float('success', '<?php echo _l("webhook_url_copied"); ?>');
|
||||
}
|
||||
|
||||
// Debug logging
|
||||
function log_debug(message) {
|
||||
var now = new Date().toISOString();
|
||||
var current = $('#debug_log').val();
|
||||
$('#debug_log').val(current + now + ': ' + message + "\n");
|
||||
|
||||
// Save to server
|
||||
$.post(admin_url + 'paystack/admin/log_debug', {
|
||||
message: message
|
||||
});
|
||||
}
|
||||
|
||||
// Clear debug log
|
||||
function clear_debug_log() {
|
||||
if(confirm('<?php echo _l("confirm_clear_log"); ?>')) {
|
||||
$('#debug_log').val('');
|
||||
$.post(admin_url + 'paystack/admin/clear_debug_log');
|
||||
alert_float('success', '<?php echo _l("log_cleared"); ?>');
|
||||
}
|
||||
}
|
||||
|
||||
// Load initial debug log
|
||||
function load_debug_log() {
|
||||
$.get(admin_url + 'paystack/admin/get_debug_log', function(response) {
|
||||
$('#debug_log').val(response);
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize
|
||||
$(function() {
|
||||
load_debug_log();
|
||||
|
||||
// Refresh debug log every 30 seconds
|
||||
setInterval(load_debug_log, 30000);
|
||||
});
|
||||
</script>
|
||||
169
views/admin/transactions.php
Normal file
169
views/admin/transactions.php
Normal file
@ -0,0 +1,169 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed'); ?>
|
||||
<?php init_head(); ?>
|
||||
<div id="wrapper">
|
||||
<div class="content">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="panel_s">
|
||||
<div class="panel-body">
|
||||
<h4 class="no-margin">
|
||||
<?php echo _l('paystack_transactions'); ?>
|
||||
</h4>
|
||||
<hr class="hr-panel-heading" />
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<?php echo form_open('', ['method' => 'GET']); ?>
|
||||
<div class="col-md-3">
|
||||
<?php echo render_date_input('start_date', 'start_date', $this->input->get('start_date')); ?>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<?php echo render_date_input('end_date', 'end_date', $this->input->get('end_date')); ?>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label for="status"><?php echo _l('status'); ?></label>
|
||||
<select name="status" id="status" class="selectpicker" data-width="100%">
|
||||
<option value=""><?php echo _l('all'); ?></option>
|
||||
<option value="success" <?php if($this->input->get('status') == 'success'){echo 'selected';} ?>>
|
||||
<?php echo _l('success'); ?>
|
||||
</option>
|
||||
<option value="failed" <?php if($this->input->get('status') == 'failed'){echo 'selected';} ?>>
|
||||
<?php echo _l('failed'); ?>
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 mtop25">
|
||||
<button type="submit" class="btn btn-primary"><?php echo _l('filter'); ?></button>
|
||||
<a href="<?php echo admin_url('paystack/admin/transactions'); ?>" class="btn btn-default">
|
||||
<?php echo _l('clear'); ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php echo form_close(); ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix mtop20"></div>
|
||||
|
||||
<!-- Transactions Table -->
|
||||
<table class="table dt-table table-transactions">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php echo _l('date'); ?></th>
|
||||
<th><?php echo _l('reference'); ?></th>
|
||||
<th><?php echo _l('invoice_dt_number'); ?></th>
|
||||
<th><?php echo _l('client'); ?></th>
|
||||
<th><?php echo _l('amount'); ?></th>
|
||||
<th><?php echo _l('status'); ?></th>
|
||||
<th><?php echo _l('options'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach($transactions as $transaction) { ?>
|
||||
<tr>
|
||||
<td><?php echo _dt($transaction['date']); ?></td>
|
||||
<td><?php echo $transaction['reference']; ?></td>
|
||||
<td>
|
||||
<a href="<?php echo admin_url('invoices/list_invoices/' . $transaction['invoice_id']); ?>">
|
||||
<?php echo format_invoice_number($transaction['invoice_id']); ?>
|
||||
</a>
|
||||
</td>
|
||||
<td><?php echo $transaction['email']; ?></td>
|
||||
<td><?php echo app_format_money($transaction['amount'], get_base_currency()); ?></td>
|
||||
<td>
|
||||
<?php
|
||||
$status_class = 'default';
|
||||
if($transaction['status'] == 'success') {
|
||||
$status_class = 'success';
|
||||
} else if($transaction['status'] == 'failed') {
|
||||
$status_class = 'danger';
|
||||
}
|
||||
?>
|
||||
<span class="label label-<?php echo $status_class; ?>">
|
||||
<?php echo _l($transaction['status']); ?>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="#" class="btn btn-default btn-icon" onclick="view_transaction(<?php echo $transaction['id']; ?>)">
|
||||
<i class="fa fa-eye"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Transaction Details Modal -->
|
||||
<div class="modal fade" id="transaction_modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title"><?php echo _l('transaction_details'); ?></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<table class="table table-striped">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong><?php echo _l('reference'); ?>:</strong></td>
|
||||
<td id="td_reference"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong><?php echo _l('amount'); ?>:</strong></td>
|
||||
<td id="td_amount"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong><?php echo _l('status'); ?>:</strong></td>
|
||||
<td id="td_status"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong><?php echo _l('date'); ?>:</strong></td>
|
||||
<td id="td_date"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong><?php echo _l('email'); ?>:</strong></td>
|
||||
<td id="td_email"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal"><?php echo _l('close'); ?></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php init_tail(); ?>
|
||||
<script>
|
||||
function view_transaction(id) {
|
||||
$.get(admin_url + 'paystack/admin/get_transaction_details/' + id, function(response) {
|
||||
var data = JSON.parse(response);
|
||||
$('#td_reference').html(data.reference);
|
||||
$('#td_amount').html(format_money(data.amount));
|
||||
$('#td_status').html(data.status);
|
||||
$('#td_date').html(data.date);
|
||||
$('#td_email').html(data.email);
|
||||
$('#transaction_modal').modal('show');
|
||||
});
|
||||
}
|
||||
|
||||
$(function() {
|
||||
var TableTransactions = $('.table-transactions').DataTable({
|
||||
"order": [[ 0, "desc" ]],
|
||||
"pageLength": 25
|
||||
});
|
||||
});
|
||||
</script>
|
||||
101
views/payment.php
Normal file
101
views/payment.php
Normal file
@ -0,0 +1,101 @@
|
||||
<?php defined('BASEPATH') or exit('No direct script access allowed'); ?>
|
||||
<?php echo payment_gateway_head(_l('payment_for_invoice') . ' ' . format_invoice_number($invoice->id)); ?>
|
||||
|
||||
<body class="gateway-payment">
|
||||
<div class="container">
|
||||
<div class="col-md-8 col-md-offset-2 mtop30">
|
||||
<!-- Payment Header -->
|
||||
<div class="mbot30 text-center">
|
||||
<h2 class="bold"><?php echo _l('payment_for_invoice'); ?></h2>
|
||||
<h3 class="bold"><?php echo format_invoice_number($invoice->id); ?></h3>
|
||||
</div>
|
||||
|
||||
<!-- Invoice Details -->
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title"><?php echo _l('invoice_details'); ?></h4>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p><strong><?php echo _l('invoice_date'); ?>:</strong><br>
|
||||
<?php echo _d($invoice->date); ?>
|
||||
</p>
|
||||
<p><strong><?php echo _l('due_date'); ?>:</strong><br>
|
||||
<?php echo _d($invoice->duedate); ?>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<p><strong><?php echo _l('invoice_total'); ?>:</strong><br>
|
||||
<?php echo app_format_money($invoice->total, $invoice->currency_name); ?>
|
||||
</p>
|
||||
<p><strong><?php echo _l('amount_due'); ?>:</strong><br>
|
||||
<?php echo app_format_money($payment_data['amount'], $payment_data['currency']); ?>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Payment Button -->
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title"><?php echo _l('payment_details'); ?></h4>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div id="payment-loading" class="text-center hide">
|
||||
<i class="fa fa-spinner fa-spin fa-3x"></i><br />
|
||||
<?php echo _l('processing_payment'); ?>
|
||||
</div>
|
||||
<button type="button" id="payWithPaystack" class="btn btn-success btn-block btn-lg">
|
||||
<i class="fa fa-lock pad-right-sm"></i> <?php echo _l('pay_securely'); ?>
|
||||
</button>
|
||||
<div class="mtop15 text-center">
|
||||
<img src="<?php echo module_dir_url('paystack', 'assets/img/secured-by-paystack.png'); ?>"
|
||||
alt="Secured by Paystack" style="max-height: 40px;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://js.paystack.co/v1/inline.js"></script>
|
||||
<script>
|
||||
document.getElementById('payWithPaystack').onclick = function() {
|
||||
// Show loading
|
||||
document.getElementById('payment-loading').classList.remove('hide');
|
||||
this.disabled = true;
|
||||
|
||||
let handler = PaystackPop.setup({
|
||||
key: '<?php echo $this->paystack_gateway->getSetting('paystack_public_key'); ?>',
|
||||
email: '<?php echo $client->email; ?>',
|
||||
amount: <?php echo $payment_data['amount'] * 100; ?>,
|
||||
currency: '<?php echo $payment_data['currency']; ?>',
|
||||
ref: '<?php echo 'INV_' . $invoice->id . '_' . time(); ?>',
|
||||
metadata: {
|
||||
invoice_id: '<?php echo $invoice->id; ?>',
|
||||
client_id: '<?php echo $invoice->clientid; ?>'
|
||||
},
|
||||
callback: function(response) {
|
||||
window.location.href = '<?php echo site_url('paystack/verify_payment/'); ?>' + response.reference;
|
||||
},
|
||||
onClose: function() {
|
||||
// Hide loading and enable button
|
||||
document.getElementById('payment-loading').classList.add('hide');
|
||||
document.getElementById('payWithPaystack').disabled = false;
|
||||
alert('<?php echo _l('payment_cancelled'); ?>');
|
||||
}
|
||||
});
|
||||
handler.openIframe();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user