Skip to content

Commit 0669cb9

Browse files
authored
Merge pull request #71 from morukele/feature/plans
feat(plan): added support for the plan endpoint.
2 parents 96db024 + df7b840 commit 0669cb9

23 files changed

Lines changed: 784 additions & 16 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ The client currently covers the following section of the API, and the sections t
2121
- [x] Dedicated Virtual Account
2222
- [x] Apple Pay
2323
- [x] Subaccounts
24-
- [ ] Plans
24+
- [x] Plans
2525
- [ ] Subscriptions
2626
- [ ] Transfer Recipients
2727
- [ ] Transfers

src/client.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
//! This file contains the Paystack API client, and it associated endpoints.
44
use crate::{
55
ApplePayEndpoints, CustomersEndpoints, DedicatedVirtualAccountEndpoints, HttpClient,
6-
SubaccountEndpoints, TerminalEndpoints, TransactionEndpoints, TransactionSplitEndpoints,
7-
VirtualTerminalEndpoints,
6+
PlansEndpoints, SubaccountEndpoints, TerminalEndpoints, TransactionEndpoints,
7+
TransactionSplitEndpoints, VirtualTerminalEndpoints,
88
};
99
use std::sync::Arc;
1010

@@ -27,6 +27,8 @@ pub struct PaystackClient<T: HttpClient + Default> {
2727
pub dedicated_virtual_account: DedicatedVirtualAccountEndpoints<T>,
2828
/// Apple Pay API route
2929
pub apple_pay: ApplePayEndpoints<T>,
30+
/// Plans API route
31+
pub plans: PlansEndpoints<T>,
3032
}
3133

3234
impl<T: HttpClient + Default> PaystackClient<T> {
@@ -45,6 +47,7 @@ impl<T: HttpClient + Default> PaystackClient<T> {
4547
Arc::clone(&http),
4648
),
4749
apple_pay: ApplePayEndpoints::new(Arc::clone(&key), Arc::clone(&http)),
50+
plans: PlansEndpoints::new(Arc::clone(&key), Arc::clone(&http)),
4851
}
4952
}
5053
}

src/endpoints/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub mod apple_pay;
22
pub mod customers;
33
pub mod dedicated_virtual_account;
4+
pub mod plans;
45
pub mod subaccount;
56
pub mod terminal;
67
pub mod transaction;
@@ -11,6 +12,7 @@ pub mod virtual_terminal;
1112
pub use apple_pay::*;
1213
pub use customers::*;
1314
pub use dedicated_virtual_account::*;
15+
pub use plans::*;
1416
pub use subaccount::*;
1517
pub use terminal::*;
1618
pub use transaction::*;

