Request for Assistance with MikroTik Integration

Hello MikroTik forum community,

I am Okinda, a Python/Django software engineer. I am currently working on a project that enables hotspot users to connect seamlessly to a MikroTik router after validation.

Users can connect in three ways:

  1. Redeem Voucher:
    The user is provided with a unique voucher code to enter, which is then validated.
    2.Mobile Payment:
    The user can pay via mobile phone, and after validation, they are granted access to the hotspot service.
    3.Activate Account:
    If the user is disconnected from the hotspot service but the voucher code is still active, they can re-enter the code to reconnect.

However, I am facing a challenge. After the user is validated, I encounter the following error:
“Error: Unable to connect to MikroTik. Check network or credentials.”

I suspect this error might be caused by MikroTik’s firewall restrictions. I am currently stuck, which is why I am reaching out for your assistance.

Additional Information:

1.My Django backend is hosted on Heroku.
2.My login.html file (frontend), built with pure HTML, CSS, and JavaScript, is hosted in MikroTik’s Files directory.
3.My MikroTik RouterOS version is 7.16.1.

I have pasted the following code snippet for your review:

  1. login.html(frontend)
  2. views.py (backend)
    3.connect_mikrotik.py(backend)

If you need more information or clarification about this project, please let me know.

# login.html
<!DOCTYPE html>
<html lang="en">
<html>
    
<head>
	<!--link rel="stylesheet" type="text/css" href="/static/mpesa/css/mikrotik_mpesa_pay3.css"-->
	<link rel="icon" type="image/x-icon" href="/static/mpesa/img/favicon.ico">
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<meta http-equiv="X-UA-Compatible" content="ie=edge">
	<title>Adniko.Net</title>

	<!--link rel="stylesheet" href="tailwind.min.css"-->
	<!--link href="hotspot/hotspot/css/tailwind.min.css" rel="stylesheet">
	<link rel="stylesheet" href="hotspot/hotspot/css/bootstrap.min.css">
	<script src="hotspot/hotspot/js/bootstrap.bundle.min.js"></script>
	<script src="hotspot/hotspot/js/sweetalert2.all.min.js"></script>
	<script src="hotspot/hotspot/js/jquery-3.3.1.slim.min.js"></script-->

    <link href="css/tailwind.min.css" rel="stylesheet">
    <link rel="stylesheet" href="css/bootstrap.min.css">

    <style>
        /* For large screens (laptops/desktops) */
        #packages-container > div {
            flex: 1 1 30%; /* Each card occupies 30% of the container width */
            max-width: 30%;
            box-sizing: border-box;
        }

        /* For medium screens (tablets) */
        @media (max-width: 1024px) {
            #packages-container > div {
                flex: 1 1 45%; /* Each card occupies 45% of the width */
                max-width: 45%;
            }
        }

        /* For small screens (phones) */
        @media (max-width: 500px) {
            #packages-container > div {
                flex: 1 1 40%; /* Each card occupies 80% of the width */
                max-width: 40%;
            }
        }
    </style>

   


</head>

