payway 0.1.0

Unofficial Rust SDK for ABA PayWay Payment Gateway
Documentation
//! Credentials on File Types

use serde::{Deserialize, Serialize};

use super::ApiStatus;
use crate::client::PayWayClient;
use crate::utils::hash::{encode_base64, encode_json_base64, generate_hash};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LinkAccountRequest {
    pub req_time: String,
    pub merchant_id: String,
    pub return_param: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub return_url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub return_deeplink: Option<String>,
    pub hash: String,
}

impl LinkAccountRequest {
    pub fn new(client: &PayWayClient, return_param: impl Into<String>) -> Self {
        let req_time = PayWayClient::generate_request_time();
        let merchant_id = client.merchant_id().to_string();
        let return_param = return_param.into();

        let fields_for_hash = vec![&merchant_id, &req_time, ""];

        let hash = generate_hash(client.api_key(), &fields_for_hash);

        Self {
            req_time,
            merchant_id,
            return_param,
            return_url: None,
            return_deeplink: None,
            hash,
        }
    }

    pub fn with_return_url(mut self, url: impl Into<String>) -> Self {
        self.return_url = Some(encode_base64(url.into()));
        self
    }

    pub fn with_return_deeplink(
        mut self,
        deeplink: serde_json::Value,
    ) -> Result<Self, crate::error::PayWayError> {
        self.return_deeplink = Some(encode_json_base64(&deeplink)?);
        Ok(self)
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LinkAccountResponse {
    pub deeplink: Option<String>,
    pub qr_string: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub qr_image: Option<String>,
    pub expire_in: i64,
    pub status: ApiStatus,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PurchaseTokenRequest {
    pub merchant_id: String,
    pub tran_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ctid: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub pwt: Option<String>,
    pub firstname: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub lastname: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub email: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub phone: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(rename = "type")]
    pub transaction_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub items: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub return_url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub custom_fields: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub return_params: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub payout: Option<String>,
    pub amount: f64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub shipping: Option<f64>,
    pub req_time: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub currency: Option<String>,
    pub hash: String,
}

impl PurchaseTokenRequest {
    pub fn builder() -> PurchaseTokenRequestBuilder {
        PurchaseTokenRequestBuilder::new()
    }
}

pub struct PurchaseTokenRequestBuilder {
    merchant_id: String,
    tran_id: String,
    ctid: Option<String>,
    pwt: Option<String>,
    firstname: String,
    lastname: Option<String>,
    email: Option<String>,
    phone: Option<String>,
    transaction_type: Option<String>,
    items: Option<Vec<serde_json::Value>>,
    return_url: Option<String>,
    custom_fields: Option<serde_json::Value>,
    return_params: Option<String>,
    payout: Option<Vec<serde_json::Value>>,
    amount: f64,
    shipping: Option<f64>,
    req_time: String,
    currency: Option<String>,
}

impl PurchaseTokenRequestBuilder {
    pub fn new() -> Self {
        Self {
            merchant_id: String::new(),
            tran_id: String::new(),
            ctid: None,
            pwt: None,
            firstname: String::new(),
            lastname: None,
            email: None,
            phone: None,
            transaction_type: None,
            items: None,
            return_url: None,
            custom_fields: None,
            return_params: None,
            payout: None,
            amount: 0.0,
            shipping: None,
            req_time: PayWayClient::generate_request_time(),
            currency: None,
        }
    }

    pub fn ctid(mut self, ctid: impl Into<String>) -> Self {
        self.ctid = Some(ctid.into());
        self
    }

    pub fn token(mut self, ctid: impl Into<String>, pwt: impl Into<String>) -> Self {
        self.ctid = Some(ctid.into());
        self.pwt = Some(pwt.into());
        self
    }

    pub fn transaction_id(mut self, id: impl Into<String>) -> Self {
        self.tran_id = id.into();
        self
    }

    pub fn first_name(mut self, name: impl Into<String>) -> Self {
        self.firstname = name.into();
        self
    }

    pub fn last_name(mut self, name: impl Into<String>) -> Self {
        self.lastname = Some(name.into());
        self
    }

    pub fn email(mut self, email: impl Into<String>) -> Self {
        self.email = Some(email.into());
        self
    }

    pub fn phone(mut self, phone: impl Into<String>) -> Self {
        self.phone = Some(phone.into());
        self
    }

    pub fn amount(mut self, amount: f64) -> Self {
        self.amount = amount;
        self
    }

    pub fn currency(mut self, currency: impl Into<String>) -> Self {
        self.currency = Some(currency.into());
        self
    }

    pub fn build_with_client(
        self,
        client: &PayWayClient,
    ) -> Result<PurchaseTokenRequest, crate::error::PayWayError> {
        let api_key = client.api_key();

        let merchant_id = if self.merchant_id.is_empty() {
            client.merchant_id().to_string()
        } else {
            self.merchant_id
        };

        let items_b64 = match &self.items {
            Some(items) => Some(encode_json_base64(items)?),
            None => None,
        };

        let payout_b64 = match &self.payout {
            Some(items) => Some(encode_json_base64(items)?),
            None => None,
        };

        let custom_fields_b64 = match &self.custom_fields {
            Some(f) => Some(encode_json_base64(f)?),
            None => None,
        };

        let amount_str = self.amount.to_string();
        let shipping_str = self
            .shipping
            .clone()
            .map(|s| s.to_string())
            .unwrap_or_default();

        let fields_for_hash = vec![
            &self.req_time,
            &merchant_id,
            &self.tran_id,
            &amount_str,
            items_b64.as_deref().unwrap_or(""),
            &shipping_str,
            self.ctid.as_deref().unwrap_or(""),
            self.pwt.as_deref().unwrap_or(""),
            &self.firstname,
            self.lastname.as_deref().unwrap_or(""),
            self.email.as_deref().unwrap_or(""),
            self.phone.as_deref().unwrap_or(""),
            self.transaction_type.as_deref().unwrap_or(""),
            self.return_url.as_deref().unwrap_or(""),
            self.currency.as_deref().unwrap_or(""),
            custom_fields_b64.as_deref().unwrap_or(""),
            self.return_params.as_deref().unwrap_or(""),
            payout_b64.as_deref().unwrap_or(""),
        ];

        let hash = generate_hash(api_key, &fields_for_hash);

        Ok(PurchaseTokenRequest {
            merchant_id,
            tran_id: self.tran_id,
            ctid: self.ctid,
            pwt: self.pwt,
            firstname: self.firstname,
            lastname: self.lastname,
            email: self.email,
            phone: self.phone,
            transaction_type: self.transaction_type,
            items: items_b64,
            return_url: self.return_url,
            custom_fields: custom_fields_b64,
            return_params: self.return_params,
            payout: payout_b64,
            amount: self.amount,
            shipping: self.shipping,
            req_time: self.req_time,
            currency: self.currency,
            hash,
        })
    }
}

impl Default for PurchaseTokenRequestBuilder {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PurchaseTokenResponse {
    pub tran_id: String,
    pub payment_status: PaymentStatusResponse,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentStatusResponse {
    pub status: String,
    pub code: String,
    pub description: String,
    pub pw_tran_id: String,
}