Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The client currently covers the following section of the API, and the sections t
- [x] Dedicated Virtual Account
- [x] Apple Pay
- [x] Subaccounts
- [ ] Plans
- [x] Plans
- [ ] Subscriptions
- [ ] Transfer Recipients
- [ ] Transfers
Expand Down
7 changes: 5 additions & 2 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
//! This file contains the Paystack API client, and it associated endpoints.
use crate::{
ApplePayEndpoints, CustomersEndpoints, DedicatedVirtualAccountEndpoints, HttpClient,
SubaccountEndpoints, TerminalEndpoints, TransactionEndpoints, TransactionSplitEndpoints,
VirtualTerminalEndpoints,
PlansEndpoints, SubaccountEndpoints, TerminalEndpoints, TransactionEndpoints,
TransactionSplitEndpoints, VirtualTerminalEndpoints,
};
use std::sync::Arc;

Expand All @@ -27,6 +27,8 @@ pub struct PaystackClient<T: HttpClient + Default> {
pub dedicated_virtual_account: DedicatedVirtualAccountEndpoints<T>,
/// Apple Pay API route
pub apple_pay: ApplePayEndpoints<T>,
/// Plans API route
pub plans: PlansEndpoints<T>,
}

impl<T: HttpClient + Default> PaystackClient<T> {
Expand All @@ -45,6 +47,7 @@ impl<T: HttpClient + Default> PaystackClient<T> {
Arc::clone(&http),
),
apple_pay: ApplePayEndpoints::new(Arc::clone(&key), Arc::clone(&http)),
plans: PlansEndpoints::new(Arc::clone(&key), Arc::clone(&http)),
}
}
}
2 changes: 2 additions & 0 deletions src/endpoints/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod apple_pay;
pub mod customers;
pub mod dedicated_virtual_account;
pub mod plans;
pub mod subaccount;
pub mod terminal;
pub mod transaction;
Expand All @@ -11,6 +12,7 @@ pub mod virtual_terminal;
pub use apple_pay::*;
pub use customers::*;
pub use dedicated_virtual_account::*;
pub use plans::*;
pub use subaccount::*;
pub use terminal::*;
pub use transaction::*;
Expand Down
167 changes: 167 additions & 0 deletions src/endpoints/plans.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
use std::{marker::PhantomData, sync::Arc};

use super::PAYSTACK_BASE_URL;
use crate::{
HttpClient, Interval, PaystackAPIError, PaystackResult, PlanRequest, PlanResponseData,
PlanStatus, PlanUpdateRequest, Response,
};

pub struct PlansEndpoints<T: HttpClient + Default> {
/// Paystack API Key
key: String,
/// Base URL for the plans route
base_url: String,
/// Http client for the route
http: Arc<T>,
}

