(20241216) Payment auth bug fix.

This commit is contained in:
2024-12-16 16:00:52 +05:30
parent 064495163f
commit 0b9976fadb
19 changed files with 589 additions and 473 deletions
+152 -227
View File
@@ -21,7 +21,8 @@
N/A
"""
import asyncio
# *****************************************************************************************************************
# ***** ****
# *** IMPORT ***
@@ -43,35 +44,31 @@ from utils_v2.date_time import date_time
from utils_v2.database.async_mysql_v2 import AsyncMySQL
from utils_v2.database.async_mongo_v2 import AsyncMongo, AsyncMongoStorage
# Base model:
from controllers.base import BaseModel
# Data models:
from models.core.user import CoreUserInfoModel
from models.core.auth_token import CoreAuthTokenModel
from models.core.message import CoreMessageModel
from models.api.sms.send import (
SMSSendRequestData,
NimbusSMSIndiaMessage,
SavvyBulkSMSKenyaMessage,
SMSSendManyResults
from models.core.payment import CorePaymentModel, PaymentEvent
from models.api.finstitutions.payments.request import (
PaymentRequestOneResult
)
# SMS Clients:
from utils_v2.sms.models.behaviour.nimbus.async_nimbus import AsyncNimbusSMS
from utils_v2.sms.models.behaviour.savvy_bulk_sms.async_savvy_bulk_sms import AsyncSavvyBulkSMS
from utils_v2.sms.models.data.sms_message import SentSMSMessageModel
# Payment Clients:
from utils_v2.payments.safaricom.models.auth import MPesaExpressAuthorization
from utils_v2.payments.safaricom.controllers.m_pesa_express import SafaricomMPesaExpress
# To work with MongoDB:
from bson import ObjectId
from pymongo import InsertOne
# To work with datatypes:
from typing import Literal, List, Dict, Any
from typing import Literal, List, Dict, Any, Union
# To make API calls:
import httpx
# For asynchronous activities:
import asyncio
# *****************************************************************************************************************
# ***** ****
@@ -110,7 +107,7 @@ import httpx
# *****************************************************************************************************************
class SMSController:
class PaymentController:
# ┏┓┓ ┓┏
# ┃ ┃┏┓┏┏ ┃┃┏┓┏┓┏
@@ -174,239 +171,167 @@ class SMSController:
token_key = token_key
)
# ┏┓
# ┗┓┏┓┏┓┏
# ┗┛┗ ┛┗┗┻
# ┳┓ ┏
# ┣┫┏┓┏┓┓┏┏┓┏╋ ┃┃┏┓┓┏┏┳┓┏┓┏┓╋┏
# ┛┗┗ ┗┫┗┻┗ ┛┗ ┣┛┗┻┗┫┛┗┗┗ ┛┗┗┛
# ┗ ┛
@staticmethod
async def __send_from_nimbus_sms_india(
http_client: httpx.AsyncClient,
auth_token: CoreAuthTokenModel,
messages: List[NimbusSMSIndiaMessage],
) -> SMSSendManyResults:
# @staticmethod
# async def __request_from_safaricom_m_pesa_express(
# http_client: httpx.AsyncClient,
# auth_token: CoreAuthTokenModel,
# payment: Union[SafaricomMPesaExpressRequest],
# callback_url: str
# ) -> PaymentRequestOneResult:
#
# # Start by assuming failure:
# request_result = PaymentRequestOneResult()
#
# # Create the client:
# client = SafaricomMPesaExpress(
# auth = MPesaExpressAuthorization(
# consumerKey = auth_token.auth["consumerKey"],
# consumerSecret = auth_token.auth["consumerSecret"],
# businessShortCode = auth_token.auth["businessShortCode"],
# appPasskey = auth_token.auth["appPasskey"]
# ),
# http_client = http_client
# )
#
# # Make the payment request:
# client_response = await client.request_payment(
# amount = payment.amount,
# party_a = payment.partyA,
# type = payment.transactionType,
# reference = payment.accountReference,
# description = payment.transactionDescription,
# callback_url = callback_url,
# payer_no = payment.phoneNo,
# party_b = payment.partyB
# )
#
# # Construct the payment details:
# payment_details = CorePaymentModel(
# user = None,
# lastEventTs = date_time.get_current_utc_date_time(as_string = False),
# lastPaymentStatus = "initiated" if client_response.success else "initFailed",
# tokenId = auth_token.authTokenId,
# amount = payment.amount,
# curencyCode = "KES",
# # metadata = payment.
# )
#
# # Done here:
# request_result.success = client_response.success
# request_result.message = client_response.message
# request_result.message = client_response.message
# return request_result
#
# async def request_payment(
# self,
# mongo_conn: AsyncMongo,
# http_client: httpx.AsyncClient,
# auth_token: CoreAuthTokenModel,
# payment: Union[SafaricomMPesaExpressRequest],
# callback_url: str
# ) -> PaymentRequestOneResult:
#
# # Start by assuming failure:
# request_result = PaymentRequestOneResult()
#
# # Now we route the message to the appropriate client:
# match auth_token.client:
# case "safaricomMPesaExpress":
# request_result = await self.__request_from_safaricom_m_pesa_express(
# http_client = http_client,
# auth_token = auth_token,
# payment = payment,
# callback_url = callback_url
# )
# case _:
# request_result.message = f"invalid client {auth_token.client}"
#
# # Done here:
# return request_result
# Start with a blank variable:
send_results = SMSSendManyResults()
# ┓ • ┓ ┏┓ ┏┓
# ┃ ┓┏╋ ┏┓┏┓┏┫ ┃┓┏┓╋ ┃┃┏┓┓┏┏┳┓┏┓┏┓╋┏
# ┗┛┗┛┗ ┗┻┛┗┗┻ ┗┛┗ ┗ ┣┛┗┻┗┫┛┗┗┗ ┛┗┗┛
# ┛
# Initialize the third-party client:
client = AsyncNimbusSMS(
entity_id = auth_token.auth["entityId"],
sender_id = auth_token.auth["senderId"],
user_id = auth_token.auth["userId"],
api_key = auth_token.auth["apiKey"],
http_client = http_client
)
# Iterate over all the messages you need to send:
for message in messages:
# Send the SMS and return the response:
client_response = await client.send_sms(
recipient_number = message.recipientNo,
message = message.text,
template_id = message.templateId
)
# Note down the results:
send_results.totalCount += 1
if client_response.success: send_results.successCount += 1
else: send_results.failureCount += 1
send_results.smsMessages.append(CoreMessageModel(
ts = client_response.ts,
syncTs = date_time.get_current_utc_date_time(as_string = False),
tokenId = auth_token.authTokenId,
serviceType = auth_token.serviceType,
client = auth_token.client,
clientMessageId = client_response.messageId,
clientThreadId = message.recipientNo,
isSent = True,
isBroadcast = False,
sentSuccessfully = client_response.success,
sender = None,
recipient = message.recipientNo,
chat = None,
message = client_response.model_dump(),
snippet = message.text,
aiSnippet = None,
tags = ["sms", "nimbusSmsIndia"]
))
# Done here:
return send_results
@staticmethod
async def __send_from_savvy_bulk_sms_kenya(
http_client: httpx.AsyncClient,
auth_token: CoreAuthTokenModel,
messages: List[SavvyBulkSMSKenyaMessage],
) -> SMSSendManyResults:
# Start with a blank variable:
send_results = SMSSendManyResults()
# Initialize the third-party client:
client = AsyncSavvyBulkSMS(
partner_id = auth_token.auth["partnerId"],
short_code = auth_token.auth["shortCode"],
api_key = auth_token.auth["apiKey"],
http_client = http_client
)
# Iterate over all the messages you need to send:
for message in messages:
# Send the SMS and return the response:
client_response = await client.send_sms(
recipient_number = message.recipientNo,
message = message.text
)
# Note down the results:
send_results.totalCount += 1
if client_response.success: send_results.successCount += 1
else: send_results.failureCount += 1
send_results.smsMessages.append(CoreMessageModel(
ts = client_response.ts,
syncTs = date_time.get_current_utc_date_time(as_string = False),
tokenId = auth_token.authTokenId,
serviceType = auth_token.serviceType,
client = auth_token.client,
clientMessageId = client_response.messageId,
clientThreadId = message.recipientNo,
isSent = True,
isBroadcast = False,
sentSuccessfully = client_response.success,
sender = None,
recipient = message.recipientNo,
chat = None,
message = client_response.model_dump(),
snippet = message.text,
aiSnippet = None,
tags = ["sms", "savvyBulkSmsKenya"]
))
# Done here:
return send_results
async def send(
self,
mongo_conn: AsyncMongo,
http_client: httpx.AsyncClient,
# token_id: ObjectId | str,
auth_token: CoreAuthTokenModel,
messages: List[NimbusSMSIndiaMessage | SavvyBulkSMSKenyaMessage]
) -> SMSSendManyResults:
# Start by assuming failure:
send_results = SMSSendManyResults()
# # We first load the authorization tokens:
# auth_token = await self.get_token(
# mongo_conn = mongo_conn,
# token_id = token_id,
# )
#
# # If we failed to load the authorization tokens:
# if not auth_token:
# send_results.message = f"no such token id '{token_id}'"
# return send_results
# Now we route the message to the appropriate client:
match auth_token.client:
case "nimbusSmsIndia":
send_results = await self.__send_from_nimbus_sms_india(
http_client = http_client,
auth_token = auth_token,
messages = messages
)
case "savvyBulkSmsKenya":
send_results = await self.__send_from_savvy_bulk_sms_kenya(
http_client = http_client,
auth_token = auth_token,
messages = messages
)
case _:
send_results.message = f"invalid client {auth_token.client}"
# Save the results to MongoDB:
tasks = []
for sms in send_results.smsMessages:
message_json = sms.model_dump()
message_json.pop("_id", None)
tasks.append(current_app.core_message_controller.insert(
mongo_conn = mongo_conn,
message = message_json
))
results = await asyncio.gather(*tasks)
# Done here:
send_results.message = f"{send_results.successCount}/{send_results.totalCount} message(s) sent"
return send_results
# ┓ • ┏┓ ┏┓ ┳┳┓
# ┃ ┓┏╋ ┣╋ ┃┓┏┓╋ ┃┃┃┏┓┏┏┏┓┏┓┏┓┏
# ┗┛┗┛┗ ┗┻ ┗┛┗ ┗ ┛ ┗┗ ┛┛┗┻┗┫┗ ┛
# ┛
# These are simply for retrieving sms messages.
# These are simply for retrieving payment records.
# You need to already have them saved to the database.
# @staticmethod
# async def list_messages(
# mongo_conn: AsyncMongo,
# token_ids: List[ObjectId | str],
# limit: int = 100,
# skip: int = 0,
# additional_filter: dict = None
# ) -> List[CoreMessageModel] | None:
#
# # regardless of what additional filter is provided from outside,
# # we add a mail-selecting filter here:
# if additional_filter is None: additional_filter = {}
# additional_filter["serviceType"] = "sms"
#
# # Simply call the core model:
# return await current_app.core_message_controller.get_message(
# mongo_conn = mongo_conn,
# token_ids = token_ids,
# limit = limit,
# skip = skip,
# additional_filter = additional_filter
# )
#
# @staticmethod
# async def get_one_mail(
# mongo_conn: AsyncMongo,
# token_id: ObjectId | str,
# message_id: ObjectId | str
# ) -> CoreMessageModel | None:
#
# # Simply call the core model:
# return await current_app.core_message_controller.get_message(
# mongo_conn = mongo_conn,
# token_id = token_id,
# message_id = message_id
# )
@staticmethod
async def list_payments(
mongo_conn: AsyncMongo,
token_ids: List[ObjectId | str],
limit: int = 100,
skip: int = 0,
additional_filter: dict = None
) -> List[CorePaymentModel] | None:
# Regardless of what additional filter is provided from outside,
# we add a payment-selecting filter here:
if additional_filter is None: additional_filter = {}
additional_filter["serviceType"] = "paymentGateway"
# Simply call the core model:
return await current_app.core_payment_controller.get_payment_previews(
mongo_conn = mongo_conn,
token_ids = token_ids,
limit = limit,
skip = skip,
additional_filter = additional_filter
)
@staticmethod
async def get_payment(
mongo_conn: AsyncMongo,
token_id: ObjectId | str,
payment_id: ObjectId | str
) -> CorePaymentModel | None:
# Simply call the core model:
return await current_app.core_payment_controller.get_payment(
mongo_conn = mongo_conn,
token_id = token_id,
payment_id = payment_id
)
# ┳┳ ┓
# ┃┃┏┓┏┫┏┓╋┏┓
# ┗┛┣┛┗┻┗┻┗┗
# ┛
@staticmethod
async def add_event(
mongo_conn: AsyncMongo,
payment_id: ObjectId | str,
event: PaymentEvent
) -> bool:
# Simply call the core model:
return await current_app.core_payment_controller.add_event(
mongo_conn = mongo_conn,
payment_id = payment_id,
event = event
)
@staticmethod
async def update_tags(
mongo_conn: AsyncMongo,
token_id: ObjectId | str,
message_id: ObjectId | str,
payment_id: ObjectId | str,
unset_tags: List[str] = None,
set_tags: List[str] = None
) -> bool:
# Simply call the core model:
return await current_app.core_message_controller.update_tags(
return await current_app.core_payment_controller.update_tags(
mongo_conn = mongo_conn,
token_id = token_id,
message_id = message_id,
payment_id = payment_id,
unset_tags = unset_tags,
set_tags = set_tags
)