Skip to content

Commit eef29ca

Browse files
committed
added partial debit transaction route
1 parent 7cc78bd commit eef29ca

File tree

7 files changed

+357
-22
lines changed

7 files changed

+357
-22
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[![paystack-rs on crates.io](https://img.shields.io/crates/v/paystack-rs.svg)](https://crates.io/crates/paystack-rs)
55
[![paystack-rs on docs.rs](https://docs.rs/paystack-rs/badge.svg)](https://docs.rs/paystack-rs)
66

7-
Convenient rust bindings and types for the Paystakc HTTP API aiming to support the entire API surface. Not the case? Please open an issue. I update the definitions on a weekly basis.
7+
Convenient **Async** rust bindings and types for the Paystack HTTP API aiming to support the entire API surface. Not the case? Please open an issue. I update the definitions on a weekly basis.
88

99
## Documentation
1010

examples/transaction.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
//! Please see the type definition to understand how it is constructed
1111
1212
use dotenv::dotenv;
13-
use paystack::{Currency, PaystackClient, Status, TransactionBuilder};
13+
use paystack::{Channel, Currency, PaystackClient, Status, TransactionBuilder};
1414
use std::env;
1515

1616
#[tokio::main]
@@ -22,8 +22,9 @@ async fn main() {
2222

2323
let body = TransactionBuilder::new()
2424
.email("email@example.com")
25-
.amount("2000")
25+
.amount("200000")
2626
.currency(Currency::NGN)
27+
.channels(vec![Channel::Qr, Channel::Ussd, Channel::BankTransfer])
2728
.build()
2829
.unwrap();
2930

src/client.rs

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ extern crate reqwest;
66
extern crate serde_json;
77

88
use crate::{
9-
Charge, PaystackResult, RequestNotSuccessful, Status, Transaction, TransactionResponse,
10-
TransactionStatus, TransactionStatusList, TransactionTimeline, TransactionTotalsResponse,
9+
Charge, Currency, ExportTransactionResponse, PartialDebitTransaction, PaystackResult,
10+
RequestNotSuccessful, Status, Transaction, TransactionResponse, TransactionStatus,
11+
TransactionStatusList, TransactionTimeline, TransactionTotalsResponse,
1112
};
1213

1314
static BASE_URL: &str = "https://api.paystack.co";
@@ -90,7 +91,7 @@ impl PaystackClient {
9091
///
9192
/// The method takes the following parameters:
9293
/// - perPage (Optional): Number of transactions to return. If None is passed as the parameter, the last 10 transactions are returned.
93-
/// - status (Optional): Filter transactions by status (`failed`, `success`, `abandoned`).
94+
/// - status (Optional): Filter transactions by status, defaults to Success if no status is passed.
9495
///
9596
pub async fn list_transactions(
9697
&self,
@@ -100,7 +101,7 @@ impl PaystackClient {
100101
let url = format!("{}/transaction", BASE_URL);
101102
let query = vec![
102103
("perPage", number_of_transactions.unwrap_or(10).to_string()),
103-
("status", status.unwrap().to_string()),
104+
("status", status.unwrap_or(Status::Abandoned).to_string()),
104105
];
105106

106107
let response = self
@@ -238,10 +239,87 @@ impl PaystackClient {
238239
return Err(
239240
RequestNotSuccessful::new(response.status(), response.text().await?).into(),
240241
);
241-
}
242+
};
242243

243244
let content = response.json::<TransactionTotalsResponse>().await?;
244245

245246
Ok(content)
246247
}
248+
249+
/// Export a list of transactions carried out on your integration
250+
///
251+
/// This method takes the following parameters
252+
/// - Status (Optional): The status of the transactions to export. Defaults to all
253+
/// - Currency (Optional): The currency of the transactions to export. Defaults to NGN
254+
/// - Settled (Optional): To state of the transactions to export. Defaults to False.
255+
pub async fn export_transaction(
256+
&self,
257+
status: Option<Status>,
258+
currency: Option<Currency>,
259+
settled: Option<bool>,
260+
) -> PaystackResult<ExportTransactionResponse> {
261+
let url = format!("{}/transaction/export", BASE_URL);
262+
263+
// Specify a default option for settled transactions.
264+
let settled = match settled {
265+
Some(settled) => settled.to_string(),
266+
None => String::from(""),
267+
};
268+
269+
let query = vec![
270+
("status", status.unwrap_or(Status::Success).to_string()),
271+
("currency", currency.unwrap_or(Currency::EMPTY).to_string()),
272+
("settled", settled),
273+
];
274+
275+
let response = self
276+
.client
277+
.get(url)
278+
.query(&query)
279+
.bearer_auth(&self.api_key)
280+
.header("Content-Type", "application/json")
281+
.send()
282+
.await?;
283+
284+
if response.error_for_status_ref().is_err() {
285+
return Err(
286+
RequestNotSuccessful::new(response.status(), response.text().await?).into(),
287+
);
288+
};
289+
290+
let content = response.json::<ExportTransactionResponse>().await?;
291+
292+
Ok(content)
293+
}
294+
295+
/// Retrieve part of a payment from a customer.
296+
///
297+
/// It takes a PartialDebitTransaction type as a parameter.
298+
///
299+
/// NB: it must be created with the PartialDebitTransaction Builder.
300+
pub async fn partial_debit(
301+
&self,
302+
transaction_body: PartialDebitTransaction,
303+
) -> PaystackResult<TransactionStatus> {
304+
let url = format!("{}/transaction/partial_debit", BASE_URL);
305+
306+
let response = self
307+
.client
308+
.post(url)
309+
.bearer_auth(&self.api_key)
310+
.header("Content-Type", "application/json")
311+
.json(&transaction_body)
312+
.send()
313+
.await?;
314+
315+
if response.error_for_status_ref().is_err() {
316+
return Err(
317+
RequestNotSuccessful::new(response.status(), response.text().await?).into(),
318+
);
319+
}
320+
321+
let content = response.json::<TransactionStatus>().await?;
322+
323+
Ok(content)
324+
}
247325
}

src/resources/paystack_enums.rs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,21 @@ pub enum Currency {
1717
USD,
1818
/// South African Rands
1919
ZAR,
20+
/// Used when currency can be empty.
21+
EMPTY,
22+
}
23+
24+
impl fmt::Display for Currency {
25+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
26+
let currency = match self {
27+
Currency::NGN => "NGN",
28+
Currency::GHS => "GHS",
29+
Currency::USD => "USD",
30+
Currency::ZAR => "ZAR",
31+
Currency::EMPTY => "",
32+
};
33+
write!(f, "{}", currency)
34+
}
2035
}
2136

2237
#[derive(Debug, Serialize, Deserialize, Clone)]
@@ -35,13 +50,13 @@ pub enum Channel {
3550
MobileMoney,
3651
/// Payment with Bank Transfer
3752
BankTransfer,
53+
/// Payment with Apple Pay
54+
ApplePay,
3855
}
3956

4057
impl fmt::Display for Channel {
4158
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
4259
write!(f, "{:?}", self)
43-
// or, alternatively:
44-
// fmt::Debug::fmt(self, f)
4560
}
4661
}
4762

@@ -52,13 +67,18 @@ pub enum Status {
5267
/// A successful transaction.
5368
Success,
5469
/// An abadoned transaction.
55-
Abadoned,
56-
/// A failed transaction
70+
Abandoned,
71+
/// A failed transaction.
5772
Failed,
5873
}
5974

6075
impl fmt::Display for Status {
61-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
62-
fmt::Debug::fmt(self, f)
76+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77+
let lowercase_string = match self {
78+
Status::Success => "success",
79+
Status::Abandoned => "abandoned",
80+
Status::Failed => "failed",
81+
};
82+
write!(f, "{}", lowercase_string)
6383
}
6484
}

src/resources/transaction.rs

Lines changed: 127 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
//! This file contains all the structs and definitions needed to
44
//! create a transaction using the paystack API.
55
6-
use crate::{error, Currency, PaystackResult};
6+
use crate::error::PaystackError;
7+
use crate::{Channel, Currency, PaystackResult};
78
use serde::Serialize;
89

910
/// This struct is used to create a transaction body for creating a transaction using the Paystack API.
@@ -27,6 +28,7 @@ pub struct TransactionBuilder {
2728
amount: Option<String>,
2829
email: Option<String>,
2930
currency: Option<Currency>,
31+
channels: Option<Vec<Channel>>,
3032
}
3133

3234
impl TransactionBuilder {
@@ -35,7 +37,7 @@ impl TransactionBuilder {
3537
TransactionBuilder::default()
3638
}
3739

38-
/// Specify the Transaction email
40+
/// Specify email for the Transaction
3941
pub fn email(mut self, email: impl Into<String>) -> Self {
4042
self.email = Some(email.into());
4143
self
@@ -53,14 +55,24 @@ impl TransactionBuilder {
5355
self
5456
}
5557

58+
/// Specify the Tranaction channels
59+
pub fn channels(mut self, channels: Vec<Channel>) -> Self {
60+
self.channels = Some(channels);
61+
self
62+
}
63+
5664
/// Build the Transaction object
5765
pub fn build(self) -> PaystackResult<Transaction> {
5866
let Some(email) = self.email else {
59-
return Err(error::PaystackError::Transaction("email is required for transaction".to_string()))
67+
return Err(
68+
PaystackError::Transaction("email is required for transaction".to_string())
69+
)
6070
};
6171

6272
let Some(amount) = self.amount else {
63-
return Err(error::PaystackError::Transaction("amount is required for transaction".to_string()))
73+
return Err(
74+
PaystackError::Transaction("amount is required for transaction".to_string())
75+
)
6476
};
6577

6678
Ok(Transaction {
@@ -70,3 +82,114 @@ impl TransactionBuilder {
7082
})
7183
}
7284
}
85+
86+
/// This struct is used to create a partial debit transaction body for creating a partial debit using the Paystack API.
87+
///
88+
/// IMPORTANT: This class can only be created using the PartialDebitTransactionBuilder.
89+
///
90+
/// The struct has the following fields:
91+
/// - authorization_code: Authorization Code for the transaction
92+
/// - amount: Amount should be in the smallest unit of the currency e.g. kobo if in NGN and cents if in USD
93+
/// - email: Customer's email address
94+
/// - currency : Currency in which amount should be charged (NGN, GHS, ZAR or USD). Defaults to your integration currency.
95+
/// - reference (Optional): Unique transaction reference.
96+
/// - at_least: Minimum amount to charge
97+
#[derive(Debug, Clone, Serialize)]
98+
pub struct PartialDebitTransaction {
99+
authorization_code: String,
100+
amount: String,
101+
email: String,
102+
currency: Currency,
103+
reference: Option<String>,
104+
at_least: Option<String>,
105+
}
106+
107+
/// Builder for the Transaction object
108+
#[derive(Default, Clone)]
109+
pub struct PartialDebitTransactionBuilder {
110+
authorization_code: Option<String>,
111+
amount: Option<String>,
112+
email: Option<String>,
113+
currency: Option<Currency>,
114+
reference: Option<String>,
115+
at_least: Option<String>,
116+
}
117+
118+
impl PartialDebitTransactionBuilder {
119+
/// Create new instance of the Partial Debit Transaction builder with default properties.
120+
pub fn new() -> Self {
121+
PartialDebitTransactionBuilder::default()
122+
}
123+
124+
/// Specify the authorization code.
125+
pub fn authorization_code(mut self, authorization_code: impl Into<String>) -> Self {
126+
self.authorization_code = Some(authorization_code.into());
127+
self
128+
}
129+
130+
/// Specify the transaction amount.
131+
pub fn amount(mut self, amount: impl Into<String>) -> Self {
132+
self.amount = Some(amount.into());
133+
self
134+
}
135+
136+
/// Specify email for the Transaction.
137+
pub fn email(mut self, email: impl Into<String>) -> Self {
138+
self.email = Some(email.into());
139+
self
140+
}
141+
142+
/// Specify transaction currency.
143+
pub fn currency(mut self, currency: Currency) -> Self {
144+
self.currency = Some(currency);
145+
self
146+
}
147+
148+
/// Specify the transaction reference.
149+
pub fn reference(mut self, reference: impl Into<String>) -> Self {
150+
self.reference = Some(reference.into());
151+
self
152+
}
153+
154+
/// Specify the minimum amount to charge for the transaction.
155+
pub fn at_least(mut self, at_least: impl Into<String>) -> Self {
156+
self.at_least = Some(at_least.into());
157+
self
158+
}
159+
160+
/// Build the PartialDebitTransaction object
161+
pub fn build(self) -> PaystackResult<PartialDebitTransaction> {
162+
let Some(authorization_code) = self.authorization_code else {
163+
return Err(
164+
PaystackError::Transaction("authorization code is required for partial debit transaction".to_string())
165+
)
166+
};
167+
168+
let Some(email) = self.email else {
169+
return Err(
170+
PaystackError::Transaction("email is required for transaction".to_string())
171+
)
172+
};
173+
174+
let Some(currency) = self.currency else {
175+
return Err(
176+
PaystackError::Transaction("currency is required for transaction".to_string())
177+
)
178+
};
179+
180+
let Some(amount) = self.amount else {
181+
return Err(
182+
PaystackError::Transaction("amount is required for transaction".to_string())
183+
)
184+
};
185+
186+
Ok(PartialDebitTransaction {
187+
authorization_code,
188+
email,
189+
amount,
190+
currency,
191+
reference: self.reference,
192+
at_least: self.at_least,
193+
})
194+
}
195+
}

0 commit comments

Comments
 (0)