use serde::{Deserialize, Serialize};
use crate::client::Mpesa;
use crate::constants::{CommandId, IdentifierTypes};
use crate::environment::ApiEnvironment;
use crate::errors::{MpesaError, MpesaResult};
#[derive(Debug, Serialize)]
struct B2bPayload<'mpesa> {
#[serde(rename(serialize = "Initiator"))]
initiator: &'mpesa str,
#[serde(rename(serialize = "SecurityCredential"))]
security_credential: &'mpesa str,
#[serde(rename(serialize = "CommandID"))]
command_id: CommandId,
#[serde(rename(serialize = "Amount"))]
amount: f64,
#[serde(rename(serialize = "PartyA"))]
party_a: &'mpesa str,
#[serde(rename(serialize = "SenderIdentifierType"))]
sender_identifier_type: &'mpesa str,
#[serde(rename(serialize = "PartyB"))]
party_b: &'mpesa str,
#[serde(rename(serialize = "RecieverIdentifierType"))]
reciever_identifier_type: &'mpesa str,
#[serde(rename(serialize = "Remarks"))]
remarks: &'mpesa str,
#[serde(
rename(serialize = "QueueTimeOutURL"),
skip_serializing_if = "Option::is_none"
)]
queue_time_out_url: Option<&'mpesa str>,
#[serde(
rename(serialize = "ResultURL"),
skip_serializing_if = "Option::is_none"
)]
result_url: Option<&'mpesa str>,
#[serde(
rename(serialize = "AccountReference"),
skip_serializing_if = "Option::is_none"
)]
account_reference: Option<&'mpesa str>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct B2bResponse {
#[serde(rename(deserialize = "ConversationID"))]
pub conversation_id: String,
#[serde(rename(deserialize = "OriginatorConversationID"))]
pub originator_conversation_id: String,
#[serde(rename(deserialize = "ResponseCode"))]
pub response_code: String,
#[serde(rename(deserialize = "ResponseDescription"))]
pub response_description: String,
}
#[derive(Debug)]
pub struct B2bBuilder<'mpesa, Env: ApiEnvironment> {
initiator_name: &'mpesa str,
client: &'mpesa Mpesa<Env>,
command_id: Option<CommandId>,
amount: Option<f64>,
party_a: Option<&'mpesa str>,
sender_id: Option<IdentifierTypes>,
party_b: Option<&'mpesa str>,
receiver_id: Option<IdentifierTypes>,
remarks: Option<&'mpesa str>,
queue_timeout_url: Option<&'mpesa str>,
result_url: Option<&'mpesa str>,
account_ref: Option<&'mpesa str>,
}
impl<'mpesa, Env: ApiEnvironment> B2bBuilder<'mpesa, Env> {
pub fn new(client: &'mpesa Mpesa<Env>, initiator_name: &'mpesa str) -> B2bBuilder<'mpesa, Env> {
B2bBuilder {
client,
initiator_name,
amount: None,
party_a: None,
sender_id: None,
party_b: None,
receiver_id: None,
remarks: None,
queue_timeout_url: None,
result_url: None,
command_id: None,
account_ref: None,
}
}
pub fn command_id(mut self, command_id: CommandId) -> B2bBuilder<'mpesa, Env> {
self.command_id = Some(command_id);
self
}
pub fn party_a(mut self, party_a: &'mpesa str) -> B2bBuilder<'mpesa, Env> {
self.party_a = Some(party_a);
self
}
pub fn party_b(mut self, party_b: &'mpesa str) -> B2bBuilder<'mpesa, Env> {
self.party_b = Some(party_b);
self
}
#[deprecated]
pub fn parties(
mut self,
party_a: &'mpesa str,
party_b: &'mpesa str,
) -> B2bBuilder<'mpesa, Env> {
self.party_a = Some(party_a);
self.party_b = Some(party_b);
self
}
pub fn timeout_url(mut self, timeout_url: &'mpesa str) -> B2bBuilder<'mpesa, Env> {
self.queue_timeout_url = Some(timeout_url);
self
}
pub fn result_url(mut self, result_url: &'mpesa str) -> B2bBuilder<'mpesa, Env> {
self.result_url = Some(result_url);
self
}
#[deprecated]
pub fn urls(
mut self,
timeout_url: &'mpesa str,
result_url: &'mpesa str,
) -> B2bBuilder<'mpesa, Env> {
self.queue_timeout_url = Some(timeout_url);
self.result_url = Some(result_url);
self
}
pub fn sender_id(mut self, sender_id: IdentifierTypes) -> B2bBuilder<'mpesa, Env> {
self.sender_id = Some(sender_id);
self
}
pub fn receiver_id(mut self, receiver_id: IdentifierTypes) -> B2bBuilder<'mpesa, Env> {
self.receiver_id = Some(receiver_id);
self
}
pub fn account_ref(mut self, account_ref: &'mpesa str) -> B2bBuilder<'mpesa, Env> {
self.account_ref = Some(account_ref);
self
}
pub fn amount<Number: Into<f64>>(mut self, amount: Number) -> B2bBuilder<'mpesa, Env> {
self.amount = Some(amount.into());
self
}
pub fn remarks(mut self, remarks: &'mpesa str) -> B2bBuilder<'mpesa, Env> {
self.remarks = Some(remarks);
self
}
#[allow(clippy::unnecessary_lazy_evaluations)]
pub async fn send(self) -> MpesaResult<B2bResponse> {
let url = format!(
"{}/mpesa/b2b/v1/paymentrequest",
self.client.environment.base_url()
);
let credentials = self.client.gen_security_credentials()?;
let payload = B2bPayload {
initiator: self.initiator_name,
security_credential: &credentials,
command_id: self
.command_id
.unwrap_or_else(|| CommandId::BusinessToBusinessTransfer),
amount: self
.amount
.ok_or(MpesaError::Message("amount is required"))?,
party_a: self
.party_a
.ok_or(MpesaError::Message("party_a is required"))?,
sender_identifier_type: &self
.sender_id
.unwrap_or_else(|| IdentifierTypes::ShortCode)
.to_string(),
party_b: self
.party_b
.ok_or(MpesaError::Message("party_b is required"))?,
reciever_identifier_type: &self
.receiver_id
.unwrap_or_else(|| IdentifierTypes::ShortCode)
.to_string(),
remarks: self.remarks.unwrap_or_else(|| stringify!(None)),
queue_time_out_url: self.queue_timeout_url,
result_url: self.result_url,
account_reference: self.account_ref,
};
let response = self
.client
.http_client
.post(&url)
.bearer_auth(self.client.auth().await?)
.json(&payload)
.send()
.await?;
if response.status().is_success() {
let value = response.json::<_>().await?;
return Ok(value);
}
let value = response.json().await?;
Err(MpesaError::B2bError(value))
}
}