<?php
namespace App\Payment;
use App\Controller\SendSms;
use App\Entity\Mpesa;
use App\Entity\MpesaAuth;
use App\Entity\MpesaMessage;
use App\Entity\MpesaPaybill;
use App\Entity\MpesaPayment;
use App\Entity\MpesaPaymentRequest;
use App\Entity\MpesaResponse;
use App\Entity\MpesaTransaction;
use App\Entity\Transaction;
use App\Entity\WayBill;
use DateTime;
use Doctrine\DBAL\Driver\PDO\Exception;
use Doctrine\ORM\EntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\ObjectManager;
use http\Message;
use JMS\Serializer\SerializationContext;
use JMS\Serializer\SerializerBuilder;
use NumberFormatter;
use Psr\Log\LoggerInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
class MpesaPaymentController extends AbstractController
{
private $logger;
private $host = 'api.safaricom.co.ke';
private $stkpushUrl = '/mpesa/stkpush/v1/processrequest';
// private $callbackUrlRegister = '/mpesa/c2b/v2/registerurl';
private $callbackUrlRegister = '/mpesa/c2b/v1/registerurl';
private $tokenRequest = '/oauth/v1/generate?grant_type=client_credentials';
private $authType = 'PROD';
private $sms;
private ObjectManager $manager;
// private $url = 'https://api.safaricom.co.ke/mpesa/stkpush/v1/processrequest';
public function __construct(LoggerInterface $logger, ManagerRegistry $managerRegistry)
{
$this->sms = new SendSms();
$this->logger = $logger;
$this->manager = $managerRegistry->getManager();
}
/**
* @Route("/request/mpesa/{waybill_id}", methods={"POST"}, name="request_mpesa_express_payment")
* @param $waybill_id
* @return Response
*/
public function requestMpesaExpressPayment(Request $request, $waybill_id)
{
$this->logger->info("Start creating request");
$em = $this->getDoctrine()->getManager();
/** @var WayBill $waybill */
$waybill = $em->getRepository(WayBill::class)
->findOneBy([
'id' => $waybill_id
]);
// /** @var Transaction $transaction */
// $transaction = $em->getRepository(Transaction::class)->findOneBy([
// 'wayBill' => $waybill
// ]);
if (!$waybill) {
return $this->redirectToRoute('payment_request_view', ['waybill', $waybill_id]);
}
$requestSenderPhoneForm = $this->postMpesaPaymentPhoneNumber($waybill_id, 'sender_form');
$requestSenderPhoneForm->handleRequest($request);
$requestReceiverPhoneForm = $this->postMpesaPaymentPhoneNumber($waybill_id, 'receiver_form');
$requestReceiverPhoneForm->handleRequest($request);
$requestOtherPhoneForm = $this->postMpesaPaymentPhoneNumber($waybill_id, 'other_form');
$requestOtherPhoneForm->handleRequest($request);
$data = '';
if ($requestSenderPhoneForm->isSubmitted() || $requestReceiverPhoneForm->isSubmitted() || $requestOtherPhoneForm->isSubmitted()) {
// $requestForm->getData('phone');
// $requestForm->get('field');
// dump($requestSenderPhoneForm);
// dump($requestReceiverPhoneForm);
// dump($requestOtherPhoneForm);
$data = null;
if ($requestSenderPhoneForm->isSubmitted()) {
dump($requestSenderPhoneForm);
// $requestForm = $requestSenderPhoneForm;
$data = $requestSenderPhoneForm->getData();
} else if ($requestReceiverPhoneForm->isSubmitted()) {
dump($requestReceiverPhoneForm);
// $requestForm = $requestSenderPhoneForm;
$data = $requestReceiverPhoneForm->getData();
} else if ($requestOtherPhoneForm->isSubmitted()) {
// $requestForm = $requestOtherPhoneForm;
dump($requestOtherPhoneForm);
$data = $requestOtherPhoneForm->getData();
}
// dump($data);
// die;
$this->logger->info('payment data', $data);
$phone = str_replace(' ', '', $data['phone']);
$phone = substr_replace($phone, '', 0, 1);
$phone = '254' . $phone;
$field = $data['field'];
$waybill->getTransaction()->setPaymentMethod('MPESA');
$waybill->getTransaction()->setMpesaPaymentPhone($phone);
$waybill->getTransaction()->setPaidBy($field);
$em->flush();
$amount = $waybill->getTransaction()->getAmount();
$date = $waybill->getCreatedAt();
$date = date_format($date, 'YmdHis');
$this->logger->info("Create formatted Date{$date}");
/** @var MpesaAuth $credentials */
$credentials = $em->getRepository(MpesaAuth::class)->findOneBy([
'station' => $request->getSession()->get('STATION'),
'authType' => $this->authType
]);
$this->logger->info("Get credentials");
$url = "https://{$this->host}{$this->stkpushUrl}";
// dump($authKey);
if ($credentials) {
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HTTPHEADER, [
"Content-Type:application/json",
"Authorization:Bearer {$credentials->getToken()}"
]
);
//setting custom header
// 'CallBackURL' => $this->generateUrl("receive_mpesa_express_callback",['waybill_id' => $waybill->getId()], UrlGeneratorInterface::ABSOLUTE_URL),
$curl_post_data = array(
//Fill in the request parameters with valid values
'BusinessShortCode' => "{$credentials->getPaybill()}",
'Password' => base64_encode("{$credentials->getPaybill()}{$credentials->getPassKey()}{$date}"),
'Timestamp' => "{$date}",
'TransactionType' => 'CustomerPayBillOnline',
'Amount' => $amount,
'PartyA' => $phone,
'PartyB' => "{$credentials->getPaybill()}",
'PhoneNumber' => $phone,
'CallBackURL' => "https://sys.nenocourier.co.ke/payment/callback/express/{$waybill->getId()}",
'AccountReference' => $waybill->getId(),
'TransactionDesc' => "Request to {$phone} to pay for waybill {$waybill->getId()}"
);
// dump($curl_post_data);
$data_string = json_encode($curl_post_data);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data_string);
$curl_response = curl_exec($curl);
$this->logger->info("Request response");
$this->logger->info($curl_response);
$curlResponse = json_decode($curl_response, true);
if (isset($curlResponse['errorCode'])) {
$response = [
'message' => 'Error occurred during request',
'error' => $curlResponse['errorMessage']
];
$jsonResponse = json_encode($response);
$this->logger->error("payment_request_error", $curlResponse);
$this->addFlash('success', 'Payment request sent to ' . $phone);
return $this->redirectToRoute('payment_request_view', ['waybill' => $waybill_id]);
//
// return new Response($jsonResponse, Response::HTTP_NOT_ACCEPTABLE,[
// 'content-type' => 'text/json'
// ]);
} elseif (isset($curlResponse['ResponseCode'])) {
$response = [
'message' => 'Request sent'
];
$this->addFlash('success', 'Payment request sent to ' . $phone);
return $this->redirectToRoute('payment_request_view', ['waybill' => $waybill_id]);
//
// $response = json_encode($response);
// return new Response($response, Response::HTTP_OK,[
// 'content-type' => 'text/json'
// ]);
}
}
}
}
/**
* @Route("/callback/express/awesome/{waybill_id}", name="receive_mpesa_express_callback_testing")
* @param Request $request
* @param $waybill_id
* @return Response
*/
public function receiveMpesaExpressCallbackTesting(Request $request, $waybill_id)
{
// dump($request->getContent());
// $this->logger->info('Printed: ', $request->getContent());
return new Response("OK", Response::HTTP_OK, [
'content-type' => 'text/html'
]);
}
/**
* @Route("/callback/express/{waybill_id}", name="receive_mpesa_express_callback")
* @param Request $request
* @param $waybill_id
* @return Response
*/
public function receiveMpesaExpressCallback(Request $request, $waybill_id)
{
$em = $this->getDoctrine()->getManager();
/** @var WayBill $waybill */
$waybill = $em->getRepository("App:WayBill")->findOneBy([
'id' => $waybill_id
]);
$transaction = $waybill->getTransaction();
$json = $request->getContent();
$this->logger->info("Gotten response mpesa");
$this->logger->info($json);
$response = json_decode($json, true);
$mpesaPayment = new MpesaPayment();
$mpesaResponse = new MpesaResponse();
$merchantRequestId = $response['Body']['stkCallback']['MerchantRequestID'];
$checkoutRequestID = $response['Body']['stkCallback']['CheckoutRequestID'];
$resultCode = $response['Body']['stkCallback']['ResultCode'];
$resultDesc = $response['Body']['stkCallback']['ResultDesc'];
$mpesaResponse->setMerchantRequestId($merchantRequestId);
$mpesaResponse->setCheckoutRequestId($checkoutRequestID);
$mpesaResponse->setCode($resultCode);
$mpesaResponse->setDescription($resultDesc);
$mpesaResponse->setCreatedAt(new \DateTime());
$mpesaResponse->setWaybill($waybill);
$em->persist($mpesaResponse);
if ($resultCode == 0) {
$items = $response['Body']['stkCallback']['CallbackMetadata']['Item'];
$amount = $items[0]['Value'];
$receiptNumber = $items[1]['Value'];
$transactionDate = '';
$phoneNumber = '';
if (count($items) == 5) {
$transactionDate = $items[3]['Value'];
$phoneNumber = $items[4]['Value'];
} else {
$transactionDate = $items[2]['Value'];
$phoneNumber = $items[3]['Value'];
}
$mpesaPayment->setAmount($amount);
$mpesaPayment->setMpesaPaymentType("MPESA_EXPRESS");
$mpesaPayment->setWaybill($waybill);
$mpesaPayment->setPhone($phoneNumber);
$mpesaPayment->setResponse($mpesaResponse);
$mpesaPayment->setTransactionId($receiptNumber);
$mpesaPayment->setCreatedAt(new \DateTime());
$em->persist($mpesaPayment);
$transaction->setPaymentMethod('MPESA');
}
$conn = $em->getConnection();
$conn->beginTransaction();
try {
$em->flush();
$em->getConnection()->commit();
} catch (\PDOException $exception) {
$em->getConnection()->rollBack();
}
return new Response("OK", Response::HTTP_OK, [
'content-type' => 'text/html'
]);
}
/**
* @Route("/callback/paybill", methods={"POST"}, name="receive_mpesa_paybill_callback")
* @param Request $request
* @return Response
*/
public function receiveMpesaPaybillCallback(Request $request)
{
$em = $this->getDoctrine()->getManager();
$json = $request->getContent();
$this->logger->debug('mpesa-paybill response' . $json);
$this->logger->error('');
$response = json_decode($json, true);
$transactionType = $response['TransactionType'];
$transactionId = $response['TransID'];
$transactionTime = $response['TransTime'];
$transactionAmount = $response['TransAmount'];
$shortCode = $response['BusinessShortCode'];
$billRefNumber = $response['BillRefNumber'];
$invoiceNumber = $response['InvoiceNumber'];
$accountBalance = $response['OrgAccountBalance'];
$thirdPartyTransactionId = $response['ThirdPartyTransID'];
$msisdn = $response['MSISDN'];
$firstName = $response['FirstName'];
$secondName = '-';
$lastName = '-';
if ($shortCode != 4022957 && $shortCode != 726614) {
$secondName = $response['MiddleName'];
$lastName = $response['LastName'];
}
$mpesa = new Mpesa();
$mpesa->setTransactionId($transactionId);
$mpesa->setTransactionType($transactionType);
$mpesa->setTransactionTime($transactionTime);
$mpesa->setTransactionAmount($transactionAmount);
$mpesa->setShortCode($shortCode);
$mpesa->setRefNumber($billRefNumber);
$mpesa->setBalance(($accountBalance ? $accountBalance : 0));
$mpesa->setMsisdn($msisdn);
$mpesa->setFirstName(($firstName ? $firstName : ''));
$mpesa->setMiddleName($secondName ? $secondName : '');
$mpesa->setLastName($lastName ? $lastName : '');
$mpesa->setCreatedAt(new \DateTime());
$mpesa->setIsUsed(false);
$conn = $em->getConnection();
$conn->beginTransaction();
try {
/** @var Mpesa $availableMpesa */
$availableMpesa = $em->getRepository(Mpesa::class)->findOneBy([
'transactionId' => $transactionId
]);
if($availableMpesa){
$availableMpesa->setTransactionType('PAYBILL_PHONE_AV');
}else{
$em->persist($mpesa);
}
$em->getConnection()->commit();
$em->flush();
return new Response("OK", Response::HTTP_OK, [
'content-type' => 'text/html'
]);
} catch (\PDOException $exception) {
$em->getConnection()->rollBack();
$this->logger->error($exception->getMessage(), []);
$date = new \DateTime();
$date = date_format($date, 'Y/m/d H:i:s');
$this->sms->sendOutSms('254716308459', "HELLO Samuel A payment mpesa error occurred during COMMIT at {$date}");
return new Response("NOT OK", Response::HTTP_INTERNAL_SERVER_ERROR, [
'content-type' => 'text/html'
]);
}
/*return new Response("NOT OK", Response::HTTP_INTERNAL_SERVER_ERROR, [
'content-type' => 'text/html'
]);*/
}
/**
* @Route("/callback/paybill/validation", methods={"POST"}, name="receive_mpesa_paybill_callback_validation")
* @param Request $request
* @return Response
*/
public function receiveMpesaPaybillCallbackValidation(Request $request)
{
$json = $request->getContent();
$this->logger->log('MPESA VALIDATION: ',$json);
return new JsonResponse([
"ResultCode" => '0',
"ResultDesc" => "Accepted"
], Response::HTTP_OK);
}
/**
* @Route("/response/mpesa/{waybill_id}", name="payment_response_verification")
* @param $waybill_id
* @return Response
*/
public function paymentResponseVerification($waybill_id)
{
$em = $this->getDoctrine()->getManager();
/** @var WayBill $waybill */
$waybill = $em->getRepository("App:WayBill")->findOneBy([
'id' => $waybill_id
]);
// dump($waybill);
if ($waybill) {
/** @var MpesaPaymentRequest $mpesaRequest */
$mpesaRequest = $em->getRepository("App:MpesaPaymentRequest")
->findBy([
'waybill' => $waybill
]);
/** @var MpesaResponse $mpesaResponse */
$mpesaResponses = $em->getRepository("App:MpesaResponse")
->findBy([
'waybill' => $waybill
]);
/** @var MpesaPayment $mpesaPayment */
$mpesaPayments = $em->getRepository("App:MpesaPayment")
->findBy([
'waybill' => $waybill
]);
if ($mpesaResponses) {
return $this->render('fos/parcels/mpesa_payment.html.twig', [
'mpesa_responses' => $mpesaResponses,
'mpesa_payments' => $mpesaPayments,
'waybill' => $waybill,
'isAvailable' => true,
'mpesa_requests' => $mpesaRequest
]);
}
return $this->render('fos/parcels/mpesa_payment.html.twig', [
'isAvailable' => false,
'waybill' => $waybill
]);
}
return $this->render('fos/parcels/mpesa_payment.html.twig', [
'isAvailable' => false,
]);
}
/**
* @Route("/renew/mpesa_credentials", name="renew_mpesa_authentication")
*/
function getMpesaAuthentication(Request $request)
{
$em = $this->getDoctrine()->getManager();
$ip = $request->getClientIp();
$date = new \DateTime();
$date = date_format($date, 'YmdHis');
$this->logger->info("Create formatted Date{$date}");
/** @var MpesaAuth $credentials */
$credentials = $em->getRepository(MpesaAuth::class)->findBy([
'authType' => $this->authType
]);
$this->logger->info("Get credentials");
dump($credentials);
// die;
// dump($credentials->getConsumerSecret().' '.$credentials->getConsumerKey());
// ips: [35.180.247.100]
$errors = '';
foreach ($credentials as $index => $credential) {
if ($credential) {
$url = "https://{$this->host}{$this->tokenRequest}";
$password = base64_encode("{$credential->getConsumerKey()}:{$credential->getConsumerSecret()}");
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HTTPHEADER, [
"Content-Type:application/json",
"Authorization:Basic {$password}"
]
);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HTTPGET, true);
// curl_setopt($curl, CURLOPT_POSTFIELDS, $data_string);
$curl_response = curl_exec($curl);
dump($curl_response);
$this->logger->info("Request response");
$this->logger->info($curl_response);
$curlResponse = json_decode($curl_response, true);
if ($curlResponse) {
$response = json_decode($curl_response, true);
$credential->setToken($response['access_token']);
$credential->setTokenUpdatedAt(new \DateTime());
$em->flush();
// $date = new \DateTime();
// $date = date_format($date, 'Y/m/d H:i:s');
// $this->sms->sendOutSms('254716308459', "HELLO Samuel MPESA TOKEN GENERATION SUCCEEDED at {$date}");
} else {
$date = new \DateTime();
$date = date_format($date, 'Y/m/d H:i:s');
$this->sms->sendOutSms('254716308459', "HELLO Samuel MPESA TOKEN GENERATION FAILED at {$date} pleae check it out");
$errors .= $credential->getPaybill() . ' Could not create credentials';
// return new Response("error", Response::HTTP_OK, [
// 'content-type'=>'text/html'
// ]);
}
}
}
die;
return new Response("AWESOME {$errors}_" . $ip, Response::HTTP_OK, [
'content-type' => 'text/html'
]);
// $sms = new SendSms();
// $sms->sendOutSms('254716308459', "HELLO CRON WORKED");
return new Response("error", Response::HTTP_OK, [
'content-type' => 'text/html'
]);
}
// private function getMSISDN($phone){
// $code = '254';
// if(substr($phone, 1) == 0){
// return $phone;
// }
// return $code.substr($phone, 1);
// }
/**
* @Route("/request_view/{waybill}", methods={"POST", "GET"}, name="payment_request_view")
*/
function paymentRequestView($waybill, Request $request){
$em = $this->getDoctrine()->getManager();
@ini_set("memory_limit", -1);
@ini_set("max_duration", -1);
/** @var Transaction $transaction */
$transaction = $em->getRepository(Transaction::class)->findOneBy([
'wayBill' => $waybill
]);
if (!$transaction) {
$this->addFlash('warning', 'this transaction has been paid fully');
return $this->redirectToRoute('new-parcel');
}
/** @var MpesaAuth $credentials */
$credentials = $em->getRepository(MpesaAuth::class)->findOneBy([
'station' => $request->getSession()->get('STATION'),
'authType' => $this->authType
]);
$requestSenderPhoneForm = $this->postMpesaPaymentPhoneNumber($waybill, 'sender_form');
$requestReceiverPhoneForm = $this->postMpesaPaymentPhoneNumber($waybill, 'receiver_form');
$requestOtherPhoneForm = $this->postMpesaPaymentPhoneNumber($waybill, 'other_form');
$mpesaPaymentSelection = $this->postMpesaPaymentSelection($waybill, $credentials, 'mpesa_payment_selection');
$mpesaCashPaymentSelection = $this->postMpesaCashPaymentSelection($waybill, $credentials, 'mpesa_cash_payment_selection');
$mpesaCashPaymentSelection->handleRequest($request);
$mpesaPaymentSelection->handleRequest($request);
$conn = $em->getConnection();
if ($mpesaCashPaymentSelection->isSubmitted()) {
$data = $mpesaCashPaymentSelection->getData();
if (!$data) {
$this->addFlash('warning', 'error transaction data not available please select again');
return $this->redirectToRoute('payment_request_view', ['id' => $waybill]);
}
// dump($data);
// die;
if ($transaction->getIsPaid()) {
if (!$transaction->getPaymentMethod() == 'CASH') {
$this->addFlash('warning', 'this transaction has been paid fully');
return $this->redirectToRoute('one_way_bill', ['id' => $waybill]);
}
}
try {
$conn->beginTransaction();
$amount = 0;
$mpesa = null;
foreach ($data['mpesa'] as $index => $datum) {
$mpesaPayment = new MpesaTransaction();
$mpesaPayment->setCreatedAt(new \DateTime());
$mpesaPayment->setCreatedBy($this->getUser());
$mpesaPayment->setMpesa($datum);
$mpesaPayment->setTransaction($transaction);
$datum->setIsUsed(true);
$em->persist($mpesaPayment);
$mpesa = $datum;
$amount += $datum->getTransactionAmount();
}
// $transaction->getDailyAccount()->setMpesa($transaction->getDailyAccount()->getMpesa() + $mpesa->getTransactionAmount());
$transaction->setPaymentMethod('MPESA_CASH');
$transaction->setMpesaAmount($amount);
$transaction->setCashAmount(0);
if ($transaction->getAmount() > $amount) {
$transaction->setCashAmount($transaction->getAmount() - $amount);
}
$transaction->setPaidBy('OTHER');
$transaction->setIsPaid(true);
$transaction->setIsComplete(true);
$em->getConnection()->commit();
$em->flush();
// $this->sms->sendOutSms($mpesa->getMsisdn(), "HELLO {$mpesa->getFirstName()} your M-PESA payment {$mpesa->getTransactionId()} has been received for waybill: {$transaction->getWayBill()->getId()}
// STAY SAFE & Travel SAFE WITH NENO SACCO Matatus during this festive season, You can also save and grow with NENO SACCO BOSA Account. Merry Christmas And A Happy New Year From all of us At NENO FRATERNITY");
$this->sms->sendOutSms($mpesa->getMsisdn(), "HELLO {$mpesa->getFirstName()} your M-PESA payment {$mpesa->getTransactionId()} has been received for waybill: {$transaction->getWayBill()->getId()}
STAY SAFE & Travel SAFE WITH NENO SACCO Matatus, You can also save and grow with NENO SACCO BOSA Account.");
$this->addFlash('success', 'Mpesa transaction successful');
return $this->redirectToRoute('one_way_bill', ['id' => $waybill]);
} catch (\PDOException $exception) {
$em->getConnection()->rollBack();
$this->logger->error($exception->getMessage(), []);
$this->addFlash('error', 'Error Occurred please contact admin');
return $this->redirectToRoute('payment_request_view', ['waybill' => $waybill]);
}
}
if ($mpesaPaymentSelection->isSubmitted()) {
$data = $mpesaPaymentSelection->getData();
// $id = $data['id'];
// $amount = $data['id'];
// dump($data);
//
// die;
if (!$data) {
$this->addFlash('warning', 'error transaction data not available please select again');
return $this->redirectToRoute('payment_request_view', ['id' => $waybill]);
}
if ($transaction->getIsPaid()) {
if (!$transaction->getPaymentMethod() == 'CASH') {
$this->addFlash('warning', 'this transaction has been paid fully');
return $this->redirectToRoute('one_way_bill', ['id' => $waybill]);
}
}
try {
$conn->beginTransaction();
$amount = 0;
$mpesa = null;
foreach ($data['mpesa'] as $index => $datum) {
$datumMpesa = $em->getRepository(Mpesa::class)->findOneBy([
'id' => $datum->getId()
]);
$mpesaPayment = new MpesaTransaction();
$mpesaPayment->setCreatedAt(new \DateTime());
$mpesaPayment->setCreatedBy($this->getUser());
$mpesaPayment->setMpesa($datum);
$mpesaPayment->setTransaction($transaction);
$datum->setIsUsed(true);
$em->persist($mpesaPayment);
$mpesa = $datum;
$amount += $datumMpesa->getTransactionAmount();
}
// $transaction->getDailyAccount()->setMpesa($transaction->getDailyAccount()->getMpesa() + $mpesa->getTransactionAmount());
$transaction->setPaymentMethod('MPESA');
$transaction->setCashAmount(0);
$transaction->setMpesaAmount($amount);
$transaction->setIsComplete(true);
$transaction->setPaidBy('OTHER');
$transaction->setIsPaid(true);
$transaction->setIsComplete(true);
$em->getConnection()->commit();
$em->flush();
// $this->sms->sendOutSms($mpesa->getMsisdn(), "HELLO {$mpesa->getFirstName()} your M-PESA payment {$mpesa->getTransactionId()} has been received for waybill: {$transaction->getWayBill()->getId()}
// STAY SAFE WITH Neno Courier Services");
// $this->sms->sendOutSms($mpesa->getMsisdn(), "HELLO {$mpesa->getFirstName()} your M-PESA payment {$mpesa->getTransactionId()} has been received for waybill: {$transaction->getWayBill()->getId()}
// STAY SAFE & Travel SAFE WITH NENO SACCO Matatus, You can also save and grow with NENO SACCO BOSA Account.");
$this->addFlash('success', 'Mpesa transaction successful');
return $this->redirectToRoute('one_way_bill', ['id' => $waybill]);
} catch (\PDOException $exception) {
$em->getConnection()->rollBack();
$this->logger->error($exception->getMessage(), []);
$this->addFlash('error', 'Error Occurred please contact admin');
return $this->redirectToRoute('payment_request_view', ['waybill' => $waybill]);
}
}
return $this->render('payment/payment.html.twig', [
'transaction' => $transaction,
'senderForm' => $requestSenderPhoneForm->createView(),
'receiverForm' => $requestReceiverPhoneForm->createView(),
'otherForm' => $requestOtherPhoneForm->createView(),
'mpesaSelectionForm' => $mpesaPaymentSelection->createView(),
'mpesaCashSelectionForm' => $mpesaCashPaymentSelection->createView()
]);
}
/**
* @Route("/callback/register/{paybill}", name="register_callback_urls")
* @return Response|null
*/
function paymentUrlRegister(Request $request, $paybill)
{
$em = $this->getDoctrine()->getManager();
/** @var MpesaAuth $credentials */
$credentials = $em->getRepository(MpesaAuth::class)->findBy([
'authType' => $this->authType,
'paybill' => $paybill
]);
// dump($credentials);
// die;
$this->logger->debug("Get credentials");
$response = [];
foreach ($credentials as $index => $credential) {
$url = "https://{$this->host}{$this->callbackUrlRegister}";
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HTTPHEADER, [
"Content-Type:application/json",
"Authorization:Bearer {$credential->getToken()}"
]
);
dump($credential->getStation());
$curl_post_data = [
'ShortCode' => $credential->getPaybill(),
'ResponseType' => 'Cancelled',
'ConfirmationURL' => 'https://courier.ohau.co.ke/payment/callback/paybill',
'ValidationURL' => 'https://courier.ohau.co.ke/payment/callback/paybill/validation'
];
// dump($curl_post_data);
$data_string = json_encode($curl_post_data);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data_string);
$curl_response = curl_exec($curl);
dump($curl_response); die;
$this->logger->info("Request response");
$this->logger->info($curl_response);
$curlResponse = json_decode($curl_response, true);
$this->logger->error('Url registration ', $curlResponse);
dump($curl_response);
}
return new Response('awesome', Response::HTTP_OK, [
'content-type' => 'text/html'
]);
}
/**
* @Route("/list", methods={"POST"}, name="mpesa_payment_list")
* @param Request $request
* @return Response
*/
public function unUsedtransactions(Request $request)
{
$em = $this->getDoctrine()->getManager();
$context = new SerializationContext();
$context->setSerializeNull(true);
$serializer = SerializerBuilder::create()->build();
/** @var MpesaAuth $credentials */
$credentials = $em->getRepository(MpesaAuth::class)->findOneBy([
'station' => $request->getSession()->get('STATION'),
'authType' => $this->authType
]);
$em = $this->getDoctrine()->getManager();
// /** @var Mpesa[] $transactions */
// $transactions = $em->getRepository('App:Mpesa')->findBy([
// 'isUsed' => false,
// 'shortCode' => $credentials->getPaybill()
// ]);
/** @var Mpesa[] $transactions */
$transactions = $em->getRepository('App\Entity\Mpesa')->findTransactions('false', $credentials->getPaybill());
$data = [
'rows' => $transactions,
'total' => count($transactions)
];
$data = $serializer->serialize($data, 'json', $context);
return new Response($data, Response::HTTP_OK);
}
private function postMpesaPaymentPhoneNumber($waybill, $formName)
{
$fb = $this->get('form.factory')
->createNamedBuilder($formName);
$fb->add('phone', TextType::class, [
'constraints' => [
new NotBlank(['message' => 'phone number should be available']),
new Length(['min' => 12, 'max' => 12, 'maxMessage' => 'Phone number is incorrect', 'minMessage' => 'Phone number is incorrect'])
]
])
->add('field', TextType::class, [
'constraints' => [
new NotBlank(['message' => 'please key in the field'])
]
]);
return $fb
->setAction($this->generateUrl('request_mpesa_express_payment', ['waybill_id' => $waybill]))
->setMethod('POST')
->getForm();
}
private function postMpesaPaymentSelection($waybill, $credentials, $formName)
{
$fb = $this->get('form.factory')
->createNamedBuilder($formName);
$fb->add('mpesa', EntityType::class, [
'constraints' => [
new NotBlank(['message' => 'Please select mpesa transaction'])
],
'class' => 'App\Entity\Mpesa',
'expanded' => true,
'multiple' => true,
'query_builder' => function (EntityRepository $er) use ($credentials) {
return $er->createQueryBuilder('m')
->andWhere('m.isUsed = false')
->andWhere('m.shortCode = ' . $credentials->getPaybill());
}
]);
return $fb
->setAction($this->generateUrl('payment_request_view', ['waybill' => $waybill]))
->setMethod('POST')
->getForm();
}
private function postMpesaCashPaymentSelection($waybill, $credentials, $formName)
{
$fb = $this->get('form.factory')
->createNamedBuilder($formName);
$fb->add('mpesa', EntityType::class, [
'constraints' => [
new NotBlank(['message' => 'Please select mpesa transaction'])
],
'class' => 'App\Entity\Mpesa',
'expanded' => true,
'multiple' => true,
'query_builder' => function (EntityRepository $er) use ($credentials) {
return $er->createQueryBuilder('m')
->andWhere('m.isUsed = false')
->andWhere('m.shortCode = ' . $credentials->getPaybill());
}
]);
return $fb
->setAction($this->generateUrl('payment_request_view', ['waybill' => $waybill]))
->setMethod('POST')
->getForm();
}
/**
* @Route("/sms", methods={"GET"}, name="send_sms")
* @param Request $request
* @return Response
*/
public function sendSms(Request $request)
{
$results = $this->sms->sendOutSms('254716308459',
"HELLO samuel your test sms STAY SAFE WITH Neno Courier Services");
dump($results);
die;
}
/**
* @Route("/transponder", methods={"POST", "GET"}, name="mpesaTransponder")
* @param Request $request
* @return JsonResponse
*/
public function mpesaTransponder(Request $request): JsonResponse
{
$em = $this->manager;
$data = json_decode($request->getContent(), true);
$SMSMessage = $data['message'];
$shortCode = $data['short_code'];
if ($shortCode == 174379) {
$shortCode = 4022961;
}
// $text = $SMSMessage;
// $SMSMessage = "RJJ2XSAA1K Confirmed. on 19/10/23 at 6:13 PM Ksh300.00 received from EUNICE NJIRU 254720781207. Account Number Wanja Ali New Utility balance is Ksh32,050.00";
// $SMSMessage = "RJJ2XUWIE0 Confirmed. on 19/10/23 at 6:29 PM Ksh250.00 received from Nickson Akhura 254719172781. Account Number nickson New Utility balance is Ksh33,000.00";
$text = $SMSMessage;
/**
* (\d{12}): This part of the regular expression is a capturing group. It's designed to match and capture exactly 12 consecutive digits (0-9). Here's a breakdown of this part:
*
* \d: Matches any digit from 0 to 9.
* {12}: Specifies that the preceding \d should occur exactly 12 times, which means it matches 12 digits in a row.
* \.: This part matches a literal period (dot) character (.).
*
* So, when you put the entire expression together, (\d{12})\. matches and captures
* a sequence of exactly 12 digits followed by a period in the input text.
* This is useful for extracting a 12-digit number followed by a period,
* which is often used for various purposes, such as account numbers or identifiers.
*/
$patternSenderPhone = '/(\d{12})\./';
/**
* 1. `^`: The caret symbol at the beginning of the regular expression denotes the start of a line or string. It ensures that the pattern matches at the very beginning of the text.
*
* 2. `(\w+)`: This is a capturing group. It matches one or more word characters, which typically include letters (both uppercase and lowercase), digits, and underscores. Here's the breakdown of this part:
* - `\w`: Matches any word character (letters, digits, or underscores).
* - `+`: Specifies that the preceding `\w` should occur one or more times.
*
* 3. `Confirmed\.`: This part of the regular expression matches the literal text "Confirmed." followed by a period (dot).
*
* So, when you put the entire expression together, `/^(\w+) Confirmed\./` matches and captures a sequence of word
* characters (e.g., a word or alphanumeric code) followed by the exact text "Confirmed."
* and a period at the beginning of a line or string. This is useful for extracting specific information,
* such as a transaction ID, that is followed by "Confirmed." in the input text.
*/
$patternTransactionId = '/^(\w+) Confirmed\./';
/**
*
* 1. `received from`: This part of the regular expression matches the literal text "received from".
*
* 2. `([A-Z\s]+)`: This is a capturing group, and it's designed to match and capture one or more uppercase letters (A to Z) or
* whitespace characters (space or tab). Here's the breakdown of this part:
* - `[A-Z\s]`: This is a character class that matches any uppercase letter (A to Z) or a whitespace character.
* - `+`: Specifies that the preceding character class `[A-Z\s]` should occur one or more times in a row.
*
* 3. `\d{12}`: This part matches exactly 12 consecutive digits (0-9). It's used to match a 12-digit number.
*
* 4. `\.`: This part matches a literal period (dot) character (`.`).
*
* So, when you put the entire expression together, `/received from ([A-Z\s]+) \d{12}\./`
* matches the text "received from" followed by a sequence of one or more uppercase letters or spaces,
* then a 12-digit number, and finally a period. This pattern is useful for extracting the sender's
* name and their associated 12-digit number, often found in transaction descriptions or logs.
*/
// $patternSenderName = '/received from ([A-Z\s]+) \d{12}\./';
// $patternSenderName = '/received from ([A-Z][A-Z\s]+?) \d{12}\./';
$patternSenderName = '/received from ([A-Z][A-Z\s]+?) \d{12}\./i';
/**
* The regular expression `/on (\d{1,2}\/\d{1,2}\/\d{2} at \d{1,2}:\d{2} [APM]{2})/` is used to match and capture a specific date and time format in a text. Here's the breakdown of this expression:
*
* 1. `on`: This part of the regular expression matches the literal text "on".
*
* 2. `(\d{1,2}\/\d{1,2}\/\d{2} at \d{1,2}:\d{2} [APM]{2})`: This is a capturing group that matches a date and time format with the following components:
* - `\d{1,2}\/\d{1,2}\/\d{2}`: This part matches a date in the format of one or two digits for the day, one or two digits for the month, and two digits for the year (e.g., "10/10/23").
* - ` at `: This matches the space and the text " at ".
* - `\d{1,2}:\d{2}`: This part matches a time in the format of one or two digits for the hour, a colon, and two digits for the minutes (e.g., "7:52").
* - `[APM]{2}`: This matches either "AM" or "PM," which represents the time of day.
*
* So, when you apply this regular expression to a text, it captures and extracts a specific date and time format,
* including the day, month, year, hour, and minute, often found in date and time descriptions.
*
*/
$patternTransactionDate = '/on (\d{1,2}\/\d{1,2}\/\d{2} at \d{1,2}:\d{2} [APM]{2})/';
/**
* The regular expression /Ksh(\d+\.\d{2}) received from/ is used to match and capture a specific currency amount format in a text. Here's the breakdown of this expression:
*
* Ksh: This part of the regular expression matches the literal text "Ksh".
*
* (\d+\.\d{2}): This is a capturing group that matches a currency amount format with the following components:
*
* \d+: This matches one or more digits (0-9). It captures the digits before the decimal point, allowing for any number of digits in the integer part of the currency amount.
* \.: This matches the literal decimal point (dot).
* \d{2}: This matches exactly two digits, which are typically used for the cents or fractional part of the currency amount.
* received from: This part matches the literal text " received from".
*
* So, when you apply this regular expression to a text, it captures and extracts a currency amount formatted as
* "Ksh" followed by one or more digits for the whole part, a decimal point,
* and exactly two digits for the cents part, often found in financial or transaction descriptions.
*/
$patternAmount = '/Ksh([\d,]+.\d{2}) received from/';
$patternAmount = '/Ksh([\d,]+.\d{2}) received/';
$patternBalance = '/Ksh([\d,]+.\d{2})/';
$patternAccountNumber = '/Account Number ([A-Z\s]+) New/';
$phoneNumber = 0;
$transactionId = 0;
$senderName = 0;
$transactionDate = 0;
$transactionAmount = 0;
$transactionBalance = 0;
$transactionAccountNumber = 0;
$fmt = new NumberFormatter( 'en_US', NumberFormatter::DECIMAL );
if (preg_match($patternSenderPhone, $text, $matches)) {
$phoneNumber = $matches[1];
}
if (preg_match($patternTransactionId, $text, $matches)) {
$transactionId = $matches[1];
}
if (preg_match($patternSenderName, $text, $matches)) {
$senderName = $matches[1];
dump($senderName);
}
if (preg_match($patternTransactionDate, $text, $matches)) {
$transactionDate = $matches[1];
}
if (preg_match($patternAmount, $text, $matches)) {
$transactionAmount = $matches[1];
$transactionAmount = $fmt->parse($transactionAmount);
}
if (preg_match($patternBalance, $text, $matches)) {
$transactionBalance = $matches[1];
$transactionBalance = $fmt->parse($transactionBalance);
}
if (preg_match($patternAccountNumber, $text, $matches)) {
$transactionAccountNumber = $matches[1];
}
/**
* We use DateTime::createFromFormat to convert the captured date and time string into a
* DateTime PHP object. The format 'd/m/y \a\t h:i A' matches the format of the captured date and time string.
* It specifies the day, month, year, hour, minute, and AM/PM format.
*/
$dateTimeObject = DateTime::createFromFormat('d/m/y \a\t h:i A', $transactionDate);
if (!$transactionId) {
$resp = [
'sms' => $text,
'message' => "Invalid SMS Message the message should an M-Pesa notification message"
];
return new JsonResponse($resp, Response::HTTP_OK);
}
$firstName = "-";
$middleName = "-";
$thirdName = "-";
// Explode the senderName into an array using space as a separator
$names = explode(' ', $senderName);
// Assign the first name to $firstName
$firstName = isset($names[0]) ? $names[0] : '-';
// Assign the second name to $middleName
$middleName = isset($names[1]) ? $names[1] : '-';
// Initialize $thirdName as an empty string
$thirdName = '';
// Loop through the array starting from the third name
for ($i = 2; $i < count($names); $i++) {
$thirdName .= $names[$i] . ' ';
}
// Trim any trailing space
$thirdName = trim($thirdName);
$availableMpesa = $em->getRepository(Mpesa::class)->findOneBy([
'transactionId' => $transactionId
]);
$message = new MpesaMessage();
$message->setMessage($SMSMessage);
$message->setShortCode($shortCode);
$message->setCallbackAvailable(true);
$message->setCreatedAt(new \DateTime());
if(!$availableMpesa){
$mpesa = new Mpesa();
$mpesa->setCreatedAt(new DateTime());
$mpesa->setShortCode($shortCode);
$mpesa->setBalance($transactionBalance);
$mpesa->setMsisdn($phoneNumber);
$mpesa->setFirstName($firstName);
$mpesa->setMiddleName($middleName);
$mpesa->setLastName($thirdName);
$mpesa->setTransactionType("PAYBILL_PHONE");
$mpesa->setTransactionTime($dateTimeObject->format('YmdHis'));
$mpesa->setTransactionAmount($transactionAmount);
$mpesa->setIsUsed(0);
$mpesa->setTransactionId($transactionId);
$mpesa->setRefNumber($transactionAccountNumber);
$em->persist($mpesa);
$message->setCallbackAvailable(false);
}
// dump($mpesa);
// die;
$em->persist($message);
$em->flush();
$resp = [
'phone_number' => $phoneNumber,
'transaction_id' => $transactionId,
'sender_name' => $senderName,
'transaction_date' => $dateTimeObject,
'transaction_amount' => $transactionAmount,
'transaction_balance' => $transactionBalance
];
return new JsonResponse($resp, Response::HTTP_OK);
}
}