<!--body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #2f2f38; justify-content: center; align-items: center; height: 100vh;"-->
<!--body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #101012;
justify-content: center; align-items: center; height: 100vh; width: 100%;"-->
<body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #101012;">



    <div style="margin: auto; max-width: 90%; padding: 0.5rem;">
        <div style="margin: auto; margin-top: 1rem; display: flex; justify-content: center; align-items: center; 
                    max-width: 40rem; overflow: hidden; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 
                    border-radius: 0.5rem; background-color: #fee2e2; height: auto;">
            <div style="position: relative; display: flex; flex-direction: column; align-items: center; padding: 1rem; max-width: 36rem;">
                <p style="margin-bottom: 1rem; text-align: center; font-size: 1.5rem; font-weight: bold; color: #1a202c;">Adniko.Net HOTSPOT LOGIN</p>
                <ol style="text-align: left; font-weight: normal; font-size: 1rem; color: #2d3748; list-style-type: decimal; margin-bottom: 1rem;">
                    <li>Choose your Preferred Package</li>
                    <li>Click on "Buy" button</li>
                    <li>Enter Mpesa No.</li>
                    <li>Enter MPESA PIN</li>
                    <li>Wait to be Connected</li>
                </ol>
                <p style="font-weight: bold; white-space: nowrap; font-size: 1rem; color: #4a5568;">
                    For any enquiries contact: 0722000000
                </p>
            </div>
        </div>
    </div>



    <div style="padding-top: 0.1rem; padding-bottom: 0.1rem;">
        <div style="margin: auto; max-width: 90%; padding: 0.5rem;">
            <div style="margin: auto; max-width: 40rem;">
                <div style="display: flex; flex-direction: column; gap: 1rem;">
                    <button type="button" 
                            style="display: flex; align-items: center; justify-content: center; gap: 0.5rem; 
                                   border-radius: 0.5rem; background-color: #ef4444; padding: 0.75rem 2rem; 
                                   text-align: center; font-size: 1rem; font-weight: 600; color: #ffffff; 
                                   outline: none; border: none; cursor: pointer; transition: background-color 0.2s ease-in-out;"
                            onclick="redeemVoucher()"
                            onmouseover="this.style.backgroundColor='#dc2626'" 
                            onmouseout="this.style.backgroundColor='#ef4444'">
                        <svg style="width: 1.25rem; height: 1.25rem; margin-right: 0.5rem;" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v13m0-13V6a2 2 0 112 2h-2zm0 0V5.5A2.5 2.5 0 109.5 8H12zm-7 4h14M5 12a2 2 0 110-4h14a2 2 0 110 4M5 12v7a2 2 0 002 2h10a2 2 0 002-2v-7"></path>
                        </svg>
                        Click here to Redeem Voucher
                    </button>
                </div>
            </div>
        </div>
    </div>


    

    <!--div id="packages-container" 
        style="width: 100%; margin: 1rem auto; display: flex; flex-wrap: wrap; justify-content: center; gap: 1rem;">
    </div-->

    <div id="packages-container" 
        style="width: 100%; margin: 0.1rem auto; display: flex; flex-wrap: wrap; justify-content: center; gap: 0.1rem;">
    </div>

    
        
        

   
    <div class="container mx-auto px-4 mb-4">
        <form method="POST" action="" class="bg-light mx-auto p-4 rounded shadow-sm" style="max-width: 400px; background-color: #ffd6d2;">
            
            <p class="text-center fw-bold mb-3" style="font-size: clamp(1rem, 2.5vw, 1.5rem);">
                Already Have an Active Package?
            </p>
            <div class="mb-3">
                <label for="accountID" class="form-label fw-bold" style="color: #555;">Enter your account number</label>
                <input 
                    id="accountID" 
                    name="accountID" 
                    type="text" 
                    placeholder="e.g YM4082" 
                    class="form-control" 
                    style="border-radius: 4px;" />
            </div>
            <div class="mb-3">
                <button 
                    id="submitBtn"
                    type="button" 
                    class="btn w-100 fw-bold text-white" 
                    style="background-color: #f44336; border-radius: 4px;" 
                    onclick="activePackage()">
                    Connect
                </button>
            </div>
            <div class="text-center" style="font-size: 0.9rem; color: black;">
                <p style="white-space: nowrap;">
                    Make sure you have an active package before connecting.
                </p>
            </div>
        </form>
    </div>
    


    <div class="container text-center mt-2">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="border-top pt-1 pb-3">
                    <p class="text-white small">&copy; All rights reserved. 2024. Created by Cleo</p>
                </div>
            </div>
        </div>
    </div>
        

    <script>

        function redeemVoucher() {
            // Display Swal.fire popup for user to enter voucher code
            Swal.fire({
                title: 'Redeem Voucher',
                input: 'text',
                inputPlaceholder: 'Enter your voucher code',
                confirmButtonText: 'Redeem',
                confirmButtonColor: '#3085d6',
                cancelButtonColor: '#d33',
                showCancelButton: true,
                cancelButtonText: 'Cancel',
                showLoaderOnConfirm: true,
                //allowOutsideClick:false,
                inputValidator: (value) => {
                    if (!value) {
                        return 'You need to enter a voucher code!';
                    }
                },

                preConfirm: (voucherCode) => {
                    return new Promise((resolve) => {
                        Swal.fire({
                            title: 'Processing, please wait...',
                            html: 'Validating your voucher code. This may take a few seconds.', // Custom message
                            allowOutsideClick: false,
                            allowEscapeKey: false,
                            didOpen: () => {
                                Swal.showLoading();
                            },
                            customClass: {
                                popup: 'custom-swal-popup',
                                title: 'custom-swal-title',
                                htmlContainer: 'custom-swal-html'
                            }
                        });

                        setTimeout(() => {
                            fetch("https://inject-50d08d8f31b6.herokuapp.com/api/payments/redeem_voucher/", {
                            //fetch("http://127.0.0.1:8000/api/payments/redeem_voucher/", {
                                method: 'POST',
                                headers: {
                                    'Content-Type': 'application/json',
                                    //'X-CSRFToken': '{{ csrf_token }}',
                                },
                                body: JSON.stringify({ voucher_code: voucherCode }),
                            })
                            .then((response) => {
                                //if (!response.ok) {
                                 //   throw new Error('Network response was not ok');
                                //}
                                return response.json();
                            })
                            .then((data) => {
                                if (data.success) {
                                    Swal.fire('Success', data.message, 'success');
                                } else {
                                    Swal.fire('Error', data.message || 'Invalid voucher code.', 'error');
                                }
                            })
                            .catch((error) => {
                                Swal.fire('Error', 'An error occurred. Please try again.', 'error');
                                console.error('Error:', error);
                            });
                        }, 4000); // 5 seconds delay for simulated loading
                    });
                }

            });
        
        }


        function activePackage() {
            
            const accountIDInput = document.getElementById("accountID");
            const accountID = accountIDInput.value;

            if (!accountID) {
                Swal.fire({
                    title: 'Validation Error',
                    html: '<div style="color:red;">Please enter your account number.</div>',
                    icon: 'error',
                    confirmButtonText: 'OK',
                    
                    customClass: {
                        icon: 'small-icon',  // Custom class for smaller icon
                        icon: 'custom-error-icon'
                    }
                });
                return;
            }
            
            // Show loading spinner immediately after the account number validation
            Swal.fire({
                title: 'Processing...',
                html: 'Validating your account number. Please wait...',
                allowOutsideClick: false,
                allowEscapeKey: false,
                didOpen: () => {
                    Swal.showLoading(); // Show loading spinner
                },

                customClass: {
                    popup: 'custom-swal-popup',
                    title: 'custom-swal-title',
                    htmlContainer: 'custom-swal-html'
                }
            });


            //Introduce a delay of 5 seconds before making the request
            setTimeout(() => {
                // Perform AJAX POST request to Django backend
                fetch("https://inject-50d08d8f31b6.herokuapp.com/api/payments/active_package/", { 
                //fetch("http://127.0.0.1:8000/api/payments/active_package/", { 
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        //'X-CSRFToken': '', // Include CSRF token
                    },
                    body: JSON.stringify({ account_id: accountID })
                })
                .then(response => response.json())
                .then(data => {
                    console.log("Response data:", data);
                    if (data.success) {
                        // If success, show success message
                        Swal.fire('Success!', data.message, 'success');
                    } else {
                        // If failure, show error message
                        Swal.fire('Error!', data.message || 'Something went wrong.', 'error');
                    }
                })
                .catch(error => {
                    console.error("Error Connecting:", error);
                    // Show error if there's a problem with the request
                    Swal.fire('Error', 'Unable to process your request. Please try again.', 'error');
                })
                .finally(() => {
                    // Clear the input field after handling the request
                    accountIDInput.value = '';
                    //Swal.close(); // Close the loading spinner once done
                });
            }, 4000); // 5 second delay before sending the request

            
        }
        
        
                
           
                
        const packages = [
            { package_id: '001', amount: 1, validity: 5 },
            { package_id: '002', amount: 2, validity: 10 },
            { package_id: '003', amount: 3, validity: 1 },
            { package_id: '004', amount: 4, validity: 3 },
            { package_id: '005', amount: 20, validity: 12 },
            { package_id: '006', amount: 40, validity: 24 }
        ];

        const container = document.getElementById("packages-container");

        packages.forEach(pkg => {
            const validityText =
                pkg.package_id === '001' || pkg.package_id === '002'
                    ? `${pkg.validity} Mins`
                    : `${pkg.validity} Hrs`;

            const cardHTML = `
                <div style="margin: 0.5rem; text-align: center; 
                            box-shadow: 0px 4px 6px rgba(0,0,0,0.1); border-radius: 0.5rem; overflow: hidden; background: #fff;">
                    <div style="background-color: #E55451; color: #fff; padding: 0.3rem; font-weight: bold;">
                        PACKAGE ${pkg.package_id}
                    </div>
                    <div style="padding: 1rem;">
                        <p style="margin-bottom: 0.5rem; font-size: 1rem;">
                            <strong>Ksh.</strong> 
                            <span style="color: #dc2626; font-size: 1.5rem; font-weight: bold;">${pkg.amount}</span>
                        </p>
                        <p style="margin: 0;"><strong>Valid for</strong> ${validityText}</p>
                    </div>
                    <div style="border-top: 3px solid #000; margin: 0 auto; width: 80%;"></div>
                    <div style="padding: 1rem; background-color: #f9f9f9;">
                        <button style="background-color: #333; color: #fff; border: none; 
                                       border-radius: 0.25rem; padding: 0.5rem 1rem; 
                                       cursor: pointer; font-weight: bold;" 
                                onclick="buyPlan('${pkg.package_id}', '${pkg.amount}', '${pkg.validity}')">
                            Buy
                        </button>
                    </div>
                </div>
            `;

            container.innerHTML += cardHTML;
        });        


        
        function buyPlan(package_id, amount, validity) {
            Swal.fire({
                title: 'Enter Your Mpesa Number',
                input: 'number',
                inputLabel: 'Enter PhoneNumber & press Pay Now to initiate payment',
                inputPlaceholder: 'Enter your phone number',
                showCancelButton: true,
                confirmButtonText: 'Pay Now',
                confirmButtonColor: '#3085d6',
                cancelButtonColor: '#d33',
                showLoaderOnConfirm: true,
                preConfirm: (phoneNumber) => {
                    if (!phoneNumber) {
                        Swal.showValidationMessage('Please enter a valid phone number');
                        return false; // Prevent further execution
                    }
                    if (!/^[0]\d{9}$/.test(phoneNumber)) {
                        Swal.showValidationMessage('Phone Number must start with 0 and be 10 digits long');
                        return false; // Prevent further execution
                    }
        
                    // Show a loading dialog
                    Swal.fire({
                        title: 'Processing...',
                        html: 'Please wait while we process your payment.',
                        allowOutsideClick: false,
                        allowEscapeKey: false,
                        didOpen: () => Swal.showLoading(),
                        customClass: {
                            popup: 'custom-swal-popup',
                            title: 'custom-swal-title',
                            htmlContainer: 'custom-swal-html'
                        }
                    });
        
                    // AJAX POST Request to backend
                    return fetch("https://inject-50d08d8f31b6.herokuapp.com/", {
                    //return fetch("http://127.0.0.1:8000/", {
                        method: 'POST',
                        headers: {
                            'Content-Type': 'application/json',
                            //'X-CSRFToken': ''
                        },
                        body: JSON.stringify({
                            package_id: package_id,
                            amount: amount,
                            validity: validity,
                            phone_number: phoneNumber,
                        })
                    })
                    .then(response => {
                        if (!response.ok) {
                            throw new Error('Network response was not ok');
                        }
                        
                        return response.json();
                    })
                    .then(data => {
                        if (data.success) {
                            console.log('CHECKOUTREQUESTID:', data.checkout_requestID);
                            console.log(data);

                            Swal.fire({
                                title: 'Success!',
                                text: data.message,
                                icon: 'success',
                                showCancelButton: false,  // Hide cancel button for automatic flow
                                showConfirmButton: false, // Hide confirm button to avoid manual interaction
                                timer: 8000,              // Automatically close after 2 seconds
                                willClose: () => {
                                    // Transition to the next modal immediately when this one closes
                                    Swal.fire({
                                        title: 'Processing',
                                        text: 'We are processing your request. Please wait...',
                                        icon: 'info',
                                        
                                        allowOutsideClick: false,
                                        allowEscapeKey: false,
                                        showConfirmButton: false,
                                        showCancelButton: false,
                                        timer: 15000,// Auto-close after 3 seconds

                                        didOpen: () => Swal.showLoading(),
                                       
                                    });
                                }
                            });
                            

                        } else {
                            Swal.fire('Error!', data.message, 'error');
                        }
                    })
                    .catch(error => {
                        console.error('Fetch error:', error);
                        Swal.fire('Error', 'Something went wrong with the payment request', 'error');
                    });
                }
            });
        }
        
    


                    
    </script>

    <script src="js/bootstrap.bundle.min.js"></script>
    <script src="js/sweetalert2.all.min.js"></script>
    
    <script src="js/jquery-3.3.1.slim.min.js"></script>

</body>
</html>

#views.py
from django.shortcuts import render, redirect, get_object_or_404
from django.views.decorators.csrf import csrf_exempt
from rest_framework import status
import re
from django.urls import reverse
from rest_framework.test import APIClient

from rest_framework.decorators import api_view
from rest_framework.response import Response
from django.http import HttpResponseRedirect, JsonResponse
import json

from mpesa.models import LNMOnline
from mpesa.api.serializers import LNMOnlineSerializer

#from django.shortcuts import render, render_to_response
from django.contrib import messages
from django.views.decorators.http import require_http_methods
from django.http import HttpResponse
from daraja.generate_account import generate_random_string

from daraja.connect_mikrotik import connect_to_mikrotik, connect_to_mikrotik_redeem
from daraja.lipanampesa import lipa_na_mpesa
from daraja.mikrotik_lipanampesa import mikrotik_lipa_na_mpesa 
from daraja.transaction_status_api import trans_status 

from daraja.bill_manager_api import bill_manager_opt_in
from daraja.transaction_status_api import trans_status
from daraja.c2b import simulate_c2b_transaction
from daraja.register_urls import register_url


import re


@csrf_exempt
@require_http_methods(["POST", "OPTIONS"])  # Allow only POST and OPTIONS
def mikrotik_redeem_voucher(request):
    
    
	if request.method == 'OPTIONS':
		# Handle preflight request
		response = JsonResponse({'message': 'CORS preflight successful'})
		response['Access-Control-Allow-Origin'] = '*'
		response['Access-Control-Allow-Methods'] = 'POST, OPTIONS'
		response['Access-Control-Allow-Headers'] = 'Content-Type, X-CSRFToken'
		return response


	if request.method == 'POST':

		try:
			# Parse the incoming JSON data
			data = json.loads(request.body)

			account_id = data.get('voucher_code')
			print(f"VOUCHER CODE: {account_id}")
   
			if not account_id:
				return JsonResponse({'success': False, 'message': "Account ID is required."}, status=400)

			try:
				voucher = LNMOnline.objects.get(account_id=account_id)

				print(f"DB VOUCHER: {voucher}")
    
    
    
    
    
				# Connect user to MikroTik
				validity = 5   # 5 minutes in seconds
				connection_rst = connect_to_mikrotik_redeem(account_id, validity)
    
				if connection_rst['success']:
					return JsonResponse({'success': True, 'message': "Connected. Redeem Voucher"}, status=200)
				else:
					return JsonResponse({'success': False, 'message': connection_rst['message']}, status=500)

    
 
			
				#return JsonResponse({'success': True, 'message': "Connected.  Redeem Voucher"}, status=200)
				
				#return JsonResponse({'success': False, 'message': "Voucher Code Inactive."})
       
			except LNMOnline.DoesNotExist:
				return JsonResponse({'success': False, 'message': "Voucher Code Inactive."}, status=404)

			# Handle the data as needed

			#return JsonResponse({'success': False, 'message': 'Data is invalid'})

		except json.JSONDecodeError:
			return JsonResponse({'success': False, 'message': 'Invalid JSON payload'}, status=400 )



	response = JsonResponse({'success': False, 'message': "Method not allowed."}, status=405)
	response['Access-Control-Allow-Origin'] = '*'
	return response
 




