(20241228) Payments module revamped!

This commit is contained in:
2024-12-28 16:34:57 +05:30
parent 7ddae1707f
commit 5c17eb28ae
18 changed files with 1477 additions and 156 deletions
@@ -6,7 +6,7 @@
DATE:
Friday, 13th Dec., 2024
Saturday, 28th Dec., 2024
OBJECTIVE:
@@ -170,9 +170,9 @@ async def authorize_payment_gateway(
if inbound_data.client == "safaricomMPesaExpress":
success = await current_app.payment_controller.set_token_direct(
db_conn = current_app.sql_writer,
mongo_conn = current_app.data_mongo,
success = await current_app.payments_controller.set_token_direct(
sql_conn = current_app.sql_writer,
mongo_data_conn = current_app.data_mongo,
auth_token = CoreAuthTokenModel(
serviceType = "paymentGateway",
client = inbound_data.client,
@@ -185,6 +185,9 @@ async def authorize_payment_gateway(
status = "active",
syncFreq = 60
),
token_notes = {"businessShortCode": inbound_data.auth.businessShortCode},
display_name = inbound_data.auth.businessShortCode,
display_picture = None,
session_token = inbound_headers["X-Session-Token"]
)
@@ -113,7 +113,7 @@ def init(blueprint_setup_state):
# ---------------------------------------------------------------------------------------------------------------------
@pg_callback_bp.route("/callback/safaricom/mpesaexpress", methods = ["POST"])
@pg_callback_bp.route("/callback/safaricom/mpesaexpress", methods = ["GET", "POST"])
@set_api_version(api_version = "1.0.0")
@read_input(sanitize_headers = False, sanitize_data = False)
@log_request_to_mongo(
@@ -144,82 +144,14 @@ async def safaricom_m_pesa_express_callback(
:return: A standard response structure.
"""
# payment_info = await current_app.payment_controller.get_payment_internal(
# mongo_conn = current_app.data_mongo,
# payment_id = ObjectId(payment_id)
# )
# print("PAYMENT INFO:", json.to_string(payment_info.model_dump(), default=str))
# ┏┓ ┓ ┓ ┏┓
# ┣┫┏┫┏┫ ┣ ┓┏┏┓┏┓╋
# ┛┗┗┻┗┻ ┗┛┗┛┗ ┛┗┗
# Map out the documented codes provided by the payment gateway.
# URL: https://developer.safaricom.co.ke/APIs/MpesaExpressSimulate
code_map = {
0: {
"status": "settled",
"message": "Payment successful :)"
}, # ... Success
1037: {
"status": "failed",
"message": "The payment gateway could not reach your customer."
}, # ... DS Timeout. User could not be reached.
1025: {
"status": "failed",
"message": "There was a system error in the payment gateway (1025)."
}, # ... System error while trying to send the push request.
9999: {
"status": "failed",
"message": "There was a system error in the payment gateway (9999)."
}, # ... System error while trying to send the push request.
1032: {
"status": "rejected",
"message": "Your customer declined the payment request."
}, # ... Request Cancelled by the user.
1: {
"status": "failed",
"message": "Your customer has insufficient balance."
}, # ... The user has insufficient balance.
2001: {
"status": "failed",
"message": "The payment gateway says your credentials are invalid."
}, # ... Invalid credentials of the initiator.
1019: {
"status": "failed",
"message": "The transaction expired before your customer processed it."
}, # ... Transaction expired.
1001: {
"status": "failed",
"message": "Your customer is already in the middle of some transaction on the payment gateway."
}, # ... The payer is already making some transaction.
}
# Figure out which of the above codes is relevant to you:
pg_reference_id = inbound_data["Body"]["stkCallback"]["CheckoutRequestID"]
pg_result_code = int(inbound_data["Body"]["stkCallback"]["ResultCode"])
pg_result_desc = inbound_data["Body"]["stkCallback"]["ResultDesc"]
relevant_code = code_map.get(
pg_result_code,
{
"status": "unknown",
"message": f"Unknown code '{pg_result_code}' from the payment gateway. PG: '{pg_result_desc}'"
}
)
# For now, we just insert the event into the record:
event_note_success = await current_app.payment_controller.add_event_by_client_reference_id(
mongo_conn = current_app.data_mongo,
event = PaymentEvent(
paymentStatus = relevant_code["status"],
message = relevant_code["message"],
initByPG = True,
ipAddr = inbound_headers["Remote-IP"],
httpCode = None,
headers = inbound_headers,
payload = inbound_data
),
client_reference_id = pg_reference_id
client_response = await current_app.safaricom_mpesa_express_controller.handle_payment_callback(
mongo_data_conn = current_app.data_mongo,
inbound_data = inbound_data,
inbound_headers = inbound_headers
)
# ┳┓
@@ -229,8 +161,9 @@ async def safaricom_m_pesa_express_callback(
# Done here:
return ResponseModel(
status_code = StatusCodes.OK if event_note_success else StatusCodes.FAILED,
http_code = HttpCodes.SUCCESS if event_note_success else HttpCodes.INTERNAL_SERVER_ERROR
status_code = StatusCodes.OK if client_response.success else StatusCodes.FAILED,
http_code = HttpCodes.SUCCESS if client_response.success else HttpCodes.INTERNAL_SERVER_ERROR,
message = client_response.message
)
@@ -6,7 +6,7 @@
DATE:
Wednesday, 18th Dec., 2024
Saturday, 28th Dec., 2024
OBJECTIVE:
@@ -177,8 +177,8 @@ async def get_one_payment_record(
# ┻ ┗ ┗┗┛┗ ┛┗┗ ┗┗┛┛ ┗┻
# Get the payment record:
record = await current_app.payment_controller.get_payment(
mongo_conn = current_app.data_mongo,
record = await current_app.payments_controller.get_payment(
mongo_data_conn = current_app.data_mongo,
payment_id = inbound_data.paymentId
)
@@ -189,7 +189,7 @@ async def get_one_payment_record(
# We check if the token that was used to fetch the mail is owned by this user:
if not await token_check.is_authorized(
mongo_conn = current_app.data_mongo,
mongo_data_conn = current_app.data_mongo,
user_info = CoreUserInfoModel(**kwargs["session_info"]),
token_ids = [record.tokenId]
): return ResponseModel(
@@ -6,7 +6,7 @@
DATE:
Tuesday, 17th Dec., 2024
Saturday, 28th Dec., 2024
OBJECTIVE:
@@ -161,30 +161,27 @@ async def payment_list(
message = "invalid session"
)
# ┏┓ ┓• ┳┳┓ •
# ┣ ┏┓┃┓┏╋ ┃┃┃┏┓┓┃
# ┗┛┛┗┗┗┛┗ ┛ ┗┗┻┗┗┛
# ┏┓ ┓•
# ┣ ┏┓┃┓┏╋ ┃┃┏┓┓┏┏┳┓┏┓┏┓╋
# ┗┛┛┗┗┗┛┗ ┣┛┗┻┗┫┛┗┗┗ ┛┗┗┛
# ┛
# Get the token ids from the token keys:
auth_tokens = await current_app.payment_controller.get_tokens_from_keys(
mongo_conn = current_app.data_mongo,
auth_tokens = await current_app.payments_controller.get_tokens_from_keys(
mongo_data_conn = current_app.data_mongo,
token_keys = inbound_data.tokenKeys
)
token_ids = [t.authTokenId for t in auth_tokens]
print("TOKEN IDS:", json.to_string(token_ids, default = str))
# Build the additional filter:
additional_filter = {}
if inbound_data.tags: additional_filter["tags"] = {"$in": inbound_data.tags}
if inbound_data.paymentStatus: additional_filter["lastPaymentStatus"] = {"$in": inbound_data.paymentStatus}
additional_filter = additional_filter or None
print("ADDFIL:", json.to_string(additional_filter))
# Get the mails:
payment_records = await current_app.payment_controller.list_payments(
mongo_conn = current_app.data_mongo,
payment_records = await current_app.payments_controller.list_payments(
mongo_data_conn = current_app.data_mongo,
token_ids = token_ids,
limit = inbound_data.count,
skip = inbound_data.fromCount,
@@ -6,7 +6,7 @@
DATE:
Monday, 16th Dec., 2024
Saturday, 28th Dec., 2024
OBJECTIVE:
@@ -161,29 +161,36 @@ async def request_payment(
http_code = HttpCodes.UNAUTHORIZED
)
# ┳┓ ┏┓
# ┣┫┏┓┏┓┓┏┏┓┏╋ ┃┃┏┓┓┏┏┳┓┏┓┏┓╋
# ┛┗┗ ┗┫┗┻┗ ┛┗ ┣┛┗┻┗┫┛┗┗┗ ┛┗┗
# ┗ ┛
# Get the token from the token key:
auth_token = await current_app.payment_controller.get_token_from_key(
mongo_conn = current_app.data_mongo,
auth_token = await current_app.payments_controller.get_token_from_key(
mongo_data_conn = current_app.data_mongo,
token_key = inbound_data.tokenKey
)
if auth_token is None: return ResponseModel(
status_code = StatusCodes.FAILED,
http_code = HttpCodes.UNAUTHORIZED,
message = f"no such token key"
message = f"No such token key."
)
# Make the request with the fetched token:
response = await current_app.payment_controller.request_payment(
mongo_conn = current_app.data_mongo,
http_client = current_app.http_client,
auth_token = auth_token,
user_info = CoreUserInfoModel(**kwargs["session_info"]),
payment_request = inbound_data
# ┳┓ ┏┓
# ┣┫┏┓┏┓┓┏┏┓┏╋ ┃┃┏┓┓┏┏┳┓┏┓┏┓╋
# ┛┗┗ ┗┫┗┻┗ ┛┗ ┣┛┗┻┗┫┛┗┗┗ ┛┗┗
# ┗ ┛
# For Safaricom's M-Pesa Express client:
if auth_token.client == "safaricomMPesaExpress":
response = await current_app.safaricom_mpesa_express_controller.request_payment(
mongo_data_conn = current_app.data_mongo,
auth_token = auth_token,
user_info = CoreUserInfoModel(**kwargs["session_info"]),
payment_request = inbound_data
)
# Invalid client:
else: return ResponseModel(
status_code = StatusCodes.FAILED,
http_code = HttpCodes.BAD_REQUEST,
message = f"Invalid/unimplemented client '{auth_token.client}'"
)
# ┳┓
@@ -6,7 +6,7 @@
DATE:
Wednesday, 18th Dec., 2024
Saturday, 28th Dec., 2024
OBJECTIVE:
@@ -176,10 +176,15 @@ async def update_payment_record_tags(
# ┻ ┗ ┗┗┛┗ ┛┗┗ ┗┗┛┛ ┗┻
# Get the payment record:
record = await current_app.payment_controller.get_payment(
mongo_conn = current_app.data_mongo,
record = await current_app.payments_controller.get_payment(
mongo_data_conn = current_app.data_mongo,
payment_id = inbound_data.paymentId
)
if not record: return ResponseModel(
status_code = StatusCodes.FAILED,
http_code = HttpCodes.NOT_FOUND,
message = "No matching payment record found."
)
# ┏┓ ┓ • ┏┓┓ ┓
# ┃┃┓┏┏┏┓┏┓┏┓┏┣┓┓┏┓ ┃ ┣┓┏┓┏┃┏
@@ -188,13 +193,13 @@ async def update_payment_record_tags(
# We check if the token that was used to fetch the mail is owned by this user:
if not await token_check.is_authorized(
mongo_conn = current_app.data_mongo,
mongo_data_conn = current_app.data_mongo,
user_info = CoreUserInfoModel(**kwargs["session_info"]),
token_ids = [record.tokenId]
): return ResponseModel(
status_code = StatusCodes.FAILED,
http_code = HttpCodes.UNAUTHORIZED,
message = "The record does not belong to this user."
message = "The payment record does not belong to this user."
)
# ┳┳ ┓ ┳┓ ┓
@@ -203,8 +208,8 @@ async def update_payment_record_tags(
# ┛
# Update the record:
success = await current_app.payment_controller.update_tags(
mongo_conn = current_app.data_mongo,
success = await current_app.payments_controller.update_tags(
mongo_data_conn = current_app.data_mongo,
payment_id = inbound_data.paymentId,
unset_tags = inbound_data.unsetTags,
set_tags = inbound_data.setTags