worldline 0.1.0

A library for integrating with Worldline's Bambora API.
Documentation
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::time::Duration;
mod error;
pub use error::Error;
mod meta;
pub use meta::Meta;
mod create_order;
mod currency;
pub use currency::Currency;
mod payment_method;
pub use payment_method::PaymentMethod;
mod order;
pub use order::Order;

pub mod prelude {
    pub use super::{Currency, Gateway, Order, PaymentMethod};
}

const BASE_URL: &str = "https://api.v1.checkout.bambora.com";

pub struct Gateway {
    client: reqwest::Client,
    _access_token: String,
    _merchant_number: String,
    _secret_token: String,
}

impl Gateway {
    pub async fn new(
        access_token: String,
        merchant_number: String,
        secret_token: String,
        timeout: Option<Duration>,
    ) -> Result<Gateway, Error> {
        let mut headers = reqwest::header::HeaderMap::new();
        headers.insert(
            "Content-Type",
            reqwest::header::HeaderValue::from_static("application/json"),
        );
        headers.insert(
            "Accept",
            reqwest::header::HeaderValue::from_static("application/json"),
        );

        let auth_header_value = format!(
            "Basic {}",
            base64::encode_config(
                format!("{}@{}:{}", access_token, merchant_number, secret_token),
                base64::URL_SAFE_NO_PAD
            )
        );
        let auth_header = match reqwest::header::HeaderValue::from_str(&auth_header_value) {
            Ok(header) => header,
            Err(err) => {
                return Err(Error::Unspecified(format!(
                    "could not create auth header ({})",
                    err.to_string()
                )))
            }
        };

        headers.insert("Authorization", auth_header);

        let timeout = match timeout {
            Some(t) => t,
            None => Duration::new(60, 0),
        };

        let client = match reqwest::ClientBuilder::new()
            .default_headers(headers)
            .https_only(true)
            .timeout(timeout)
            .build()
        {
            Ok(r) => r,
            Err(err) => {
                return Err(Error::Unspecified(format!(
                    "could not create reqwest client ({})",
                    err.to_string()
                )))
            }
        };

        let c = Gateway {
            client,
            _access_token: access_token,
            _merchant_number: merchant_number,
            _secret_token: secret_token,
        };
        Ok(c)
    }

    async fn send<'a, T: DeserializeOwned>(
        &self,
        url: &str,
        body: impl Serialize,
    ) -> Result<T, Error> {
        let res = match self.client.post(url).json(&body).send().await {
            Ok(r) => r,
            Err(err) => {
                return Err(Error::NetworkError(format!(
                    "could not send request ({})",
                    err.to_string()
                )))
            }
        };

        let status = res.status();
        let text = res
            .text()
            .await
            .unwrap_or_else(|_| String::from("Could not retrieve body text."));

        if status.as_u16() < 200 || status.as_u16() >= 300 {
            #[derive(Deserialize, Debug, Clone)]
            #[serde(rename_all = "camelCase")]
            struct ApiError {
                pub meta: Option<Meta>,
            }

            let api_error: ApiError =
                serde_json::from_str(&text).unwrap_or_else(|_| ApiError { meta: None });

            let meta = match api_error.meta {
                Some(m) => m,
                None => {
                    return Err(Error::ApiError(
                        status.as_u16().to_string(),
                        "unknown".to_string(),
                        None,
                        None,
                        text,
                    ))
                }
            };

            let action = match meta.action {
                Some(m) => m,
                None => {
                    return Err(Error::ApiError(
                        status.as_u16().to_string(),
                        "unknown".to_string(),
                        None,
                        None,
                        text,
                    ))
                }
            };

            let code = match action.code {
                Some(m) => m,
                None => {
                    return Err(Error::ApiError(
                        status.as_u16().to_string(),
                        "unknown".to_string(),
                        None,
                        None,
                        text,
                    ))
                }
            };

            let source = match action.source {
                Some(m) => m,
                None => {
                    return Err(Error::ApiError(
                        status.as_u16().to_string(),
                        "unknown".to_string(),
                        None,
                        None,
                        text,
                    ))
                }
            };

            let (enduser_message, merchant_message) = match meta.message {
                Some(m) => (m.enduser, m.merchant),
                None => (None, None),
            };

            return Err(Error::ApiError(
                code,
                source,
                enduser_message,
                merchant_message,
                text,
            ));
        }

        let body: T = match serde_json::from_str(&text) {
            Ok(r) => r,
            Err(err) => {
                return Err(Error::SerializationError(format!(
                    "could not deserialize response ({}): {}",
                    err, text
                )))
            }
        };
        Ok(body)
    }

    async fn get<'a, T: DeserializeOwned>(&self, url: &str) -> Result<T, Error> {
        let res = match self.client.get(url).send().await {
            Ok(r) => r,
            Err(err) => {
                return Err(Error::NetworkError(format!(
                    "could not send request ({})",
                    err
                )))
            }
        };

        let status = res.status();
        let text = res
            .text()
            .await
            .unwrap_or_else(|_| String::from("Could not retrieve body text."));

        if status.as_u16() < 200 || status.as_u16() >= 300 {
            #[derive(Deserialize, Debug, Clone)]
            #[serde(rename_all = "camelCase")]
            struct ApiError {
                pub meta: Option<Meta>,
            }

            let api_error: ApiError =
                serde_json::from_str(&text).unwrap_or_else(|_| ApiError { meta: None });

            let meta = match api_error.meta {
                Some(m) => m,
                None => {
                    return Err(Error::ApiError(
                        status.as_u16().to_string(),
                        "unknown".to_string(),
                        None,
                        None,
                        text,
                    ))
                }
            };

            let action = match meta.action {
                Some(m) => m,
                None => {
                    return Err(Error::ApiError(
                        status.as_u16().to_string(),
                        "unknown".to_string(),
                        None,
                        None,
                        text,
                    ))
                }
            };

            let code = match action.code {
                Some(m) => m,
                None => {
                    return Err(Error::ApiError(
                        status.as_u16().to_string(),
                        "unknown".to_string(),
                        None,
                        None,
                        text,
                    ))
                }
            };

            let source = match action.source {
                Some(m) => m,
                None => {
                    return Err(Error::ApiError(
                        status.as_u16().to_string(),
                        "unknown".to_string(),
                        None,
                        None,
                        text,
                    ))
                }
            };

            let (enduser_message, merchant_message) = match meta.message {
                Some(m) => (m.enduser, m.merchant),
                None => (None, None),
            };

            return Err(Error::ApiError(
                code,
                source,
                enduser_message,
                merchant_message,
                text,
            ));
        }

        let body: T = match serde_json::from_str(&text) {
            Ok(r) => r,
            Err(err) => {
                return Err(Error::SerializationError(format!(
                    "could not deserialize response ({}): {}",
                    err, text
                )))
            }
        };
        Ok(body)
    }
}