import logging

@csrf_exempt
def mikrotik_active_package(request):
	#print(logging.info(f"FrontEndData: {request.body}"))
 
	if request.method == 'OPTIONS':
		# Handle preflight request
		response = JsonResponse({'message': 'CORS preflight successful'})
		response['Access-Control-Allow-Origin'] = '*'
		response['Access-Control-Allow-Methods'] = 'POST, OPTIONS'
		response['Access-Control-Allow-Headers'] = 'Content-Type, X-CSRFToken'
		return response

	elif request.method == 'POST':

		try:
			# Parse the incoming JSON data
			data = json.loads(request.body)

			account_id = data.get('account_id')
			print(account_id)
   
			if not account_id:
				return JsonResponse({'success': False, 'message': "Account ID is required."}, status=400)

			try:
				account = LNMOnline.objects.get(account_id=account_id)

				#phone_number, package_id, account_id, amount, validity, checkout_request_id = 0, 1,account_id, 3,4,5
				#connected = connect_to_mikrotik(phone_number, package_id, account_id, amount, validity,checkout_request_id)
    
				#if connected:
				#return JsonResponse({'success': True, 'message': "Connected."})
 
				if account:
					return JsonResponse({'success': True, 'message': "Connected. Active Package"})
 
				else:
					return JsonResponse({'success': False, 'message': "Account Inactive."})
       
			except LNMOnline.DoesNotExist:
				return JsonResponse({'success': False, 'message': "Account Inactive."}, status=400)

			# Handle the data as needed

			return JsonResponse({'success': False, 'message': 'Data is invalid'})

		except json.JSONDecodeError:
			return JsonResponse({'success': False, 'message': 'Invalid Phone Number:' + str(e) })

	else:
		return JsonResponse({"success": False,"message": "Invalid JSON payload."}, status=405)
 
	
from django.core.cache import cache
# Save data to cache
def save_temp_data_to_cache(checkout_request_id, data):
    cache.set(checkout_request_id, data, timeout=600)  # Cache expires in 10 minutes





@csrf_exempt
def mikrotik_mpesa_pay(request):
    
	if request.method == 'POST':

		try:
			# Parse the incoming JSON data
			data = json.loads(request.body)

			package_id, amount = data.get('package_id'), data.get('amount')
			validity, account_id = data.get('validity'), generate_random_string()
   
   
			validity_mapping = {"001":5,"002":10,"003":60,"004":3*60,"005":12*60,"006":24*60,}
			#validity = validity_mapping[package_id]
			validity = validity_mapping.get(package_id,0)
			print(validity)
   
			phone_number = data.get('phone_number')

			if not re.match(r'^0\d{9}$',phone_number):
				return JsonResponse({
					'success': False,
					'message': 'PhoneNumber must start with 0 and be 10 digits long'
				}, status=400)

			
			phone_number = phone_number.replace('0', '254', 1)
			print(phone_number), print(amount), print(account_id)

			checkout_requestid = mikrotik_lipa_na_mpesa(phone_number,amount,account_id)
			print(checkout_requestid)
			
			# Serialize and save frontend data
			serializer = LNMOnlineSerializer(data={
			    'package_id': package_id,
			    'amount': amount,
			    'validity': validity,
			    'account_id': account_id,
			    'phone_number': phone_number,
			    'status': 'Pending',
			    'CheckoutRequestID':checkout_requestid
			})

   
			if serializer.is_valid():
				serializer.save()
		

				return JsonResponse({
					'success': True,
					'message': 'STK Push sent, check Phone for a PIN prompt',
					'checkout_requestID': checkout_requestid,
				})
    
			else:
				return JsonResponse({'success': False, 'message': 'Data is invalid'})

			
		except Exception as e:
			return JsonResponse({
       				'success': False, 
           			'message': 'Invalid Phone Number:' + str(e)
              })

	else:

		packages = [
	        {'package_id': '001', 'amount': 1, 'validity': 5},
	        {'package_id': '002', 'amount': 2, 'validity': 10},
	        {'package_id': '003', 'amount': 3, 'validity': 1},
	        {'package_id': '004', 'amount': 4, 'validity': 3},
	        {'package_id': '005', 'amount': 20, 'validity': 12},
	        {'package_id': '006', 'amount': 40, 'validity': 24},
	    ]


	context = {'title': True, 'mikrotik-mpesapay': True }
	return render(request, 'mpesa/mikrotik_mpesa_pay3.html', context)

