use super::environment::Environment;
use super::services::{
AccountBalanceBuilder, B2bBuilder, B2cBuilder, C2bRegisterBuilder, C2bSimulateBuilder,
};
use crate::services::MpesaExpressRequestBuilder;
use crate::MpesaError;
use reqwest::blocking::Client;
use serde_json::Value;
use std::cell::RefCell;
/// `Result` enum type alias
pub type MpesaResult<T> = Result<T, MpesaError>;
/// Mpesa client that will facilitate communication with the Safaricom API
#[derive(Debug)]
pub struct Mpesa {
client_key: String,
client_secret: String,
initiator_password: RefCell<Option<String>>,
environment: Environment,
}
impl<'a> Mpesa {
/// Constructs a new `Mpesa` instance.
///
/// # Example
/// ```ignore
/// let client: Mpesa = Mpesa::new(
/// env::var("CLIENT_KEY").unwrap(),
/// env::var("CLIENT_SECRET").unwrap(),
/// "sandbox".parse().unwrap(),
/// );
/// ```
pub fn new(client_key: String, client_secret: String, environment: Environment) -> Self {
Self {
client_key,
client_secret,
initiator_password: RefCell::new(None),
environment,
}
}
/// Gets the current `Environment`
pub(crate) fn environment(&'a self) -> &Environment {
&self.environment
}
/// Gets the initiator password as a byte slice
/// If `None`, the default password is b"Safcom496!"
pub(crate) fn initiator_password(&'a self) -> String {
if let Some(p) = &*self.initiator_password.borrow() {
return p.to_owned();
}
"Safcom496!".to_owned()
}
/// Optional in development but required for production, you will need to call this method and set your production initiator password.
/// If in development, default initiator password is already pre-set
/// ```ignore
/// use mpesa::Mpesa;
///
/// let client: Mpesa = Mpesa::new(
/// env::var("CLIENT_KEY").unwrap(),
/// env::var("CLIENT_SECRET").unwrap(),
/// "sandbox".parse().unwrap(),
/// );
///
/// client.set_initiator_password("your_initiator_password");
/// ```
pub fn set_initiator_password(&self, initiator_password: &str) {
*self.initiator_password.borrow_mut() = Some(initiator_password.to_string());
}
/// Checks if the client can be authenticated
pub fn is_connected(&self) -> bool {
let token = self.auth().ok();
if let Some(_) = token {
return true;
}
false
}
/// **Safaricom Oauth**
///
/// Generates an access token
/// Sends `GET` request to Safaricom oauth to acquire token for token authentication
/// The OAuth access token expires after an hour, after which, you will need to generate another access token
///
/// See more from the Safaricom API docs [here](https://developer.safaricom.co.ke/docs#authentication)
///
/// Returns the auth token as a `String`.
///
/// # Errors
/// Returns a `MpesaError` on failure
pub(crate) fn auth(&self) -> MpesaResult<String> {
let url = format!(
"{}/oauth/v1/generate?grant_type=client_credentials",
self.environment.base_url()
);
let resp = Client::new()
.get(&url)
.basic_auth(&self.client_key, Some(&self.client_secret))
.send()?;
if resp.status().is_success() {
let value: Value = resp.json()?;
// "value" -> value
return Ok(value["access_token"].to_string().replace("\"", ""));
}
Err(MpesaError::Message(
"Could not authenticate to Safaricom, please check your credentials",
))
}
/// **B2C Builder**
///
/// Creates a `B2cBuilder` for building a B2C transaction struct.
/// The builder is consumed and request made by calling its `send` method.
/// See more from Safaricom the API docs [here](https://developer.safaricom.co.ke/docs?shell#b2c-api).
///
/// Requires an `initiator_name`, the credential/ username used to authenticate the transaction request
///
/// # Example
/// ```ignore
/// let response = client
/// .b2c("testapi496")
/// .party_a("600496")
/// .party_b("600000")
/// .result_url("https://testdomain.com/err")
/// .timeout_url("https://testdomain.com/ok")
/// .amount(1000)
/// .remarks("Your Remark") // optional, defaults to "None"
/// .occasion("Your Occasion") // optional, defaults to "None"
/// .command_id(mpesa::CommandId::BusinessPayment) // optional, defaults to `CommandId::BusinessPayment`
/// .send();
/// ```
pub fn b2c(&'a self, initiator_name: &'a str) -> B2cBuilder<'a> {
B2cBuilder::new(&self, initiator_name)
}
/// **B2B Builder**
///
/// Creates a `B2bBuilder` for building B2B transaction struct.
///
/// See more from the Safaricom API docs [here](https://developer.safaricom.co.ke/docs#b2b-api)
///
/// Requires an `initiator_name`, the credential/ username used to authenticate the transaction request
///
/// # Example
/// ```ignore
/// let response = client.b2b("testapi496")
/// .party_a("600496")
/// .party_b("600000")
/// .result_url("https://testdomain.com/err")
/// .timeout_url("https://testdomain.com/ok")
/// .account_ref("254708374149")
/// .amount(1000)
/// .command_id(mpesa::CommandId::BusinessToBusinessTransfer) // optional, defaults to `CommandId::BusinessToBusinessTransfer`
/// .remarks("None") // optional, defaults to "None"
/// .sender_id(mpesa::IdentifierTypes::ShortCode) // optional, defaults to `IdentifierTypes::ShortCode`
/// .receiver_id(mpesa::IdentifierTypes::ShortCode) // optional, defaults to `IdentifierTypes::ShortCode`
/// .send();
/// ```
pub fn b2b(&'a self, initiator_name: &'a str) -> B2bBuilder<'a> {
B2bBuilder::new(&self, initiator_name)
}
/// **C2B Register builder**
///
/// Creates a `C2bRegisterBuilder` for registering URLs to the 3rd party shortcode.
///
/// See more from the Safaricom API docs [here](https://developer.safaricom.co.ke/docs?shell#c2b-api)
///
/// # Example
/// ```ignore
/// let response = client
/// .c2b_register()
/// .short_code("600496")
/// .confirmation_url("https://testdomain.com/true")
/// .validation_url("https://testdomain.com/valid")
/// .response_type(mpesa::ResponseTypes::Complete) // optional, defaults to `ResponseTypes::Complete`
/// .send();
/// ```
pub fn c2b_register(&'a self) -> C2bRegisterBuilder<'a> {
C2bRegisterBuilder::new(&self)
}
/// **C2B Simulate Builder**
///
/// Creates a `C2bSimulateBuilder` for simulating C2B transactions
///
/// See more [here](https://developer.safaricom.co.ke/c2b/apis/post/simulate)
///
/// # Example
/// ```ignore
/// let response = client.c2b_simulate()
/// .short_code("600496")
/// .msisdn("254700000000")
/// .amount(1000)
/// .command_id(mpesa::CommandId::CustomerPayBillOnline) // optional, defaults to `CommandId::CustomerPayBillOnline`
/// .bill_ref_number("Your_BillRefNumber>") // optional, defaults to "None"
/// .send();
/// ```
pub fn c2b_simulate(&'a self) -> C2bSimulateBuilder<'a> {
C2bSimulateBuilder::new(&self)
}
/// **Account Balance Builder**
///
/// Creates an `AccountBalanceBuilder` for enquiring the balance on an MPESA BuyGoods.
/// Requires an `initiator_name`.
///
/// See more from the Safaricom API docs [here](https://developer.safaricom.co.ke/docs#account-balance-api)
///
/// # Example
/// ```ignore
/// let response = client
/// .account_balance("testapi496")
/// .result_url("https://testdomain.com/err")
/// .timeout_url("https://testdomain.com/ok")
/// .party_a("600496")
/// .command_id(mpesa::CommandId::AccountBalance) // optional, defaults to `CommandId::AccountBalance`
/// .identifier_type(mpesa::IdentifierTypes::ShortCode) // optional, defaults to `IdentifierTypes::ShortCode`
/// .remarks("Your Remarks") // optional, defaults to "None"
/// .send();
/// ```
pub fn account_balance(&'a self, initiator_name: &'a str) -> AccountBalanceBuilder<'a> {
AccountBalanceBuilder::new(&self, initiator_name)
}
/// **Mpesa Express Request/ STK push Builder**
///
/// Creates a `MpesaExpressRequestBuilder` struct
/// Requires a `business_short_code` - The organization shortcode used to receive the transaction
///
/// See more from the Safaricom API docs [here](https://developer.safaricom.co.ke/docs#lipa-na-m-pesa-online-payment)
///
/// # Example
///```ignore
/// let response = client
/// .express_request("174379")
/// .phone_number("254708374149")
/// .party_a("254708374149")
/// .party_b("174379")
/// .amount(500)
/// .callback_url("https://test.example.com/api")
/// .transaction_type(CommandId::CustomerPayBillOnline) // Optional, defaults to `CommandId::CustomerPayBillOnline`
/// .transaction_desc("Description") // Optional, defaults to "None"
/// .send();
/// ```
pub fn express_request(
&'a self,
business_short_code: &'a str,
) -> MpesaExpressRequestBuilder<'a> {
MpesaExpressRequestBuilder::new(&self, business_short_code)
}
}