/// Create a new `PlansEndpoints<T>` instance
///
/// # Arguments
/// - `key` - The Paystack API key
/// - `http`: The HTTP client implementation to use for the API requests
///
/// # Returns
/// A new PlansEndpoints instance
impl<T: HttpClient + Default> PlansEndpoints<T> {
pub fn new(key: Arc<String>, http: Arc<T>) -> PlansEndpoints<T> {
let base_url = format!("{PAYSTACK_BASE_URL}/plan");
PlansEndpoints {
key: key.to_string(),
base_url,
http,
}
}

/// Create a plan on your integration
///
/// # Arguments
/// * `plan_request` - The request data to create the plan.
/// Should be created with a `PlanRequestBuilder` struct.
///
/// # Returns
/// A Result containing the plan response data or an error
pub async fn create_plan(&self, plan_request: PlanRequest) -> PaystackResult<PlanResponseData> {
let url = &self.base_url;
let body = serde_json::to_value(plan_request)
.map_err(|e| PaystackAPIError::Plan(e.to_string()))?;

let response = self
.http
.post(url, &self.key, &body)
.await
.map_err(|e| PaystackAPIError::Plan(e.to_string()))?;

let parsed_response: Response<PlanResponseData> =
serde_json::from_str(&response).map_err(|e| PaystackAPIError::Plan(e.to_string()))?;

Ok(parsed_response)
}

/// Lists plans available in your integration
///
/// # Arguments
/// * `per_page` - specify how many records you want to retrieve per page. Defaults to 50 if None
/// * `page` - specify exactly what page you want to retrieve. Defaults to 1 if None
/// * `status` - Optional parameter to filter list by plans with specified status
/// * `interval` - Optional parameter to filter list by plans with specified interval
/// * `amount`- Optional parameter to filter list by plans with specified amount using the supported currency
///
/// # Returns
/// A Result containing a vector of plan response data or an error
pub async fn list_plans(
&self,
per_page: Option<u8>,
page: Option<u8>,
status: Option<PlanStatus>,
interval: Option<Interval>,
amount: Option<u32>,
) -> PaystackResult<Vec<PlanResponseData>> {
let url = &self.base_url;

let per_page = per_page.unwrap_or(50).to_string();
let page = page.unwrap_or(1).to_string();

let mut query = vec![("perPage", per_page), ("page", page)];

// Process optional parameters
if let Some(s) = status {
query.push(("status", s.to_string()));
}

if let Some(i) = interval {
query.push(("interval", i.to_string()));
}

if let Some(a) = amount {
query.push(("amount", a.to_string()));
}

// convert all string to &str
// TODO: there has to be a cleaner way of doing this
let query: Vec<(&str, &str)> = query.iter().map(|(k, v)| (*k, v.as_str())).collect();

let response = self
.http
.get(url, &self.key, Some(&query))
.await
.map_err(|e| PaystackAPIError::Plan(e.to_string()))?;

let parsed_response: Response<Vec<PlanResponseData>> =
serde_json::from_str(&response).map_err(|e| PaystackAPIError::Plan(e.to_string()))?;

Ok(parsed_response)
}

/// Get details of a plan on your integration
///
/// # Arguments
/// * `id_or_code` - the plan `ID` or `code` you want to fetch
///
/// # Returns
/// A Result containing the plan response data or an error
pub async fn fetch_plan(&self, id_or_code: String) -> PaystackResult<PlanResponseData> {
let url = format!("{}/{}", &self.base_url, id_or_code);

let response = self
.http
.get(&url, &self.key, None)
.await
.map_err(|e| PaystackAPIError::Plan(e.to_string()))?;

let parsed_response: Response<PlanResponseData> =
serde_json::from_str(&response).map_err(|e| PaystackAPIError::Plan(e.to_string()))?;

Ok(parsed_response)
}

/// Update a plan details on your integration
///
/// # Arguments
/// * `id_or_code` - the plan `ID` or `code` you want to update
/// * `plan_update_request` - The request data to update the plan with.
/// Should be created with a `PlanUpdateRequestBuilder` struct.
///
/// # Returns
/// A Result containing a success message if the plan has been updated
pub async fn update_plan(
&self,
id_or_code: String,
plan_update_request: PlanUpdateRequest,
) -> PaystackResult<PhantomData<String>> {
let url = format!("{}/{}", self.base_url, id_or_code);
let body = serde_json::to_value(plan_update_request)
.map_err(|e| PaystackAPIError::Plan(e.to_string()))?;

let response = self
.http
.put(&url, &self.key, &body)
.await
.map_err(|e| PaystackAPIError::Plan(e.to_string()))?;

let parsed_response: Response<PhantomData<String>> =
serde_json::from_str(&response).map_err(|e| PaystackAPIError::Plan(e.to_string()))?;

Ok(parsed_response)
}
}
2 changes: 1 addition & 1 deletion src/endpoints/subaccount.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use std::sync::Arc;
pub struct SubaccountEndpoints<T: HttpClient + Default> {
/// Paystack API Key
key: String,
/// Base URL for the transaction route
/// Base URL for the subaccount route
base_url: String,
/// Http client for the route
http: Arc<T>,
Expand Down
2 changes: 1 addition & 1 deletion src/endpoints/terminal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use super::PAYSTACK_BASE_URL;
pub struct TerminalEndpoints<T: HttpClient + Default> {
/// Paystack API Key
key: String,
/// Base URL for the transaction route
/// Base URL for the terminal route
base_url: String,
/// Http client for the route
http: Arc<T>,
Expand Down
2 changes: 1 addition & 1 deletion src/endpoints/transaction_split.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use std::sync::Arc;
pub struct TransactionSplitEndpoints<T: HttpClient + Default> {
/// Paystack API Key
key: String,
/// Base URL for the transaction route
/// Base URL for the transaction split route
base_url: String,
/// Http client for the route
http: Arc<T>,
Expand Down
2 changes: 1 addition & 1 deletion src/endpoints/virtual_terminal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use std::{marker::PhantomData, sync::Arc};
pub struct VirtualTerminalEndpoints<T: HttpClient + Default> {
/// Paystack API key
key: String,
/// Base URL for the transaction route
/// Base URL for the virtual terminal route
base_url: String,
/// Http client for the route
http: Arc<T>,
Expand Down
2 changes: 2 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,6 @@ pub enum PaystackAPIError {
DedicatedVirtualAccount(String),
#[error("Apple Pay Error: {0}")]
ApplePay(String),
#[error("Plan Error: {0}")]
Plan(String),
}
10 changes: 10 additions & 0 deletions src/models/currency_models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ use std::fmt;
/// - `GHS`: Ghanaian Cedis.
/// - `USD`: American Dollar.
/// - `ZAR`: South African Rands.
/// - `KES`: Kenya Shilling.
/// - `XOF`: West African CFA Franc.
/// - `EMPTY`: Used when the currency can be empty.
///
/// # Examples
Expand All @@ -29,6 +31,8 @@ use std::fmt;
/// let ghs = Currency::GHS;
/// let usd = Currency::USD;
/// let zar = Currency::ZAR;
/// let kes = Currency::KES;
/// let xof = Currency::XOF;
/// let empty = Currency::EMPTY;
///
/// println!("{:?}", ngn); // Prints: NGN
Expand All @@ -47,6 +51,10 @@ pub enum Currency {
USD,
/// South African Rands
ZAR,
/// Kenya Shilling
KES,
/// West African CFA Franc
XOF,
/// Used when currency can be empty.
EMPTY,
}
Expand All @@ -58,6 +66,8 @@ impl fmt::Display for Currency {
Currency::GHS => "GHS",
Currency::USD => "USD",
Currency::ZAR => "ZAR",
Currency::KES => "KES",
Currency::XOF => "XOF",
Currency::EMPTY => "",
};
write!(f, "{currency}")
Expand Down
4 changes: 3 additions & 1 deletion src/models/customer_models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ use std::fmt;
use derive_builder::Builder;
use serde::{Deserialize, Serialize};

use crate::Domain;

use super::{Authorization, Subscription, TransactionStatusData};

/// This struct represents the Paystack customer data
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
pub struct CustomerResponseData {
pub id: u64,
pub integration: Option<u64>,
pub domain: Option<String>,
pub domain: Option<Domain>,
pub identified: Option<bool>,
pub first_name: Option<String>,
pub last_name: Option<String>,
Expand Down
29 changes: 29 additions & 0 deletions src/models/domain_models.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//! Domain
//! ======
//! This file constians the domain options for the integration in the paystack API.

use std::fmt;

use serde::{Deserialize, Serialize};

/// An enum of options for the paystack integration domain
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[serde(rename_all = "lowercase")]
pub enum Domain {
/// Integration in the test environment
// Defaulting to test here for less danger
#[default]
Test,
/// Integration in the live environment
Live,
}

impl fmt::Display for Domain {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let domain = match self {
Domain::Test => "test",
Domain::Live => "live",
};
write!(f, "{domain}")
}
}
4 changes: 4 additions & 0 deletions src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ pub mod charge_models;
pub mod currency_models;
pub mod customer_models;
pub mod dedicated_virtual_account_models;
pub mod domain_models;
pub mod plans_models;
pub mod response_models;
pub mod split_models;
pub mod status_models;
Expand All @@ -25,6 +27,8 @@ pub use charge_models::*;
pub use currency_models::*;
pub use customer_models::*;
pub use dedicated_virtual_account_models::*;
pub use domain_models::*;
pub use plans_models::*;
pub use response_models::*;
pub use split_models::*;
pub use status_models::*;
Expand Down
Loading