#connect_mikrotik.py

import socket

#from routeros_api import RouterOsApiPool
import routeros_api
from contextlib import closing
from django.conf import settings

from mpesa.models import LNMOnline
#from mpesa.api.serializers import LNMOnlineSerializer





def connect_to_mikrotik_redeem(account_id, validity):
    # Set a socket timeout globally
    socket.setdefaulttimeout(30)

    try:
        # Establish connection to MikroTik
        api_pool = routeros_api.RouterOsApiPool(
            host=settings.MIKROTIK_CONFIG["host"],
            username=settings.MIKROTIK_CONFIG["username"],
            password=settings.MIKROTIK_CONFIG["password"],
            port=settings.MIKROTIK_CONFIG["port"],
            use_ssl=False,
            plaintext_login=True  # Ensure plaintext login if SSL is not used
        )
        api = api_pool.get_api()
        print(f"Connecting to MikroTik at {settings.MIKROTIK_CONFIG['host']}:{settings.MIKROTIK_CONFIG['port']} with user {settings.MIKROTIK_CONFIG['username']}")

        # Access MikroTik hotspot resources
        hotspot_user = api.get_resource('/ip/hotspot/user')

        # Add user to MikroTik hotspot with specified validity
        hotspot_user.add(
            name=account_id,  # Use account_id as username
            password=account_id,  # Use account_id as password (can be customized)
            limit_uptime=f"{validity}m",  # Set validity in minutes
            profile="2mbps-limit"  # Replace with the appropriate profile if needed
        )

        print(f"User {account_id} connected successfully")

        # Return success response
        return {'success': True, 'message': "User connected successfully"}

    except routeros_api.exceptions.RouterOsApiConnectionError:
        # Handle specific MikroTik connection errors
        print("Error: Unable to connect to MikroTik. Check network or credentials.")
        return {'success': False, 'message': "Connection to MikroTik failed. Check configuration."}

    except Exception as e:
        # Handle other exceptions
        print(f"Error connecting to MikroTik: {e}")
        return {'success': False, 'message': str(e)}

    finally:
        # Ensure the connection is closed
        try:
            api_pool.disconnect()
        except Exception as disconnect_error:
            print(f"Error during disconnect: {disconnect_error}")




