File size: 3,937 Bytes
01a1238
 
 
427638d
01a1238
 
 
 
 
427638d
 
 
 
 
 
01a1238
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2046386
01a1238
 
 
db9d9b2
01a1238
 
 
427638d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
01a1238
 
 
 
427638d
c335f43
 
 
 
 
01a1238
427638d
01a1238
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# App/Messages/Routes.py
from fastapi import APIRouter, HTTPException, Request
from .Model import Message
from .Schema import MessageCreate, MessageResponse, TransactionSchema
from uuid import UUID
from typing import List, 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

message_router = APIRouter(tags=["Messages"], prefix="/messages")


@message_router.post("/sms_received", response_model=MessageResponse)
async def receive_message(request: Request):

    message_raw = await request.json()
    message_data = MessageCreate(**message_raw)
    try:
        # Extract data from the message content using regex
        text = message_data.payload.message
        parsed_data = parse_message_content(text)

        # Create a new message record with parsed_data
        new_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=text,
            phone_number=message_data.payload.phoneNumber,
            received_at=message_data.payload.receivedAt,
            sim_number=message_data.payload.simNumber,
            parsed_data=parsed_data,
        )

        # Process Mpesa Payments
        if parsed_data:
            user: User = User.get_or_none(phoneNumber=parsed_data.phone_number)
            data_plan: Plan = Plan.get_or_none(
                amount=Decimal(parsed_data.amount_received)
            )
            payment_details = CreatePaymentRequest(
                user_id=user.id,
                plan_id=data_plan.id,
                amount=Decimal(parsed_data.amount_received),
                payment_method=PaymentMethod.MPESA,
                transaction_id=parsed_data.transaction_id,
            )
        data = MessageResponse.from_orm(new_message)

        payment: Payment = await create_payment(payment_details)
        await payment.create_subscription_or_balance()
        return data
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))


def parse_message_content(text: str) -> Optional[TransactionSchema]:
    # Regular expression to capture the data from the message, allowing for different types of spaces
    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 in the input text
    text = re.sub(r"\s+", " ", text)

    matches: TransactionSchema = 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 Decimal
    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:
        return datetime_str  # Return as-is if parsing fails