Update pages/callback.php
Signed-off-by: kevinowino869 <kevinowino869@www.codelab.nestict.africa>
This commit is contained in:
parent
0901fc1015
commit
818a1957be
@ -1,73 +1,182 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* M-Pesa Payment Module - Payment Processing
|
* Dolipesa - M-Pesa Payment Module Callback Handler
|
||||||
* Author: NESTICT INFOTECH
|
* Author: NESTICT INFOTECH
|
||||||
* Version: 1.0.0
|
* Version: 1.0.1
|
||||||
|
* License: GNU General Public License v3.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
require '../../main.inc.php';
|
require '../../main.inc.php';
|
||||||
require_once DOL_DOCUMENT_ROOT . "/core/lib/admin.lib.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 (you’d 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');
|
$invoiceId = GETPOST('invoice_id', 'int');
|
||||||
$token = GETPOST('token', 'alpha');
|
$token = GETPOST('token', 'alpha');
|
||||||
|
|
||||||
if (!$invoiceId || !$token) {
|
if (!$invoiceId || !$token) {
|
||||||
accessforbidden();
|
accessforbidden($langs->trans('MissingParameters'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate token
|
// Validate token (simple example; improve as needed)
|
||||||
$expectedToken = base64_encode(hash('sha256', $invoiceId . time()));
|
$expectedToken = md5($invoiceId . $conf->global->MAIN_SECURITY_SALT); // Use a more secure method in production
|
||||||
if ($token !== $expectedToken) {
|
if ($token !== $expectedToken) {
|
||||||
accessforbidden();
|
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
|
// Process payment submission
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
$phoneNumber = GETPOST('phone_number', 'alpha');
|
$phoneNumber = GETPOST('phone_number', 'alpha');
|
||||||
$amount = GETPOST('amount', 'int');
|
$amount = floatval($invoice->total_ttc); // Use invoice total
|
||||||
|
|
||||||
$paybill = dolibarr_get_const($db, "MPESAPAY_PAYBILL");
|
// Validate phone number (Kenyan format: 2547XXXXXXXX)
|
||||||
$passkey = dolibarr_get_const($db, "MPESAPAY_PASSKEY");
|
if (!preg_match('/^254[0-9]{9}$/', $phoneNumber)) {
|
||||||
|
setEventMessages($langs->trans('InvalidPhoneNumber'), null, 'errors');
|
||||||
$payload = array(
|
|
||||||
"BusinessShortCode" => $paybill,
|
|
||||||
"Password" => base64_encode($paybill . $passkey . time()),
|
|
||||||
"Timestamp" => date('YmdHis'),
|
|
||||||
"TransactionType" => "CustomerPayBillOnline",
|
|
||||||
"Amount" => $amount,
|
|
||||||
"PartyA" => $phoneNumber,
|
|
||||||
"PartyB" => $paybill,
|
|
||||||
"PhoneNumber" => $phoneNumber,
|
|
||||||
"CallBackURL" => DOL_URL_ROOT . "/custom/mpesapay/callback.php",
|
|
||||||
"AccountReference" => $invoiceId,
|
|
||||||
"TransactionDesc" => "Payment for Invoice #$invoiceId"
|
|
||||||
);
|
|
||||||
|
|
||||||
$module = new modMpesapay($db);
|
|
||||||
$response = $module->sendMpesaRequest($payload);
|
|
||||||
|
|
||||||
if ($response && $response['ResponseCode'] == "0") {
|
|
||||||
echo "<p>Payment initiated. Check your phone to complete.</p>";
|
|
||||||
} else {
|
} else {
|
||||||
echo "<p>Failed to initiate payment. Please try again.</p>";
|
// Load M-Pesa credentials
|
||||||
}
|
$consumerKey = $conf->global->MPESAPAY_CONSUMER_KEY;
|
||||||
} else {
|
$consumerSecret = dol_decrypt($conf->global->MPESAPAY_CONSUMER_SECRET);
|
||||||
// Display payment form
|
$shortcode = $conf->global->MPESAPAY_SHORTCODE;
|
||||||
$sql = "SELECT ref, total FROM ".MAIN_DB_PREFIX."facture WHERE rowid = $invoiceId";
|
$passkey = dol_decrypt($conf->global->MPESAPAY_PASSKEY);
|
||||||
$result = $db->query($sql);
|
$callbackUrl = $conf->global->MPESAPAY_CALLBACK_URL ?: DOL_MAIN_URL_ROOT . '/custom/dolipesa/callback.php';
|
||||||
if ($result) {
|
|
||||||
$invoice = $db->fetch_object($result);
|
// Generate OAuth token
|
||||||
print load_fiche_titre("Pay Invoice #{$invoice->ref}");
|
$credentials = base64_encode($consumerKey . ':' . $consumerSecret);
|
||||||
echo '<form method="POST">';
|
$tokenResponse = dol_http_get('https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials', [
|
||||||
echo '<label>Phone Number (254...): <input type="text" name="phone_number" pattern="^254[0-9]{9}$" required></label><br>';
|
'Authorization: Basic ' . $credentials
|
||||||
echo '<label>Amount: <input type="number" name="amount" value="'.$invoice->total.'" readonly></label><br>';
|
]);
|
||||||
echo '<button type="submit">Pay Now</button>';
|
$tokenData = json_decode($tokenResponse, true);
|
||||||
echo '</form>';
|
$accessToken = $tokenData['access_token'] ?? null;
|
||||||
} else {
|
|
||||||
echo "<p>Invoice not found.</p>";
|
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();
|
llxFooter();
|
||||||
$db->close();
|
$db->close();
|
||||||
?>
|
?>
|
Loading…
x
Reference in New Issue
Block a user