def connect_to_mikrotik(phone_number, package_id,account_id, amount, validity, checkout_request_id):
 
	try:
		#Connect to Mikrotik RouterOS
		api_pool = RouterOsApiPool(
			settings.MIKROTIK_CONFIG["host"],
			username=settings.MIKROTIK_CONFIG["username"],
			password=settings.MIKROTIK_CONFIG["password"],
			port=settings.MIKROTIK_CONFIG["port"],
			plaintext_login=True # Replace "default" with the appropriate profile if needed

			)
		api = api_pool.get_api()
  
		#Query the LNMOnline model for the checkout_request_id
		try:
			lnm_trans = LNMOnline.objects.get(CheckoutRequestID=checkout_request_id) 
			return f"Status changed to ACTIVE"
		except LNMOnline.DoesNotExist:
			print(f"Record with CheckoutRequestID {checkout_request_id} not found")
			return False


		# Add user to MikroTik hotspot or authenticate
		hotspot_user = api.get_resource('/ip/hotspot/user')
  
		hotspot_user.add(
			name=phone_number,
			password=account_id,
			limit_uptime=f"{validity}m",
			profile="2mbps-limit" ## Replace "default" with the appropriate profile if needed

			)

		#Update LNMOnline status to "Active"

		#Disconnect Mikrotik api from Django
		api_pool.disconnect()
		print(f"User{account_id} connected successfully")
		return f"Connected Successfully!"


	except Exception as e:
		# Handle connection failure, logging, etc.
		print(f"Error connecting to MikroTik: {e}")
		return f"Error Connecting to MikroTik. Contact Admin"

