payway 0.1.0

Unofficial Rust SDK for ABA PayWay Payment Gateway
Documentation
//! QR Payment Types

use serde::{Deserialize, Serialize};

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

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GenerateQrRequest {
    pub req_time: String,
    pub merchant_id: String,
    pub tran_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub first_name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub last_name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub email: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub phone: Option<String>,
    pub amount: String,
    pub currency: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub purchase_type: Option<String>,
    pub payment_option: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub items: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub callback_url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub return_deeplink: 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>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub lifetime: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub qr_image_template: Option<String>,
    pub hash: String,
}

impl GenerateQrRequest {
    pub fn builder() -> GenerateQrRequestBuilder {
        GenerateQrRequestBuilder::new()
    }
}

pub struct GenerateQrRequestBuilder {
    req_time: String,
    merchant_id: String,
    tran_id: String,
    first_name: Option<String>,
    last_name: Option<String>,
    email: Option<String>,
    phone: Option<String>,
    amount: f64,
    currency: String,
    purchase_type: Option<String>,
    payment_option: PaymentOption,
    items: Option<Vec<QrItem>>,
    callback_url: Option<String>,
    return_deeplink: Option<String>,
    custom_fields: Option<serde_json::Value>,
    return_params: Option<String>,
    payout: Option<serde_json::Value>,
    lifetime: Option<i32>,
    qr_image_template: Option<String>,
}

impl GenerateQrRequestBuilder {
    pub fn new() -> Self {
        Self {
            req_time: PayWayClient::generate_request_time(),
            merchant_id: String::new(),
            tran_id: String::new(),
            first_name: None,
            last_name: None,
            email: None,
            phone: None,
            amount: 0.0,
            currency: "USD".to_string(),
            purchase_type: None,
            payment_option: PaymentOption::AbapayKhqr,
            items: None,
            callback_url: None,
            return_deeplink: None,
            custom_fields: None,
            return_params: None,
            payout: None,
            lifetime: None,
            qr_image_template: None,
        }
    }

    pub fn merchant_id(mut self, id: impl Into<String>) -> Self {
        self.merchant_id = id.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.first_name = Some(name.into());
        self
    }

    pub fn last_name(mut self, name: impl Into<String>) -> Self {
        self.last_name = 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: Currency) -> Self {
        self.currency = currency.as_str().to_string();
        self
    }

    pub fn payment_option(mut self, option: PaymentOption) -> Self {
        self.payment_option = option;
        self
    }

    pub fn items(mut self, items: Vec<QrItem>) -> Self {
        self.items = Some(items);
        self
    }

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

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

    pub fn payout(mut self, payout: serde_json::Value) -> Self {
        self.payout = Some(payout);
        self
    }

    pub fn lifetime(mut self, minutes: i32) -> Self {
        self.lifetime = Some(minutes);
        self
    }

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

    pub fn build_with_client(
        self,
        client: &PayWayClient,
    ) -> Result<GenerateQrRequest, 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(p) => Some(encode_json_base64(p)?),
            None => None,
        };

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

        let amount_str = match self.currency.as_str() {
            "KHR" => format!("{}", self.amount.round() as i64),
            _ => format!("{:.2}", self.amount),
        };
        let lifetime_str = self.lifetime.map(|l| l.to_string()).unwrap_or_default();
        let payment_option_str = self.payment_option.as_str().to_string();

        let fields_for_hash = vec![
            &self.req_time,
            &merchant_id,
            &self.tran_id,
            &amount_str,
            items_b64.as_deref().unwrap_or(""),
            self.first_name.as_deref().unwrap_or(""),
            self.last_name.as_deref().unwrap_or(""),
            self.email.as_deref().unwrap_or(""),
            self.phone.as_deref().unwrap_or(""),
            self.purchase_type.as_deref().unwrap_or(""),
            &payment_option_str,
            self.callback_url.as_deref().unwrap_or(""),
            self.return_deeplink.as_deref().unwrap_or(""),
            &self.currency,
            custom_fields_b64.as_deref().unwrap_or(""),
            self.return_params.as_deref().unwrap_or(""),
            payout_b64.as_deref().unwrap_or(""),
            &lifetime_str,
            self.qr_image_template.as_deref().unwrap_or(""),
        ];

        let hash = generate_hash(api_key, &fields_for_hash);

        Ok(GenerateQrRequest {
            req_time: self.req_time,
            merchant_id,
            tran_id: self.tran_id,
            first_name: self.first_name,
            last_name: self.last_name,
            email: self.email,
            phone: self.phone,
            amount: amount_str,
            currency: self.currency,
            purchase_type: self.purchase_type,
            payment_option: self.payment_option.as_str().to_string(),
            items: items_b64,
            callback_url: self.callback_url,
            return_deeplink: self.return_deeplink,
            custom_fields: custom_fields_b64,
            return_params: self.return_params,
            payout: payout_b64,
            lifetime: self.lifetime,
            qr_image_template: self.qr_image_template,
            hash,
        })
    }
}

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

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GenerateQrResponse {
    pub status: ApiStatus,
    pub amount: f64,
    #[serde(skip_serializing_if = "Option::is_none", rename = "qrString")]
    pub qr_string: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none", rename = "qrImage")]
    pub qr_image: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub abapay_deeplink: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub app_store: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub play_store: Option<String>,
    pub currency: String,
}