src/endpoints/plans.rs

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
use std::{marker::PhantomData, sync::Arc};
2+
3+
use super::PAYSTACK_BASE_URL;
4+
use crate::{
5+
HttpClient, Interval, PaystackAPIError, PaystackResult, PlanRequest, PlanResponseData,
6+
PlanStatus, PlanUpdateRequest, Response,
7+
};
8+
9+
pub struct PlansEndpoints<T: HttpClient + Default> {
10+
/// Paystack API Key
11+
key: String,
12+
/// Base URL for the plans route
13+
base_url: String,
14+
/// Http client for the route
15+
http: Arc<T>,
16+
}
17+
18+
/// Create a new `PlansEndpoints<T>` instance
19+
///
20+
/// # Arguments
21+
/// - `key` - The Paystack API key
22+
/// - `http`: The HTTP client implementation to use for the API requests
23+
///
24+
/// # Returns
25+
/// A new PlansEndpoints instance
26+
impl<T: HttpClient + Default> PlansEndpoints<T> {
27+
pub fn new(key: Arc<String>, http: Arc<T>) -> PlansEndpoints<T> {
28+
let base_url = format!("{PAYSTACK_BASE_URL}/plan");
29+
PlansEndpoints {
30+
key: key.to_string(),
31+
base_url,
32+
http,
33+
}
34+
}
35+
36+
/// Create a plan on your integration
37+
///
38+
/// # Arguments
39+
/// * `plan_request` - The request data to create the plan.
40+
/// Should be created with a `PlanRequestBuilder` struct.
41+
///
42+
/// # Returns
43+
/// A Result containing the plan response data or an error
44+
pub async fn create_plan(&self, plan_request: PlanRequest) -> PaystackResult<PlanResponseData> {
45+
let url = &self.base_url;
46+
let body = serde_json::to_value(plan_request)
47+
.map_err(|e| PaystackAPIError::Plan(e.to_string()))?;
48+
49+
let response = self
50+
.http
51+
.post(url, &self.key, &body)
52+
.await
53+
.map_err(|e| PaystackAPIError::Plan(e.to_string()))?;
54+
55+
let parsed_response: Response<PlanResponseData> =
56+
serde_json::from_str(&response).map_err(|e| PaystackAPIError::Plan(e.to_string()))?;
57+
58+
Ok(parsed_response)
59+
}
60+
61+
/// Lists plans available in your integration
62+
///
63+
/// # Arguments
64+
/// * `per_page` - specify how many records you want to retrieve per page. Defaults to 50 if None
65+
/// * `page` - specify exactly what page you want to retrieve. Defaults to 1 if None
66+
/// * `status` - Optional parameter to filter list by plans with specified status
67+
/// * `interval` - Optional parameter to filter list by plans with specified interval
68+
/// * `amount`- Optional parameter to filter list by plans with specified amount using the supported currency
69+
///
70+
/// # Returns
71+
/// A Result containing a vector of plan response data or an error
72+
pub async fn list_plans(
73+
&self,
74+
per_page: Option<u8>,
75+
page: Option<u8>,
76+
status: Option<PlanStatus>,
77+
interval: Option<Interval>,
78+
amount: Option<u32>,
79+
) -> PaystackResult<Vec<PlanResponseData>> {
80+
let url = &self.base_url;
81+
82+
let per_page = per_page.unwrap_or(50).to_string();
83+
let page = page.unwrap_or(1).to_string();
84+
85+
let mut query = vec![("perPage", per_page), ("page", page)];
86+
87+
// Process optional parameters
88+
if let Some(s) = status {
89+
query.push(("status", s.to_string()));
90+
}
91+
92+
if let Some(i) = interval {
93+
query.push(("interval", i.to_string()));
94+
}
95+
96+
if let Some(a) = amount {
97+
query.push(("amount", a.to_string()));
98+
}
99+
100+
// convert all string to &str
101+
// TODO: there has to be a cleaner way of doing this
102+
let query: Vec<(&str, &str)> = query.iter().map(|(k, v)| (*k, v.as_str())).collect();
103+
104+
let response = self
105+
.http
106+
.get(url, &self.key, Some(&query))
107+
.await
108+
.map_err(|e| PaystackAPIError::Plan(e.to_string()))?;
109+
110+
let parsed_response: Response<Vec<PlanResponseData>> =
111+
serde_json::from_str(&response).map_err(|e| PaystackAPIError::Plan(e.to_string()))?;
112+
113+
Ok(parsed_response)
114+
}
115+
116+
/// Get details of a plan on your integration
117+
///
118+
/// # Arguments
119+
/// * `id_or_code` - the plan `ID` or `code` you want to fetch
120+
///
121+
/// # Returns
122+
/// A Result containing the plan response data or an error
123+
pub async fn fetch_plan(&self, id_or_code: String) -> PaystackResult<PlanResponseData> {
124+
let url = format!("{}/{}", &self.base_url, id_or_code);
125+
126+
let response = self
127+
.http
128+
.get(&url, &self.key, None)
129+
.await
130+
.map_err(|e| PaystackAPIError::Plan(e.to_string()))?;
131+
132+
let parsed_response: Response<PlanResponseData> =
133+
serde_json::from_str(&response).map_err(|e| PaystackAPIError::Plan(e.to_string()))?;
134+
135+
Ok(parsed_response)
136+
}
137+
138+
/// Update a plan details on your integration
139+
///
140+
/// # Arguments
141+
/// * `id_or_code` - the plan `ID` or `code` you want to update
142+
/// * `plan_update_request` - The request data to update the plan with.
143+
/// Should be created with a `PlanUpdateRequestBuilder` struct.
144+
///
145+
/// # Returns
146+
/// A Result containing a success message if the plan has been updated
147+
pub async fn update_plan(
148+
&self,
149+
id_or_code: String,
150+
plan_update_request: PlanUpdateRequest,
151+
) -> PaystackResult<PhantomData<String>> {
152+
let url = format!("{}/{}", self.base_url, id_or_code);
153+
let body = serde_json::to_value(plan_update_request)
154+
.map_err(|e| PaystackAPIError::Plan(e.to_string()))?;
155+
156+
let response = self
157+
.http
158+
.put(&url, &self.key, &body)
159+
.await
160+
.map_err(|e| PaystackAPIError::Plan(e.to_string()))?;
161+
162+
let parsed_response: Response<PhantomData<String>> =
163+
serde_json::from_str(&response).map_err(|e| PaystackAPIError::Plan(e.to_string()))?;
164+
165+
Ok(parsed_response)
166+
}
167+
}

