Initial commit
This commit is contained in:
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>
|
||||
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