diff --git a/Cargo.toml b/Cargo.toml index 3813850..3f42c74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "paystack-rs" -version = "0.1.5" +version = "0.1.6" # updated version number already description = "A unofficial client library for the Paystack API" authors = ["Oghenemarho Orukele "] edition = "2021" diff --git a/README.md b/README.md index 8e0c1c0..d5abdc0 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Convenient **Async** rust bindings and types for the Paystack HTTP API aiming to The client current covers the follow section of the API: - Transactions +- Transaction Splits ## Documentation diff --git a/src/client.rs b/src/client.rs index fea6db6..f35e5fe 100644 --- a/src/client.rs +++ b/src/client.rs @@ -12,7 +12,8 @@ use serde::Serialize; use crate::{ Charge, Currency, ExportTransactionResponse, PartialDebitTransaction, PaystackError, - PaystackResult, RequestNotSuccessful, Status, Transaction, TransactionResponse, + PaystackResult, RequestNotSuccessful, ResponseWithoutData, Status, Subaccount, Transaction, + TransactionResponse, TransactionSplit, TransactionSplitListResponse, TransactionSplitResponse, TransactionStatus, TransactionStatusList, TransactionTimeline, TransactionTotalsResponse, }; @@ -38,7 +39,34 @@ impl PaystackClient { } } - /// A function for sending post requests to a specified url + /// A function for sending GET request to a specified url + /// with optional query parameters using reqwest client. + async fn get_request( + &self, + url: &String, + query: Option>, + ) -> PaystackResult { + let response = self + .client + .get(url) + .query(&query) + .bearer_auth(&self.api_key) + .header("Content-Type", "application/json") + .send() + .await; + + match response { + Ok(response) => match response.status() { + StatusCode::OK => Ok(response), + _ => { + Err(RequestNotSuccessful::new(response.status(), response.text().await?).into()) + } + }, + Err(err) => Err(PaystackError::Generic(err.to_string())), + } + } + + /// A function for sending POST requests to a specified url /// using the reqwest client. async fn post_request(&self, url: &String, body: T) -> PaystackResult where @@ -64,19 +92,44 @@ impl PaystackClient { } } - /// A function for sending get request to a specified url - /// with optional query parameters using reqwest client. - async fn get_request( - &self, - url: &String, - query: Option>, - ) -> PaystackResult { + /// A function for sending PUT requests to a specified url + /// using the reqwest client. + async fn put_request(&self, url: &String, body: T) -> PaystackResult + where + T: Debug + Serialize, + { let response = self .client - .get(url) - .query(&query) + .put(url) + .bearer_auth(&self.api_key) + .header("Content-Type", "application/json") + .json(&body) + .send() + .await; + + match response { + Ok(response) => match response.status() { + StatusCode::OK => Ok(response), + _ => { + Err(RequestNotSuccessful::new(response.status(), response.text().await?).into()) + } + }, + Err(err) => Err(PaystackError::Generic(err.to_string())), + } + } + + /// A function for sending DELETE requests to a specified url + /// using the reqwest client. + async fn _delete_request(&self, url: &String, body: T) -> PaystackResult + where + T: Debug + Serialize, + { + let response = self + .client + .delete(url) .bearer_auth(&self.api_key) .header("Content-Type", "application/json") + .json(&body) .send() .await; @@ -211,7 +264,7 @@ impl PaystackClient { /// View the timeline of a transaction /// - /// This function takes in the Transaction id or reference as a parameter + /// This method takes in the Transaction id or reference as a parameter pub async fn view_transaction_timeline( &self, id: Option, @@ -336,4 +389,175 @@ impl PaystackClient { Err(err) => Err(err), } } + + /// Create a split payment on your integration. + /// + /// This method takes a TransactionSplit object as a parameter. + pub async fn create_transaction_split( + &self, + split_body: TransactionSplit, + ) -> PaystackResult { + let url = format!("{}/split", BASE_URL); + + match self.post_request(&url, split_body).await { + Ok(response) => match response.status() { + reqwest::StatusCode::OK => { + match response.json::().await { + Ok(content) => Ok(content), + Err(err) => Err(PaystackError::TransactionSplit(err.to_string())), + } + } + _ => { + Err(RequestNotSuccessful::new(response.status(), response.text().await?).into()) + } + }, + Err(err) => Err(err), + } + } + + /// List the transaction splits available on your integration + /// + /// Takes in the following parameters: + /// - `split_name`: (Optional) name of the split to retrieve. + /// - `split_active`: (Optional) status of the split to retrieve. + pub async fn list_transaction_splits( + &self, + split_name: Option, + split_active: Option, + ) -> PaystackResult { + let url = format!("{}/split", BASE_URL); + + // Specify a default option for active splits + let split_active = match split_active { + Some(active) => active.to_string(), + None => String::from(""), + }; + + let query = vec![ + ("name", split_name.unwrap_or("".to_string())), + ("active", split_active), + ]; + + match self.get_request(&url, Some(query)).await { + Ok(response) => match response.status() { + reqwest::StatusCode::OK => { + match response.json::().await { + Ok(content) => Ok(content), + Err(err) => Err(PaystackError::TransactionSplit(err.to_string())), + } + } + _ => { + Err(RequestNotSuccessful::new(response.status(), response.text().await?).into()) + } + }, + Err(err) => Err(err), + } + } + + /// Get details of a split on your integration. + /// + /// Takes in the following parameter: + /// - `split_id`: Id of the transaction split. + pub async fn fetch_transaction_split( + &self, + split_id: String, + ) -> PaystackResult { + let url = format!("{}/split{}", BASE_URL, split_id); + + match self.get_request(&url, None).await { + Ok(response) => match response.status() { + reqwest::StatusCode::OK => { + match response.json::().await { + Ok(content) => Ok(content), + Err(err) => Err(PaystackError::TransactionSplit(err.to_string())), + } + } + _ => { + Err(RequestNotSuccessful::new(response.status(), response.text().await?).into()) + } + }, + Err(err) => Err(err), + } + } + + /// Update a transaction split details on your integration. + /// + /// Takes in the following parameters: + /// - `split_id`: Id of the transaction split. + /// - `split_name`: updated name for the split. + /// - `split_status`: updated states for the split. + /// - `bearer_type`: (Optional) updated bearer type for the split. + /// - `bearer_subaccount`: (Optional) updated bearer subaccount for the split + pub async fn update_transaction_split( + &self, + split_id: String, + body: TransactionSplit, + ) -> PaystackResult { + let url = format!("{}/split/{}", BASE_URL, split_id); + + match self.put_request(&url, body).await { + Ok(respone) => match respone.status() { + reqwest::StatusCode::OK => match respone.json::().await { + Ok(content) => Ok(content), + Err(err) => Err(PaystackError::TransactionSplit(err.to_string())), + }, + _ => Err(RequestNotSuccessful::new(respone.status(), respone.text().await?).into()), + }, + Err(err) => Err(err), + } + } + + /// Add a Subaccount to a Transaction Split, or update the share of an existing Subaccount in a Transaction Split + /// + /// Takes in the following parameters: + /// - `split_id`: Id of the transaction split to update. + /// - `body`: Subacount to add to the transaction split. + pub async fn add_or_update_subaccount_split( + &self, + split_id: String, + body: Subaccount, + ) -> PaystackResult { + let url = format!("{}/split/{}/subaccount/add", BASE_URL, split_id); + + match self.post_request(&url, body).await { + Ok(response) => match response.status() { + reqwest::StatusCode::OK => { + match response.json::().await { + Ok(content) => Ok(content), + Err(err) => Err(PaystackError::TransactionSplit(err.to_string())), + } + } + _ => { + Err(RequestNotSuccessful::new(response.status(), response.text().await?).into()) + } + }, + Err(err) => Err(err), + } + } + + /// Remove a subaccount from a transaction split. + /// + /// Takes in the following parameters + /// - split_id: Id of the transaction split + /// - subaccount: subaccount code to remove + pub async fn remove_subaccount_from_transaction_split( + &self, + split_id: String, + subaccount: String, + ) -> PaystackResult { + let url = format!("{}/split/{}/subaccount/remove", BASE_URL, split_id); + + match self.post_request(&url, subaccount).await { + Ok(response) => match response.status() { + reqwest::StatusCode::OK => match response.json::().await { + Ok(content) => Ok(content), + Err(err) => Err(PaystackError::TransactionSplit(err.to_string())), + }, + _ => { + Err(RequestNotSuccessful::new(response.status(), response.text().await?).into()) + } + }, + Err(err) => Err(err), + } + } } diff --git a/src/error.rs b/src/error.rs index 06d5cee..48982b7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -21,6 +21,10 @@ pub enum PaystackError { #[error("Charge Error: {0}")] Charge(String), + /// Error associated with Transaction Split + #[error("Transaction Split Error: {0}")] + TransactionSplit(String), + /// Error for unsuccessful request to the Paystack API #[error("Request failed: `{0}`")] RequestNotSuccessful(#[from] RequestNotSuccessful), diff --git a/src/resources/charge.rs b/src/resources/charge.rs index 54d8c34..65ef37d 100644 --- a/src/resources/charge.rs +++ b/src/resources/charge.rs @@ -4,6 +4,7 @@ //! create charges usingt the Paystack API. use crate::{error, Channel, Currency, PaystackResult}; +use serde::Serialize; /// This struct is used to create a charge body for creating a Charge Authorization using the Paystack API. /// @@ -24,7 +25,7 @@ use crate::{error, Channel, Currency, PaystackResult}; /// Ideally, you will need to use this if you are splitting in flat rates /// (since subaccount creation only allows for percentage split). e.g. 7000 for a 70 naira -#[derive(serde::Serialize, Debug)] +#[derive(Serialize, Debug)] pub struct Charge { email: String, amount: String, @@ -35,7 +36,29 @@ pub struct Charge { transaction_charge: Option, } -/// Builder for the Charge object +/// The `ChargeBuilder` struct provides a convenient way to construct a charge object +/// with optional fields. Each field can be set individually using the builder's methods. +/// Once all the desired fields are set, the `build` method can be called to create +/// an instance of the `Charge` struct. +/// +/// # Errors +/// +/// Returns a `PaystackResult` with an `Err` variant if any required fields are missing, +/// including email, amount, and authorization code. The error indicates which field is missing. +/// +/// # Examples +/// +/// ```rust +/// use paystack::{Currency, Channel, ChargeBuilder}; +/// +/// let charge = ChargeBuilder::new() +/// .email("user@example.com") +/// .amount("10000") +/// .authorization_code("AUTH_CODE") +/// .currency(Currency::USD) +/// .channel(vec![Channel::Card, Channel::Bank]) +/// .build(); +/// ``` #[derive(Default, Clone)] pub struct ChargeBuilder { email: Option, diff --git a/src/resources/paystack_enums.rs b/src/resources/paystack_enums.rs index 39568fe..320b453 100644 --- a/src/resources/paystack_enums.rs +++ b/src/resources/paystack_enums.rs @@ -6,8 +6,38 @@ use std::fmt; use serde::{Deserialize, Serialize}; +/// Represents different currencies supported by the Paystack API. +/// +/// The `Currency` enum defines the possible currency options that can be used with Paystack, +/// including Nigerian Naira (NGN), Ghanaian Cedis (GHS), American Dollar (USD), +/// and South African Rands (ZAR). It also includes an `EMPTY` variant to represent cases +/// where the currency can be empty. +/// +/// # Variants +/// +/// - `NGN`: Nigerian Naira. +/// - `GHS`: Ghanaian Cedis. +/// - `USD`: American Dollar. +/// - `ZAR`: South African Rands. +/// - `EMPTY`: Used when the currency can be empty. +/// +/// # Examples +/// +/// ``` +/// use paystack::Currency; +/// +/// let ngn_currency = Currency::NGN; +/// let ghs_currency = Currency::GHS; +/// let usd_currency = Currency::USD; +/// let zar_currency = Currency::ZAR; +/// let empty_currency = Currency::EMPTY; +/// +/// println!("{:?}", ngn_currency); // Prints: NGN +/// ``` +/// +/// The example demonstrates the usage of the `Currency` enum from the Paystack crate, +/// creating instances of each variant and printing their debug representation. #[derive(Debug, Serialize, Deserialize, Clone, Default)] -/// Respresents the currencies supported by Paystack pub enum Currency { /// Nigerian Naira #[default] @@ -35,11 +65,45 @@ impl fmt::Display for Currency { } } -#[derive(Debug, Serialize, Deserialize, Clone)] +/// Represents the payment channels supported by Paystack. +/// +/// The `Channel` enum defines the possible payment channels that can be used with Paystack, +/// including debit card, bank interface, USSD code, QR code, mobile money, bank transfer, +/// and Apple Pay. +/// +/// # Variants +/// +/// - `Card`: Payment with a debit card. +/// - `Bank`: Payment with a bank interface. +/// - `Ussd`: Payment with a USSD code. +/// - `Qr`: Payment with a QR code. +/// - `MobileMoney`: Payment with mobile money. +/// - `BankTransfer`: Payment with a bank transfer. +/// - `ApplePay`: Payment with Apple Pay. +/// +/// # Examples +/// +/// ``` +/// use paystack::Channel; +/// +/// let card_channel = Channel::Card; +/// let bank_channel = Channel::Bank; +/// let ussd_channel = Channel::Ussd; +/// let qr_channel = Channel::Qr; +/// let mobile_money_channel = Channel::MobileMoney; +/// let bank_transfer_channel = Channel::BankTransfer; +/// let apple_pay_channel = Channel::ApplePay; +/// +/// println!("{:?}", card_channel); // Prints: Card +/// ``` +/// +/// The example demonstrates the usage of the `Channel` enum from the Paystack crate, +/// creating instances of each variant and printing their debug representation. +#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[serde(rename_all = "snake_case")] -/// Represents the payment channels supported by Paystack pub enum Channel { /// Debit Card + #[default] Card, /// Payment with Bank Interface Bank, @@ -61,9 +125,33 @@ impl fmt::Display for Channel { } } +/// Represents the status of a transaction. +/// +/// The `Status` enum defines the possible status values for a transaction, +/// indicating whether the transaction was successful, abandoned, or failed. +/// +/// # Variants +/// +/// - `Success`: Represents a successful transaction. +/// - `Abandoned`: Represents an abandoned transaction. +/// - `Failed`: Represents a failed transaction. +/// +/// # Examples +/// +/// ``` +/// use paystack::Status; +/// +/// let success_status = Status::Success; +/// let abandoned_status = Status::Abandoned; +/// let failed_status = Status::Failed; +/// +/// println!("{:?}", success_status); // Prints: Success +/// ``` +/// +/// The example demonstrates the usage of the `Status` enum, creating instances of each variant +/// and printing their debug representation. #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "lowercase")] -/// Represents the status of the Transaction. pub enum Status { /// A successful transaction. Success, @@ -83,3 +171,100 @@ impl fmt::Display for Status { write!(f, "{}", lowercase_string) } } + +/// Represents the type of transaction split. +/// +/// The `SplitType` enum defines the possible types of transaction splits that can be created, +/// indicating whether the split is based on a percentage or a flat amount. +/// +/// # Variants +/// +/// - `Percentage`: A split based on a percentage. +/// - `Flat`: A split based on an amount. +/// +/// # Examples +/// +/// ``` +/// use paystack::SplitType; +/// +/// let percentage_split = SplitType::Percentage; +/// let flat_split = SplitType::Flat; +/// +/// println!("{:?}", percentage_split); // Prints: Percentage +/// ``` +/// +/// The example demonstrates the usage of the `SplitType` enum, creating instances of each variant +/// and printing their debug representation. +#[derive(Debug, Serialize, Clone, Default)] +#[serde(rename_all = "lowercase")] +pub enum SplitType { + /// A split based on a percentage + #[default] + Percentage, + /// A split based on an amount + Flat, +} + +impl fmt::Display for SplitType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let lowercase_string = match self { + SplitType::Percentage => "percentage", + SplitType::Flat => "flat", + }; + write!(f, "{}", lowercase_string) + } +} + +/// Represents the type of bearer for a charge. +/// +/// The `BearerType` enum defines the possible types of bearers for a charge, indicating who +/// is responsible for the transaction split. +/// +/// # Variants +/// +/// - `Subaccount`: The subaccount bears the transaction split. +/// - `Account`: The main account bears the transaction split. +/// - `AllProportional`: The transaction is split proportionally to all accounts. +/// - `All`: The transaction is paid by all accounts. +/// +/// # Examples +/// +/// ``` +/// use paystack::BearerType; +/// +/// let subaccount_bearer = BearerType::Subaccount; +/// let account_bearer = BearerType::Account; +/// let all_proportional_bearer = BearerType::AllProportional; +/// let all_bearer = BearerType::All; +/// +/// println!("{:?}", subaccount_bearer); // Prints: Subaccount +/// ``` +/// +/// The example demonstrates the usage of the `BearerType` enum, creating instances of each variant +/// and printing their debug representation. +#[derive(Debug, Serialize, Clone, Default)] +#[serde(rename_all = "lowercase")] +pub enum BearerType { + /// The subaccount bears the transaction split + #[default] + Subaccount, + /// The main account bears the transaction split + Account, + /// The transaction is split proportionally to all accounts + #[serde(rename = "all-proportional")] + AllProportional, + /// The transaction is paid by all accounts + All, +} + +impl fmt::Display for BearerType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let lowercase_string = match self { + BearerType::Subaccount => "subaccount", + BearerType::Account => "account", + BearerType::AllProportional => "all-proportional", + BearerType::All => "all", + }; + write!(f, "{}", lowercase_string) + } +} diff --git a/src/resources/transaction_split.rs b/src/resources/transaction_split.rs index 6d1a9c9..2531280 100644 --- a/src/resources/transaction_split.rs +++ b/src/resources/transaction_split.rs @@ -3,4 +3,191 @@ //! This file contains the structs and definitions need to create //! transaction splits for the Paystack API. -use crate::{error, Channel, Currency, PaystackResult}; +use crate::{error::PaystackError, BearerType, Currency, PaystackResult, SplitType}; +use serde::Serialize; + +/// This struct is used to create a split payment on your integration. +/// +/// IMPORTANT: This class can obly be created using the TransactionSplitBuilder. +/// THe struct has the following fields: +/// - name: Name of the transaction split +/// - type: The type of transaction split you want to create. +/// You can use one of the following: percentage | flat. +/// - currency: Any of NGN, GHS, ZAR, or USD +/// - subaccounts: A list of object containing subaccount +/// code and number of shares +/// - bearer_type: Any of subaccount | account | all-proportional | all +/// - bearer_subaccount: Subaccount code +#[derive(Serialize, Debug, Default)] +pub struct TransactionSplit { + name: String, + #[serde(rename = "type")] + split_type: SplitType, + currency: Currency, + subaccounts: Vec, + bearer_type: BearerType, + bearer_subaccount: String, +} + +/// This struct represents the subacount that bears the transaction split +#[derive(Serialize, Debug, Clone)] +pub struct Subaccount { + /// This is the sub account code + pub subaccount: String, + /// This is the transaction share for the subaccount + pub share: u32, +} + +impl Subaccount { + /// Creates a new subaccount for the Paystack API + pub fn new(subaccount: impl Into, share: u32) -> Self { + Subaccount { + subaccount: subaccount.into(), + share, + } + } +} + +/// A builder pattern implementation for constructing `PercentageSplit` instances. +/// +/// The `PercentageSplitBuilder` struct provides a fluent and readable way to construct +/// instances of the `PercentageSplit` struct. +/// +/// # Errors +/// +/// Returns a `PaystackResult` with an `Err` variant if any required fields are missing, +/// including email, amount, and currency. The error indicates which field is missing. +/// +/// # Examples +/// +/// ```rust +/// use paystack::{Currency, SplitType, BearerType, PercentageSplitBuilder, Subaccount}; +/// +/// let sub_accounts = vec![ +/// Subaccount::new( +/// "ACCT_z3x6z3nbo14xsil", +/// 20, +/// ), +/// Subaccount::new( +/// "ACCT_pwwualwty4nhq9d", +/// 30, +/// ), +/// ]; +/// +/// let percentage_split = PercentageSplitBuilder::new() +/// .name("Percentage Split") +/// .split_type(SplitType::Percentage) +/// .currency(Currency::NGN) +/// .add_subaccount(sub_accounts) +/// .bearer_type(BearerType::Subaccount) +/// .bearer_subaccount("ACCT_hdl8abxl8drhrl3") +/// .build(); +/// ``` +#[derive(Debug, Default, Clone)] +pub struct PercentageSplitBuilder { + name: Option, + split_type: Option, + currency: Option, + subaccounts: Option>, + bearer_type: Option, + bearer_subaccount: Option, +} + +impl PercentageSplitBuilder { + /// Create a new instance of the Percentage Split builder with default properties + pub fn new() -> Self { + PercentageSplitBuilder::default() + } + + /// Specify the transaction split name + pub fn name(mut self, name: impl Into) -> Self { + self.name = Some(name.into()); + self + } + + /// Specify the transaction split amount + pub fn split_type(mut self, split_type: SplitType) -> Self { + self.split_type = Some(split_type); + self + } + + /// Specify the transaction split currency + pub fn currency(mut self, currency: Currency) -> Self { + self.currency = Some(currency); + self + } + + /// Specify the subaccount for the transaction split + pub fn add_subaccount(mut self, sub_accounts: Vec) -> Self { + self.subaccounts = Some(sub_accounts); + self + } + + /// Specify the bearer type for the transaction split + pub fn bearer_type(mut self, bearer_type: BearerType) -> Self { + self.bearer_type = Some(bearer_type); + self + } + + /// Specify the bearer subaccount code + pub fn bearer_subaccount(mut self, code: impl Into) -> Self { + self.bearer_subaccount = Some(code.into()); + self + } + + /// Build the TransactionSplit object + pub fn build(self) -> PaystackResult { + let Some(name) = self.name else { + return Err( + PaystackError::TransactionSplit("name is required to create a transaction split".to_string()) + ) + }; + + let Some(split_type) = self.split_type else { + return Err( + PaystackError::TransactionSplit("split type is required to create a transaction split".to_string()) + ) + }; + + let Some(currency) = self.currency else { + return Err( + PaystackError::Transaction( + "currency is required to create a transaction split".to_string() + ) + ) + }; + + let Some(subaccounts) = self.subaccounts else { + return Err( + PaystackError::TransactionSplit( + "sub accounts are required to create a transaction split".to_string() + ) + ) + }; + + let Some(bearer_type) = self.bearer_type else { + return Err( + PaystackError::TransactionSplit( + "bearer type is required to create a transaction split".to_string() + ) + ) + }; + + let Some(bearer_subaccount) = self.bearer_subaccount else { + return Err( + PaystackError::TransactionSplit( + "bearer subaccount is required to create a transaction split".to_string() + ) + ) + }; + + Ok(TransactionSplit { + name, + split_type, + currency, + subaccounts, + bearer_type, + bearer_subaccount, + }) + } +} diff --git a/src/response.rs b/src/response.rs index 07ff6e4..0ef3c43 100644 --- a/src/response.rs +++ b/src/response.rs @@ -49,8 +49,6 @@ pub struct TransactionStatusList { /// This contains the results of your request. /// In this case, it is a vector of objects. pub data: Vec, - /// This contains the meta data associated with the response. - pub meta: MetaData, } /// This struct represents the transaction timeline. @@ -99,26 +97,6 @@ pub struct TranasctionHistory { pub time: u32, } -/// This struct represents the mata data of the response. -#[derive(Deserialize, Debug, Clone)] -pub struct MetaData { - /// This is the total number of transactions that were performed by the customer. - pub total: Option, - /// This is the number of records skipped before the first record in the array returned. - pub skipped: Option, - /// This is the maximum number of records that will be returned per request. - /// This can be modified by passing a new value as a perPage query parameter. Default: 50 - pub per_page: Option, - /// This is the current page being returned. - /// This is dependent on what page was requested using the page query parameter. - /// - /// `Default: 1` - pub page: Option, - /// This is how many pages in total are available for retrieval considering the maximum records per page specified. - /// For context, if there are 51 records and perPage is left at its default value, pageCount will have a value of 2. - pub page_count: Option, -} - /// This struct represents the data of the transaction status response. #[derive(Deserialize, Debug, Clone)] pub struct TransactionStatusData { @@ -208,7 +186,7 @@ pub struct Customer { pub international_format_phone: Option, } -/// Total amount received on your account +/// Represents the response of the total amount received on your account #[derive(Debug, Deserialize)] pub struct TransactionTotalsResponse { /// This lets you know if your request was succesful or not. @@ -245,7 +223,7 @@ pub struct VolumeByCurrency { pub amount: u32, } -/// Result of the export transaction information. +/// Represents the response of the export transaction. #[derive(Debug, Serialize, Deserialize)] pub struct ExportTransactionResponse { /// This lets you know if your request was succesful or not. @@ -263,7 +241,7 @@ pub struct ExportTransactionData { pub path: String, } -/// Result of the partial debit transaction. +/// Represents the response of the partial debit transaction. #[derive(Debug, Deserialize)] pub struct PartialDebitTransactionResponse { /// This lets you know if your request was succesful or not. @@ -273,3 +251,104 @@ pub struct PartialDebitTransactionResponse { /// This contains the results of your request. pub data: TransactionStatusData, } + +/// Represents the JSON response containing percentage split information. +#[derive(Debug, Deserialize, Serialize)] +pub struct TransactionSplitResponse { + /// The status of the JSON response. + pub status: bool, + /// The message associated with the JSON response. + pub message: String, + /// The percentage split data. + pub data: SplitData, +} + +/// Represents the percentage split data received in the JSON response. +#[derive(Debug, Deserialize, Serialize)] +pub struct SplitData { + /// The ID of the percentage split. + pub id: u32, + /// The name of the percentage split. + pub name: String, + /// The type of the percentage split. + #[serde(rename = "type")] + pub split_type: String, + /// The currency used for the percentage split. + pub currency: String, + /// The integration associated with the percentage split. + pub integration: u32, + /// The domain associated with the percentage split. + pub domain: String, + /// The split code of the percentage split. + pub split_code: String, + /// Indicates whether the percentage split is active or not. + pub active: Option, + /// The bearer type of the percentage split. + pub bearer_type: String, + /// The subaccount ID of the bearer associated with the percentage split. + pub bearer_subaccount: u32, + /// The creation timestamp of the percentage split. + pub created_at: String, + /// The last update timestamp of the percentage split. + pub updated_at: String, + /// The list of subaccounts involved in the percentage split. + pub subaccounts: Vec, + /// The total count of subaccounts in the percentage split. + pub total_subaccounts: u32, +} + +/// Represents the data of th Subaccounts +#[derive(Debug, Deserialize, Serialize)] +pub struct SubaccountData { + /// Sub account data + pub subaccount: SubaccountResponse, + /// Share of split assigned to this sub + pub share: u32, +} + +/// Represents a subaccount in the percentage split data. +#[derive(Debug, Deserialize, Serialize)] +pub struct SubaccountResponse { + /// The ID of the subaccount. + pub id: u32, + /// The code of the subaccount. + pub subaccount_code: String, + /// The name of the business associated with the subaccount. + pub business_name: String, + /// The description of the business associated with the subaccount. + pub description: String, + /// The name of the primary contact for the business, if available. + pub primary_contact_name: Option, + /// The email of the primary contact for the business, if available. + pub primary_contact_email: Option, + /// The phone number of the primary contact for the business, if available. + pub primary_contact_phone: Option, + /// Additional metadata associated with the subaccount, if available. + pub metadata: Option, + /// The percentage charge for transactions associated with the subaccount. + pub percentage_charge: u32, + /// The name of the settlement bank for the subaccount. + pub settlement_bank: String, + /// The account number of the subaccount. + pub account_number: String, +} + +/// Represents the JSON response containing percentage split information. +#[derive(Debug, Deserialize, Serialize)] +pub struct TransactionSplitListResponse { + /// The status of the JSON response. + pub status: bool, + /// The message associated with the JSON response. + pub message: String, + /// The percentage split data. + pub data: Vec, +} + +/// Represents the JSON response of the Paystack API when there is no data property +#[derive(Debug, Deserialize, Serialize)] +pub struct ResponseWithoutData { + /// The status of the JSON response. + pub status: bool, + /// The message associated with the JSON response. + pub message: String, +} diff --git a/tests/api/main.rs b/tests/api/main.rs index 271d2b9..76a7f3f 100644 --- a/tests/api/main.rs +++ b/tests/api/main.rs @@ -1,3 +1,4 @@ pub mod charge; pub mod helpers; pub mod transaction; +pub mod transaction_split; diff --git a/tests/api/transaction.rs b/tests/api/transaction.rs index c919d6a..19ec19d 100644 --- a/tests/api/transaction.rs +++ b/tests/api/transaction.rs @@ -92,7 +92,6 @@ async fn valid_transaction_is_verified() { .expect("unable to verify transaction"); // Assert - // println!("{:#?}", response); assert!(response.status); assert_eq!(response.message, "Verification successful"); assert!(response.data.status.is_some()); @@ -173,7 +172,6 @@ async fn view_transaction_timeline_passes_with_id() { .expect("unable to get transaction timeline"); // Assert - // println!("{:#?}", transaction_timeline); assert!(transaction_timeline.status); assert_eq!(transaction_timeline.message, "Timeline retrieved"); } @@ -189,6 +187,8 @@ async fn view_transaction_timeline_passes_with_reference() { .await .expect("unable to get list of integrated transactions"); + // println!("{:#?}", response); + let transaction_timeline = client .view_transaction_timeline(None, response.data[0].reference.clone()) .await diff --git a/tests/api/transaction_split.rs b/tests/api/transaction_split.rs new file mode 100644 index 0000000..5be6405 --- /dev/null +++ b/tests/api/transaction_split.rs @@ -0,0 +1 @@ +// TODO: Test this route only after implementing the subaccounts route for the client