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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![paystack-rs on docs.rs](https://docs.rs/paystack-rs/badge.svg)](https://docs.rs/paystack-rs)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

> _NB: a core rewrite of the project is being carried out, the goal is to make the crate easier to maintain,
> _NB: a core rewrite of the project is being carried out, the goal is to make the crate easier to maintain,
> improve the performance, abstract certain features to improve flexibility, and support for both blocking and non-blocking
> operations._

Expand All @@ -19,7 +19,8 @@ The client currently covers the following section of the API, and the sections t

- [x] Transaction
- [x] Transaction Split
- [ ] Terminal
- [x] Terminal
- [ ] Virtual Terminal
- [ ] Customers
- [ ] Dedicated Virtual Account
- [ ] Apple Pay
Expand Down
197 changes: 194 additions & 3 deletions src/endpoints/terminal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@

use std::sync::Arc;

use crate::{EventRequest, HttpClient, PaystackResult, SendEventResponseData};
use crate::{
EventRequest, FetchEventStatusResponseData, FetchTerminalStatusResponseData, HttpClient,
PaystackAPIError, PaystackResult, Response, SendEventResponseData, TerminalData,
UpdateTerminalRequest,
};

/// A struct to hold all the functions of the terminal API endpoint
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -33,11 +37,198 @@ impl<T: HttpClient + Default> TerminalEndpoints<T> {
/// Takes in the following:
/// - `terminal_id`: The ID of the Terminal the event should be sent to.
/// - `EventRequest`: A struct containing the information of the event to send to the terminal. It is created with the `EventRequestBuilder`.
pub fn send_event(
pub async fn send_event(
&self,
terminal_id: String,
event_request: EventRequest,
) -> PaystackResult<SendEventResponseData> {
todo!()
let url = format!("{}/{}/event", self.base_url, terminal_id);
let body = serde_json::to_value(event_request)
.map_err(|e| PaystackAPIError::Terminal(e.to_string()))?;

let response = self.http.post(&url, &self.key, &body).await;

match response {
Ok(response) => {
let parsed_response: Response<SendEventResponseData> =
serde_json::from_str(&response)
.map_err(|e| PaystackAPIError::Terminal(e.to_string()))?;

Ok(parsed_response)
}
Err(e) => Err(PaystackAPIError::Terminal(e.to_string())),
}
}

/// Check the status of an event sent to the Paystack Terminal
///
/// Takes in the following:
/// - `terminal_id`: The ID of the Terminal the event was sent to.
/// - `event_id`: The ID of the event that was sent to the Terminal.
pub async fn fetch_event_status(
&self,
terminal_id: String,
event_id: String,
) -> PaystackResult<FetchEventStatusResponseData> {
let url = format!("{}/{}/event/{}", self.base_url, terminal_id, event_id);

let response = self.http.get(&url, &self.key, None).await;

match response {
Ok(response) => {
let parsed_response: Response<FetchEventStatusResponseData> =
serde_json::from_str(&response)
.map_err(|e| PaystackAPIError::Terminal(e.to_string()))?;

Ok(parsed_response)
}
Err(e) => Err(PaystackAPIError::Terminal(e.to_string())),
}
}

/// Check the availiability of a Terminal before sending an event to it
///
/// Takes in the following:
/// - `terminal_id`: The ID of the Terminal you want to check.
pub async fn fetch_terminal_status(
&self,
terminal_id: String,
) -> PaystackResult<FetchTerminalStatusResponseData> {
let url = format!("{}/{}/presence", self.base_url, terminal_id);

let response = self.http.get(&url, &self.key, None).await;

match response {
Ok(response) => {
let parsed_response: Response<FetchTerminalStatusResponseData> =
serde_json::from_str(&response)
.map_err(|e| PaystackAPIError::Terminal(e.to_string()))?;

Ok(parsed_response)
}
Err(e) => Err(PaystackAPIError::Terminal(e.to_string())),
}
}

/// List the Terminals available on your integration
///
/// Takes in the following:
/// - `per_page`: Specify how many records you want retrieved. Defaults to a value of 50
pub async fn list_terminals(&self, per_page: Option<i32>) -> PaystackResult<Vec<TerminalData>> {
let url = format!("{}", self.base_url);
let per_page = per_page.unwrap_or(50).to_string();
let query = vec![("perPage", per_page.as_str())];

let response = self.http.get(&url, &self.key, Some(&query)).await;

match response {
Ok(response) => {
let parsed_response: Response<Vec<TerminalData>> = serde_json::from_str(&response)
.map_err(|e| PaystackAPIError::Terminal(e.to_string()))?;

Ok(parsed_response)
}
Err(e) => Err(PaystackAPIError::Terminal(e.to_string())),
}
}

/// Get the details of a Terminal
///
/// Takes in the following:
/// - `terminal_id`: The ID of the Terminal the event was sent to.
pub async fn fetch_terminal(&self, terminal_id: String) -> PaystackResult<TerminalData> {
let url = format!("{}/{}", self.base_url, terminal_id);

let response = self.http.get(&url, &self.key, None).await;

match response {
Ok(response) => {
let parsed_response: Response<TerminalData> = serde_json::from_str(&response)
.map_err(|e| PaystackAPIError::Terminal(e.to_string()))?;

Ok(parsed_response)
}
Err(e) => Err(PaystackAPIError::Terminal(e.to_string())),
}
}

/// Update the details of a Terminal.
///
/// Takes in the following:
/// - `terminal_id`: The ID of the Terminal you want to update.
/// - `UpdateTerminalRequest`: Contains the datails to be updated in the terminal. Should be constructed using the `UpdateTerminalRequestBuilder`
///
/// NB: The generic for the result here is a `String`, because there is no data field in the response from the API.
/// The string will be ignored because the underlying `data` field in the `response` is an `Option`.
pub async fn update_terminal(
&self,
terminal_id: String,
update_request: UpdateTerminalRequest,
) -> PaystackResult<String> {
let url = format!("{}/{}", self.base_url, terminal_id);
let body = serde_json::to_value(update_request)
.map_err(|e| PaystackAPIError::Terminal(e.to_string()))?;

let response = self.http.post(&url, &self.key, &body).await;

match response {
Ok(response) => {
let parsed_response: Response<String> = serde_json::from_str(&response)
.map_err(|e| PaystackAPIError::Terminal(e.to_string()))?;

Ok(parsed_response)
}
Err(e) => Err(PaystackAPIError::Terminal(e.to_string())),
}
}

/// Activate your debug device by linking it to your integration
///
/// Take in the following:
/// - `serial_number`: The device serial number
/// NB: The generic for the result here is a `String`, because there is no data field in the response from the API.
/// The string will be ignored because the underlying `data` field in the `response` is an `Option`.
pub async fn commission_terminal(&self, serial_number: String) -> PaystackResult<String> {
let url = format!("{}/commission_device", self.base_url);
let body = serde_json::json!({
"serial_number": serial_number
});

let response = self.http.post(&url, &self.key, &body).await;

match response {
Ok(response) => {
let parsed_response: Response<String> = serde_json::from_str(&response)
.map_err(|e| PaystackAPIError::Terminal(e.to_string()))?;

Ok(parsed_response)
}
Err(e) => Err(PaystackAPIError::Terminal(e.to_string())),
}
}

/// Unlink your debug device from your integration
///
/// Take in the following:
/// - `serial_number`: The device serial number
/// NB: The generic for the result here is a `String`, because there is no data field in the response from the API.
/// The string will be ignored because the underlying `data` field in the `response` is an `Option`.
pub async fn decommission_terminal(&self, serial_number: String) -> PaystackResult<String> {
let url = format!("{}/decommission_device", self.base_url);
let body = serde_json::json!({
"serial_number": serial_number
});

let response = self.http.post(&url, &self.key, &body).await;

match response {
Ok(response) => {
let parsed_response: Response<String> = serde_json::from_str(&response)
.map_err(|e| PaystackAPIError::Terminal(e.to_string()))?;

Ok(parsed_response)
}
Err(e) => Err(PaystackAPIError::Terminal(e.to_string())),
}
}
}
6 changes: 2 additions & 4 deletions src/endpoints/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,19 +91,17 @@ impl<T: HttpClient + Default> TransactionEndpoints<T> {
///
pub async fn list_transactions(
&self,
number_of_transactions: Option<u32>,
per_page: Option<u32>,
status: Option<Status>,
) -> PaystackResult<Vec<TransactionStatusData>> {
let url = self.base_url.to_string();

let per_page = number_of_transactions.unwrap_or(10).to_string();
let per_page = per_page.unwrap_or(10).to_string();
let status = status.unwrap_or(Status::Success).to_string();
let query = vec![("perPage", per_page.as_str()), ("status", status.as_str())];

let response = self.http.get(&url, &self.key, Some(&query)).await;

dbg!("{:#?}", &response);

match response {
Ok(response) => {
let parsed_response: Response<Vec<TransactionStatusData>> =
Expand Down
4 changes: 3 additions & 1 deletion src/models/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub struct Response<T> {
pub message: String,
/// This contains the result of your request
#[serde(default)]
pub data: T,
pub data: Option<T>,
/// This contains meta data object
pub meta: Option<Meta>,
}
Expand All @@ -37,4 +37,6 @@ pub struct Meta {
/// This is how many pages in total are available for retrieval considering the maximum records per page specified.
#[serde(deserialize_with = "string_or_number_to_u16")]
pub page_count: u16,
pub next: Option<String>,
pub previous: Option<String>,
}
84 changes: 79 additions & 5 deletions src/models/terminal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ pub struct EventRequest {
/// For the invoice type, you need to pass the invoice id and offline reference: {id: invoice_id, reference: offline_reference}.
///
/// For the transaction type, you can pass the transaction id: {id: transaction_id}, reference field can be `None`
#[derive(Debug, Serialize, Deserialize, Clone)]
#[derive(Debug, Serialize, Deserialize, Clone, Builder)]
pub struct EventRequestData {
pub id: String,
pub reference: Option<String>,
}

/// The type of event to push.
/// Paystack currently support `invoice` and `transaction`
#[derive(Debug, Serialize, Deserialize, Clone)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub enum EventType {
Invoice,
Transaction,
Expand All @@ -39,7 +39,7 @@ pub enum EventType {
/// - For the `Invoice` type, the action can either be `Process` or `View`.
///
/// - For the `Transaction` type, the action can either be `Process` or `Print`.
#[derive(Debug, Serialize, Deserialize, Clone)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub enum TerminalAction {
Process,
View,
Expand Down Expand Up @@ -67,8 +67,82 @@ impl fmt::Display for EventType {
}
}

/// Contains response data for the send event route in the terminal endpoint.
#[derive(Serialize, Deserialize, Debug, Clone)]
/// Update request for terminal
#[derive(Debug, Serialize, Deserialize, Builder, Default)]
pub struct UpdateTerminalRequest {
/// Name of the terminal
pub address: Option<String>,
/// The address of the terminal
pub name: Option<String>,
}

/// Response data for the send event route in the terminal endpoint.
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct SendEventResponseData {
pub id: String,
}

/// Response data for the fetch event status route in the terminal endpoint.
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct FetchEventStatusResponseData {
pub delivered: bool,
}

/// Response data for fetch terminal status route in the terminal endpoint.
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct FetchTerminalStatusResponseData {
pub online: bool,
pub available: bool,
}

/// Response data for terminal
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct TerminalData {
pub id: u64,
pub serial_number: String,
pub device_make: Option<String>,
pub terminal_id: String,
pub integration: u64,
pub domain: String,
pub name: String,
pub address: Option<String>,
pub status: String,
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn create_event_request() {
let even_request_data = EventRequestDataBuilder::default()
.id("some-id".into())
.reference(Some("some-ref".into()))
.build()
.expect("failed to build event request data");

let event_request = EventRequestBuilder::default()
.event_type(EventType::Invoice)
.action(TerminalAction::Process)
.data(even_request_data)
.build()
.expect("failed to build event request");

assert_eq!(&event_request.event_type, &EventType::Invoice);
assert_eq!(&event_request.action, &TerminalAction::Process);
assert_eq!(&event_request.data.id, "some-id");
assert_eq!(&event_request.data.reference, &Some("some-ref".to_string()))
}

#[test]
fn create_update_terminal_request() {
let update_request = UpdateTerminalRequestBuilder::default()
.address(Some("some-address".to_string()))
.name(Some("some-name".to_string()))
.build()
.expect("failed to build update terminal request");

assert_eq!(update_request.address, Some("some-address".to_string()));
assert_eq!(update_request.name, Some("some-name".to_string()));
}
}
Loading