diff --git a/README.md b/README.md index e2043b3..7c55f10 100644 --- a/README.md +++ b/README.md @@ -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._ @@ -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 diff --git a/src/endpoints/terminal.rs b/src/endpoints/terminal.rs index f9b3a28..70027f7 100644 --- a/src/endpoints/terminal.rs +++ b/src/endpoints/terminal.rs @@ -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)] @@ -33,11 +37,198 @@ impl TerminalEndpoints { /// 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 { - 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 = + 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 { + 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 = + 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 { + 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 = + 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) -> PaystackResult> { + 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> = 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 { + 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 = 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 { + 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 = 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 { + 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 = 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 { + 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 = serde_json::from_str(&response) + .map_err(|e| PaystackAPIError::Terminal(e.to_string()))?; + + Ok(parsed_response) + } + Err(e) => Err(PaystackAPIError::Terminal(e.to_string())), + } } } diff --git a/src/endpoints/transaction.rs b/src/endpoints/transaction.rs index 6371e6a..338e918 100644 --- a/src/endpoints/transaction.rs +++ b/src/endpoints/transaction.rs @@ -91,19 +91,17 @@ impl TransactionEndpoints { /// pub async fn list_transactions( &self, - number_of_transactions: Option, + per_page: Option, status: Option, ) -> PaystackResult> { 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> = diff --git a/src/models/response.rs b/src/models/response.rs index fcf5d29..75c2dd8 100644 --- a/src/models/response.rs +++ b/src/models/response.rs @@ -13,7 +13,7 @@ pub struct Response { pub message: String, /// This contains the result of your request #[serde(default)] - pub data: T, + pub data: Option, /// This contains meta data object pub meta: Option, } @@ -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, + pub previous: Option, } diff --git a/src/models/terminal.rs b/src/models/terminal.rs index 7d17c7c..bb2ff31 100644 --- a/src/models/terminal.rs +++ b/src/models/terminal.rs @@ -20,7 +20,7 @@ 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, @@ -28,7 +28,7 @@ pub struct EventRequestData { /// 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, @@ -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, @@ -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, + /// The address of the terminal + pub name: Option, +} + +/// 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, + pub terminal_id: String, + pub integration: u64, + pub domain: String, + pub name: String, + pub address: Option, + 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())); + } +} diff --git a/tests/api/charge.rs b/tests/api/charge.rs index 177deea..ddf91a6 100644 --- a/tests/api/charge.rs +++ b/tests/api/charge.rs @@ -27,14 +27,12 @@ async fn charge_authorization_succeeds() -> Result<(), Box> { let charge_response = client.transaction.charge_authorization(charge).await?; // Assert + let data = charge_response.data.unwrap(); assert!(charge_response.status); - assert_eq!(charge_response.data.customer.email, "susanna@example.net"); + assert_eq!(data.customer.email, "susanna@example.net"); + assert_eq!(data.authorization.clone().channel, Some("card".into())); assert_eq!( - charge_response.data.authorization.clone().channel, - Some("card".into()) - ); - assert_eq!( - charge_response.data.authorization.authorization_code, + data.authorization.authorization_code, Some("AUTH_ik4t69fo2y".into()) ); diff --git a/tests/api/main.rs b/tests/api/main.rs index 76a7f3f..f636cdf 100644 --- a/tests/api/main.rs +++ b/tests/api/main.rs @@ -1,4 +1,5 @@ pub mod charge; pub mod helpers; +pub mod terminal; pub mod transaction; pub mod transaction_split; diff --git a/tests/api/terminal.rs b/tests/api/terminal.rs new file mode 100644 index 0000000..4d255ba --- /dev/null +++ b/tests/api/terminal.rs @@ -0,0 +1,3 @@ +// TODO: to conduct the test, you need access to a paystack terminal which I do not have +#[tokio::test] +async fn terminal_send_event_succeed() {} diff --git a/tests/api/transaction.rs b/tests/api/transaction.rs index 1007d04..29189f6 100644 --- a/tests/api/transaction.rs +++ b/tests/api/transaction.rs @@ -102,14 +102,14 @@ async fn valid_transaction_is_verified() { let response = client .transaction - .verify_transaction(&content.data.reference) + .verify_transaction(&content.data.unwrap().reference) .await .expect("unable to verify transaction"); // Assert assert!(response.status); assert_eq!(response.message, "Verification successful"); - assert_eq!(response.data.status, "abandoned"); + assert_eq!(response.data.unwrap().status, "abandoned"); } #[tokio::test] @@ -125,7 +125,7 @@ async fn list_specified_number_of_transactions_in_the_integration() { .expect("unable to get list of integrated transactions"); // Assert - assert_eq!(5, response.data.len()); + assert_eq!(5, response.data.unwrap().len()); assert!(response.status); assert_eq!("Transactions retrieved", response.message); } @@ -144,7 +144,7 @@ async fn list_transactions_passes_with_default_values() { // Assert assert!(response.status); - assert_eq!(10, response.data.len()); + assert_eq!(10, response.data.unwrap().len()); assert_eq!("Transactions retrieved", response.message); } @@ -160,18 +160,17 @@ async fn fetch_transaction_succeeds() { .await .expect("unable to get list of integrated transactions"); + let data = response.data.unwrap(); let fetched_transaction = client .transaction - .fetch_transactions(response.data[0].id) + .fetch_transactions(data[0].id) .await .expect("unable to fetch transaction"); // Assert - assert_eq!(response.data[0].id, fetched_transaction.data.id); - assert_eq!( - response.data[0].reference, - fetched_transaction.data.reference - ); + let res = fetched_transaction.data.unwrap(); + assert_eq!(&data[0].id, &res.id); + assert_eq!(&data[0].reference, &res.reference); } #[tokio::test] @@ -186,7 +185,8 @@ async fn view_transaction_timeline_passes_with_id() { .await .expect("unable to get list of integrated transactions"); - let identifier = TransactionIdentifier::Id(response.data[0].id); + let data = response.data.unwrap(); + let identifier = TransactionIdentifier::Id(data[0].id); let transaction_timeline = client .transaction @@ -212,7 +212,8 @@ async fn view_transaction_timeline_passes_with_reference() { .expect("unable to get list of integrated transactions"); // println!("{:#?}", response); - let reference = response.data[0].reference.clone(); + let data = response.data.unwrap(); + let reference = data[0].reference.clone(); let identifier = TransactionIdentifier::Reference(reference); let transaction_timeline = client .transaction @@ -238,10 +239,11 @@ async fn get_transaction_total_is_successful() { .expect("unable to get transaction total"); // Assert + let data = res.data.unwrap(); assert!(res.status); assert_eq!(res.message, "Transaction totals"); - assert!(res.data.total_transactions.is_some()); - assert!(res.data.total_volume.is_some()); + assert!(data.total_transactions.is_some()); + assert!(data.total_volume.is_some()); } #[tokio::test] @@ -257,9 +259,10 @@ async fn export_transaction_succeeds_with_default_parameters() { .expect("unable to export transactions"); // Assert + let data = res.data.unwrap(); assert!(res.status); assert_eq!(res.message, "Export successful"); - assert!(!res.data.path.is_empty()); + assert!(!data.path.is_empty()); } #[tokio::test] @@ -274,7 +277,8 @@ async fn partial_debit_transaction_passes_or_fails_depending_on_merchant_status( .await .expect("Unable to get transaction list"); - let transaction = transaction.data[0].clone(); + let data = transaction.data.unwrap(); + let transaction = data[0].clone(); let email = transaction.customer.email; let authorization_code = transaction.authorization.authorization_code.unwrap(); let body = PartialDebitTransactionRequestBuilder::default() diff --git a/tests/api/transaction_split.rs b/tests/api/transaction_split.rs index acb6e92..eba601d 100644 --- a/tests/api/transaction_split.rs +++ b/tests/api/transaction_split.rs @@ -34,9 +34,10 @@ async fn create_subaccount_body( .await .expect("Unable to Create a subaccount"); + let data = subaccount.data.unwrap(); SubaccountBodyBuilder::default() .share(share) - .subaccount(subaccount.data.subaccount_code) + .subaccount(data.subaccount_code) .build() .unwrap() } @@ -83,9 +84,10 @@ async fn create_transaction_split_passes_with_valid_data() { .expect("Failed to create transaction split"); // Assert + let data = res.data.unwrap(); assert!(res.status); assert_eq!(res.message, "Split created"); - assert_eq!(res.data.currency, Currency::NGN.to_string()); + assert_eq!(data.currency, Currency::NGN.to_string()); } #[tokio::test] @@ -137,18 +139,18 @@ async fn list_transaction_splits_in_the_integration() { .await; // Assert - if let Ok(data) = res { - assert!(data.status); - assert_eq!(data.message, "Split retrieved".to_string()); - assert!(data.data.len() > 0); + if let Ok(res) = res { + let data = res.data.unwrap(); + assert!(res.status); + assert_eq!(res.message, "Split retrieved".to_string()); + assert!(data.len() > 0); - let transaction_split = data.data.first().unwrap(); + let transaction_split = data.first().unwrap(); assert_eq!( transaction_split.split_type, paystack::SplitType::Percentage.to_string() ); } else { - dbg!("response: {:?}", &res); panic!(); } } @@ -166,19 +168,17 @@ async fn fetch_a_transaction_split_in_the_integration() { .await .expect("Failed to create transaction split"); + let data = transaction_split.data.unwrap(); let res = client .transaction_split - .fetch_transaction_split(&transaction_split.data.id.to_string()) + .fetch_transaction_split(&data.id.to_string()) .await .unwrap(); // Assert - dbg!(&res); + let data = res.data.unwrap(); assert!(res.status); - assert_eq!( - res.data.total_subaccounts as usize, - res.data.subaccounts.len() - ); + assert_eq!(data.total_subaccounts as usize, data.subaccounts.len()); assert_eq!(res.message, "Split retrieved".to_string()); } @@ -206,18 +206,20 @@ async fn update_a_transaction_split_passes_with_valid_data() { .unwrap(); // Act - let split_id = transaction_split.data[0].id.to_string(); + let data = transaction_split.data.unwrap(); + let split_id = data[0].id.to_string(); let res = client .transaction_split .update_transaction_split(&split_id, update_split_body) .await; // Assert - if let Ok(data) = res { - assert!(data.status); - assert_eq!(data.message, "Split group updated".to_string()); - assert!(!data.data.active.unwrap()); - assert_eq!(data.data.name, new_split_name); + if let Ok(res) = res { + let data = res.data.unwrap(); + assert!(res.status); + assert_eq!(res.message, "Split group updated".to_string()); + assert!(!data.active.unwrap()); + assert_eq!(data.name, new_split_name); } else { panic!(); } @@ -245,7 +247,8 @@ async fn update_a_transaction_split_fails_with_invalid_data() { .unwrap(); // Act - let split_id = transaction_split.data.id.to_string(); + let data = transaction_split.data.unwrap(); + let split_id = data.id.to_string(); let res = client .transaction_split .update_transaction_split(&split_id, update_split_body) @@ -274,7 +277,8 @@ async fn add_a_transaction_split_subaccount_passes_with_valid_data() { let new_subaccount_body = create_subaccount_body(&client, 2.8, 4.0).await; - let split_id = transaction_split.data.id.to_string(); + let data = transaction_split.data.unwrap(); + let split_id = data.id.to_string(); let res = client .transaction_split .add_or_update_subaccount_split(&split_id, new_subaccount_body.clone()) @@ -282,9 +286,10 @@ async fn add_a_transaction_split_subaccount_passes_with_valid_data() { .unwrap(); // Assert + let data = res.data.unwrap(); assert!(res.status); assert_eq!(res.message, "Subaccount added"); - assert_eq!(res.data.subaccounts.len(), 3); + assert_eq!(data.subaccounts.len(), 3); } #[tokio::test] @@ -302,7 +307,8 @@ async fn add_a_transaction_split_subaccount_fails_with_invalid_data() { let new_subaccount_body = create_subaccount_body(&client, 55.0, 120.0).await; - let split_id = transaction_split.data.id.to_string(); + let data = transaction_split.data.unwrap(); + let split_id = data.id.to_string(); let res = client .transaction_split .add_or_update_subaccount_split(&split_id, new_subaccount_body.clone()) @@ -310,7 +316,6 @@ async fn add_a_transaction_split_subaccount_fails_with_invalid_data() { // Assert if let Err(err) = res { - dbg!(&err); assert!(err.to_string().contains("400 Bad Request")); } else { panic!(); @@ -329,12 +334,13 @@ async fn remove_a_subaccount_from_a_transaction_split_passes_with_valid_data() { .create_transaction_split(split_body) .await .expect("Failed to create transaction split"); - let split_id = transaction_split.data.id.to_string(); + let data = transaction_split.data.unwrap(); + let split_id = data.id.to_string(); // Validate the number of subaccounts attached - assert_eq!(transaction_split.data.subaccounts.len(), 2); + assert_eq!(data.subaccounts.len(), 2); - let subaccount_data = transaction_split.data.subaccounts.first().unwrap(); + let subaccount_data = data.subaccounts.first().unwrap(); let code = &subaccount_data.subaccount.subaccount_code; // Remove subaccount let res = client @@ -360,9 +366,10 @@ async fn remove_a_subaccount_from_a_transaction_split_passes_with_valid_data() { .unwrap(); // Assert + let data = transaction_split.data.unwrap(); assert!(transaction_split.status); - assert_eq!(transaction_split.data.total_subaccounts, 1); - let remaining_subaccount = transaction_split.data.subaccounts.first().unwrap(); + assert_eq!(data.total_subaccounts, 1); + let remaining_subaccount = data.subaccounts.first().unwrap(); assert_ne!( remaining_subaccount.subaccount.subaccount_code, subaccount_data.subaccount.subaccount_code @@ -381,10 +388,11 @@ async fn remove_a_subaccount_from_a_transaction_split_fails_with_invalid_data() .create_transaction_split(split_body) .await .expect("Failed to create transaction split"); - let split_id = transaction_split.data.id.to_string(); + let data = transaction_split.data.unwrap(); + let split_id = data.id.to_string(); // Validate the number of subaccounts attached - assert_eq!(transaction_split.data.subaccounts.len(), 2); + assert_eq!(data.subaccounts.len(), 2); // Remove subaccount let res = client