src/endpoints/subaccount.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use std::sync::Arc;
1515
pub struct SubaccountEndpoints<T: HttpClient + Default> {
1616
/// Paystack API Key
1717
key: String,
18-
/// Base URL for the transaction route
18+
/// Base URL for the subaccount route
1919
base_url: String,
2020
/// Http client for the route
2121
http: Arc<T>,

src/endpoints/terminal.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use super::PAYSTACK_BASE_URL;
1616
pub struct TerminalEndpoints<T: HttpClient + Default> {
1717
/// Paystack API Key
1818
key: String,
19-
/// Base URL for the transaction route
19+
/// Base URL for the terminal route
2020
base_url: String,
2121
/// Http client for the route
2222
http: Arc<T>,

src/endpoints/transaction_split.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use std::sync::Arc;
1515
pub struct TransactionSplitEndpoints<T: HttpClient + Default> {
1616
/// Paystack API Key
1717
key: String,
18-
/// Base URL for the transaction route
18+
/// Base URL for the transaction split route
1919
base_url: String,
2020
/// Http client for the route
2121
http: Arc<T>,

src/endpoints/virtual_terminal.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use std::{marker::PhantomData, sync::Arc};
1515
pub struct VirtualTerminalEndpoints<T: HttpClient + Default> {
1616
/// Paystack API key
1717
key: String,
18-
/// Base URL for the transaction route
18+
/// Base URL for the virtual terminal route
1919
base_url: String,
2020
/// Http client for the route
2121
http: Arc<T>,

src/errors.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,6 @@ pub enum PaystackAPIError {
3535
DedicatedVirtualAccount(String),
3636
#[error("Apple Pay Error: {0}")]
3737
ApplePay(String),
38+
#[error("Plan Error: {0}")]
39+
Plan(String),
3840
}

src/models/currency_models.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ use std::fmt;
1818
/// - `GHS`: Ghanaian Cedis.
1919
/// - `USD`: American Dollar.
2020
/// - `ZAR`: South African Rands.
21+
/// - `KES`: Kenya Shilling.
22+
/// - `XOF`: West African CFA Franc.
2123
/// - `EMPTY`: Used when the currency can be empty.
2224
///
2325
/// # Examples
@@ -29,6 +31,8 @@ use std::fmt;
2931
/// let ghs = Currency::GHS;
3032
/// let usd = Currency::USD;
3133
/// let zar = Currency::ZAR;
34+
/// let kes = Currency::KES;
35+
/// let xof = Currency::XOF;
3236
/// let empty = Currency::EMPTY;
3337
///
3438
/// println!("{:?}", ngn); // Prints: NGN
@@ -47,6 +51,10 @@ pub enum Currency {
4751
USD,
4852
/// South African Rands
4953
ZAR,
54+
/// Kenya Shilling
55+
KES,
56+
/// West African CFA Franc
57+
XOF,
5058
/// Used when currency can be empty.
5159
EMPTY,
5260
}
@@ -58,6 +66,8 @@ impl fmt::Display for Currency {
5866
Currency::GHS => "GHS",
5967
Currency::USD => "USD",
6068
Currency::ZAR => "ZAR",
69+
Currency::KES => "KES",
70+
Currency::XOF => "XOF",
6171
Currency::EMPTY => "",
6272
};
6373
write!(f, "{currency}")

0 commit comments

Comments
 (0)