src/Payment/MpesaPaymentController.php line 912

Open in your IDE?
  1. <?php
  2. namespace App\Payment;
  3. use App\Controller\SendSms;
  4. use App\Entity\Mpesa;
  5. use App\Entity\MpesaAuth;
  6. use App\Entity\MpesaMessage;
  7. use App\Entity\MpesaPaybill;
  8. use App\Entity\MpesaPayment;
  9. use App\Entity\MpesaPaymentRequest;
  10. use App\Entity\MpesaResponse;
  11. use App\Entity\MpesaTransaction;
  12. use App\Entity\Transaction;
  13. use App\Entity\WayBill;
  14. use DateTime;
  15. use Doctrine\DBAL\Driver\PDO\Exception;
  16. use Doctrine\ORM\EntityRepository;
  17. use Doctrine\Persistence\ManagerRegistry;
  18. use Doctrine\Persistence\ObjectManager;
  19. use http\Message;
  20. use JMS\Serializer\SerializationContext;
  21. use JMS\Serializer\SerializerBuilder;
  22. use NumberFormatter;
  23. use Psr\Log\LoggerInterface;
  24. use Symfony\Bridge\Doctrine\Form\Type\EntityType;
  25. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  26. use Symfony\Component\Form\Extension\Core\Type\FormType;
  27. use Symfony\Component\Form\Extension\Core\Type\NumberType;
  28. use Symfony\Component\Form\Extension\Core\Type\TextType;
  29. use Symfony\Component\HttpFoundation\JsonResponse;
  30. use Symfony\Component\HttpFoundation\Request;
  31. use Symfony\Component\HttpFoundation\Response;
  32. use Symfony\Component\Routing\Annotation\Route;
  33. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  34. use Symfony\Component\Validator\Constraints\Length;
  35. use Symfony\Component\Validator\Constraints\NotBlank;
  36. class MpesaPaymentController extends AbstractController
  37. {
  38. private $logger;
  39. private $host = 'api.safaricom.co.ke';
  40. private $tokenRequest = '/oauth/v1/generate?grant_type=client_credentials';
  41. private $stkpushUrl = '/mpesa/stkpush/v1/processrequest';
  42. // private $callbackUrlRegister = '/mpesa/c2b/v2/registerurl';
  43. private $callbackUrlRegister = '/mpesa/c2b/v1/registerurl';
  44. private $authType = 'PROD';
  45. private $sms;
  46. private ObjectManager $manager;
  47. // private $url = 'https://api.safaricom.co.ke/mpesa/stkpush/v1/processrequest';
  48. public function __construct(LoggerInterface $logger, ManagerRegistry $managerRegistry)
  49. {
  50. $this->sms = new SendSms();
  51. $this->logger = $logger;
  52. $this->manager = $managerRegistry->getManager();
  53. }
  54. /**
  55. * @Route("/request/mpesa/{waybill_id}", methods={"POST"}, name="request_mpesa_express_payment")
  56. * @param $waybill_id
  57. * @return Response
  58. */
  59. public function requestMpesaExpressPayment(Request $request, $waybill_id)
  60. {
  61. $this->logger->info("Start creating request");
  62. $em = $this->getDoctrine()->getManager();
  63. /** @var WayBill $waybill */
  64. $waybill = $em->getRepository(WayBill::class)
  65. ->findOneBy([
  66. 'id' => $waybill_id
  67. ]);
  68. // /** @var Transaction $transaction */
  69. // $transaction = $em->getRepository(Transaction::class)->findOneBy([
  70. // 'wayBill' => $waybill
  71. // ]);
  72. if (!$waybill) {
  73. return $this->redirectToRoute('payment_request_view', ['waybill', $waybill_id]);
  74. }
  75. $requestSenderPhoneForm = $this->postMpesaPaymentPhoneNumber($waybill_id, 'sender_form');
  76. $requestSenderPhoneForm->handleRequest($request);
  77. $requestReceiverPhoneForm = $this->postMpesaPaymentPhoneNumber($waybill_id, 'receiver_form');
  78. $requestReceiverPhoneForm->handleRequest($request);
  79. $requestOtherPhoneForm = $this->postMpesaPaymentPhoneNumber($waybill_id, 'other_form');
  80. $requestOtherPhoneForm->handleRequest($request);
  81. $data = '';
  82. if ($requestSenderPhoneForm->isSubmitted() || $requestReceiverPhoneForm->isSubmitted() || $requestOtherPhoneForm->isSubmitted()) {
  83. // $requestForm->getData('phone');
  84. // $requestForm->get('field');
  85. // dump($requestSenderPhoneForm);
  86. // dump($requestReceiverPhoneForm);
  87. // dump($requestOtherPhoneForm);
  88. $data = null;
  89. if ($requestSenderPhoneForm->isSubmitted()) {
  90. dump($requestSenderPhoneForm);
  91. // $requestForm = $requestSenderPhoneForm;
  92. $data = $requestSenderPhoneForm->getData();
  93. } else if ($requestReceiverPhoneForm->isSubmitted()) {
  94. dump($requestReceiverPhoneForm);
  95. // $requestForm = $requestSenderPhoneForm;
  96. $data = $requestReceiverPhoneForm->getData();
  97. } else if ($requestOtherPhoneForm->isSubmitted()) {
  98. // $requestForm = $requestOtherPhoneForm;
  99. dump($requestOtherPhoneForm);
  100. $data = $requestOtherPhoneForm->getData();
  101. }
  102. // dump($data);
  103. // die;
  104. $this->logger->info('payment data', $data);
  105. $phone = str_replace(' ', '', $data['phone']);
  106. $phone = substr_replace($phone, '', 0, 1);
  107. $phone = '254' . $phone;
  108. $field = $data['field'];
  109. $waybill->getTransaction()->setPaymentMethod('MPESA');
  110. $waybill->getTransaction()->setMpesaPaymentPhone($phone);
  111. $waybill->getTransaction()->setPaidBy($field);
  112. $em->flush();
  113. $amount = $waybill->getTransaction()->getAmount();
  114. $date = $waybill->getCreatedAt();
  115. $date = date_format($date, 'YmdHis');
  116. $this->logger->info("Create formatted Date{$date}");
  117. /** @var MpesaAuth $credentials */
  118. $credentials = $em->getRepository(MpesaAuth::class)->findOneBy([
  119. 'station' => $request->getSession()->get('STATION'),
  120. 'authType' => $this->authType
  121. ]);
  122. $this->logger->info("Get credentials");
  123. // api.safaricom.co.ke/mpesa/stkpush/v1/processrequest'
  124. $url = "https://{$this->host}{$this->stkpushUrl}";
  125. // dump($authKey);
  126. if ($credentials) {
  127. $curl = curl_init();
  128. curl_setopt($curl, CURLOPT_URL, $url);
  129. curl_setopt($curl, CURLOPT_HTTPHEADER, [
  130. "Content-Type:application/json",
  131. "Authorization:Bearer {$credentials->getToken()}"
  132. ]
  133. );
  134. //setting custom header
  135. // 'CallBackURL' => $this->generateUrl("receive_mpesa_express_callback",['waybill_id' => $waybill->getId()], UrlGeneratorInterface::ABSOLUTE_URL),
  136. $curl_post_data = array(
  137. //Fill in the request parameters with valid values
  138. 'BusinessShortCode' => "{$credentials->getPaybill()}",
  139. 'Password' => base64_encode("{$credentials->getPaybill()}{$credentials->getPassKey()}{$date}"),
  140. 'Timestamp' => "{$date}",
  141. 'TransactionType' => 'CustomerPayBillOnline',
  142. 'Amount' => $amount,
  143. 'PartyA' => $phone,
  144. 'PartyB' => "{$credentials->getPaybill()}",
  145. 'PhoneNumber' => $phone,
  146. 'CallBackURL' => "https://sys.nenocourier.co.ke/payment/callback/express/{$waybill->getId()}",
  147. 'AccountReference' => $waybill->getId(),
  148. 'TransactionDesc' => "Request to {$phone} to pay for waybill {$waybill->getId()}"
  149. );
  150. // dump($curl_post_data);
  151. $data_string = json_encode($curl_post_data);
  152. curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  153. curl_setopt($curl, CURLOPT_POST, true);
  154. curl_setopt($curl, CURLOPT_POSTFIELDS, $data_string);
  155. $curl_response = curl_exec($curl);
  156. $this->logger->info("Request response");
  157. $this->logger->info($curl_response);
  158. $curlResponse = json_decode($curl_response, true);
  159. if (isset($curlResponse['errorCode'])) {
  160. $response = [
  161. 'message' => 'Error occurred during request',
  162. 'error' => $curlResponse['errorMessage']
  163. ];
  164. $jsonResponse = json_encode($response);
  165. $this->logger->error("payment_request_error", $curlResponse);
  166. $this->addFlash('success', 'Payment request sent to ' . $phone);
  167. return $this->redirectToRoute('payment_request_view', ['waybill' => $waybill_id]);
  168. //
  169. // return new Response($jsonResponse, Response::HTTP_NOT_ACCEPTABLE,[
  170. // 'content-type' => 'text/json'
  171. // ]);
  172. } elseif (isset($curlResponse['ResponseCode'])) {
  173. $response = [
  174. 'message' => 'Request sent'
  175. ];
  176. $this->addFlash('success', 'Payment request sent to ' . $phone);
  177. return $this->redirectToRoute('payment_request_view', ['waybill' => $waybill_id]);
  178. //
  179. // $response = json_encode($response);
  180. // return new Response($response, Response::HTTP_OK,[
  181. // 'content-type' => 'text/json'
  182. // ]);
  183. }
  184. }
  185. }
  186. }
  187. /**
  188. * @Route("/callback/express/awesome/{waybill_id}", name="receive_mpesa_express_callback_testing")
  189. * @param Request $request
  190. * @param $waybill_id
  191. * @return Response
  192. */
  193. public function receiveMpesaExpressCallbackTesting(Request $request, $waybill_id)
  194. {
  195. // dump($request->getContent());
  196. // $this->logger->info('Printed: ', $request->getContent());
  197. return new Response("OK", Response::HTTP_OK, [
  198. 'content-type' => 'text/html'
  199. ]);
  200. }
  201. /**
  202. * @Route("/callback/express/{waybill_id}", name="receive_mpesa_express_callback")
  203. * @param Request $request
  204. * @param $waybill_id
  205. * @return Response
  206. */
  207. public function receiveMpesaExpressCallback(Request $request, $waybill_id)
  208. {
  209. $em = $this->getDoctrine()->getManager();
  210. /** @var WayBill $waybill */
  211. $waybill = $em->getRepository("App:WayBill")->findOneBy([
  212. 'id' => $waybill_id
  213. ]);
  214. $transaction = $waybill->getTransaction();
  215. $json = $request->getContent();
  216. $this->logger->info("Gotten response mpesa");
  217. $this->logger->info($json);
  218. $response = json_decode($json, true);
  219. $mpesaPayment = new MpesaPayment();
  220. $mpesaResponse = new MpesaResponse();
  221. $merchantRequestId = $response['Body']['stkCallback']['MerchantRequestID'];
  222. $checkoutRequestID = $response['Body']['stkCallback']['CheckoutRequestID'];
  223. $resultCode = $response['Body']['stkCallback']['ResultCode'];
  224. $resultDesc = $response['Body']['stkCallback']['ResultDesc'];
  225. $mpesaResponse->setMerchantRequestId($merchantRequestId);
  226. $mpesaResponse->setCheckoutRequestId($checkoutRequestID);
  227. $mpesaResponse->setCode($resultCode);
  228. $mpesaResponse->setDescription($resultDesc);
  229. $mpesaResponse->setCreatedAt(new \DateTime());
  230. $mpesaResponse->setWaybill($waybill);
  231. $em->persist($mpesaResponse);
  232. if ($resultCode == 0) {
  233. $items = $response['Body']['stkCallback']['CallbackMetadata']['Item'];
  234. $amount = $items[0]['Value'];
  235. $receiptNumber = $items[1]['Value'];
  236. $transactionDate = '';
  237. $phoneNumber = '';
  238. if (count($items) == 5) {
  239. $transactionDate = $items[3]['Value'];
  240. $phoneNumber = $items[4]['Value'];
  241. } else {
  242. $transactionDate = $items[2]['Value'];
  243. $phoneNumber = $items[3]['Value'];
  244. }
  245. $mpesaPayment->setAmount($amount);
  246. $mpesaPayment->setMpesaPaymentType("MPESA_EXPRESS");
  247. $mpesaPayment->setWaybill($waybill);
  248. $mpesaPayment->setPhone($phoneNumber);
  249. $mpesaPayment->setResponse($mpesaResponse);
  250. $mpesaPayment->setTransactionId($receiptNumber);
  251. $mpesaPayment->setCreatedAt(new \DateTime());
  252. $em->persist($mpesaPayment);
  253. $transaction->setPaymentMethod('MPESA');
  254. }
  255. $conn = $em->getConnection();
  256. $conn->beginTransaction();
  257. try {
  258. $em->flush();
  259. $em->getConnection()->commit();
  260. } catch (\PDOException $exception) {
  261. $em->getConnection()->rollBack();
  262. }
  263. return new Response("OK", Response::HTTP_OK, [
  264. 'content-type' => 'text/html'
  265. ]);
  266. }
  267. /**
  268. * @Route("/callback/paybill", methods={"POST"}, name="receive_mpesa_paybill_callback")
  269. * @param Request $request
  270. * @return Response
  271. */
  272. public function receiveMpesaPaybillCallback(Request $request)
  273. {
  274. $em = $this->getDoctrine()->getManager();
  275. $json = $request->getContent();
  276. $this->logger->debug('mpesa-paybill response' . $json);
  277. $this->logger->error('');
  278. $response = json_decode($json, true);
  279. $transactionType = $response['TransactionType'];
  280. $transactionId = $response['TransID'];
  281. $transactionTime = $response['TransTime'];
  282. $transactionAmount = $response['TransAmount'];
  283. $shortCode = $response['BusinessShortCode'];
  284. $billRefNumber = $response['BillRefNumber'];
  285. $invoiceNumber = $response['InvoiceNumber'];
  286. $accountBalance = $response['OrgAccountBalance'];
  287. $thirdPartyTransactionId = $response['ThirdPartyTransID'];
  288. $msisdn = $response['MSISDN'];
  289. $firstName = $response['FirstName'];
  290. $secondName = '-';
  291. $lastName = '-';
  292. if ($shortCode != 4022957 && $shortCode != 726614) {
  293. $secondName = $response['MiddleName'];
  294. $lastName = $response['LastName'];
  295. }
  296. $mpesa = new Mpesa();
  297. $mpesa->setTransactionId($transactionId);
  298. $mpesa->setTransactionType($transactionType);
  299. $mpesa->setTransactionTime($transactionTime);
  300. $mpesa->setTransactionAmount($transactionAmount);
  301. $mpesa->setShortCode($shortCode);
  302. $mpesa->setRefNumber($billRefNumber);
  303. $mpesa->setBalance(($accountBalance ? $accountBalance : 0));
  304. $mpesa->setMsisdn($msisdn);
  305. $mpesa->setFirstName(($firstName ? $firstName : ''));
  306. $mpesa->setMiddleName($secondName ? $secondName : '');
  307. $mpesa->setLastName($lastName ? $lastName : '');
  308. $mpesa->setCreatedAt(new \DateTime());
  309. $mpesa->setIsUsed(false);
  310. $conn = $em->getConnection();
  311. $conn->beginTransaction();
  312. try {
  313. /** @var Mpesa $availableMpesa */
  314. $availableMpesa = $em->getRepository(Mpesa::class)->findOneBy([
  315. 'transactionId' => $transactionId
  316. ]);
  317. if($availableMpesa){
  318. $availableMpesa->setTransactionType('PAYBILL_PHONE_AV');
  319. }else{
  320. $em->persist($mpesa);
  321. }
  322. $em->getConnection()->commit();
  323. $em->flush();
  324. return new Response("OK", Response::HTTP_OK, [
  325. 'content-type' => 'text/html'
  326. ]);
  327. } catch (\PDOException $exception) {
  328. $em->getConnection()->rollBack();
  329. $this->logger->error($exception->getMessage(), []);
  330. $date = new \DateTime();
  331. $date = date_format($date, 'Y/m/d H:i:s');
  332. $this->sms->sendOutSms('254716308459', "HELLO Samuel A payment mpesa error occurred during COMMIT at {$date}");
  333. return new Response("NOT OK", Response::HTTP_INTERNAL_SERVER_ERROR, [
  334. 'content-type' => 'text/html'
  335. ]);
  336. }
  337. /*return new Response("NOT OK", Response::HTTP_INTERNAL_SERVER_ERROR, [
  338. 'content-type' => 'text/html'
  339. ]);*/
  340. }
  341. /**
  342. * @Route("/callback/paybill/validation", methods={"POST"}, name="receive_mpesa_paybill_callback_validation")
  343. * @param Request $request
  344. * @return Response
  345. */
  346. public function receiveMpesaPaybillCallbackValidation(Request $request)
  347. {
  348. $json = $request->getContent();
  349. $this->logger->log('MPESA VALIDATION: ',$json);
  350. return new JsonResponse([
  351. "ResultCode" => '0',
  352. "ResultDesc" => "Accepted"
  353. ], Response::HTTP_OK);
  354. }
  355. /**
  356. * @Route("/response/mpesa/{waybill_id}", name="payment_response_verification")
  357. * @param $waybill_id
  358. * @return Response
  359. */
  360. public function paymentResponseVerification($waybill_id)
  361. {
  362. $em = $this->getDoctrine()->getManager();
  363. /** @var WayBill $waybill */
  364. $waybill = $em->getRepository("App:WayBill")->findOneBy([
  365. 'id' => $waybill_id
  366. ]);
  367. // dump($waybill);
  368. if ($waybill) {
  369. /** @var MpesaPaymentRequest $mpesaRequest */
  370. $mpesaRequest = $em->getRepository("App:MpesaPaymentRequest")
  371. ->findBy([
  372. 'waybill' => $waybill
  373. ]);
  374. /** @var MpesaResponse $mpesaResponse */
  375. $mpesaResponses = $em->getRepository("App:MpesaResponse")
  376. ->findBy([
  377. 'waybill' => $waybill
  378. ]);
  379. /** @var MpesaPayment $mpesaPayment */
  380. $mpesaPayments = $em->getRepository("App:MpesaPayment")
  381. ->findBy([
  382. 'waybill' => $waybill
  383. ]);
  384. if ($mpesaResponses) {
  385. return $this->render('fos/parcels/mpesa_payment.html.twig', [
  386. 'mpesa_responses' => $mpesaResponses,
  387. 'mpesa_payments' => $mpesaPayments,
  388. 'waybill' => $waybill,
  389. 'isAvailable' => true,
  390. 'mpesa_requests' => $mpesaRequest
  391. ]);
  392. }
  393. return $this->render('fos/parcels/mpesa_payment.html.twig', [
  394. 'isAvailable' => false,
  395. 'waybill' => $waybill
  396. ]);
  397. }
  398. return $this->render('fos/parcels/mpesa_payment.html.twig', [
  399. 'isAvailable' => false,
  400. ]);
  401. }
  402. /**
  403. * @Route("/renew/mpesa_credentials", name="renew_mpesa_authentication")
  404. */
  405. function getMpesaAuthentication(Request $request)
  406. {
  407. $em = $this->getDoctrine()->getManager();
  408. $ip = $request->getClientIp();
  409. $date = new \DateTime();
  410. $date = date_format($date, 'YmdHis');
  411. $this->logger->info("Create formatted Date{$date}");
  412. /** @var MpesaAuth $credentials */
  413. $credentials = $em->getRepository(MpesaAuth::class)->findBy([
  414. 'authType' => $this->authType
  415. ]);
  416. $this->logger->info("Get credentials");
  417. dump($credentials);
  418. // die;
  419. // dump($credentials->getConsumerSecret().' '.$credentials->getConsumerKey());
  420. // ips: [35.180.247.100]
  421. $errors = '';
  422. foreach ($credentials as $index => $credential) {
  423. if ($credential) {
  424. // api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials
  425. $url = "https://{$this->host}{$this->tokenRequest}";
  426. $password = base64_encode("{$credential->getConsumerKey()}:{$credential->getConsumerSecret()}");
  427. $curl = curl_init();
  428. curl_setopt($curl, CURLOPT_URL, $url);
  429. curl_setopt($curl, CURLOPT_HTTPHEADER, [
  430. "Content-Type:application/json",
  431. "Authorization:Basic {$password}"
  432. ]
  433. );
  434. curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  435. curl_setopt($curl, CURLOPT_HTTPGET, true);
  436. // curl_setopt($curl, CURLOPT_POSTFIELDS, $data_string);
  437. $curl_response = curl_exec($curl);
  438. dump($curl_response);
  439. $this->logger->info("Request response");
  440. $this->logger->info($curl_response);
  441. $curlResponse = json_decode($curl_response, true);
  442. if ($curlResponse) {
  443. $response = json_decode($curl_response, true);
  444. $credential->setToken($response['access_token']);
  445. $credential->setTokenUpdatedAt(new \DateTime());
  446. $em->flush();
  447. // $date = new \DateTime();
  448. // $date = date_format($date, 'Y/m/d H:i:s');
  449. // $this->sms->sendOutSms('254716308459', "HELLO Samuel MPESA TOKEN GENERATION SUCCEEDED at {$date}");
  450. } else {
  451. $date = new \DateTime();
  452. $date = date_format($date, 'Y/m/d H:i:s');
  453. $this->sms->sendOutSms('254716308459', "HELLO Samuel MPESA TOKEN GENERATION FAILED at {$date} pleae check it out");
  454. $errors .= $credential->getPaybill() . ' Could not create credentials';
  455. // return new Response("error", Response::HTTP_OK, [
  456. // 'content-type'=>'text/html'
  457. // ]);
  458. }
  459. }
  460. }
  461. die;
  462. return new Response("AWESOME {$errors}_" . $ip, Response::HTTP_OK, [
  463. 'content-type' => 'text/html'
  464. ]);
  465. // $sms = new SendSms();
  466. // $sms->sendOutSms('254716308459', "HELLO CRON WORKED");
  467. return new Response("error", Response::HTTP_OK, [
  468. 'content-type' => 'text/html'
  469. ]);
  470. }
  471. // private function getMSISDN($phone){
  472. // $code = '254';
  473. // if(substr($phone, 1) == 0){
  474. // return $phone;
  475. // }
  476. // return $code.substr($phone, 1);
  477. // }
  478. /**
  479. * @Route("/request_view/{waybill}", methods={"POST", "GET"}, name="payment_request_view")
  480. */
  481. function paymentRequestView($waybill, Request $request){
  482. $em = $this->getDoctrine()->getManager();
  483. @ini_set("memory_limit", -1);
  484. @ini_set("max_duration", -1);
  485. /** @var Transaction $transaction */
  486. $transaction = $em->getRepository(Transaction::class)->findOneBy([
  487. 'wayBill' => $waybill
  488. ]);
  489. if (!$transaction) {
  490. $this->addFlash('warning', 'this transaction has been paid fully');
  491. return $this->redirectToRoute('new-parcel');
  492. }
  493. /** @var MpesaAuth $credentials */
  494. $credentials = $em->getRepository(MpesaAuth::class)->findOneBy([
  495. 'station' => $request->getSession()->get('STATION'),
  496. 'authType' => $this->authType
  497. ]);
  498. $requestSenderPhoneForm = $this->postMpesaPaymentPhoneNumber($waybill, 'sender_form');
  499. $requestReceiverPhoneForm = $this->postMpesaPaymentPhoneNumber($waybill, 'receiver_form');
  500. $requestOtherPhoneForm = $this->postMpesaPaymentPhoneNumber($waybill, 'other_form');
  501. $mpesaPaymentSelection = $this->postMpesaPaymentSelection($waybill, $credentials, 'mpesa_payment_selection');
  502. $mpesaCashPaymentSelection = $this->postMpesaCashPaymentSelection($waybill, $credentials, 'mpesa_cash_payment_selection');
  503. $mpesaCashPaymentSelection->handleRequest($request);
  504. $mpesaPaymentSelection->handleRequest($request);
  505. $conn = $em->getConnection();
  506. if ($mpesaCashPaymentSelection->isSubmitted()) {
  507. $data = $mpesaCashPaymentSelection->getData();
  508. if (!$data) {
  509. $this->addFlash('warning', 'error transaction data not available please select again');
  510. return $this->redirectToRoute('payment_request_view', ['id' => $waybill]);
  511. }
  512. // dump($data);
  513. // die;
  514. if ($transaction->getIsPaid()) {
  515. if (!$transaction->getPaymentMethod() == 'CASH') {
  516. $this->addFlash('warning', 'this transaction has been paid fully');
  517. return $this->redirectToRoute('one_way_bill', ['id' => $waybill]);
  518. }
  519. }
  520. try {
  521. $conn->beginTransaction();
  522. $amount = 0;
  523. $mpesa = null;
  524. foreach ($data['mpesa'] as $index => $datum) {
  525. $mpesaPayment = new MpesaTransaction();
  526. $mpesaPayment->setCreatedAt(new \DateTime());
  527. $mpesaPayment->setCreatedBy($this->getUser());
  528. $mpesaPayment->setMpesa($datum);
  529. $mpesaPayment->setTransaction($transaction);
  530. $datum->setIsUsed(true);
  531. $em->persist($mpesaPayment);
  532. $mpesa = $datum;
  533. $amount += $datum->getTransactionAmount();
  534. }
  535. // $transaction->getDailyAccount()->setMpesa($transaction->getDailyAccount()->getMpesa() + $mpesa->getTransactionAmount());
  536. $transaction->setPaymentMethod('MPESA_CASH');
  537. $transaction->setMpesaAmount($amount);
  538. $transaction->setCashAmount(0);
  539. if ($transaction->getAmount() > $amount) {
  540. $transaction->setCashAmount($transaction->getAmount() - $amount);
  541. }
  542. $transaction->setPaidBy('OTHER');
  543. $transaction->setIsPaid(true);
  544. $transaction->setIsComplete(true);
  545. $em->getConnection()->commit();
  546. $em->flush();
  547. // $this->sms->sendOutSms($mpesa->getMsisdn(), "HELLO {$mpesa->getFirstName()} your M-PESA payment {$mpesa->getTransactionId()} has been received for waybill: {$transaction->getWayBill()->getId()}
  548. // 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");
  549. $this->sms->sendOutSms($mpesa->getMsisdn(), "HELLO {$mpesa->getFirstName()} your M-PESA payment {$mpesa->getTransactionId()} has been received for waybill: {$transaction->getWayBill()->getId()}
  550. STAY SAFE & Travel SAFE WITH NENO SACCO Matatus, You can also save and grow with NENO SACCO BOSA Account.");
  551. $this->addFlash('success', 'Mpesa transaction successful');
  552. return $this->redirectToRoute('one_way_bill', ['id' => $waybill]);
  553. } catch (\PDOException $exception) {
  554. $em->getConnection()->rollBack();
  555. $this->logger->error($exception->getMessage(), []);
  556. $this->addFlash('error', 'Error Occurred please contact admin');
  557. return $this->redirectToRoute('payment_request_view', ['waybill' => $waybill]);
  558. }
  559. }
  560. if ($mpesaPaymentSelection->isSubmitted()) {
  561. $data = $mpesaPaymentSelection->getData();
  562. // $id = $data['id'];
  563. // $amount = $data['id'];
  564. // dump($data);
  565. //
  566. // die;
  567. if (!$data) {
  568. $this->addFlash('warning', 'error transaction data not available please select again');
  569. return $this->redirectToRoute('payment_request_view', ['id' => $waybill]);
  570. }
  571. if ($transaction->getIsPaid()) {
  572. if (!$transaction->getPaymentMethod() == 'CASH') {
  573. $this->addFlash('warning', 'this transaction has been paid fully');
  574. return $this->redirectToRoute('one_way_bill', ['id' => $waybill]);
  575. }
  576. }
  577. try {
  578. $conn->beginTransaction();
  579. $amount = 0;
  580. $mpesa = null;
  581. foreach ($data['mpesa'] as $index => $datum) {
  582. $datumMpesa = $em->getRepository(Mpesa::class)->findOneBy([
  583. 'id' => $datum->getId()
  584. ]);
  585. $mpesaPayment = new MpesaTransaction();
  586. $mpesaPayment->setCreatedAt(new \DateTime());
  587. $mpesaPayment->setCreatedBy($this->getUser());
  588. $mpesaPayment->setMpesa($datum);
  589. $mpesaPayment->setTransaction($transaction);
  590. $datum->setIsUsed(true);
  591. $em->persist($mpesaPayment);
  592. $mpesa = $datum;
  593. $amount += $datumMpesa->getTransactionAmount();
  594. }
  595. // $transaction->getDailyAccount()->setMpesa($transaction->getDailyAccount()->getMpesa() + $mpesa->getTransactionAmount());
  596. $transaction->setPaymentMethod('MPESA');
  597. $transaction->setCashAmount(0);
  598. $transaction->setMpesaAmount($amount);
  599. $transaction->setIsComplete(true);
  600. $transaction->setPaidBy('OTHER');
  601. $transaction->setIsPaid(true);
  602. $transaction->setIsComplete(true);
  603. $em->getConnection()->commit();
  604. $em->flush();
  605. // $this->sms->sendOutSms($mpesa->getMsisdn(), "HELLO {$mpesa->getFirstName()} your M-PESA payment {$mpesa->getTransactionId()} has been received for waybill: {$transaction->getWayBill()->getId()}
  606. // STAY SAFE WITH Neno Courier Services");
  607. // $this->sms->sendOutSms($mpesa->getMsisdn(), "HELLO {$mpesa->getFirstName()} your M-PESA payment {$mpesa->getTransactionId()} has been received for waybill: {$transaction->getWayBill()->getId()}
  608. // STAY SAFE & Travel SAFE WITH NENO SACCO Matatus, You can also save and grow with NENO SACCO BOSA Account.");
  609. $this->addFlash('success', 'Mpesa transaction successful');
  610. return $this->redirectToRoute('one_way_bill', ['id' => $waybill]);
  611. } catch (\PDOException $exception) {
  612. $em->getConnection()->rollBack();
  613. $this->logger->error($exception->getMessage(), []);
  614. $this->addFlash('error', 'Error Occurred please contact admin');
  615. return $this->redirectToRoute('payment_request_view', ['waybill' => $waybill]);
  616. }
  617. }
  618. return $this->render('payment/payment.html.twig', [
  619. 'transaction' => $transaction,
  620. 'senderForm' => $requestSenderPhoneForm->createView(),
  621. 'receiverForm' => $requestReceiverPhoneForm->createView(),
  622. 'otherForm' => $requestOtherPhoneForm->createView(),
  623. 'mpesaSelectionForm' => $mpesaPaymentSelection->createView(),
  624. 'mpesaCashSelectionForm' => $mpesaCashPaymentSelection->createView()
  625. ]);
  626. }
  627. /**
  628. * @Route("/callback/register/{paybill}", name="register_callback_urls")
  629. * @return Response|null
  630. */
  631. function paymentUrlRegister(Request $request, $paybill)
  632. {
  633. $em = $this->getDoctrine()->getManager();
  634. /** @var MpesaAuth $credentials */
  635. $credentials = $em->getRepository(MpesaAuth::class)->findBy([
  636. 'authType' => $this->authType,
  637. 'paybill' => $paybill
  638. ]);
  639. // dump($credentials);
  640. // die;
  641. $this->logger->debug("Get credentials");
  642. $response = [];
  643. foreach ($credentials as $index => $credential) {
  644. $url = "https://{$this->host}{$this->callbackUrlRegister}";
  645. $curl = curl_init();
  646. curl_setopt($curl, CURLOPT_URL, $url);
  647. curl_setopt($curl, CURLOPT_HTTPHEADER, [
  648. "Content-Type:application/json",
  649. "Authorization:Bearer {$credential->getToken()}"
  650. ]
  651. );
  652. dump($credential->getStation());
  653. $curl_post_data = [
  654. 'ShortCode' => $credential->getPaybill(),
  655. 'ResponseType' => 'Cancelled',
  656. 'ConfirmationURL' => 'https://sys.nenocourier.co.ke/payment/callback/paybill',
  657. 'ValidationURL' => 'https://sys.nenocourier.co.ke/payment/callback/paybill/validation'
  658. ];
  659. // dump($curl_post_data);
  660. $data_string = json_encode($curl_post_data);
  661. curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  662. curl_setopt($curl, CURLOPT_POST, true);
  663. curl_setopt($curl, CURLOPT_POSTFIELDS, $data_string);
  664. $curl_response = curl_exec($curl);
  665. dump($curl_response); die;
  666. $this->logger->info("Request response");
  667. $this->logger->info($curl_response);
  668. $curlResponse = json_decode($curl_response, true);
  669. $this->logger->error('Url registration ', $curlResponse);
  670. dump($curl_response);
  671. }
  672. return new Response('awesome', Response::HTTP_OK, [
  673. 'content-type' => 'text/html'
  674. ]);
  675. }
  676. /**
  677. * @Route("/list", methods={"POST"}, name="mpesa_payment_list")
  678. * @param Request $request
  679. * @return Response
  680. */
  681. public function unUsedtransactions(Request $request)
  682. {
  683. $em = $this->getDoctrine()->getManager();
  684. $context = new SerializationContext();
  685. $context->setSerializeNull(true);
  686. $serializer = SerializerBuilder::create()->build();
  687. /** @var MpesaAuth $credentials */
  688. $credentials = $em->getRepository(MpesaAuth::class)->findOneBy([
  689. 'station' => $request->getSession()->get('STATION'),
  690. 'authType' => $this->authType
  691. ]);
  692. $em = $this->getDoctrine()->getManager();
  693. // /** @var Mpesa[] $transactions */
  694. // $transactions = $em->getRepository('App:Mpesa')->findBy([
  695. // 'isUsed' => false,
  696. // 'shortCode' => $credentials->getPaybill()
  697. // ]);
  698. /** @var Mpesa[] $transactions */
  699. $transactions = $em->getRepository('App\Entity\Mpesa')->findTransactions('false', $credentials->getPaybill());
  700. $data = [
  701. 'rows' => $transactions,
  702. 'total' => count($transactions)
  703. ];
  704. $data = $serializer->serialize($data, 'json', $context);
  705. return new Response($data, Response::HTTP_OK);
  706. }
  707. private function postMpesaPaymentPhoneNumber($waybill, $formName)
  708. {
  709. $fb = $this->get('form.factory')
  710. ->createNamedBuilder($formName);
  711. $fb->add('phone', TextType::class, [
  712. 'constraints' => [
  713. new NotBlank(['message' => 'phone number should be available']),
  714. new Length(['min' => 12, 'max' => 12, 'maxMessage' => 'Phone number is incorrect', 'minMessage' => 'Phone number is incorrect'])
  715. ]
  716. ])
  717. ->add('field', TextType::class, [
  718. 'constraints' => [
  719. new NotBlank(['message' => 'please key in the field'])
  720. ]
  721. ]);
  722. return $fb
  723. ->setAction($this->generateUrl('request_mpesa_express_payment', ['waybill_id' => $waybill]))
  724. ->setMethod('POST')
  725. ->getForm();
  726. }
  727. private function postMpesaPaymentSelection($waybill, $credentials, $formName)
  728. {
  729. $fb = $this->get('form.factory')
  730. ->createNamedBuilder($formName);
  731. $fb->add('mpesa', EntityType::class, [
  732. 'constraints' => [
  733. new NotBlank(['message' => 'Please select mpesa transaction'])
  734. ],
  735. 'class' => 'App\Entity\Mpesa',
  736. 'expanded' => true,
  737. 'multiple' => true,
  738. 'query_builder' => function (EntityRepository $er) use ($credentials) {
  739. return $er->createQueryBuilder('m')
  740. ->andWhere('m.isUsed = false')
  741. ->andWhere('m.shortCode = ' . $credentials->getPaybill());
  742. }
  743. ]);
  744. return $fb
  745. ->setAction($this->generateUrl('payment_request_view', ['waybill' => $waybill]))
  746. ->setMethod('POST')
  747. ->getForm();
  748. }
  749. private function postMpesaCashPaymentSelection($waybill, $credentials, $formName)
  750. {
  751. $fb = $this->get('form.factory')
  752. ->createNamedBuilder($formName);
  753. $fb->add('mpesa', EntityType::class, [
  754. 'constraints' => [
  755. new NotBlank(['message' => 'Please select mpesa transaction'])
  756. ],
  757. 'class' => 'App\Entity\Mpesa',
  758. 'expanded' => true,
  759. 'multiple' => true,
  760. 'query_builder' => function (EntityRepository $er) use ($credentials) {
  761. return $er->createQueryBuilder('m')
  762. ->andWhere('m.isUsed = false')
  763. ->andWhere('m.shortCode = ' . $credentials->getPaybill());
  764. }
  765. ]);
  766. return $fb
  767. ->setAction($this->generateUrl('payment_request_view', ['waybill' => $waybill]))
  768. ->setMethod('POST')
  769. ->getForm();
  770. }
  771. /**
  772. * @Route("/sms", methods={"GET"}, name="send_sms")
  773. * @param Request $request
  774. * @return Response
  775. */
  776. public function sendSms(Request $request)
  777. {
  778. $results = $this->sms->sendOutSms('254716308459',
  779. "HELLO samuel your test sms STAY SAFE WITH Neno Courier Services");
  780. dump($results);
  781. die;
  782. }
  783. /**
  784. * @Route("/transponder", methods={"POST", "GET"}, name="mpesaTransponder")
  785. * @param Request $request
  786. * @return JsonResponse
  787. */
  788. public function mpesaTransponder(Request $request): JsonResponse
  789. {
  790. $em = $this->manager;
  791. $data = json_decode($request->getContent(), true);
  792. $SMSMessage = $data['message'];
  793. $shortCode = $data['short_code'];
  794. if ($shortCode == 174379) {
  795. $shortCode = 4022961;
  796. }
  797. // $text = $SMSMessage;
  798. // $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";
  799. // $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";
  800. $text = $SMSMessage;
  801. /**
  802. * (\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:
  803. *
  804. * \d: Matches any digit from 0 to 9.
  805. * {12}: Specifies that the preceding \d should occur exactly 12 times, which means it matches 12 digits in a row.
  806. * \.: This part matches a literal period (dot) character (.).
  807. *
  808. * So, when you put the entire expression together, (\d{12})\. matches and captures
  809. * a sequence of exactly 12 digits followed by a period in the input text.
  810. * This is useful for extracting a 12-digit number followed by a period,
  811. * which is often used for various purposes, such as account numbers or identifiers.
  812. */
  813. $patternSenderPhone = '/(\d{12})\./';
  814. /**
  815. * 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.
  816. *
  817. * 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:
  818. * - `\w`: Matches any word character (letters, digits, or underscores).
  819. * - `+`: Specifies that the preceding `\w` should occur one or more times.
  820. *
  821. * 3. `Confirmed\.`: This part of the regular expression matches the literal text "Confirmed." followed by a period (dot).
  822. *
  823. * So, when you put the entire expression together, `/^(\w+) Confirmed\./` matches and captures a sequence of word
  824. * characters (e.g., a word or alphanumeric code) followed by the exact text "Confirmed."
  825. * and a period at the beginning of a line or string. This is useful for extracting specific information,
  826. * such as a transaction ID, that is followed by "Confirmed." in the input text.
  827. */
  828. $patternTransactionId = '/^(\w+) Confirmed\./';
  829. /**
  830. *
  831. * 1. `received from`: This part of the regular expression matches the literal text "received from".
  832. *
  833. * 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
  834. * whitespace characters (space or tab). Here's the breakdown of this part:
  835. * - `[A-Z\s]`: This is a character class that matches any uppercase letter (A to Z) or a whitespace character.
  836. * - `+`: Specifies that the preceding character class `[A-Z\s]` should occur one or more times in a row.
  837. *
  838. * 3. `\d{12}`: This part matches exactly 12 consecutive digits (0-9). It's used to match a 12-digit number.
  839. *
  840. * 4. `\.`: This part matches a literal period (dot) character (`.`).
  841. *
  842. * So, when you put the entire expression together, `/received from ([A-Z\s]+) \d{12}\./`
  843. * matches the text "received from" followed by a sequence of one or more uppercase letters or spaces,
  844. * then a 12-digit number, and finally a period. This pattern is useful for extracting the sender's
  845. * name and their associated 12-digit number, often found in transaction descriptions or logs.
  846. */
  847. // $patternSenderName = '/received from ([A-Z\s]+) \d{12}\./';
  848. // $patternSenderName = '/received from ([A-Z][A-Z\s]+?) \d{12}\./';
  849. $patternSenderName = '/received from ([A-Z][A-Z\s]+?) \d{12}\./i';
  850. /**
  851. * 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:
  852. *
  853. * 1. `on`: This part of the regular expression matches the literal text "on".
  854. *
  855. * 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:
  856. * - `\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").
  857. * - ` at `: This matches the space and the text " at ".
  858. * - `\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").
  859. * - `[APM]{2}`: This matches either "AM" or "PM," which represents the time of day.
  860. *
  861. * So, when you apply this regular expression to a text, it captures and extracts a specific date and time format,
  862. * including the day, month, year, hour, and minute, often found in date and time descriptions.
  863. *
  864. */
  865. $patternTransactionDate = '/on (\d{1,2}\/\d{1,2}\/\d{2} at \d{1,2}:\d{2} [APM]{2})/';
  866. /**
  867. * 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:
  868. *
  869. * Ksh: This part of the regular expression matches the literal text "Ksh".
  870. *
  871. * (\d+\.\d{2}): This is a capturing group that matches a currency amount format with the following components:
  872. *
  873. * \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.
  874. * \.: This matches the literal decimal point (dot).
  875. * \d{2}: This matches exactly two digits, which are typically used for the cents or fractional part of the currency amount.
  876. * received from: This part matches the literal text " received from".
  877. *
  878. * So, when you apply this regular expression to a text, it captures and extracts a currency amount formatted as
  879. * "Ksh" followed by one or more digits for the whole part, a decimal point,
  880. * and exactly two digits for the cents part, often found in financial or transaction descriptions.
  881. */
  882. $patternAmount = '/Ksh([\d,]+.\d{2}) received from/';
  883. $patternAmount = '/Ksh([\d,]+.\d{2}) received/';
  884. $patternBalance = '/Ksh([\d,]+.\d{2})/';
  885. $patternAccountNumber = '/Account Number ([A-Z\s]+) New/';
  886. $phoneNumber = 0;
  887. $transactionId = 0;
  888. $senderName = 0;
  889. $transactionDate = 0;
  890. $transactionAmount = 0;
  891. $transactionBalance = 0;
  892. $transactionAccountNumber = 0;
  893. $fmt = new NumberFormatter( 'en_US', NumberFormatter::DECIMAL );
  894. if (preg_match($patternSenderPhone, $text, $matches)) {
  895. $phoneNumber = $matches[1];
  896. }
  897. if (preg_match($patternTransactionId, $text, $matches)) {
  898. $transactionId = $matches[1];
  899. }
  900. if (preg_match($patternSenderName, $text, $matches)) {
  901. $senderName = $matches[1];
  902. dump($senderName);
  903. }
  904. if (preg_match($patternTransactionDate, $text, $matches)) {
  905. $transactionDate = $matches[1];
  906. }
  907. if (preg_match($patternAmount, $text, $matches)) {
  908. $transactionAmount = $matches[1];
  909. $transactionAmount = $fmt->parse($transactionAmount);
  910. }
  911. if (preg_match($patternBalance, $text, $matches)) {
  912. $transactionBalance = $matches[1];
  913. $transactionBalance = $fmt->parse($transactionBalance);
  914. }
  915. if (preg_match($patternAccountNumber, $text, $matches)) {
  916. $transactionAccountNumber = $matches[1];
  917. }
  918. /**
  919. * We use DateTime::createFromFormat to convert the captured date and time string into a
  920. * DateTime PHP object. The format 'd/m/y \a\t h:i A' matches the format of the captured date and time string.
  921. * It specifies the day, month, year, hour, minute, and AM/PM format.
  922. */
  923. $dateTimeObject = DateTime::createFromFormat('d/m/y \a\t h:i A', $transactionDate);
  924. if (!$transactionId) {
  925. $resp = [
  926. 'sms' => $text,
  927. 'message' => "Invalid SMS Message the message should an M-Pesa notification message"
  928. ];
  929. return new JsonResponse($resp, Response::HTTP_OK);
  930. }
  931. $firstName = "-";
  932. $middleName = "-";
  933. $thirdName = "-";
  934. // Explode the senderName into an array using space as a separator
  935. $names = explode(' ', $senderName);
  936. // Assign the first name to $firstName
  937. $firstName = isset($names[0]) ? $names[0] : '-';
  938. // Assign the second name to $middleName
  939. $middleName = isset($names[1]) ? $names[1] : '-';
  940. // Initialize $thirdName as an empty string
  941. $thirdName = '';
  942. // Loop through the array starting from the third name
  943. for ($i = 2; $i < count($names); $i++) {
  944. $thirdName .= $names[$i] . ' ';
  945. }
  946. // Trim any trailing space
  947. $thirdName = trim($thirdName);
  948. $availableMpesa = $em->getRepository(Mpesa::class)->findOneBy([
  949. 'transactionId' => $transactionId
  950. ]);
  951. $message = new MpesaMessage();
  952. $message->setMessage($SMSMessage);
  953. $message->setShortCode($shortCode);
  954. $message->setCallbackAvailable(true);
  955. $message->setCreatedAt(new \DateTime());
  956. if(!$availableMpesa){
  957. $mpesa = new Mpesa();
  958. $mpesa->setCreatedAt(new DateTime());
  959. $mpesa->setShortCode($shortCode);
  960. $mpesa->setBalance($transactionBalance);
  961. $mpesa->setMsisdn($phoneNumber);
  962. $mpesa->setFirstName($firstName);
  963. $mpesa->setMiddleName($middleName);
  964. $mpesa->setLastName($thirdName);
  965. $mpesa->setTransactionType("PAYBILL_PHONE");
  966. $mpesa->setTransactionTime($dateTimeObject->format('YmdHis'));
  967. $mpesa->setTransactionAmount($transactionAmount);
  968. $mpesa->setIsUsed(0);
  969. $mpesa->setTransactionId($transactionId);
  970. $mpesa->setRefNumber($transactionAccountNumber);
  971. $em->persist($mpesa);
  972. $message->setCallbackAvailable(false);
  973. }
  974. // dump($mpesa);
  975. // die;
  976. $em->persist($message);
  977. $em->flush();
  978. $resp = [
  979. 'phone_number' => $phoneNumber,
  980. 'transaction_id' => $transactionId,
  981. 'sender_name' => $senderName,
  982. 'transaction_date' => $dateTimeObject,
  983. 'transaction_amount' => $transactionAmount,
  984. 'transaction_balance' => $transactionBalance
  985. ];
  986. return new JsonResponse($resp, Response::HTTP_OK);
  987. }
  988. }