diff --git a/src/paystack.rs b/src/paystack.rs index 86d488a..3548718 100644 --- a/src/paystack.rs +++ b/src/paystack.rs @@ -1,6 +1,11 @@ +pub mod bulk_charges; +pub mod charge; +pub mod control_panel; pub mod customers; pub mod dedicated_nuban; +pub mod disputes; pub mod invoices; +pub mod miscellaneous; pub mod payment_pages; pub mod plans; pub mod products; @@ -10,9 +15,18 @@ pub mod subaccounts; pub mod subscription; pub mod transactions; pub mod transactions_split; +pub mod transfer_recipients; +pub mod transfers; +pub mod transfers_control; +pub mod verification; +use bulk_charges::BulkCharges; +use charge::Charge; +use control_panel::ControlPanel; use dedicated_nuban::DedicatedNuban; +use disputes::Disputes; use invoices::Invoices; +use miscellaneous::Miscellaneous; use payment_pages::PaymentPages; use plans::Plans; use products::Products; @@ -22,6 +36,10 @@ use subaccounts::Subaccount; use subscription::Subscription; use transactions::Transaction; use transactions_split::TransactionSplit; +use transfer_recipients::TransferRecipients; +use transfers::Transfers; +use transfers_control::TransfersControl; +use verification::Verification; #[derive(Default)] pub struct Paystack { @@ -36,6 +54,15 @@ pub struct Paystack { pub payment_pages: PaymentPages, pub invoices: Invoices, pub settlements: Settlements, + pub transfer_recipients: TransferRecipients, + pub transfers: Transfers, + pub transfers_control: TransfersControl, + pub bulk_charges: BulkCharges, + pub control_panel: ControlPanel, + pub charge: Charge, + pub disputes: Disputes, + pub verification: Verification, + pub miscellaneous: Miscellaneous, } impl Paystack { @@ -76,6 +103,33 @@ impl Paystack { settlements: Settlements { bearer_auth: formatted_bearer.to_string(), }, + transfer_recipients: TransferRecipients { + bearer_auth: formatted_bearer.to_string(), + }, + transfers: Transfers { + bearer_auth: formatted_bearer.to_string(), + }, + transfers_control: TransfersControl { + bearer_auth: formatted_bearer.to_string(), + }, + bulk_charges: BulkCharges { + bearer_auth: formatted_bearer.to_string(), + }, + control_panel: ControlPanel { + bearer_auth: formatted_bearer.to_string(), + }, + charge: Charge { + bearer_auth: formatted_bearer.to_string(), + }, + disputes: Disputes { + bearer_auth: formatted_bearer.to_string(), + }, + verification: Verification { + bearer_auth: formatted_bearer.to_string(), + }, + miscellaneous: Miscellaneous { + bearer_auth: formatted_bearer.to_string(), + }, } } } diff --git a/src/paystack/bulk_charges.rs b/src/paystack/bulk_charges.rs new file mode 100644 index 0000000..0b9ffe5 --- /dev/null +++ b/src/paystack/bulk_charges.rs @@ -0,0 +1,114 @@ +use crate::utils::make_get_request; +use chrono::{DateTime, Local}; +use reqwest::blocking::Response; +use serde::Serialize; + +/// The Bulk Charges API allows you create and manage multiple recurring payments from your customers +#[derive(Debug, Default)] +pub struct BulkCharges { + pub(crate) bearer_auth: String, +} + +// #[derive(Debug, Serialize)] +// pub struct InitiateBulkChargesBody { +// pub +// } + +#[derive(Debug, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum BulkChargesStatus { + FAILED, + SUCCESS, + PENDING, +} + +#[derive(Debug, Serialize)] +pub struct ListBulkChargesParams { + /// Specify how many records you want to retrieve per page. If not specify we use a default value of 50. + #[serde(rename = "perPage")] + pub per_page: Option, + /// Specify exactly what page you want to retrieve. If not specify we use a default value of 1. + pub page: Option, + /// A timestamp from which to start listing product e.g. 2016-09-24T00:00:05.000Z, 2016-09-21 + pub from: Option>, + /// A timestamp at which to stop listing product e.g. 2016-09-24T00:00:05.000Z, 2016-09-21 + pub to: Option>, +} + +#[derive(Debug, Serialize)] +pub struct FetchChargesInABatchParams { + /// Either one of these values: pending, success or failed + pub status: BulkChargesStatus, + /// Specify how many records you want to retrieve per page. If not specify we use a default value of 50. + #[serde(rename = "perPage")] + pub per_page: Option, + /// Specify exactly what page you want to retrieve. If not specify we use a default value of 1. + pub page: Option, + /// A timestamp from which to start listing product e.g. 2016-09-24T00:00:05.000Z, 2016-09-21 + pub from: Option>, + /// A timestamp at which to stop listing product e.g. 2016-09-24T00:00:05.000Z, 2016-09-21 + pub to: Option>, +} +const BULK_CHARGES_URL: &str = "https://api.paystack.co/bulkcharge"; +impl BulkCharges { + // FIXME: the docs dont say what it is here, hence I wont be implementing this method until the docs are clear + // pub fn initiate_bulk_charges(&self, body: InitiateBulkChargesBody) -> Result { + // let res = make_request( + // &self.bearer_auth, + // BULK_CHARGES_URL, + // Some(body), + // REQUEST::POST, + // ); + // return res; + // } + + /// This lists all bulk charge batches created by the integration. Statuses can be active, paused, or complete. + pub fn list_bulk_charges( + &self, + params: Option, + ) -> Result { + let res = make_get_request(&self.bearer_auth, BULK_CHARGES_URL, params); + return res; + } + + /// This endpoint retrieves a specific batch code. + /// It also returns useful information on its progress by way of the `total_charges` and `pending_charges` attributes. + /// - id_or_code: + /// An ID or code for the charge whose batches you want to retrieve. + pub fn fetch_bulk_charge_batch(&self, id_or_code: &str) -> Result { + let url = format!("{}/{}", BULK_CHARGES_URL, id_or_code); + let res = make_get_request(&self.bearer_auth, &url, None::); + return res; + } + /// This endpoint retrieves the charges associated with a specified batch code. Pagination parameters are available. + /// You can also filter by status. Charge statuses can be pending, success or failed. + /// - id_or_code: + /// An ID or code for the charge whose batches you want to retrieve. + pub fn fetch_charges_in_a_batch( + &self, + id_or_code: &str, + params: FetchChargesInABatchParams, + ) -> Result { + let url = format!("{}/{}", BULK_CHARGES_URL, id_or_code); + let res = make_get_request(&self.bearer_auth, &url, Some(params)); + return res; + } + + /// Use this endpoint to pause processing a batch + /// - batch_code: + /// The batch code for the bulk charge you want to pause + pub fn pause_bulk_charge_batch(&self, batch_code: &str) -> Result { + let url = format!("{}/pause/{}", BULK_CHARGES_URL, batch_code); + let res = make_get_request(&self.bearer_auth, &url, None::); + return res; + } + + /// Use this endpoint to pause processing a batch + /// - batch_code: + /// The batch code for the bulk charge you want to pause + pub fn resume_bulk_charge_batch(&self, batch_code: &str) -> Result { + let url = format!("{}/resume/{}", BULK_CHARGES_URL, batch_code); + let res = make_get_request(&self.bearer_auth, &url, None::); + return res; + } +} diff --git a/src/paystack/charge.rs b/src/paystack/charge.rs new file mode 100644 index 0000000..a65104d --- /dev/null +++ b/src/paystack/charge.rs @@ -0,0 +1,136 @@ +use chrono::{DateTime, Local}; +use reqwest::blocking::Response; +use serde::Serialize; +use serde_json::Value as JSON; + +use crate::utils::{make_get_request, make_request, REQUEST}; + +#[derive(Debug, Default)] +pub struct Charge { + pub(crate) bearer_auth: String, +} + +#[derive(Debug, Serialize)] +pub struct CreateChargeBody<'a> { + /// Customer's email address + pub email: &'a str, + /// Amount should be in kobo if currency is `NGN`, pesewas, if currency is `GHS`, and cents, if currency is `ZAR` + pub amount: &'a str, + /// Bank account to charge (don't send if charging an authorization code) + pub bank: Option, + /// An authorization code to charge (don't send if charging a bank account) + pub authorization_code: Option<&'a str>, + /// 4-digit PIN (send with a non-reusable authorization code) + pub pin: Option<&'a str>, + /// A JSON object + pub metadata: Option, + /// Unique transaction reference. Only -, .`, = and alphanumeric characters allowed. + pub reference: Option<&'a str>, + /// USSD type to charge (don't send if charging an authorization code, bank or card) + pub ussd: Option, + /// Mobile details (don't send if charging an authorization code, bank or card) + pub mobile_money: Option, + /// This is the unique identifier of the device a user uses in making payment. + /// Only -, .`, = and alphanumeric characters allowed. + pub device_id: Option<&'a str>, +} + +#[derive(Debug, Serialize)] +pub struct SubmitPinBody<'a> { + /// PIN submitted by user + pub pin: &'a str, + /// Reference for transaction that requested pin + pub reference: &'a str, +} + +#[derive(Debug, Serialize)] +pub struct SubmitOTPBody<'a> { + /// OTP submitted by user + pub otp: &'a str, + /// Reference for ongoing transaction + pub reference: &'a str, +} + +#[derive(Debug, Serialize)] +pub struct SubmitPhoneBody<'a> { + /// Phone submitted by user + pub phone: &'a str, + /// Reference for ongoing transaction + pub reference: &'a str, +} + +#[derive(Debug, Serialize)] +pub struct SubmitBirthdayBody<'a> { + /// Birthday submitted by user e.g. 2016-09-21 + pub birthday: DateTime, + /// Reference for ongoing transaction + pub reference: &'a str, +} + +#[derive(Debug, Serialize)] +pub struct SubmitAddressBody<'a> { + /// Address submitted by user + pub address: &'a str, + /// Reference for ongoing transaction + pub reference: &'a str, + /// City submitted by user + pub city: &'a str, + /// State submitted by user + pub state: &'a str, + /// Zipcode submitted by user + pub zipcode: &'a str, +} + +const CHARGE_URL: &str = "https://api.paystack.co/charge"; +impl Charge { + // TODO: link payment channel here + /// Initiate a payment by integrating the [][payment channel] of your choice. + pub fn create_charge(&self, body: CreateChargeBody) -> Result { + let res = make_request(&self.bearer_auth, CHARGE_URL, Some(body), REQUEST::POST); + return res; + } + + /// Submit PIN to continue a charge + pub fn submit_pin(&self, body: SubmitPinBody) -> Result { + let url = format!("{}/submit_pin", CHARGE_URL); + let res = make_request(&self.bearer_auth, &url, Some(body), REQUEST::POST); + return res; + } + + /// Submit OTP to complete a charge + pub fn submit_otp(&self, body: SubmitOTPBody) -> Result { + let url = format!("{}/submit_otp", CHARGE_URL); + let res = make_request(&self.bearer_auth, &url, Some(body), REQUEST::POST); + return res; + } + + /// Submit phone when requested + pub fn submit_phone(&self, body: SubmitPhoneBody) -> Result { + let url = format!("{}/submit_phone", CHARGE_URL); + let res = make_request(&self.bearer_auth, &url, Some(body), REQUEST::POST); + return res; + } + + /// Submit birthday when requested + pub fn submit_birthday(&self, body: SubmitBirthdayBody) -> Result { + let url = format!("{}/submit_birthday", CHARGE_URL); + let res = make_request(&self.bearer_auth, &url, Some(body), REQUEST::POST); + return res; + } + + /// Submit address to continue a charge + pub fn submit_address(&self, body: SubmitAddressBody) -> Result { + let url = format!("{}/submit_address", CHARGE_URL); + let res = make_request(&self.bearer_auth, &url, Some(body), REQUEST::POST); + return res; + } + + /// When you get "pending" as a charge status or if there was an exception when calling any of the /charge endpoints, + /// wait 10 seconds or more, then make a check to see if its status has changed. + /// Don't call too early as you may get a lot more pending than you should. + pub fn check_pending_charge(&self, reference: &str) -> Result { + let url = format!("{}/{}", CHARGE_URL, reference); + let res = make_get_request(&self.bearer_auth, &url, None::); + return res; + } +} diff --git a/src/paystack/control_panel.rs b/src/paystack/control_panel.rs new file mode 100644 index 0000000..9aa1f0a --- /dev/null +++ b/src/paystack/control_panel.rs @@ -0,0 +1,36 @@ +use reqwest::blocking::Response; +use serde::Serialize; + +use crate::utils::{make_get_request, make_request, REQUEST}; + +/// The Control Panel API allows you manage some settings on your integration +#[derive(Debug, Default)] +pub struct ControlPanel { + pub(crate) bearer_auth: String, +} + +#[derive(Debug, Serialize)] +pub struct UpdatePaymentSessionTimeoutBody { + /// Time before stopping session (in seconds). Set to 0 to cancel session timeouts + pub timeout: i64, +} + +const CONTROL_PANEL_URL: &str = "https://api.paystack.co/integration"; +impl ControlPanel { + /// Fetch the payment session timeout on your integration + pub fn fetch_payment_session_timeout(&self) -> Result { + let url = format!("{}/payment_session_timeout", CONTROL_PANEL_URL); + let res = make_get_request(&self.bearer_auth, &url, None::); + return res; + } + + /// Update the payment session timeout on your integration + pub fn update_payment_session_timeout( + &self, + body: UpdatePaymentSessionTimeoutBody, + ) -> Result { + let url = format!("{}/payment_session_timeout", CONTROL_PANEL_URL); + let res = make_request(&self.bearer_auth, &url, Some(body), REQUEST::PUT); + return res; + } +} diff --git a/src/paystack/disputes.rs b/src/paystack/disputes.rs new file mode 100644 index 0000000..1c3b8a5 --- /dev/null +++ b/src/paystack/disputes.rs @@ -0,0 +1,158 @@ +use chrono::{DateTime, Local}; +use reqwest::blocking::Response; +use serde::Serialize; + +use crate::utils::{make_get_request, make_request, REQUEST}; + +/// The Disputes API allows you manage transaction disputes on your integration +#[derive(Debug, Default)] +pub struct Disputes { + pub(crate) bearer_auth: String, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "kebab-case")] +pub enum DisputeStatus { + AwaitingMerchantFeedback, + AwaitingBankFeedback, + Pending, + Resolved, + Declined, + MerchantAccepted, +} +#[derive(Debug, Serialize)] +pub struct ListDisputesParams<'a> { + /// A timestamp from which to start listing product e.g. 2016-09-24T00:00:05.000Z, 2016-09-21 + pub from: DateTime, + /// A timestamp at which to stop listing product e.g. 2016-09-24T00:00:05.000Z, 2016-09-21 + pub to: DateTime, + /// Specify how many records you want to retrieve per page. If not specify we use a default value of 50. + #[serde(rename = "perPage")] + pub per_page: Option, + /// Specify exactly what page you want to retrieve. If not specify we use a default value of 1. + pub page: Option, + pub transaction: &'a str, + /// Dispute Status. Acceptable values: `{ awaiting-merchant-feedback | awaiting-bank-feedback | pending | resolved }` + pub status: DisputeStatus, +} + +#[derive(Debug, Serialize)] +pub struct UpdateDisputeBody<'a> { + /// the amount to refund, in **kobo** if currency is `NGN`, **pesewas**, if currency is `GHS`, and **cents**, if currency is `ZAR` + pub refund_amount: i64, + // TODO: link to upload dispute url + /// filename of attachment returned via response from upload url(GET /dispute/:id/upload_url) + pub uploaded_filename: Option<&'a str>, +} + +#[derive(Debug, Serialize)] +pub struct AddEvidenceBody<'a> { + /// Customer email + pub customer_email: &'a str, + /// Customer name + pub customer_name: &'a str, + /// Customer phone + pub customer_phone: &'a str, + /// Details of service involved + pub service_details: &'a str, + /// Delivery Address + pub delivery_address: Option<&'a str>, + /// ISO 8601 representation of delivery date (YYYY-MM-DD) + pub delivery_date: Option>, +} + +#[derive(Debug, Serialize)] +pub struct GetUploadURLParams<'a> { + /// The file name, with its extension, that you want to upload. e.g `filename.pdf` + pub upload_filename: &'a str, +} + +#[derive(Debug, Serialize)] +pub struct ResolveDisputeBody<'a> { + /// Dispute resolution. Accepted values: { merchant-accepted | declined }. + pub resolution: &'a str, + /// Reason for resolving + pub message: &'a str, + /// the amount to refund, in **kobo** if currency is `NGN`, **pesewas**, if currency is `GHS`, and **cents**, if currency is `ZAR` + pub refund_amount: i64, + /// filename of attachment returned via response from upload url(GET /dispute/:id/upload_url) + pub uploaded_filename: &'a str, + /// Evidence Id for fraud claims + pub evidence: Option, +} + +#[derive(Debug, Serialize)] +pub struct ExportDisputesBody<'a> { + /// A timestamp from which to start listing product e.g. 2016-09-24T00:00:05.000Z, 2016-09-21 + pub from: DateTime, + /// A timestamp at which to stop listing product e.g. 2016-09-24T00:00:05.000Z, 2016-09-21 + pub to: DateTime, + /// Specify how many records you want to retrieve per page. If not specify we use a default value of 50. + #[serde(rename = "perPage")] + pub per_page: Option, + /// Specify exactly what page you want to retrieve. If not specify we use a default value of 1. + pub page: Option, + pub transaction: Option<&'a str>, + /// Dispute Status. Acceptable values: `{ awaiting-merchant-feedback | awaiting-bank-feedback | pending | resolved }` + pub status: Option, +} + +const DISPUTE_URL: &str = "https://api.paystack.co/dispute"; +impl Disputes { + /// List disputes filed against you + pub fn list_disputes(&self, params: ListDisputesParams) -> Result { + let res = make_get_request(&self.bearer_auth, DISPUTE_URL, Some(params)); + return res; + } + + /// Get more details about a dispute. + /// - id: The dispute `ID` you want to fetch + pub fn fetch_dispute(&self, id: &str) -> Result { + let url = format!("{}/{}", DISPUTE_URL, id); + let res = make_get_request(&self.bearer_auth, &url, None::); + return res; + } + + /// Get more details about a dispute. + /// - id: The transaction `ID` you want to fetch + pub fn list_transaction_disputes(&self, id: &str) -> Result { + let url = format!("{}/transaction/{}", DISPUTE_URL, id); + let res = make_get_request(&self.bearer_auth, &url, None::); + return res; + } + + /// Update details of a dispute on your integration + pub fn update_dispute(&self, id: &str, body: UpdateDisputeBody) -> Result { + let url = format!("{}/{}", DISPUTE_URL, id); + let res = make_request(&self.bearer_auth, &url, Some(body), REQUEST::PUT); + return res; + } + + /// Provide evidence for a dispute + pub fn add_evidence(&self, id: &str, body: AddEvidenceBody) -> Result { + let url = format!("{}/{}/dispute", DISPUTE_URL, id); + let res = make_request(&self.bearer_auth, &url, Some(body), REQUEST::POST); + return res; + } + + /// Resolve a dispute on your integration + pub fn get_upload_url(&self, id: &str, params: GetUploadURLParams) -> Result { + let url = format!("{}/{}/upload_url", DISPUTE_URL, id); + let res = make_get_request(&self.bearer_auth, &url, Some(params)); + return res; + } + + /// Resolve a dispute on your integration + pub fn resolve_dispute(&self, id: &str, body: ResolveDisputeBody) -> Result { + let url = format!("{}/{}/resolve", DISPUTE_URL, id); + let res = make_request(&self.bearer_auth, &url, Some(body), REQUEST::PUT); + return res; + } + + /// Export disputes available on your integration + pub fn export_disputes(&self, params: ExportDisputesBody) -> Result { + let url = format!("{}/export", DISPUTE_URL); + let res = make_get_request(&self.bearer_auth, &url, Some(params)); + return res; + } +} diff --git a/src/paystack/invoices.rs b/src/paystack/invoices.rs index 03cc397..6f5c49f 100644 --- a/src/paystack/invoices.rs +++ b/src/paystack/invoices.rs @@ -41,7 +41,7 @@ pub struct CreateInvoiceBody<'a> { #[derive(Debug, Serialize)] pub struct ListInvoicesParams<'a> { - #[serde(rename = "per_page")] + #[serde(rename = "perPage")] /// Specify how many records you want to retrieve per page. If not specify we use a default value of 50. pub per_page: Option, /// Specify exactly what invoice you want to page. If not specify we use a default value of 1. diff --git a/src/paystack/miscellaneous.rs b/src/paystack/miscellaneous.rs new file mode 100644 index 0000000..c6cd9a0 --- /dev/null +++ b/src/paystack/miscellaneous.rs @@ -0,0 +1,79 @@ +use reqwest::blocking::Response; +use serde::Serialize; + +use crate::{prelude::Currency, utils::make_get_request}; + +/// The Miscellaneous API are supporting APIs that can be used to provide more details to other APIs +#[derive(Debug, Default)] +pub struct Miscellaneous { + pub(crate) bearer_auth: String, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum Gateway { + Emandate, + DigitalBankMandate, +} +const LIST_BANKS_URL: &str = "https://api.paystack.co/bank"; +const LIST_COUNTRIES_URL: &str = "https://api.paystack.co/country"; +const LIST_STATES_URL: &str = "https://api.paystack.co/address_verification/states"; +#[derive(Debug, Serialize)] +pub struct ListBanksParams<'a> { + /// The country from which to obtain the list of supported banks. e.g `country=ghana` or `country=nigeria` + pub country: &'a str, + /// Flag to enable cursor pagination on the endpoint + pub use_cursor: bool, + #[serde(rename = "perPage")] + /// Specify how many records you want to retrieve per page. If not specify we use a default value of 50. + pub per_page: i64, + /// A cursor that indicates your place in the list. It can be used to fetch the next page of the list + pub next: Option<&'a str>, + /// A cursor that indicates your place in the list. It should be used to fetch the previous page of the list after an intial next request + pub previous: Option<&'a str>, + /// The gateway type of the bank. It can be one of these: [emandate, digitalbankmandate] + pub gateway: Option, + /// Type of financial channel. For Ghanaian channels, please use either **mobile_money** for mobile money channels OR **ghipps** for bank channels + #[serde(rename = "type")] + pub ttype: &'a str, + /// Any of `NGN`, `USD`, `GHS` or `ZAR` + pub currency: Option, +} + +#[derive(Debug, Serialize)] +pub struct ListProvidersParams { + /// A flag to filter for available providers + pub pay_with_bank_transfer: bool, +} + +#[derive(Debug, Serialize)] +pub struct ListStatesParams { + /// The country code of the states to list. It is gotten after the charge request. + pub country: i64, +} +impl Miscellaneous { + /// Get a list of all supported banks and their properties + pub fn list_banks(&self, params: ListBanksParams) -> Result { + let res = make_get_request(&self.bearer_auth, LIST_BANKS_URL, Some(params)); + return res; + } + + // TODO: link with dedicated nuban + /// Get a list of all providers for [][Dedicated NUBAN] + pub fn list_providers(&self, params: ListProvidersParams) -> Result { + let res = make_get_request(&self.bearer_auth, LIST_BANKS_URL, Some(params)); + return res; + } + + /// Gets a list of Countries that Paystack currently supports + pub fn list_or_search_countries(&self) -> Result { + let res = make_get_request(&self.bearer_auth, LIST_COUNTRIES_URL, None::); + return res; + } + + /// Get a list of states for a country for address verification. + pub fn list_states(&self, params: ListStatesParams) -> Result { + let res = make_get_request(&self.bearer_auth, LIST_STATES_URL, Some(params)); + return res; + } +} diff --git a/src/paystack/refund.rs b/src/paystack/refund.rs index 249e19b..64f46d0 100644 --- a/src/paystack/refund.rs +++ b/src/paystack/refund.rs @@ -53,10 +53,9 @@ impl Refunds { return res; } /// Get details of a refund on your integration. - /// takes a parameter reference. An transaction reference for the refund you want to fetch + /// takes a parameter reference. A transaction reference for the refund you want to fetch pub fn fetch_refund(&self, reference: &str) -> Result { - let url = format!("{}/{}", REFUND_URL.to_owned(), reference); - println!("{}", url); + let url = format!("{}/{}", REFUND_URL, reference); let res = make_get_request(&self.bearer_auth, &url, None::); return res; } diff --git a/src/paystack/settlements.rs b/src/paystack/settlements.rs index 150f4d8..9d49aba 100644 --- a/src/paystack/settlements.rs +++ b/src/paystack/settlements.rs @@ -2,7 +2,6 @@ use crate::utils::make_get_request; use chrono::{DateTime, Local}; use reqwest::blocking::Response; use serde::Serialize; -use serde_json::Value as JSON; #[derive(Debug, Serialize)] pub struct FetchSettlementsBody<'a> { /// Specify how many records you want to retrieve per page. If not specify we use a default value of 50. diff --git a/src/paystack/subaccounts.rs b/src/paystack/subaccounts.rs index 67e7abe..1ac667c 100644 --- a/src/paystack/subaccounts.rs +++ b/src/paystack/subaccounts.rs @@ -3,7 +3,7 @@ use chrono::{DateTime, Utc}; use reqwest::blocking::Response; use serde::Serialize; use serde_json::Value; -use std::{collections::HashMap, fmt::Debug}; +use std::fmt::Debug; const SUBACCOUNT_URL: &str = "https://api.paystack.co/subaccount"; diff --git a/src/paystack/transfer_recipients.rs b/src/paystack/transfer_recipients.rs new file mode 100644 index 0000000..6d1dc26 --- /dev/null +++ b/src/paystack/transfer_recipients.rs @@ -0,0 +1,133 @@ +use chrono::{DateTime, Local}; +use reqwest::blocking::Response; +use serde::Serialize; +use serde_json::Value as JSON; + +use crate::{ + prelude::Currency, + utils::{make_get_request, make_request, REQUEST}, +}; + +const TRANSFER_RECIPIENT_URL: &str = "https://api.paystack.co/transferrecipient"; +/// The Transfer Recipients API allows you create and manage beneficiaries that you send money to +/// +/// ```text +/// - 💡 Feature Availability +/// This feature is only available to businesses in Nigeria and Ghana. +/// ``` +#[derive(Debug, Default)] +pub struct TransferRecipients { + pub(crate) bearer_auth: String, +} + +#[derive(Debug, Serialize)] +pub struct CreateTransferRecipientBody<'a> { + #[serde(rename = "type")] + /// Recipient Type (Only `nuban` at this time) + pub recipient_type: &'a str, + /// A name for the recipient + pub name: &'a str, + /// Required if type is `nuban` + pub account_number: &'a str, + // TODO: link to module that implements list of Bank codes + /// Required if type is nuban. You can get the [CreatePaymentPagesBody][list of Bank Codes] by calling the List Banks endpoint. + pub bank_code: &'a str, + /// A description for this plan + pub description: Option<&'a str>, + /// Currency for the account receiving the transfer + pub currency: Option, + /// An authorization code from a previous transaction + pub authorization_code: Option<&'a str>, + /// Store additional information about your recipient in a structured format, JSON + pub metadata: Option, +} + +/// A list of transfer recipient object. Each object should contain `type`, `name`, and `bank_code`. +/// Any [create_transfer_recipient][Create Transfer Recipient] param can also be passed. +#[derive(Debug, Serialize)] +pub struct BulkCreateTransferRecipient { + pub batch: Vec, +} + +#[derive(Debug, Serialize)] +pub struct ListTransferRecipientsParams { + /// Specify how many records you want to retrieve per page. If not specify we use a default value of 50. + #[serde(rename = "perPage")] + pub per_page: Option, + /// Specify exactly what page you want to retrieve. If not specify we use a default value of 1. + pub page: Option, + /// A timestamp from which to start listing product e.g. 2016-09-24T00:00:05.000Z, 2016-09-21 + pub from: Option>, + /// A timestamp at which to stop listing product e.g. 2016-09-24T00:00:05.000Z, 2016-09-21 + pub to: Option>, +} + +#[derive(Debug, Serialize)] +pub struct UpdateTransferRecipient<'a> { + /// A name for the recipient + pub name: &'a str, + /// Email address of the recipient + pub email: &'a str, + /// A description for this plan + pub description: &'a str, +} +impl TransferRecipients { + /// Creates a new recipient. A duplicate account number will lead to the retrieval of the existing record. + pub fn create_transfer_recipient( + &self, + body: CreateTransferRecipientBody, + ) -> Result { + let res = make_request( + &self.bearer_auth, + TRANSFER_RECIPIENT_URL, + Some(body), + REQUEST::POST, + ); + return res; + } + + ///Create multiple transfer recipients in batches. A duplicate account number will lead to the retrieval of the existing record. + pub fn bulk_create_transfer_recipient( + &self, + body: BulkCreateTransferRecipient, + ) -> Result { + let url = format!("{}/bulk", TRANSFER_RECIPIENT_URL); + let res = make_request(&self.bearer_auth, &url, Some(body), REQUEST::POST); + return res; + } + + /// List transfer recipients available on your integration + pub fn list_transfer_recipients( + &self, + params: ListTransferRecipientsParams, + ) -> Result { + let res = make_get_request(&self.bearer_auth, TRANSFER_RECIPIENT_URL, Some(params)); + return res; + } + + /// Fetch the details of a transfer recipient + /// - id_or_code: An ID or code for the recipient whose details you want to receive. + pub fn fetch_transfer_recipient(&self, id_or_code: &str) -> Result { + let url = format!("{}/{}", TRANSFER_RECIPIENT_URL, id_or_code); + let res = make_get_request(&self.bearer_auth, &url, None::); + return res; + } + + /// Update an existing recipient. An duplicate account number will lead to the retrieval of the existing record. + pub fn update_transfer_recipient( + &self, + body: UpdateTransferRecipient, + id_or_code: &str, + ) -> Result { + let url = format!("{}/{}", TRANSFER_RECIPIENT_URL, id_or_code); + let res = make_request(&self.bearer_auth, &url, Some(body), REQUEST::PUT); + return res; + } + + /// Deletes a transfer recipient (sets the transfer recipient to inactive) + pub fn delete_transfer_recipient(&self, id_or_code: &str) -> Result { + let url = format!("{}/{}", TRANSFER_RECIPIENT_URL, id_or_code); + let res = make_request(&self.bearer_auth, &url, None::, REQUEST::DELETE); + return res; + } +} diff --git a/src/paystack/transfers.rs b/src/paystack/transfers.rs new file mode 100644 index 0000000..b4f3db0 --- /dev/null +++ b/src/paystack/transfers.rs @@ -0,0 +1,112 @@ +use chrono::{DateTime, Local}; +use reqwest::blocking::Response; +use serde::Serialize; +use serde_json::Value as JSON; + +use crate::{ + prelude::Currency, + utils::{make_get_request, make_request, REQUEST}, +}; + +const TRANSFERS_URL: &str = "https://api.paystack.co/transfer"; +/// The Transfers API allows you automate sending money on your integration +/// - 💡 Feature Availability +/// This feature is only available to businesses in Nigeria and Ghana. +#[derive(Debug, Default)] +pub struct Transfers { + pub(crate) bearer_auth: String, +} + +#[derive(Debug, Serialize)] +pub struct InitiateTransferBody<'a> { + /// Where should we transfer from. Only `balance` for now + pub source: &'a str, + /// Amount to transfer in kobo if currency is NGN and pesewas if currency is GHS. + pub amount: i64, + /// Code for transfer recipient + pub recipient: &'a str, + /// The reason for the transfer + pub reason: Option<&'a str>, + /// Specify the currency of the transfer. Defaults to NGN + pub currency: Option, + /// If specified, the field should be a unique identifier (in lowercase) for the object. + /// Only -,_ and alphanumeric characters allowed. + pub reference: Option<&'a str>, +} + +#[derive(Debug, Serialize)] +pub struct InitiateBulkTransferBody<'a> { + /// The transfer code you want to finalize + pub source: &'a str, + /// A list of transfer object. Each object should contain `amount`, `recipient`, and `reference` + pub transfers: Vec, +} + +#[derive(Debug, Serialize)] +pub struct FinalizeTransferBody<'a> { + /// The transfer code you want to finalize + pub transfer_code: &'a str, + /// OTP sent to business phone to verify transfer + pub otp: &'a str, +} + +#[derive(Debug, Serialize)] +pub struct ListTransfersParams<'a> { + /// Specify how many records you want to retrieve per page. If not specify we use a default value of 50. + #[serde(rename = "perPage")] + pub per_page: Option, + /// Specify exactly what page you want to retrieve. If not specify we use a default value of 1. + pub page: Option, + /// Filter by customer ID. + pub customer: &'a str, + /// A timestamp from which to start listing product e.g. 2016-09-24T00:00:05.000Z, 2016-09-21 + pub from: Option>, + /// A timestamp at which to stop listing product e.g. 2016-09-24T00:00:05.000Z, 2016-09-21 + pub to: Option>, +} + +impl Transfers { + /// Status of transfer object returned will be `pending` if OTP is disabled. + /// In the event that an OTP is required, status will read `otp`. + pub fn initiate_transfers(&self, body: InitiateTransferBody) -> Result { + let res = make_request(&self.bearer_auth, TRANSFERS_URL, Some(body), REQUEST::POST); + return res; + } + + /// Finalize an initiated transfer + pub fn finalize_transfer(&self, body: FinalizeTransferBody) -> Result { + let url = format!("{}/finalize_transfer", TRANSFERS_URL); + let res = make_request(&self.bearer_auth, &url, Some(body), REQUEST::POST); + return res; + } + + /// You need to disable the Transfers OTP requirement to use this endpoint. + pub fn initiate_bulk_transfer( + &self, + body: InitiateBulkTransferBody, + ) -> Result { + let url = format!("{}/bulk", TRANSFERS_URL); + let res = make_request(&self.bearer_auth, &url, Some(body), REQUEST::POST); + return res; + } + + /// List the transfers made on your integration. + pub fn list_transfers(&self, params: ListTransfersParams) -> Result { + let res = make_get_request(&self.bearer_auth, TRANSFERS_URL, Some(params)); + return res; + } + + /// Get details of a transfer on your integration. + pub fn fetch_transfer(&self, id_or_code: &str) -> Result { + let url = format!("{}/{}", TRANSFERS_URL, id_or_code); + let res = make_get_request(&self.bearer_auth, &url, None::); + return res; + } + + /// Verify the status of a transfer on your integration. + pub fn verify_transfer(&self, reference: &str) -> Result { + let url = format!("{}/verify/{}", TRANSFERS_URL, reference); + let res = make_get_request(&self.bearer_auth, &url, None::); + return res; + } +} diff --git a/src/paystack/transfers_control.rs b/src/paystack/transfers_control.rs new file mode 100644 index 0000000..878a931 --- /dev/null +++ b/src/paystack/transfers_control.rs @@ -0,0 +1,79 @@ +use reqwest::blocking::Response; +use serde::Serialize; + +use crate::utils::{make_get_request, make_request, REQUEST}; + +const TRANSFERS_CONTROL_URL: &str = "https://api.paystack.co/balance"; + +/// The Transfers Control API allows you manage settings of your transfers +#[derive(Debug, Default)] +pub struct TransfersControl { + pub(crate) bearer_auth: String, +} + +#[derive(Debug, Serialize)] +pub struct ResendTransfersOTPBody<'a> { + /// Transfer code + pub transfer_code: &'a str, + /// Either `resend_otp` or `transfer` + pub reason: &'a str, +} +#[derive(Debug, Serialize)] +pub struct FinalizeDisableTransferOTPBody<'a> { + /// OTP sent to business phone to verify disabling OTP requirement + pub otp: &'a str, +} +impl TransfersControl { + /// Fetch the available balance on your integration + pub fn check_balance(&self) -> Result { + let res = make_get_request(&self.bearer_auth, TRANSFERS_CONTROL_URL, None::); + return res; + } + + /// Fetch all pay-ins and pay-outs that occured on your integration + pub fn fetch_balance_ledger(&self) -> Result { + let res = make_get_request(&self.bearer_auth, TRANSFERS_CONTROL_URL, None::); + return res; + } + + /// Generates a new OTP and sends to customer in the event they are having trouble receiving one. + /// - 💡 Feature Availability + /// This feature is only available to businesses in Nigeria and Ghana. + pub fn resend_transfers_otp(&self, body: ResendTransfersOTPBody) -> Result { + let url = format!("{}/resend_otp", TRANSFERS_CONTROL_URL); + let res = make_request(&self.bearer_auth, &url, Some(body), REQUEST::POST); + return res; + } + + /// This is used in the event that you want to be able to complete transfers programmatically without use of OTPs. + /// No arguments required. You will get an OTP to complete the request. + /// - 💡 Feature Availability + /// This feature is only available to businesses in Nigeria and Ghana. + pub fn disable_transfers_otp(&self) -> Result { + let url = format!("{}/disable_otp", TRANSFERS_CONTROL_URL); + let res = make_request(&self.bearer_auth, &url, None::, REQUEST::POST); + return res; + } + + /// Finalize the request to disable OTP on your transfers. + /// - 💡 Feature Availability + /// This feature is only available to businesses in Nigeria and Ghana. + pub fn finalize_disable_transfers_otp( + &self, + body: FinalizeDisableTransferOTPBody, + ) -> Result { + let url = format!("{}/disable_otp_finalize", TRANSFERS_CONTROL_URL); + let res = make_request(&self.bearer_auth, &url, Some(body), REQUEST::POST); + return res; + } + + /// In the event that a customer wants to stop being able to complete transfers programmatically, this endpoint helps turn OTP requirement back on. + /// No arguments required. + /// - 💡 Feature Availability + /// This feature is only available to businesses in Nigeria and Ghana. + pub fn enable_transfers_otp(&self) -> Result { + let url = format!("{}/enable_otp", TRANSFERS_CONTROL_URL); + let res = make_request(&self.bearer_auth, &url, None::, REQUEST::POST); + return res; + } +} diff --git a/src/paystack/verification.rs b/src/paystack/verification.rs new file mode 100644 index 0000000..092f638 --- /dev/null +++ b/src/paystack/verification.rs @@ -0,0 +1,68 @@ +use crate::utils::{make_get_request, make_request, REQUEST}; +use reqwest::blocking::Response; +use serde::Serialize; + +/// The Verification API allows you perform KYC processes. +/// +/// *NB: due to regulations, Paystack has disabled this service.* +/// - 💡 Feature Availability +/// This feature is only available to businesses in Nigeria. +#[derive(Debug, Default)] +pub struct Verification { + pub(crate) bearer_auth: String, +} + +#[derive(Debug, Serialize)] +pub struct VerifyBVNBody<'a> { + /// Bank Account Number + pub account_number: &'a str, + // TODO: link to list of bank here. + /// You can get the [][list of banks] codes by calling the List Bank endpoint + pub bank_code: i64, + /// 11 digits Bank Verification Number + pub bvn: &'a str, + /// Customer's First Name + pub first_name: Option<&'a str>, + /// Customer's Middle Name + pub middle_name: Option<&'a str>, + /// Customer's Last Name + pub last_name: Option<&'a str>, +} + +#[derive(Debug, Serialize)] +pub struct ResolveAcctNoBody<'a> { + /// Bank Account Number + pub account_number: &'a str, + // TODO: link to list of bank here. + /// You can get the [][list of banks] codes by calling the List Bank endpoint + pub bank_code: i64, +} +const VERIFY_BVN_MATCH_URL: &str = "https://api.paystack.co/bvn/match"; +const RESOLVE_ACCT_NO_URL: &str = "https://api.paystack.co/bank/resolve"; +const RESOLVE_CARD_BIN: &str = "https://api.paystack.co/decision/bin"; + +impl Verification { + /// Check if an account number and BVN are linked + pub fn verify_bvn_match(&self, body: VerifyBVNBody) -> Result { + let res = make_request( + &self.bearer_auth, + VERIFY_BVN_MATCH_URL, + Some(body), + REQUEST::POST, + ); + return res; + } + + /// Confirm an account belongs to the right customer + pub fn resolve_account_number(&self, params: ResolveAcctNoBody) -> Result { + let res = make_get_request(&self.bearer_auth, RESOLVE_ACCT_NO_URL, Some(params)); + return res; + } + + /// Get more information about a customer's card + pub fn resolve_card_bin(&self, bin: &str) -> Result { + let url = format!("{}/{}", RESOLVE_CARD_BIN, bin); + let res = make_get_request(&self.bearer_auth, &url, None::); + return res; + } +}