Skip to content

Commit

Permalink
added charge authorization function
Browse files Browse the repository at this point in the history
  • Loading branch information
morukele committed Jul 4, 2023
1 parent 9b700da commit dc14ebe
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 17 deletions.
4 changes: 3 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@

Still working on a defined process for contributing, feel free to create a pull request. I will review it and merge if it passes the CI pipeline.

Feel free to contribute to this contribution fill too!
If you make any change to the current code, please make sure all test pass.

Feel free to contribute to this contribution instruction too!
59 changes: 57 additions & 2 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ extern crate reqwest;
extern crate serde_json;

use crate::{
PaystackResult, RequestNotSuccessful, Transaction, TransactionResponse, TransactionStatus,
TransactionStatusList,
Charge, PaystackResult, RequestNotSuccessful, Transaction, TransactionResponse,
TransactionStatus, TransactionStatusList,
};

static BASE_URL: &str = "https://api.paystack.co";
Expand Down Expand Up @@ -87,7 +87,9 @@ impl PaystackClient {
}

/// This method returns a Vec of transactions carried out on your integrations.
///
/// The method takes an Optional parameter for the number of transactions to return.
///
/// If None is passed as the parameter, the last 10 transactions are returned
pub async fn list_transactions(
&self,
Expand All @@ -114,4 +116,57 @@ impl PaystackClient {
let contents = response.json::<TransactionStatusList>().await?;
Ok(contents)
}

/// Get details of a transaction carried out on your integration
///
/// This methods take the Id of the desired transaction as a parameter
pub async fn fetch_transactions(
&self,
transaction_id: u32,
) -> PaystackResult<TransactionStatus> {
let url = format!("{}/transaction/{}", BASE_URL, transaction_id);

let response = self
.client
.get(url)
.bearer_auth(&self.api_key)
.header("Content-Type", "application/json")
.send()
.await?;

if response.error_for_status_ref().is_err() {
return Err(
RequestNotSuccessful::new(response.status(), response.text().await?).into(),
);
}

let content = response.json::<TransactionStatus>().await?;

Ok(content)
}

/// All authorizations marked as reusable can be charged with this endpoint whenever you need to receive payments
///
/// This function takes a Charge Struct as parameter
pub async fn charge_authorization(&self, charge: Charge) -> PaystackResult<TransactionStatus> {
let url = format!("{}/transaction/charge_authorization", BASE_URL);

let response = self
.client
.post(url)
.bearer_auth(&self.api_key)
.header("Content-Type", "application/json")
.json(&charge)
.send()
.await?;

if response.error_for_status_ref().is_err() {
return Err(
RequestNotSuccessful::new(response.status(), response.text().await?).into(),
);
}
let content = response.json::<TransactionStatus>().await?;

Ok(content)
}
}
3 changes: 3 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ pub enum PaystackError {
#[error("Transaction Creation Error: {0}")]
TransactionCreation(String),

#[error("Charge Creation Error: {0}")]
ChargeCreation(String),

#[error("Request failed: `{0}`")]
RequestNotSuccessful(#[from] RequestNotSuccessful),

Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ mod response;
// public re-exports
pub use client::PaystackClient;
pub use error::{PaystackError, RequestNotSuccessful};
pub use request::{Transaction, TransactionBuilder};
pub use request::{Charge, ChargeBuilder, Transaction, TransactionBuilder};
pub use response::{
Customer, TransactionResponse, TransactionResponseData, TransactionStatus,
TransactionStatusData, TransactionStatusList,
Expand Down
120 changes: 118 additions & 2 deletions src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use crate::{error, PaystackResult};
/// The struct has the following fields:
/// - amount: Amount should be in the smallest unit of the currency e.g. kobo if in NGN and cents if in USD
/// - email: Customer's email address
/// - currency (Optional): The transaction currency (NGN, GHS, ZAR or USD). Defaults to your integration currency.
#[derive(serde::Serialize)]
/// - currency (Optional): Currency in which amount should be charged (NGN, GHS, ZAR or USD). Defaults to your integration currency.
#[derive(serde::Serialize, Debug)]
pub struct Transaction {
amount: String,
email: String,
Expand Down Expand Up @@ -69,3 +69,119 @@ impl TransactionBuilder {
})
}
}

/// This struct is used to create a charge body for creating a Charge Authorization using the Paystack API.
///
/// IMPORTANT: This class can only be created using the ChargeBuilder.
///
/// The struct has the following fields:
/// - amount: Amount should be in the smallest unit of the currency e.g. kobo if in NGN and cents if in USD
/// - email: Customer's email address
/// - currency (Optional): Currency in which amount should be charged (NGN, GHS, ZAR or USD). Defaults to your integration currency.
/// - authorizatuin_code: A valid authorization code to charge
/// - reference (Optional): Unique transaction reference. Only -, ., = and alphanumeric characters allowed.
/// - channel (Optional): Send us 'card' or 'bank' or 'card','bank' as an array to specify what options to show the user paying
/// - transaction_charge (Optional): A flat fee to charge the subaccount for this transaction (in kobo if currency is NGN, pesewas,
/// if currency is GHS, and cents, if currency is ZAR). This overrides the split percentage set when the subaccount was created.
/// 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)]
pub struct Charge {
email: String,
amount: String,
authorization_code: String,
reference: Option<String>,
currency: Option<String>,
channel: Option<Vec<String>>,
transaction_charge: Option<u32>,
}

/// Builder for the Charge object
#[derive(Default, Clone)]
pub struct ChargeBuilder {
email: Option<String>,
amount: Option<String>,
authorization_code: Option<String>,
reference: Option<String>,
currency: Option<String>,
channel: Option<Vec<String>>,
transaction_charge: Option<u32>,
}

impl ChargeBuilder {
/// Create a new instance of the Transaction builder with default properties
pub fn new() -> Self {
ChargeBuilder::default()
}

/// Specify the transaction email
pub fn email(mut self, email: impl Into<String>) -> Self {
self.email = Some(email.into());
self
}

/// Specify the Transaction amount
pub fn amount(mut self, amount: impl Into<String>) -> Self {
self.amount = Some(amount.into());
self
}

/// Specify the charge Authorization Code
pub fn authorization_code(mut self, code: impl Into<String>) -> Self {
self.authorization_code = Some(code.into());
self
}

/// Specify charge reference
pub fn reference(mut self, reference: impl Into<String>) -> Self {
self.reference = Some(reference.into());
self
}

/// Specify charge currency
pub fn currency(mut self, currency: impl Into<String>) -> Self {
self.currency = Some(currency.into());
self
}

/// Specify charge channel
pub fn channel(mut self, channel: Vec<String>) -> Self {
self.channel = Some(channel);
self
}

/// Specify charge transaction charge
pub fn transaction_charge(mut self, transaction_charge: u32) -> Self {
self.transaction_charge = Some(transaction_charge);
self
}

pub fn build(self) -> PaystackResult<Charge> {
let Some(email) = self.email else {
return Err(error::PaystackError::ChargeCreation("email is required for creating a charge".to_string()))
};

let Some(amount) = self.amount else {
return Err(error::PaystackError::ChargeCreation(
"amount is required for creating charge".to_string()
))
};

let Some(authorization_code) = self.authorization_code else {
return Err(error::PaystackError::ChargeCreation(
"authorization code is required for creating a charge".to_string()
))
};

Ok(Charge {
email,
amount,
authorization_code,
reference: self.reference,
currency: self.currency,
channel: self.channel,
transaction_charge: self.transaction_charge,
})
}
}
21 changes: 11 additions & 10 deletions src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,44 +8,44 @@
use serde::{Deserialize, Serialize};

/// This struct represents the response of the Paystack transaction initalization.
#[derive(Deserialize, Debug)]
#[derive(Deserialize, Debug, Clone)]
pub struct TransactionResponse {
pub status: bool,
pub message: String,
pub data: TransactionResponseData,
}

/// This struct represents the data of the transaction response
#[derive(Deserialize, Debug)]
#[derive(Deserialize, Debug, Clone)]
pub struct TransactionResponseData {
pub authorization_url: String,
pub access_code: String,
pub reference: String,
}

/// This struct represents the transaction status response
#[derive(Deserialize, Debug)]
#[derive(Deserialize, Debug, Clone)]
pub struct TransactionStatus {
pub status: bool,
pub message: String,
pub data: TransactionStatusData,
}

/// This struct represents a list of transaction status
#[derive(Deserialize, Debug)]
#[derive(Deserialize, Debug, Clone)]
pub struct TransactionStatusList {
pub status: bool,
pub message: String,
pub data: Vec<TransactionStatusData>,
}

/// This struct represents the data of the transaction status response
#[derive(Deserialize, Debug)]
#[derive(Deserialize, Debug, Clone)]
pub struct TransactionStatusData {
pub id: Option<u64>,
pub id: Option<u32>,
pub status: Option<String>,
pub reference: Option<String>,
pub amount: Option<u64>,
pub amount: Option<u32>,
pub message: Option<String>,
pub gateway_response: Option<String>,
pub paid_at: Option<String>,
Expand All @@ -56,10 +56,11 @@ pub struct TransactionStatusData {
pub metadata: Option<String>,
pub fees: Option<i32>,
pub customer: Option<Customer>,
pub authorization: Option<Authorization>,
}

/// This struct represents the authorization data of the transaction status response
#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, Clone)]
pub struct Authorization {
pub authorization_code: Option<String>,
pub bin: Option<String>,
Expand All @@ -77,9 +78,9 @@ pub struct Authorization {
}

/// This struct represents the Paystack customer data
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Customer {
pub id: Option<u64>,
pub id: Option<u32>,
pub first_name: Option<String>,
pub last_name: Option<String>,
pub email: Option<String>,
Expand Down
Loading

0 comments on commit dc14ebe

Please sign in to comment.