flutterwave_v3/
v3_client.rs

1use async_symm_crypto::AsyncEncryption;
2use flutterwave_models::{
3    api_responses::ResponseType,
4    common::payload::Payload,fwcall::ToFwCall,
5    charge::{ach::AchReq, bank_transfer::BankTransferReq, direct_charge::CardChargeReq},
6    encrypted_payload::EncryptedPayload,
7    errors::FWaveError,
8    payment_plans::{CancelPlanReq, CreatePlanReq, GetPlanReq, GetPlansReq, UpdatePlanReq},
9    preauthorization::{
10        capture_preauth_charge::CapturePreAuthChargeReq,
11        refund_preauth_charge::RefundPreAuthChargeReq, void_preauth_charge::VoidPreAuthChargeReq,
12    },
13    transactions::{
14        fetch_refunded_trans::{FetchMultiRefundedTransReq, FetchRefundedTransReq},
15        get_transactions::GetTransactionsReq,
16        query_trans_fees::QueryTransFeesReq,
17        refund_trans::RefundTransactionReq,
18        resend_failed_webhook::ResendFailedWebhookReq,
19        transaction_verify::{VerifyTransByIdReq, VerifyTransByTxRefReq},
20        view_trans_timeline::ViewTransTimelineReq,
21    },
22    validate_charge::ValidateChargeReq,
23    virtual_acct_number::{
24        create_virtual_account::{VirtualAcctBulkCreationReq, VirtualAcctCreationReq},
25        get_bulk_virtual_acct_details::BulkVirtualAcctDetailsReq,
26    },
27};
28use reqwest::{
29    header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE},
30    Url,
31};
32use std::str::FromStr;
33
34macro_rules! generate_client_methods {
35    (
36        $(
37            ($t:ty, $func_name:ident)
38        )+
39    ) => {
40        $(
41            #[allow(unused)]
42            pub async fn $func_name(
43                &self,
44                req: $t,
45            ) -> Result<ResponseType<<$t as ToFwCall>::ApiResponse>, FWaveError> {
46                self.make_v3_request(req).await
47            }
48        )+
49    };
50}
51
52static BASE_URL: &str = "https://api.flutterwave.com/";
53
54pub struct FWV3Client<'a> {
55    #[allow(unused)]
56    enc_key: &'a str,
57    #[allow(unused)]
58    public: &'a str,
59    #[allow(unused)]
60    secret: &'a str,
61    client: reqwest::Client,
62    crypt: AsyncEncryption<'a>,
63}
64
65impl<'a> FWV3Client<'a> {
66    pub fn new(secret_key: &'a str, public_key: &'a str, encryption_key: &'a str) -> Self {
67        let mut default_headers = HeaderMap::new();
68        default_headers.append(CONTENT_TYPE, HeaderValue::from_static("application/json"));
69        default_headers.append(ACCEPT, HeaderValue::from_static("application/json"));
70
71        let client = reqwest::ClientBuilder::new()
72            .https_only(true)
73            .default_headers(default_headers)
74            .build()
75            .unwrap();
76
77        Self {
78            secret: secret_key,
79            public: public_key,
80            enc_key: encryption_key,
81            client,
82            crypt: AsyncEncryption::new(
83                openssl::symm::Cipher::des_ede3_ecb(),
84                secret_key.as_bytes(),
85                None,
86            ),
87        }
88    }
89
90    async fn make_v3_request<'b, C: ToFwCall<'b> + 'b>(
91        &'b self,
92        call: C,
93    ) -> Result<ResponseType<C::ApiResponse>, FWaveError> {
94        let call = call.get_call();
95
96        let mut request = self
97            .client
98            .request(
99                call.method.clone(),
100                Url::from_str(BASE_URL)
101                    .unwrap()
102                    .join(call.path.as_ref())
103                    .unwrap(),
104            )
105            .bearer_auth(self.secret)
106            .header(CONTENT_TYPE, "application/json")
107            .header(ACCEPT, "application/json");
108
109        if [reqwest::Method::PUT, reqwest::Method::POST].contains(&call.method) {
110            match &call.payload {
111                Some(Payload::Plain(pload)) => {
112                    request = request.json(&pload);
113                }
114                Some(Payload::ToEncrypt(pload)) => {
115                    let to_enc_bytes = serde_json::to_string(pload)?.as_bytes().to_vec();
116                    let encrypted_bytes = self.crypt.encrypt(&to_enc_bytes).await.unwrap();
117                    request = request.json(&EncryptedPayload::new(openssl::base64::encode_block(
118                        &encrypted_bytes,
119                    )));
120                }
121                None => {}
122            }
123        }
124
125        let response = request.send().await?;
126        let status = response.status();
127        Ok(response.json::<ResponseType<C::ApiResponse>>().await?.replace_stat_code(status))
128    }
129
130    generate_client_methods!(
131        // Charge
132        (CardChargeReq, initiate_card_charge)
133        (BankTransferReq, initiate_bank_transfer)
134        (AchReq, initiate_ach_payment)
135        
136        // Verify Trans
137        (VerifyTransByIdReq, verify_trans_by_id)
138        (VerifyTransByTxRefReq, verify_trans_by_txref)
139        
140        // Validate Charge
141        (ValidateChargeReq, validate_charge)
142        
143        // PreAuth
144        (CapturePreAuthChargeReq, capture_preauth_charge)
145        (VoidPreAuthChargeReq, void_preauth_charge)
146        (RefundPreAuthChargeReq, refund_preauth_charge)
147    
148        // Virtual Acct
149        (BulkVirtualAcctDetailsReq, get_bulk_virtual_acct_details)
150        (VirtualAcctCreationReq, create_virtual_acct_no)
151        (VirtualAcctBulkCreationReq, create_bulk_virtual_acct_no)
152    
153        // Transactions
154        (FetchRefundedTransReq, fetch_refunded_transactions)
155        (FetchMultiRefundedTransReq, fetch_multi_refunded_transactions)
156        (GetTransactionsReq, get_transaction)
157        (QueryTransFeesReq, query_transaction_fees)
158        (RefundTransactionReq, refund_transaction)
159        (ResendFailedWebhookReq, resend_failed_webhook)
160        (ViewTransTimelineReq, view_trans_imeline)
161    
162        // Payment Plans
163        (CreatePlanReq, create_payment_plan)
164        (GetPlanReq, get_payment_plan)
165        (GetPlansReq, get_payment_plans)
166        (CancelPlanReq, cancel_payment_plan)
167        (UpdatePlanReq, update_payment_plan)
168    );
169
170}