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); } }