hotspot / App /Messages /MessagesRoute.py
Mbonea's picture
Working registration, testing payments
7875b25
raw
history blame
5.27 kB
# App/Messages/Routes.py
from fastapi import APIRouter, HTTPException
from .Model import Message
from .Schema import MessageCreate, MessageResponse
from typing import Optional
import re
from datetime import datetime
from decimal import Decimal
from App.Payments.PaymentsRoutes import create_payment
from App.Payments.Schema import CreatePaymentRequest
from App.Users.Model import User
from App.Payments.Schema import PaymentMethod
from App.Payments.Model import Payment
from App.Plans.Model import Plan
from tortoise.contrib.pydantic import pydantic_model_creator
import traceback
import logging
message_router = APIRouter(tags=["Messages"], prefix="/messages")
logging.basicConfig(level=logging.WARNING)
@message_router.post("/sms_received", response_model=MessageResponse)
async def receive_message(message_data: MessageCreate):
Message_Pydantic = pydantic_model_creator(Message)
try:
# Extract data from the message content using regex
text = message_data.payload.message
parsed_data = parse_message_content(text)
# Validate parsed_data
if not parsed_data:
raise HTTPException(
status_code=400, detail="Failed to parse message content."
)
required_keys = ["phone_number", "amount_received", "transaction_id"]
missing_keys = [key for key in required_keys if key not in parsed_data]
if missing_keys:
raise HTTPException(
status_code=400, detail=f"Missing keys in parsed data: {missing_keys}"
)
# Prevent double entry from the SMS gateway app
message = await Message.get_or_none(message_id=message_data.id)
if not message:
# Process Mpesa Payments
user: User = await User.get_or_none(
phoneNumber="+" + parsed_data["phone_number"]
)
data_plan: Plan = await Plan.get_or_none(
amount=Decimal(parsed_data["amount_received"])
)
payment_details = CreatePaymentRequest(
user_id=user.id if user else None,
plan_id=str(data_plan.id) if data_plan else None,
amount=Decimal(parsed_data["amount_received"]),
payment_method=PaymentMethod.MPESA,
transaction_id=parsed_data["transaction_id"],
)
# Ensure 'create_payment' is an async function
payment: Payment = await create_payment(payment_details, internal=True)
await payment.create_subscription_or_balance()
# Create a new message record with parsed_data
message = await Message.create(
device_id=message_data.deviceId,
event=message_data.event,
message_id=message_data.id,
webhook_id=message_data.webhookId,
message_content=message_data.payload.message,
phone_number=message_data.payload.phoneNumber,
received_at=message_data.payload.receivedAt,
sim_number=message_data.payload.simNumber,
parsed_data=parsed_data,
)
# Serialize the message
data = await Message_Pydantic.from_tortoise_orm(message)
return data
except Exception as e:
# Log the full traceback
traceback_str = "".join(traceback.format_exception(None, e, e.__traceback__))
logging.error(f"An error occurred: {traceback_str}")
# Provide a default error message if 'e' is empty
error_message = str(e) if str(e) else "An unexpected error occurred."
raise HTTPException(status_code=500, detail=error_message)
def parse_message_content(text: str) -> Optional[dict]:
# Regular expression to capture the data from the message
pattern = r"(\w+)\sConfirmed\.You have received Tsh([\d,]+\.\d{2}) from (\d{12}) - ([A-Z ]+) on (\d{1,2}/\d{1,2}/\d{2}) at ([\d:]+ [APM]+).*?balance\sis\sTsh([\d,]+\.\d{2})"
# Replace non-breaking spaces and other whitespace characters with regular spaces
text = re.sub(r"\s+", " ", text)
matches = re.search(pattern, text)
if matches:
data = {
"transaction_id": matches.group(1),
"amount_received": parse_decimal(matches.group(2)),
"phone_number": matches.group(3),
"name": matches.group(4).strip(),
"date": parse_date(matches.group(5), matches.group(6)),
"new_balance": parse_decimal(matches.group(7)),
}
return data
else:
# Return None if the message doesn't match the expected format
return None
def parse_decimal(amount_str: str) -> float:
# Remove commas and convert to float
amount_str = amount_str.replace(",", "")
return float(Decimal(amount_str))
def parse_date(date_str: str, time_str: str) -> str:
# Combine date and time strings and parse into ISO format
datetime_str = f"{date_str} {time_str}"
try:
dt = datetime.strptime(datetime_str, "%d/%m/%y %I:%M %p")
return dt.isoformat()
except ValueError:
# Log the error
logging.error(f"Failed to parse date: {datetime_str}")
# Return current time as fallback
return datetime.now().isoformat()