DoliPesa/pages/callback.php
kevinowino869 818a1957be Update pages/callback.php
Signed-off-by: kevinowino869 <kevinowino869@www.codelab.nestict.africa>
2025-03-30 13:36:57 +02:00

182 lines
7.7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* Dolipesa - M-Pesa Payment Module Callback Handler
* Author: NESTICT INFOTECH
* Version: 1.0.1
* License: GNU General Public License v3.0
*/
require '../../main.inc.php';
require_once DOL_DOCUMENT_ROOT . '/core/lib/admin.lib.php';
require_once DOL_DOCUMENT_ROOT . '/compta/facture/class/facture.class.php';
// Load translations
$langs->load('dolipesa@dolipesa');
// Check if this is a callback from M-Pesa (no GET params, JSON input expected)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && empty($_GET)) {
// Handle M-Pesa callback
$callbackData = json_decode(file_get_contents('php://input'), true);
if (!isset($callbackData['Body']['stkCallback'])) {
http_response_code(400);
exit('Invalid callback data');
}
$stkCallback = $callbackData['Body']['stkCallback'];
$resultCode = $stkCallback['ResultCode'];
$resultDesc = $stkCallback['ResultDesc'];
$checkoutRequestID = $stkCallback['CheckoutRequestID'];
// Log the callback response (assumes llx_dolipesa_transactions table exists)
$sql = "INSERT INTO " . MAIN_DB_PREFIX . "dolipesa_transactions (checkout_request_id, result_code, result_desc, datec) ";
$sql .= "VALUES ('" . $db->escape($checkoutRequestID) . "', '" . $db->escape($resultCode) . "', ";
$sql .= "'" . $db->escape($resultDesc) . "', NOW())";
$db->query($sql);
// Process successful payment (ResultCode 0 = success)
if ($resultCode == 0) {
$callbackMetadata = $stkCallback['CallbackMetadata']['Item'];
$transactionId = null;
$amount = null;
$phoneNumber = null;
foreach ($callbackMetadata as $item) {
switch ($item['Name']) {
case 'MpesaReceiptNumber':
$transactionId = $item['Value'];
break;
case 'Amount':
$amount = $item['Value'];
break;
case 'PhoneNumber':
$phoneNumber = $item['Value'];
break;
}
}
// Find invoice linked to this CheckoutRequestID (youd need to store this mapping)
$sql = "SELECT fk_invoice FROM " . MAIN_DB_PREFIX . "dolipesa_transactions ";
$sql .= "WHERE checkout_request_id = '" . $db->escape($checkoutRequestID) . "'";
$resql = $db->query($sql);
if ($resql && $obj = $db->fetch_object($resql)) {
$invoice = new Facture($db);
$invoice->fetch($obj->fk_invoice);
if ($invoice->id && $invoice->statut == 1) { // Validated invoice
// Mark invoice as paid
$invoice->setPaid($user, '', $transactionId);
// Log transaction with invoice link
$sql = "UPDATE " . MAIN_DB_PREFIX . "dolipesa_transactions ";
$sql .= "SET fk_invoice = " . $invoice->id . ", transaction_id = '" . $db->escape($transactionId) . "' ";
$sql .= "WHERE checkout_request_id = '" . $db->escape($checkoutRequestID) . "'";
$db->query($sql);
}
}
}
http_response_code(200);
exit('Callback processed');
}
// Payment initiation (assumes this is accessed via GET with invoice_id and token)
$invoiceId = GETPOST('invoice_id', 'int');
$token = GETPOST('token', 'alpha');
if (!$invoiceId || !$token) {
accessforbidden($langs->trans('MissingParameters'));
}
// Validate token (simple example; improve as needed)
$expectedToken = md5($invoiceId . $conf->global->MAIN_SECURITY_SALT); // Use a more secure method in production
if ($token !== $expectedToken) {
accessforbidden($langs->trans('InvalidToken'));
}
// Load invoice
$invoice = new Facture($db);
if ($invoice->fetch($invoiceId) <= 0 || $invoice->statut != 1) {
accessforbidden($langs->trans('InvoiceNotFoundOrNotValidated'));
}
// Process payment submission
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$phoneNumber = GETPOST('phone_number', 'alpha');
$amount = floatval($invoice->total_ttc); // Use invoice total
// Validate phone number (Kenyan format: 2547XXXXXXXX)
if (!preg_match('/^254[0-9]{9}$/', $phoneNumber)) {
setEventMessages($langs->trans('InvalidPhoneNumber'), null, 'errors');
} else {
// Load M-Pesa credentials
$consumerKey = $conf->global->MPESAPAY_CONSUMER_KEY;
$consumerSecret = dol_decrypt($conf->global->MPESAPAY_CONSUMER_SECRET);
$shortcode = $conf->global->MPESAPAY_SHORTCODE;
$passkey = dol_decrypt($conf->global->MPESAPAY_PASSKEY);
$callbackUrl = $conf->global->MPESAPAY_CALLBACK_URL ?: DOL_MAIN_URL_ROOT . '/custom/dolipesa/callback.php';
// Generate OAuth token
$credentials = base64_encode($consumerKey . ':' . $consumerSecret);
$tokenResponse = dol_http_get('https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials', [
'Authorization: Basic ' . $credentials
]);
$tokenData = json_decode($tokenResponse, true);
$accessToken = $tokenData['access_token'] ?? null;
if (!$accessToken) {
setEventMessages($langs->trans('FailedToGetMpesaToken'), null, 'errors');
} else {
// Prepare STK Push payload
$timestamp = date('YmdHis');
$password = base64_encode($shortcode . $passkey . $timestamp);
$payload = [
'BusinessShortCode' => $shortcode,
'Password' => $password,
'Timestamp' => $timestamp,
'TransactionType' => 'CustomerPayBillOnline',
'Amount' => $amount,
'PartyA' => $phoneNumber,
'PartyB' => $shortcode,
'PhoneNumber' => $phoneNumber,
'CallBackURL' => $callbackUrl,
'AccountReference' => $invoice->ref,
'TransactionDesc' => 'Payment for Invoice #' . $invoice->ref
];
// Send STK Push request
$response = dol_http_post('https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest', json_encode($payload), [
'Authorization: Bearer ' . $accessToken,
'Content-Type: application/json'
]);
$responseData = json_decode($response, true);
if ($responseData && $responseData['ResponseCode'] == '0') {
// Store CheckoutRequestID for callback mapping
$checkoutRequestID = $responseData['CheckoutRequestID'];
$sql = "INSERT INTO " . MAIN_DB_PREFIX . "dolipesa_transactions (checkout_request_id, fk_invoice, datec) ";
$sql .= "VALUES ('" . $db->escape($checkoutRequestID) . "', " . $invoice->id . ", NOW())";
$db->query($sql);
setEventMessages($langs->trans('PaymentInitiated'), null, 'mesgs');
} else {
setEventMessages($langs->trans('PaymentInitiationFailed') . ': ' . ($responseData['errorMessage'] ?? 'Unknown error'), null, 'errors');
}
}
}
}
// Display payment form
print load_fiche_titre($langs->trans('PayInvoice', $invoice->ref));
print '<form method="POST" action="' . $_SERVER['PHP_SELF'] . '?invoice_id=' . $invoiceId . '&token=' . $token . '">';
print '<table class="noborder">';
print '<tr><td><label for="phone_number">' . $langs->trans('PhoneNumber') . ' (254...):</label></td>';
print '<td><input type="text" name="phone_number" id="phone_number" pattern="^254[0-9]{9}$" value="254" required></td></tr>';
print '<tr><td><label for="amount">' . $langs->trans('Amount') . ':</label></td>';
print '<td><input type="number" name="amount" id="amount" value="' . price($invoice->total_ttc) . '" readonly></td></tr>';
print '</table>';
print '<div class="center"><input type="submit" class="button" value="' . $langs->trans('PayNow') . '"></div>';
print '</form>';
llxFooter();
$db->close();
?>