def list_active_users():
 
	try:
		#Connect to Mikrotik RouterOS
		api_pool = RouterOsApiPool(
			settings.MIKROTIK_CONFIG["host"],
			username=settings.MIKROTIK_CONFIG["username"],
			password=settings.MIKROTIK_CONFIG["password"],
			port=settings.MIKROTIK_CONFIG["port"],
			)
  
		api = api_pool.get_api()


		# Access the Hotspot active users resource
		active_users = api.get_resource('/ip/hotspot/active')
  
		#list all active users
		for user in active_users.get():
			print(f"User: {user['user']}, Uptime: {user['uptime']}, Bytes: {user['bytes-in']}/{user['bytes-out']}")
  
		

		#Disconnect Django api from the Mikrotik router
		api_pool.disconnect()
		print(f"Active users retrieved successfully")
		return f"Retrieved Successfully!"


	except Exception as e:
		# Handle connection failure, logging, etc.
		print(f"Error Retrieving from MikroTik: {e}")
		return f"Error Retrieving from MikroTik. Contact Admin"

I have taken a quick look at your index.html code and noticed something that might be causing a problem.

The line with

const cardHTML

uses the backtick (`) to define the block of text. Is this correct? I would have used either the normal single quote (') or double quote (") to define the text block (you need to escape the inner double quotes if you use the double quote to define the text block).

In Linux terms, a backtick block defines code that the script needs to execute (I’m not 100% sure that this is true for JavaScript but it certainly is for PHP, Bash Perl, etc.).

Also, to make it easier to read the code you post, can you, please, either post the code in a code block or attach the code files to your post. Just inserting the code in your post messes up the (whitespace) formatting of the code (HTML can survive this but it’s fatal for Python code).


Backups are your friend. Always make a backup!

/system backup save encryption=aes-sha256 name=MyBackup

Please, export and attach your current config to your post if you want help with a config issue:
/export hide-sensitive file=MyConfig/export file=MyConfig

///

Could you please stop posting whole listings in plain text? Attach sources as files please or use proper “code” tags. It would be appreciated.