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;"
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;"
>
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">© 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.c ... m_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.c ... e_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;"
>
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 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;"
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;"
>
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">© 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.c ... m_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.c ... e_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;"
>
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"
Statistics: Posted by okinda — Mon Dec 30, 2024 2:41 pm