Created
June 10, 2022 12:59
-
-
Save aphilas/d4ae2549a199626b3774c14892b3c17c to your computer and use it in GitHub Desktop.
Pydantic models/schema for Daraja M-Pesa Express API.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Python 3.9.6+ | |
from typing import Literal, Optional, Union | |
import humps | |
from pydantic import BaseModel, Field, validator | |
from typing_extensions import Annotated | |
### Queue charge ### | |
# TODO: Create custom timestamp de/serializer | |
class PushRequest(BaseModel): | |
business_short_code: int # 654321 | |
password: str # base64(shortcode,passkey,timestamp) | |
timestamp: str # yyyymmddhhmmss | |
transaction_type: Literal["CustomerPayBillOnline"] | |
amount: int # | |
party_a: int # 2547XXXXXXXX | |
party_b: int # Same as shortcode | |
phone_number: int # Same as party_a | |
call_back_url: str = Field(..., alias="CallBackURL") | |
account_reference: str = Field(..., max_length=12) # Max 12-chars | |
transaction_desc: Optional[str] = Field(None, max_length=13) # Max 13-chars | |
@validator("account_reference") | |
def account_reference_alphanumeric(cls, v: str): | |
assert v.isalnum(), "must be alphanumeric" | |
return v | |
class Config: | |
alias_generator = humps.pascalize # type: ignore[reportPrivateImportUsage] | |
# Status 500, On payment queue error | |
class PushQueueError(BaseModel): | |
request_id: Optional[str] = Field(None, alias="requestID") # 168-...-1 | |
error_code: Optional[str] = None # ws..., Conflicting docs? | |
error_message: Optional[str] = None # You can display this message to the customer | |
class Config: | |
alias_generator = humps.camelize # type: ignore[reportPrivateImportUsage] | |
class PushQueueSuccess(BaseModel): | |
merchant_request_id: str = Field(..., alias="MerchantRequestID") # 168-...-1 | |
checkout_request_id: str = Field(..., alias="CheckoutRequestID") # ws..., Conflicting docs? | |
response_description: str # "The service request has been accepted successfully", "The service request has failed" | |
response_code: str # "0" is success | |
customer_message: str # Display to customer | |
class Config: | |
alias_generator = humps.pascalize # type: ignore[reportPrivateImportUsage] | |
## Callback ### | |
class AmountItem(BaseModel): | |
name: Literal["Amount"] | |
value: float | |
class Config: | |
alias_generator = humps.pascalize # type: ignore[reportPrivateImportUsage] | |
class ReceiptNumberItem(BaseModel): | |
name: Literal["MpesaReceiptNumber"] | |
value: str | |
class Config: | |
alias_generator = humps.pascalize # type: ignore[reportPrivateImportUsage] | |
class TransactionDateItem(BaseModel): | |
name: Literal["TransactionDate"] | |
value: int | |
class Config: | |
alias_generator = humps.pascalize # type: ignore[reportPrivateImportUsage] | |
class PhoneNumberItem(BaseModel): | |
name: Literal["PhoneNumber"] | |
value: int # Send only int amounts | |
class Config: | |
alias_generator = humps.pascalize # type: ignore[reportPrivateImportUsage] | |
class BalanceItem(BaseModel): | |
name: Literal["Balance"] | |
value: Optional[int] = None | |
class Config: | |
alias_generator = humps.pascalize # type: ignore[reportPrivateImportUsage] | |
Item = Annotated[ | |
Union[ | |
AmountItem, ReceiptNumberItem, TransactionDateItem, PhoneNumberItem, BalanceItem | |
], | |
Field(discriminator="name"), | |
] | |
Items = list[Item] | |
class CallbackMetada(BaseModel): | |
# !Are the items always ordered? | |
item: Items | |
class Config: | |
alias_generator = humps.pascalize # type: ignore[reportPrivateImportUsage] | |
class CallbackBase(BaseModel): | |
merchant_request_id: str = Field(..., alias="MerchantRequestID") # 168-...-1 | |
checkout_request_id: str = Field(..., alias="CheckoutRequestID") # 168-...-1 | |
# result_code: int # 0 for success | |
result_desc: str | |
class Config: | |
alias_generator = humps.pascalize # type: ignore[reportPrivateImportUsage] | |
class StkCallbackSuccess(CallbackBase): | |
callback_metadata: CallbackMetada | |
result_code: Literal[0] | |
class Config: | |
alias_generator = humps.pascalize # type: ignore[reportPrivateImportUsage] | |
class StkCallbackError(CallbackBase): | |
result_code: int | |
class Config: | |
# Correctly discriminate with StkCallbackSuccess | |
# Since this object has fewer fields | |
extra = "forbid" | |
class CallbackResponseBody(BaseModel): | |
stk_callback: Union[StkCallbackSuccess, StkCallbackError] = Field( | |
..., | |
alias="stkCallback", | |
) | |
# On successful payment | |
class CallbackSuccess(BaseModel): | |
body: CallbackResponseBody | |
class Config: | |
alias_generator = humps.pascalize # type: ignore[reportPrivateImportUsage] | |
class CallbackError(BaseModel): | |
request_id: str | |
error_code: str | |
error_message: str | |
class Config: | |
alias_generator = humps.camelize # type: ignore[reportPrivateImportUsage] | |
CallbackResponse = Union[CallbackSuccess, CallbackError] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment