pay_u 0.1.9

PayU Rest API wrapper
Documentation
//! Objects used to send requests to PayU

use serde::{Deserialize, Serialize};

use crate::credit::Credit;

#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Refund {
    pub(crate) description: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) amount: Option<super::Price>,
}

impl Refund {
    pub fn new<Description>(description: Description, amount: Option<super::Price>) -> Self
    where
        Description: Into<String>,
    {
        Self {
            description: description.into(),
            amount,
        }
    }

    pub fn description(&self) -> &str {
        &self.description
    }

    pub fn amount(&self) -> Option<super::Price> {
        self.amount
    }
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct OrderCreate {
    /// ID of an order used in merchant system. Order identifier assigned by
    /// the merchant. It enables merchants to find a specific order
    /// in their system. This value must be unique within a single
    /// POS.
    pub(crate) ext_order_id: Option<String>,
    /// URL to which web hook will be send. It's important to return 200 to
    /// all notifications.
    ///
    /// All notifications are send as POST with JSON payload
    ///
    /// Notifications are sent immediately after a payment status changes.
    /// If the notification is not received by the Shop application,
    /// it will be sent again in accordance with the table below:
    ///
    /// | Attempt | Time |
    /// |---------|------|
    /// | 1 | immediately |
    /// | 2 | 1 minute |
    /// | 3 | 2 minutes |
    /// | 4 | 5 minutes |
    /// | 5 | 10 minutes |
    /// | 6 | 30 minutes |
    /// | 7 | 1 hour |
    /// | 8 | 2 hours |
    /// | 9 | 3 hours |
    /// | 10| 6 hours |
    /// | 11| 9 hours |
    /// | 12| 12 hours |
    /// | 13| 15 hours |
    /// | 14| 18 hours |
    /// | 15| 21 hours |
    /// | 16| 24 hours |
    /// | 17| 36 hours |
    /// | 18| 48 hours |
    /// | 19| 60 hours |
    /// | 20| 72 hours |
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) notify_url: Option<String>,
    /// Address for redirecting the customer after payment is commenced. If
    /// the payment has not been authorized, error=501 parameter
    /// will be added. Please note that no decision regarding
    /// payment status should be made depending on the presence or
    /// lack of this parameter (to get payment status, wait for
    /// notification or retrieve order details).
    ///
    /// IMPORTANT: the address must be compliant with the structure below:
    /// <img src="https://developers.payu.com/images/continueUrlStructure_en.png" />
    ///
    /// Please keep in mind:
    /// * accepted schemas are http and https,
    /// * such elements as port, path, query and fragment are optional,
    /// * query values must be encoded.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) continue_url: Option<String>,
    /// Payer’s IP address, e.g. 123.123.123.123. Note: 0.0.0.0 is not
    /// accepted.
    pub(crate) customer_ip: String,
    /// Secret pos ip. This is connected to PayU account
    #[serde(
        serialize_with = "crate::serialize::serialize_newtype",
        deserialize_with = "crate::deserialize::deserialize_i32_newtype"
    )]
    pub(crate) merchant_pos_id: super::MerchantPosId,
    /// Transaction description
    pub(crate) description: String,
    /// 3 characters currency identifier, ex. PLN
    pub(crate) currency_code: String,
    /// Total price of the order in pennies (e.g. 1000 is 10.00 EUR).
    /// Applies also to currencies without subunits (e.g. 1000 is 10
    /// HUF).
    #[serde(
        serialize_with = "crate::serialize::serialize_i32",
        deserialize_with = "crate::deserialize::deserialize_i32"
    )]
    pub(crate) total_amount: super::Price,
    /// @see [crate::Buyer]
    pub(crate) buyer: Option<super::Buyer>,
    /// List of products
    pub(crate) products: Vec<super::Product>,
    #[serde(skip_serializing)]
    pub(crate) order_create_date: Option<String>,
    /// Duration for the validity of an order (in seconds), during which
    /// time payment must be made. Default value 86400.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) validity_time: Option<u16>,
    ///  Additional description of the order.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) additional_description: Option<String>,
    /// Text visible on the PayU payment page (max. 80 chars).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) visible_description: Option<String>,
    /// Payment recipient name followed by payment description (order ID,
    /// ticket number etc) visible on card statement (max. 22
    /// chars). The name should be easy to recognize by the
    /// cardholder (e.g "shop.com 124343"). If field
    /// is not provided, static name configured by PayU will be used.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) statement_description: Option<String>,
    #[serde(flatten, skip_serializing_if = "Option::is_none")]
    pub(crate) muct: Option<super::muct::MultiUseCartToken>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) credit: Option<Credit>,
}

impl OrderCreate {
    pub fn build<CustomerIp, Currency, Description>(
        buyer: super::Buyer,
        customer_ip: CustomerIp,
        currency: Currency,
        description: Description,
    ) -> super::Result<Self>
    where
        CustomerIp: Into<String>,
        Currency: Into<String>,
        Description: Into<String>,
    {
        let customer_ip = customer_ip.into();
        if &customer_ip == "0.0.0.0" {
            return Err(super::Error::CustomerIp);
        }
        Ok(Self {
            ext_order_id: None,
            notify_url: None,
            continue_url: None,
            customer_ip,
            merchant_pos_id: 0.into(),
            description: description.into(),
            currency_code: currency.into(),
            total_amount: 0,
            buyer: Some(buyer),
            products: Vec::new(),
            order_create_date: None,
            validity_time: None,
            additional_description: None,
            visible_description: None,
            statement_description: None,
            muct: None,
            credit: None,
        })
    }

    /// ID of an order used in merchant system. Order identifier assigned by
    /// the merchant. It enables merchants to find a specific order
    /// in their system. This value must be unique within a single
    /// POS.
    pub fn with_ext_order_id<S: Into<String>>(mut self, ext_order_id: S) -> Self {
        self.ext_order_id = Some(ext_order_id.into());
        self
    }

    /// Duration for the validity of an order (in seconds), during which
    /// time payment must be made. Default value 86400.
    pub fn with_validity_time(mut self, validity_time: u16) -> Self {
        self.validity_time = Some(validity_time);
        self
    }

    pub fn with_multi_use_token(
        mut self,
        recurring: super::muct::Recurring,
        card_on_file: super::muct::CardOnFile,
    ) -> Self {
        self.muct = Some(super::muct::MultiUseCartToken {
            recurring,
            card_on_file,
        });
        self
    }

    pub fn with_products<Products>(mut self, products: Products) -> Self
    where
        Products: Iterator<Item = super::Product>,
    {
        self.products.extend(products);
        self.total_amount = self
            .products
            .iter()
            .fold(0, |agg, p| agg + (p.quantity as i32 * p.unit_price as i32));
        self
    }

    pub fn with_product(mut self, product: super::Product) -> Self {
        self.products.push(product);
        self.total_amount = self
            .products
            .iter()
            .fold(0, |agg, p| agg + (p.quantity as i32 * p.unit_price as i32));
        self
    }

    /// Description of the an order.
    ///
    /// > This method will override initial description!
    pub fn with_description<Description>(mut self, desc: Description) -> Self
    where
        Description: Into<String>,
    {
        self.description = String::from(desc.into().trim());
        self
    }

    /// Additional description of the order.
    pub fn with_additional_description<S: Into<String>>(
        mut self,
        additional_description: S,
    ) -> Self {
        self.additional_description = Some(additional_description.into());
        self
    }

    /// Text visible on the PayU payment page (max. 80 chars).
    pub fn with_visible_description(mut self, visible_description: &str) -> Self {
        let visible_description = if visible_description.len() > 60 {
            &visible_description[..60]
        } else {
            visible_description
        };
        self.visible_description = Some(String::from(visible_description));
        self
    }

    /// Payment recipient name followed by payment description (order ID,
    /// ticket number etc) visible on card statement (max. 22
    /// chars). The name should be easy to recognize by the
    /// cardholder (e.g "shop.com 124343"). If field
    /// is not provided, static name configured by PayU will be used.
    pub fn with_statement_description<Description>(mut self, desc: Description) -> Self
    where
        Description: Into<String>,
    {
        self.statement_description = Some(String::from(desc.into().trim()));
        self
    }

    /// Add url to which PayU will be able to send http request with payment
    /// status updates
    ///
    /// All requests from PayU should receive 200 response!
    ///
    /// See more [crate::res::Order::notify_url]
    pub fn with_notify_url<NotifyUrl>(mut self, notify_url: NotifyUrl) -> Self
    where
        NotifyUrl: Into<String>,
    {
        self.notify_url = Some(notify_url.into());
        self
    }

    /// Address for redirecting the customer after payment is commenced. If
    /// the payment has not been authorized, error=501 parameter
    /// will be added. Please note that no decision regarding
    /// payment status should be made depending on the presence or
    /// lack of this parameter (to get payment status, wait for
    /// notification or retrieve order details).
    pub fn with_continue_url<ContinueUrl>(mut self, continue_url: ContinueUrl) -> Self
    where
        ContinueUrl: Into<String>,
    {
        self.continue_url = Some(continue_url.into());
        self
    }

    /// Section containing credit data. This information is not required,
    /// but it is strongly recommended to include it. Otherwise the
    /// buyer will be prompted to provide missing data on provider
    /// page when payment by Installments or Pay later.
    pub fn with_credit(mut self, credit: Credit) -> Self {
        self.credit = Some(credit);
        self
    }

    /// URL to which web hook will be send. It's important to return 200 to
    /// all notifications.
    ///
    /// All notifications are send as POST with JSON payload
    ///
    /// Notifications are sent immediately after a payment status changes.
    /// If the notification is not received by the Shop application,
    /// it will be sent again in accordance with the table below:
    ///
    /// | Attempt | Time |
    /// |---------|------|
    /// | 1 | immediately |
    /// | 2 | 1 minute |
    /// | 3 | 2 minutes |
    /// | 4 | 5 minutes |
    /// | 5 | 10 minutes |
    /// | 6 | 30 minutes |
    /// | 7 | 1 hour |
    /// | 8 | 2 hours |
    /// | 9 | 3 hours |
    /// | 10| 6 hours |
    /// | 11| 9 hours |
    /// | 12| 12 hours |
    /// | 13| 15 hours |
    /// | 14| 18 hours |
    /// | 15| 21 hours |
    /// | 16| 24 hours |
    /// | 17| 36 hours |
    /// | 18| 48 hours |
    /// | 19| 60 hours |
    /// | 20| 72 hours |
    pub fn notify_url(&self) -> &Option<String> {
        &self.notify_url
    }

    /// Customer IP address from http request received from client
    pub fn customer_ip(&self) -> &String {
        &self.customer_ip
    }

    pub fn merchant_pos_id(&self) -> super::MerchantPosId {
        self.merchant_pos_id
    }

    pub fn description(&self) -> &String {
        &self.description
    }

    pub fn currency_code(&self) -> &String {
        &self.currency_code
    }

    pub fn total_amount(&self) -> &super::Price {
        &self.total_amount
    }

    pub fn buyer(&self) -> &Option<super::Buyer> {
        &self.buyer
    }

    pub fn products(&self) -> &[super::Product] {
        &self.products
    }

    pub fn order_create_date(&self) -> &Option<String> {
        &self.order_create_date
    }

    pub(crate) fn with_merchant_pos_id(mut self, merchant_pos_id: super::MerchantPosId) -> Self {
        self.merchant_pos_id = merchant_pos_id;
        self
    }
}