From 2e9e38d30b21a6e3551e216219002912cc35c70c Mon Sep 17 00:00:00 2001 From: Yasir Shariff Date: Sat, 18 Nov 2023 17:17:42 +0300 Subject: [PATCH] Fix error handling in API calls --- src/auth.rs | 6 ++-- src/client.rs | 4 +-- src/errors.rs | 40 +++------------------ src/lib.rs | 2 +- src/services/account_balance.rs | 35 ++++++------------ src/services/b2b.rs | 36 +++++++------------ src/services/b2c.rs | 27 ++++++-------- src/services/bill_manager/bulk_invoice.rs | 31 +++++----------- src/services/bill_manager/cancel_invoice.rs | 34 ++++++------------ src/services/bill_manager/onboard.rs | 37 ++++++------------- src/services/bill_manager/onboard_modify.rs | 33 ++++++----------- src/services/bill_manager/reconciliation.rs | 31 +++++----------- src/services/bill_manager/single_invoice.rs | 31 +++++----------- src/services/c2b_register.rs | 36 ++++++------------- src/services/c2b_simulate.rs | 35 ++++++------------ src/services/express_request.rs | 34 ++++++------------ src/services/transaction_reversal.rs | 30 +++++----------- src/services/transaction_status.rs | 30 +++++----------- 18 files changed, 150 insertions(+), 362 deletions(-) diff --git a/src/auth.rs b/src/auth.rs index 4392d3073..6833b7380 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -2,7 +2,7 @@ use cached::proc_macro::cached; use serde::{Deserialize, Serialize}; use serde_aux::field_attributes::deserialize_number_from_string; -use crate::{ApiEnvironment, ApiError, Mpesa, MpesaError, MpesaResult}; +use crate::{ApiEnvironment, Mpesa, MpesaError, MpesaResult, ResponseError}; const AUTHENTICATION_URL: &str = "/oauth/v1/generate?grant_type=client_credentials"; @@ -30,8 +30,8 @@ pub(crate) async fn auth(client: &Mpesa) -> MpesaResult().await?; - Err(MpesaError::AuthenticationError(error)) + let error = response.json::().await?; + Err(MpesaError::Service(error)) } /// Response returned from the authentication function diff --git a/src/client.rs b/src/client.rs index 2514a34a4..b82c31f61 100644 --- a/src/client.rs +++ b/src/client.rs @@ -587,9 +587,9 @@ impl<'mpesa, Env: ApiEnvironment> Mpesa { Ok(body) } else { - let err = res.json::().await?; + let err = res.json::().await?; - Err(crate::MpesaError::ApiError(err)) + Err(crate::MpesaError::Service(err)) } } } diff --git a/src/errors.rs b/src/errors.rs index 4800e08aa..31f2e2e3c 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -7,40 +7,8 @@ use thiserror::Error; /// Mpesa error stack #[derive(Error, Debug)] pub enum MpesaError { - #[error("Api error: {0}")] - ApiError(ApiError), - #[error("{0}")] - AuthenticationError(ApiError), - #[error("B2B request failed: {0}")] - B2bError(ApiError), - #[error("B2C request failed: {0}")] - B2cError(ApiError), - #[error("C2B register request failed: {0}")] - C2bRegisterError(ApiError), - #[error("C2B simulate request failed: {0}")] - C2bSimulateError(ApiError), - #[error("Account Balance request failed: {0}")] - AccountBalanceError(ApiError), - #[error("Bill manager onboarding failed: {0}")] - OnboardError(ApiError), - #[error("Bill manager onboarding modify failed: {0}")] - OnboardModifyError(ApiError), - #[error("Bill manager bulk invoice failed: {0}")] - BulkInvoiceError(ApiError), - #[error("Bill manager reconciliation failed: {0}")] - ReconciliationError(ApiError), - #[error("Bill manager single invoice failed: {0}")] - SingleInvoiceError(ApiError), - #[error("Bill manager cancel invoice failed: {0}")] - CancelInvoiceError(ApiError), - #[error("Mpesa Express request/ STK push failed: {0}")] - MpesaExpressRequestError(ApiError), - #[error("Mpesa Transaction reversal failed: {0}")] - MpesaTransactionReversalError(ApiError), - #[error("Mpesa Transaction status failed: {0}")] - MpesaTransactionStatusError(ApiError), - #[error("Mpesa Dynamic QR failed: {0}")] - MpesaDynamicQrError(ApiError), + #[error("Service error: {0}")] + Service(ResponseError), #[error("An error has occured while performing the http request")] NetworkError(#[from] reqwest::Error), #[error("An error has occured while serializig/ deserializing")] @@ -60,13 +28,13 @@ pub type MpesaResult = Result; #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all(deserialize = "camelCase"))] -pub struct ApiError { +pub struct ResponseError { pub request_id: String, pub error_code: String, pub error_message: String, } -impl fmt::Display for ApiError { +impl fmt::Display for ResponseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, diff --git a/src/lib.rs b/src/lib.rs index 240eb0a5d..55959d6fe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,4 +14,4 @@ pub use constants::{ }; pub use environment::ApiEnvironment; pub use environment::Environment::{self, Production, Sandbox}; -pub use errors::{ApiError, MpesaError, MpesaResult}; +pub use errors::{MpesaError, MpesaResult, ResponseError}; diff --git a/src/services/account_balance.rs b/src/services/account_balance.rs index 6ed46db88..81aadfe0e 100644 --- a/src/services/account_balance.rs +++ b/src/services/account_balance.rs @@ -4,6 +4,8 @@ use crate::constants::{CommandId, IdentifierTypes}; use crate::environment::ApiEnvironment; use crate::{Mpesa, MpesaError, MpesaResult}; +const ACCOUNT_BALANCE_URL: &str = "mpesa/accountbalance/v1/query"; + #[derive(Debug, Serialize)] /// Account Balance payload struct AccountBalancePayload<'mpesa> { @@ -148,23 +150,17 @@ impl<'mpesa, Env: ApiEnvironment> AccountBalanceBuilder<'mpesa, Env> { /// /// # Errors /// Returns a `MpesaError` on failure - #[allow(clippy::unnecessary_lazy_evaluations)] pub async fn send(self) -> MpesaResult { - let url = format!( - "{}/mpesa/accountbalance/v1/query", - self.client.environment.base_url() - ); - let credentials = self.client.gen_security_credentials()?; let payload = AccountBalancePayload { - command_id: self.command_id.unwrap_or_else(|| CommandId::AccountBalance), + command_id: self.command_id.unwrap_or(CommandId::AccountBalance), party_a: self .party_a .ok_or(MpesaError::Message("party_a is required"))?, identifier_type: &self .identifier_type - .unwrap_or_else(|| IdentifierTypes::ShortCode) + .unwrap_or(IdentifierTypes::ShortCode) .to_string(), remarks: self.remarks.unwrap_or_else(|| stringify!(None)), initiator: self.initiator_name, @@ -177,21 +173,12 @@ impl<'mpesa, Env: ApiEnvironment> AccountBalanceBuilder<'mpesa, Env> { security_credential: &credentials, }; - 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::AccountBalanceError(value)) + self.client + .send(crate::client::Request { + method: reqwest::Method::POST, + path: ACCOUNT_BALANCE_URL, + body: payload, + }) + .await } } diff --git a/src/services/b2b.rs b/src/services/b2b.rs index 431e07a70..fd4fb5197 100644 --- a/src/services/b2b.rs +++ b/src/services/b2b.rs @@ -5,6 +5,8 @@ use crate::constants::{CommandId, IdentifierTypes}; use crate::environment::ApiEnvironment; use crate::errors::{MpesaError, MpesaResult}; +const B2B_URL: &str = "mpesa/b2b/v1/paymentrequest"; + #[derive(Debug, Serialize)] struct B2bPayload<'mpesa> { #[serde(rename(serialize = "Initiator"))] @@ -215,12 +217,7 @@ impl<'mpesa, Env: ApiEnvironment> B2bBuilder<'mpesa, Env> { /// /// # Errors /// Returns a `MpesaError` on failure - #[allow(clippy::unnecessary_lazy_evaluations)] pub async fn send(self) -> MpesaResult { - let url = format!( - "{}/mpesa/b2b/v1/paymentrequest", - self.client.environment.base_url() - ); let credentials = self.client.gen_security_credentials()?; let payload = B2bPayload { @@ -228,7 +225,7 @@ impl<'mpesa, Env: ApiEnvironment> B2bBuilder<'mpesa, Env> { security_credential: &credentials, command_id: self .command_id - .unwrap_or_else(|| CommandId::BusinessToBusinessTransfer), + .unwrap_or(CommandId::BusinessToBusinessTransfer), amount: self .amount .ok_or(MpesaError::Message("amount is required"))?, @@ -237,14 +234,14 @@ impl<'mpesa, Env: ApiEnvironment> B2bBuilder<'mpesa, Env> { .ok_or(MpesaError::Message("party_a is required"))?, sender_identifier_type: &self .sender_id - .unwrap_or_else(|| IdentifierTypes::ShortCode) + .unwrap_or(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) + .unwrap_or(IdentifierTypes::ShortCode) .to_string(), remarks: self.remarks.unwrap_or_else(|| stringify!(None)), queue_time_out_url: self.queue_timeout_url, @@ -252,21 +249,12 @@ impl<'mpesa, Env: ApiEnvironment> B2bBuilder<'mpesa, Env> { 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)) + self.client + .send(crate::client::Request { + method: reqwest::Method::POST, + path: B2B_URL, + body: payload, + }) + .await } } diff --git a/src/services/b2c.rs b/src/services/b2c.rs index d2dc96059..c6458617f 100644 --- a/src/services/b2c.rs +++ b/src/services/b2c.rs @@ -3,6 +3,8 @@ use serde::{Deserialize, Serialize}; use crate::environment::ApiEnvironment; use crate::{CommandId, Mpesa, MpesaError, MpesaResult}; +const B2C_URL: &str = "mpesa/b2c/v1/paymentrequest"; + #[derive(Debug, Serialize)] /// Payload to allow for b2c transactions: struct B2cPayload<'mpesa> { @@ -183,7 +185,7 @@ impl<'mpesa, Env: ApiEnvironment> B2cBuilder<'mpesa, Env> { /// # Errors /// Returns a `MpesaError` on failure. pub async fn send(self) -> MpesaResult { - let url = format!( + let _url = format!( "{}/mpesa/b2c/v1/paymentrequest", self.client.environment.base_url() ); @@ -212,21 +214,12 @@ impl<'mpesa, Env: ApiEnvironment> B2cBuilder<'mpesa, Env> { occasion: self.occasion.unwrap_or_else(|| stringify!(None)), }; - 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::B2cError(value)) + self.client + .send(crate::client::Request { + method: reqwest::Method::POST, + path: B2C_URL, + body: payload, + }) + .await } } diff --git a/src/services/bill_manager/bulk_invoice.rs b/src/services/bill_manager/bulk_invoice.rs index 5004076c2..2ca3a8728 100644 --- a/src/services/bill_manager/bulk_invoice.rs +++ b/src/services/bill_manager/bulk_invoice.rs @@ -5,6 +5,8 @@ use crate::constants::Invoice; use crate::environment::ApiEnvironment; use crate::errors::{MpesaError, MpesaResult}; +const BILL_MANAGER_BULK_INVOICE_API_URL: &str = "v1/billmanager-invoice/bulk-invoicing"; + #[derive(Clone, Debug, Deserialize)] pub struct BulkInvoiceResponse { #[serde(rename(deserialize = "rescode"))] @@ -51,32 +53,17 @@ impl<'mpesa, Env: ApiEnvironment> BulkInvoiceBuilder<'mpesa, Env> { /// /// # Errors /// Returns an `MpesaError` on failure. - #[allow(clippy::unnecessary_lazy_evaluations)] pub async fn send(self) -> MpesaResult { - let url = format!( - "{}/v1/billmanager-invoice/bulk-invoicing", - self.client.environment.base_url() - ); - if self.invoices.is_empty() { return Err(MpesaError::Message("invoices cannot be empty")); } - let response = self - .client - .http_client - .post(&url) - .bearer_auth(self.client.auth().await?) - .json(&self.invoices) - .send() - .await?; - - if response.status().is_success() { - let value = response.json().await?; - return Ok(value); - } - - let value = response.json().await?; - Err(MpesaError::BulkInvoiceError(value)) + self.client + .send(crate::client::Request { + method: reqwest::Method::POST, + path: BILL_MANAGER_BULK_INVOICE_API_URL, + body: self.invoices, + }) + .await } } diff --git a/src/services/bill_manager/cancel_invoice.rs b/src/services/bill_manager/cancel_invoice.rs index 0b12f4f18..b38565e74 100644 --- a/src/services/bill_manager/cancel_invoice.rs +++ b/src/services/bill_manager/cancel_invoice.rs @@ -2,7 +2,9 @@ use serde::{Deserialize, Serialize}; use crate::client::Mpesa; use crate::environment::ApiEnvironment; -use crate::errors::{MpesaError, MpesaResult}; +use crate::errors::MpesaResult; + +const BILL_MANAGER_CANCEL_INVOICE_API_URL: &str = "v1/billmanager-invoice/cancel-single-invoice"; #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] @@ -67,28 +69,14 @@ impl<'mpesa, Env: ApiEnvironment> CancelInvoiceBuilder<'mpesa, Env> { /// /// # Errors /// Returns an `MpesaError` on failure - #[allow(clippy::unnecessary_lazy_evaluations)] - pub async fn send(self) -> MpesaResult { - let url = format!( - "{}/v1/billmanager-invoice/cancel-single-invoice", - self.client.environment.base_url() - ); - - let response = self - .client - .http_client - .post(&url) - .bearer_auth(self.client.auth().await?) - .json(&self.external_references) - .send() - .await?; - if response.status().is_success() { - let value = response.json().await?; - return Ok(value); - } - - let value = response.json().await?; - Err(MpesaError::CancelInvoiceError(value)) + pub async fn send(self) -> MpesaResult { + self.client + .send(crate::client::Request { + method: reqwest::Method::POST, + path: BILL_MANAGER_CANCEL_INVOICE_API_URL, + body: self.external_references, + }) + .await } } diff --git a/src/services/bill_manager/onboard.rs b/src/services/bill_manager/onboard.rs index 79e23619b..9ea11d8ac 100644 --- a/src/services/bill_manager/onboard.rs +++ b/src/services/bill_manager/onboard.rs @@ -5,14 +5,14 @@ use crate::constants::SendRemindersTypes; use crate::environment::ApiEnvironment; use crate::errors::{MpesaError, MpesaResult}; +const BILL_MANAGER_ONBOARD_API_URL: &str = "v1/billmanager-invoice/optin"; + #[derive(Debug, Serialize)] /// Payload to opt you in as a biller to the bill manager features. struct OnboardPayload<'mpesa> { #[serde(rename(serialize = "callbackUrl"))] callback_url: &'mpesa str, - #[serde(rename(serialize = "email"))] email: &'mpesa str, - #[serde(rename(serialize = "logo"))] logo: &'mpesa str, #[serde(rename(serialize = "officialContact"))] official_contact: &'mpesa str, @@ -125,13 +125,7 @@ impl<'mpesa, Env: ApiEnvironment> OnboardBuilder<'mpesa, Env> { /// /// # Errors /// Returns an `MpesaError` on failure - #[allow(clippy::unnecessary_lazy_evaluations)] pub async fn send(self) -> MpesaResult { - let url = format!( - "{}/v1/billmanager-invoice/optin", - self.client.environment.base_url() - ); - let payload = OnboardPayload { callback_url: self .callback_url @@ -141,29 +135,18 @@ impl<'mpesa, Env: ApiEnvironment> OnboardBuilder<'mpesa, Env> { official_contact: self .official_contact .ok_or(MpesaError::Message("official_contact is required"))?, - send_reminders: self - .send_reminders - .unwrap_or_else(|| SendRemindersTypes::Disable), + send_reminders: self.send_reminders.unwrap_or(SendRemindersTypes::Disable), short_code: self .short_code .ok_or(MpesaError::Message("short_code is required"))?, }; - 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::OnboardError(value)) + self.client + .send(crate::client::Request { + method: reqwest::Method::POST, + path: BILL_MANAGER_ONBOARD_API_URL, + body: payload, + }) + .await } } diff --git a/src/services/bill_manager/onboard_modify.rs b/src/services/bill_manager/onboard_modify.rs index bf990a58a..59d5e4101 100644 --- a/src/services/bill_manager/onboard_modify.rs +++ b/src/services/bill_manager/onboard_modify.rs @@ -3,7 +3,9 @@ use serde::{Deserialize, Serialize}; use crate::client::Mpesa; use crate::constants::SendRemindersTypes; use crate::environment::ApiEnvironment; -use crate::errors::{MpesaError, MpesaResult}; +use crate::errors::MpesaResult; + +const BILL_MANAGER_ONBOARD_MODIFY_API_URL: &str = "v1/billmanager-invoice/change-optin-details"; #[derive(Debug, Serialize)] /// Payload to modify opt-in details to the bill manager api. @@ -117,13 +119,7 @@ impl<'mpesa, Env: ApiEnvironment> OnboardModifyBuilder<'mpesa, Env> { /// /// # Errors /// Returns an `MpesaError` on failure - #[allow(clippy::unnecessary_lazy_evaluations)] pub async fn send(self) -> MpesaResult { - let url = format!( - "{}/v1/billmanager-invoice/change-optin-details", - self.client.environment.base_url() - ); - let payload = OnboardModifyPayload { callback_url: self.callback_url, email: self.email, @@ -133,21 +129,12 @@ impl<'mpesa, Env: ApiEnvironment> OnboardModifyBuilder<'mpesa, Env> { short_code: self.short_code, }; - 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::OnboardModifyError(value)) + self.client + .send(crate::client::Request { + method: reqwest::Method::POST, + path: BILL_MANAGER_ONBOARD_MODIFY_API_URL, + body: payload, + }) + .await } } diff --git a/src/services/bill_manager/reconciliation.rs b/src/services/bill_manager/reconciliation.rs index 0cbc75f2f..4fc8080dc 100644 --- a/src/services/bill_manager/reconciliation.rs +++ b/src/services/bill_manager/reconciliation.rs @@ -5,6 +5,8 @@ use crate::client::Mpesa; use crate::environment::ApiEnvironment; use crate::errors::{MpesaError, MpesaResult}; +const BILL_MANAGER_RECONCILIATION_API_URL: &str = "v1/billmanager-invoice/reconciliation"; + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] struct ReconciliationPayload<'mpesa> { @@ -126,13 +128,7 @@ impl<'mpesa, Env: ApiEnvironment> ReconciliationBuilder<'mpesa, Env> { /// /// # Errors /// Returns an `MpesaError` on failure. - #[allow(clippy::unnecessary_lazy_evaluations)] pub async fn send(self) -> MpesaResult { - let url = format!( - "{}/v1/billmanager-invoice/reconciliation", - self.client.environment.base_url() - ); - let payload = ReconciliationPayload { account_reference: self .account_reference @@ -160,21 +156,12 @@ impl<'mpesa, Env: ApiEnvironment> ReconciliationBuilder<'mpesa, Env> { .ok_or(MpesaError::Message("transaction_id is required"))?, }; - 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::ReconciliationError(value)) + self.client + .send(crate::client::Request { + method: reqwest::Method::POST, + path: BILL_MANAGER_RECONCILIATION_API_URL, + body: payload, + }) + .await } } diff --git a/src/services/bill_manager/single_invoice.rs b/src/services/bill_manager/single_invoice.rs index a62f0b7b5..b3feb8c93 100644 --- a/src/services/bill_manager/single_invoice.rs +++ b/src/services/bill_manager/single_invoice.rs @@ -6,6 +6,8 @@ use crate::constants::{Invoice, InvoiceItem}; use crate::environment::ApiEnvironment; use crate::errors::{MpesaError, MpesaResult}; +const BILL_MANAGER_SINGLE_INVOICE_API_URL: &str = "v1/billmanager-invoice/single-invoicing"; + #[derive(Clone, Debug, Deserialize)] pub struct SingleInvoiceResponse { #[serde(rename(deserialize = "rescode"))] @@ -130,13 +132,7 @@ impl<'mpesa, Env: ApiEnvironment> SingleInvoiceBuilder<'mpesa, Env> { /// /// # Errors /// Returns an `MpesaError` on failure - #[allow(clippy::unnecessary_lazy_evaluations)] pub async fn send(self) -> MpesaResult { - let url = format!( - "{}/v1/billmanager-invoice/single-invoicing", - self.client.environment.base_url() - ); - let payload = Invoice { amount: self .amount @@ -165,21 +161,12 @@ impl<'mpesa, Env: ApiEnvironment> SingleInvoiceBuilder<'mpesa, Env> { .ok_or(MpesaError::Message("invoice_name is required"))?, }; - 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::SingleInvoiceError(value)) + self.client + .send(crate::client::Request { + method: reqwest::Method::POST, + path: BILL_MANAGER_SINGLE_INVOICE_API_URL, + body: payload, + }) + .await } } diff --git a/src/services/c2b_register.rs b/src/services/c2b_register.rs index 0e0d844c8..8468cdcaa 100644 --- a/src/services/c2b_register.rs +++ b/src/services/c2b_register.rs @@ -5,6 +5,8 @@ use crate::constants::ResponseType; use crate::environment::ApiEnvironment; use crate::errors::{MpesaError, MpesaResult}; +const C2B_REGISTER_URL: &str = "mpesa/c2b/v1/registerurl"; + #[derive(Debug, Serialize)] /// Payload to register the 3rd party’s confirmation and validation URLs to M-Pesa struct C2bRegisterPayload<'mpesa> { @@ -105,13 +107,8 @@ impl<'mpesa, Env: ApiEnvironment> C2bRegisterBuilder<'mpesa, Env> { /// /// # Errors /// Returns a `MpesaError` on failure - #[allow(clippy::unnecessary_lazy_evaluations)] - pub async fn send(self) -> MpesaResult { - let url = format!( - "{}/mpesa/c2b/v1/registerurl", - self.client.environment.base_url() - ); + pub async fn send(self) -> MpesaResult { let payload = C2bRegisterPayload { validation_url: self .validation_url @@ -119,29 +116,18 @@ impl<'mpesa, Env: ApiEnvironment> C2bRegisterBuilder<'mpesa, Env> { confirmation_url: self .confirmation_url .ok_or(MpesaError::Message("confirmation_url is required"))?, - response_type: self - .response_type - .unwrap_or_else(|| ResponseType::Completed), + response_type: self.response_type.unwrap_or(ResponseType::Completed), short_code: self .short_code .ok_or(MpesaError::Message("short_code is required"))?, }; - 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::C2bRegisterError(value)) + self.client + .send(crate::client::Request { + method: reqwest::Method::POST, + path: C2B_REGISTER_URL, + body: payload, + }) + .await } } diff --git a/src/services/c2b_simulate.rs b/src/services/c2b_simulate.rs index dc7c78b6d..d9cd9be68 100644 --- a/src/services/c2b_simulate.rs +++ b/src/services/c2b_simulate.rs @@ -5,6 +5,8 @@ use crate::constants::CommandId; use crate::environment::ApiEnvironment; use crate::errors::{MpesaError, MpesaResult}; +const C2B_SIMULATE_URL: &str = "mpesa/c2b/v1/simulate"; + #[derive(Debug, Serialize)] /// Payload to make payment requests from C2B. /// See more: https://developer.safaricom.co.ke/docs#c2b-api @@ -119,17 +121,9 @@ impl<'mpesa, Env: ApiEnvironment> C2bSimulateBuilder<'mpesa, Env> { /// /// # Errors /// Returns a `MpesaError` on failure - #[allow(clippy::unnecessary_lazy_evaluations)] pub async fn send(self) -> MpesaResult { - let url = format!( - "{}/mpesa/c2b/v1/simulate", - self.client.environment.base_url() - ); - let payload = C2bSimulatePayload { - command_id: self - .command_id - .unwrap_or_else(|| CommandId::CustomerPayBillOnline), + command_id: self.command_id.unwrap_or(CommandId::CustomerPayBillOnline), amount: self .amount .ok_or(MpesaError::Message("amount is required"))?, @@ -144,21 +138,12 @@ impl<'mpesa, Env: ApiEnvironment> C2bSimulateBuilder<'mpesa, Env> { .ok_or(MpesaError::Message("short_code is required"))?, }; - 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::C2bSimulateError(value)) + self.client + .send(crate::client::Request { + method: reqwest::Method::POST, + path: C2B_SIMULATE_URL, + body: payload, + }) + .await } } diff --git a/src/services/express_request.rs b/src/services/express_request.rs index d6340a78b..1fb6f6abf 100644 --- a/src/services/express_request.rs +++ b/src/services/express_request.rs @@ -7,6 +7,8 @@ use crate::constants::CommandId; use crate::environment::ApiEnvironment; use crate::errors::{MpesaError, MpesaResult}; +const EXPRESS_REQUEST_URL: &str = "mpesa/stkpush/v1/processrequest"; + /// Source: [test credentials](https://developer.safaricom.co.ke/test_credentials) static DEFAULT_PASSKEY: &str = "bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919"; @@ -215,14 +217,7 @@ impl<'mpesa, Env: ApiEnvironment> MpesaExpressRequestBuilder<'mpesa, Env> { /// /// # Errors /// Returns a `MpesaError` on failure - #[allow(clippy::or_fun_call)] - #[allow(clippy::unnecessary_lazy_evaluations)] pub async fn send(self) -> MpesaResult { - let url = format!( - "{}/mpesa/stkpush/v1/processrequest", - self.client.environment.base_url() - ); - let (password, timestamp) = self.generate_password_and_timestamp(); let payload = MpesaExpressRequestPayload { @@ -251,25 +246,16 @@ impl<'mpesa, Env: ApiEnvironment> MpesaExpressRequestBuilder<'mpesa, Env> { account_reference: self.account_ref.unwrap_or_else(|| stringify!(None)), transaction_type: self .transaction_type - .unwrap_or_else(|| CommandId::CustomerPayBillOnline), + .unwrap_or(CommandId::CustomerPayBillOnline), transaction_desc: self.transaction_desc.unwrap_or_else(|| stringify!(None)), }; - 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::MpesaExpressRequestError(value)) + self.client + .send(crate::client::Request { + method: reqwest::Method::POST, + path: EXPRESS_REQUEST_URL, + body: payload, + }) + .await } } diff --git a/src/services/transaction_reversal.rs b/src/services/transaction_reversal.rs index 0080485ce..75e7ba48f 100644 --- a/src/services/transaction_reversal.rs +++ b/src/services/transaction_reversal.rs @@ -2,6 +2,8 @@ use serde::{Deserialize, Serialize}; use crate::{ApiEnvironment, CommandId, IdentifierTypes, Mpesa, MpesaError, MpesaResult}; +const TRANSACTION_REVERSAL_URL: &str = "mpesa/reversal/v1/request"; + #[derive(Debug, Serialize)] pub struct TransactionReversalPayload<'mpesa> { #[serde(rename(serialize = "Initiator"))] @@ -169,11 +171,6 @@ impl<'mpesa, Env: ApiEnvironment> TransactionReversalBuilder<'mpesa, Env> { /// # Errors /// Returns a `MpesaError` on failure. pub async fn send(self) -> MpesaResult { - let url = format!( - "{}/mpesa/reversal/v1/request", - self.client.environment.base_url() - ); - let credentials = self.client.gen_security_credentials()?; let payload = TransactionReversalPayload { @@ -202,21 +199,12 @@ impl<'mpesa, Env: ApiEnvironment> TransactionReversalBuilder<'mpesa, Env> { .ok_or(MpesaError::Message("amount is required"))?, }; - 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 Err(MpesaError::MpesaTransactionReversalError(value)); - }; - - let response = response.json::<_>().await?; - Ok(response) + self.client + .send(crate::client::Request { + method: reqwest::Method::POST, + path: TRANSACTION_REVERSAL_URL, + body: payload, + }) + .await } } diff --git a/src/services/transaction_status.rs b/src/services/transaction_status.rs index 5a7a50b99..049bebf5d 100644 --- a/src/services/transaction_status.rs +++ b/src/services/transaction_status.rs @@ -2,6 +2,8 @@ use serde::{Deserialize, Serialize}; use crate::{ApiEnvironment, CommandId, IdentifierTypes, Mpesa, MpesaError, MpesaResult}; +const TRANSACTION_STATUS_URL: &str = "mpesa/transactionstatus/v1/query"; + #[derive(Debug, Serialize)] pub struct TransactionStatusPayload<'mpesa> { #[serde(rename(serialize = "Initiator"))] @@ -154,11 +156,6 @@ impl<'mpesa, Env: ApiEnvironment> TransactionStatusBuilder<'mpesa, Env> { /// # Errors /// Returns a `MpesaError` on failure. pub async fn send(self) -> MpesaResult { - let url = format!( - "{}/mpesa/transactionstatus/v1/query", - self.client.environment.base_url() - ); - let credentials = self.client.gen_security_credentials()?; let payload = TransactionStatusPayload { @@ -182,21 +179,12 @@ impl<'mpesa, Env: ApiEnvironment> TransactionStatusBuilder<'mpesa, Env> { occasion: self.occasion.unwrap_or(stringify!(None)), }; - 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 Err(MpesaError::MpesaTransactionStatusError(value)); - }; - - let response = response.json::<_>().await?; - Ok(response) + self.client + .send(crate::client::Request { + method: reqwest::Method::POST, + path: TRANSACTION_STATUS_URL, + body: payload, + }) + .await } }