extern crate reqwest;
extern crate serde_json;
use std::fmt::Debug;
use reqwest::{Response, StatusCode};
use serde::Serialize;
use crate::{
Charge, Currency, ExportTransactionResponse, PartialDebitTransaction, PaystackError,
PaystackResult, RequestNotSuccessful, ResponseWithoutData, Status, Subaccount, Transaction,
TransactionResponse, TransactionSplit, TransactionSplitListResponse, TransactionSplitResponse,
TransactionStatus, TransactionStatusList, TransactionTimeline, TransactionTotalsResponse,
};
static BASE_URL: &str = "https://api.paystack.co";
#[derive(Clone, Debug)]
pub struct PaystackClient {
client: reqwest::Client,
api_key: String,
}
impl PaystackClient {
pub fn new(key: impl Into<String>) -> Self {
Self {
client: reqwest::Client::new(),
api_key: key.into(),
}
}
async fn get_request(
&self,
url: &String,
query: Option<Vec<(&str, String)>>,
) -> PaystackResult<Response> {
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())),
}
}
async fn post_request<T>(&self, url: &String, body: T) -> PaystackResult<Response>
where
T: Debug + Serialize,
{
let response = self
.client
.post(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())),
}
}
async fn put_request<T>(&self, url: &String, body: T) -> PaystackResult<Response>
where
T: Debug + Serialize,
{
let response = self
.client
.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())),
}
}
async fn _delete_request<T>(&self, url: &String, body: T) -> PaystackResult<Response>
where
T: Debug + Serialize,
{
let response = self
.client
.delete(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())),
}
}
pub async fn initialize_transaction(
&self,
transaction_body: Transaction,
) -> PaystackResult<TransactionResponse> {
let url = format!("{}/transaction/initialize", BASE_URL);
match self.post_request(&url, transaction_body).await {
Ok(response) => match response.status() {
reqwest::StatusCode::OK => match response.json::<TransactionResponse>().await {
Ok(content) => Ok(content),
Err(err) => Err(PaystackError::Transaction(err.to_string())),
},
_ => {
Err(RequestNotSuccessful::new(response.status(), response.text().await?).into())
}
},
Err(err) => Err(err),
}
}
pub async fn verify_transaction(&self, reference: String) -> PaystackResult<TransactionStatus> {
let url = format!("{}/transaction/verify/{}", BASE_URL, reference);
match self.get_request(&url, None).await {
Ok(response) => match response.status() {
reqwest::StatusCode::OK => match response.json::<TransactionStatus>().await {
Ok(content) => Ok(content),
Err(err) => Err(PaystackError::Transaction(err.to_string())),
},
_ => {
Err(RequestNotSuccessful::new(response.status(), response.text().await?).into())
}
},
Err(err) => Err(err),
}
}
pub async fn list_transactions(
&self,
number_of_transactions: Option<u32>,
status: Option<Status>,
) -> PaystackResult<TransactionStatusList> {
let url = format!("{}/transaction", BASE_URL);
let query = vec![
("perPage", number_of_transactions.unwrap_or(10).to_string()),
("status", status.unwrap_or(Status::Success).to_string()),
];
match self.get_request(&url, Some(query)).await {
Ok(response) => match response.status() {
reqwest::StatusCode::OK => match response.json::<TransactionStatusList>().await {
Ok(content) => Ok(content),
Err(err) => Err(PaystackError::Transaction(err.to_string())),
},
_ => {
Err(RequestNotSuccessful::new(response.status(), response.text().await?).into())
}
},
Err(err) => Err(err),
}
}
pub async fn fetch_transactions(
&self,
transaction_id: u32,
) -> PaystackResult<TransactionStatus> {
let url = format!("{}/transaction/{}", BASE_URL, transaction_id);
match self.get_request(&url, None).await {
Ok(response) => match response.status() {
reqwest::StatusCode::OK => match response.json::<TransactionStatus>().await {
Ok(content) => Ok(content),
Err(err) => Err(PaystackError::Transaction(err.to_string())),
},
_ => {
Err(RequestNotSuccessful::new(response.status(), response.text().await?).into())
}
},
Err(err) => Err(err),
}
}
pub async fn charge_authorization(&self, charge: Charge) -> PaystackResult<TransactionStatus> {
let url = format!("{}/transaction/charge_authorization", BASE_URL);
match self.post_request(&url, charge).await {
Ok(response) => match response.status() {
reqwest::StatusCode::OK => match response.json::<TransactionStatus>().await {
Ok(content) => Ok(content),
Err(err) => Err(PaystackError::Charge(err.to_string())),
},
_ => {
Err(RequestNotSuccessful::new(response.status(), response.text().await?).into())
}
},
Err(err) => Err(err),
}
}
pub async fn view_transaction_timeline(
&self,
id: Option<u32>,
reference: Option<String>,
) -> PaystackResult<TransactionTimeline> {
let url: PaystackResult<String> = match (id, reference) {
(Some(id), None) => Ok(format!("{}/transaction/timeline/{}", BASE_URL, id)),
(None, Some(reference)) => {
Ok(format!("{}/transaction/timeline/{}", BASE_URL, &reference))
}
_ => {
return Err(PaystackError::Transaction(
"Transaction Id or Reference is need to view transaction timeline".to_string(),
))
}
};
let url = url.unwrap(); match self.get_request(&url, None).await {
Ok(response) => match response.status() {
reqwest::StatusCode::OK => match response.json::<TransactionTimeline>().await {
Ok(content) => Ok(content),
Err(err) => Err(PaystackError::Transaction(err.to_string())),
},
_ => {
Err(RequestNotSuccessful::new(response.status(), response.text().await?).into())
}
},
Err(err) => Err(err),
}
}
pub async fn total_transactions(&self) -> PaystackResult<TransactionTotalsResponse> {
let url = format!("{}/transaction/totals", BASE_URL);
match self.get_request(&url, None).await {
Ok(response) => match response.status() {
reqwest::StatusCode::OK => match response.json::<TransactionTotalsResponse>().await
{
Ok(content) => Ok(content),
Err(err) => Err(PaystackError::Transaction(err.to_string())),
},
_ => {
Err(RequestNotSuccessful::new(response.status(), response.text().await?).into())
}
},
Err(err) => Err(err),
}
}
pub async fn export_transaction(
&self,
status: Option<Status>,
currency: Option<Currency>,
settled: Option<bool>,
) -> PaystackResult<ExportTransactionResponse> {
let url = format!("{}/transaction/export", BASE_URL);
let settled = match settled {
Some(settled) => settled.to_string(),
None => String::from(""),
};
let query = vec![
("status", status.unwrap_or(Status::Success).to_string()),
("currency", currency.unwrap_or(Currency::EMPTY).to_string()),
("settled", settled),
];
match self.get_request(&url, Some(query)).await {
Ok(response) => match response.status() {
reqwest::StatusCode::OK => match response.json::<ExportTransactionResponse>().await
{
Ok(content) => Ok(content),
Err(err) => Err(PaystackError::Transaction(err.to_string())),
},
_ => {
Err(RequestNotSuccessful::new(response.status(), response.text().await?).into())
}
},
Err(err) => Err(err),
}
}
pub async fn partial_debit(
&self,
transaction_body: PartialDebitTransaction,
) -> PaystackResult<TransactionStatus> {
let url = format!("{}/transaction/partial_debit", BASE_URL);
match self.post_request(&url, transaction_body).await {
Ok(response) => match response.status() {
reqwest::StatusCode::OK => match response.json::<TransactionStatus>().await {
Ok(content) => Ok(content),
Err(err) => Err(PaystackError::Transaction(err.to_string())),
},
_ => {
Err(RequestNotSuccessful::new(response.status(), response.text().await?).into())
}
},
Err(err) => Err(err),
}
}
pub async fn create_transaction_split(
&self,
split_body: TransactionSplit,
) -> PaystackResult<TransactionSplitResponse> {
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::<TransactionSplitResponse>().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),
}
}
pub async fn list_transaction_splits(
&self,
split_name: Option<String>,
split_active: Option<bool>,
) -> PaystackResult<TransactionSplitListResponse> {
let url = format!("{}/split", BASE_URL);
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::<TransactionSplitListResponse>().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),
}
}
pub async fn fetch_transaction_split(
&self,
split_id: String,
) -> PaystackResult<TransactionSplitResponse> {
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::<TransactionSplitResponse>().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),
}
}
pub async fn update_transaction_split(
&self,
split_id: String,
body: TransactionSplit,
) -> PaystackResult<TransactionSplitResponse> {
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::<TransactionSplitResponse>().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),
}
}
pub async fn add_or_update_subaccount_split(
&self,
split_id: String,
body: Subaccount,
) -> PaystackResult<TransactionSplitResponse> {
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::<TransactionSplitResponse>().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),
}
}
pub async fn remove_subaccount_from_transaction_split(
&self,
split_id: String,
subaccount: String,
) -> PaystackResult<ResponseWithoutData> {
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::<ResponseWithoutData>().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),
}
}
}