378 lines
12 KiB
PHP
378 lines
12 KiB
PHP
<?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
|
|
]);
|
|
}
|
|
} |