Add full refund and order details
This commit is contained in:
parent
4886a76a76
commit
1f2ba82519
@ -42,10 +42,11 @@ impl PaymentManager {
|
|||||||
merchant_pos_id: MerchantPosId,
|
merchant_pos_id: MerchantPosId,
|
||||||
) -> Result<Self>
|
) -> Result<Self>
|
||||||
where
|
where
|
||||||
ClientId: Into<String>,
|
ClientId: Into<pay_u::ClientId>,
|
||||||
ClientSecret: Into<String>,
|
ClientSecret: Into<pay_u::ClientSecret>,
|
||||||
{
|
{
|
||||||
let mut client = pay_u::Client::new(client_id, client_secret, merchant_pos_id);
|
let mut client =
|
||||||
|
pay_u::Client::new(client_id.into(), client_secret.into(), merchant_pos_id);
|
||||||
client.authorize().await?;
|
client.authorize().await?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
client: Arc::new(Mutex::new(client)),
|
client: Arc::new(Mutex::new(client)),
|
||||||
|
@ -11,6 +11,7 @@ use actix_web::{App, HttpServer};
|
|||||||
use gumdrop::Options;
|
use gumdrop::Options;
|
||||||
use jemallocator::Jemalloc;
|
use jemallocator::Jemalloc;
|
||||||
use password_hash::SaltString;
|
use password_hash::SaltString;
|
||||||
|
use pay_u::MerchantPosId;
|
||||||
use validator::{validate_email, validate_length};
|
use validator::{validate_email, validate_length};
|
||||||
|
|
||||||
use crate::actors::{database, email_manager, order_manager, payment_manager, token_manager};
|
use crate::actors::{database, email_manager, order_manager, payment_manager, token_manager};
|
||||||
@ -189,7 +190,8 @@ async fn server(opts: ServerOpts) -> Result<()> {
|
|||||||
std::env::var("PAYU_CLIENT_SECRET").expect("Missing PAYU_CLIENT_SECRET env");
|
std::env::var("PAYU_CLIENT_SECRET").expect("Missing PAYU_CLIENT_SECRET env");
|
||||||
let merchant_id = std::env::var("PAYU_CLIENT_MERCHANT_ID")
|
let merchant_id = std::env::var("PAYU_CLIENT_MERCHANT_ID")
|
||||||
.expect("Missing PAYU_CLIENT_MERCHANT_ID env")
|
.expect("Missing PAYU_CLIENT_MERCHANT_ID env")
|
||||||
.parse()
|
.parse::<i32>()
|
||||||
|
.map(MerchantPosId::from)
|
||||||
.expect("Variable PAYU_CLIENT_MERCHANT_ID must be number");
|
.expect("Variable PAYU_CLIENT_MERCHANT_ID must be number");
|
||||||
payment_manager::PaymentManager::build(client_id, client_secret, merchant_id)
|
payment_manager::PaymentManager::build(client_id, client_secret, merchant_id)
|
||||||
.await
|
.await
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "pay_u"
|
name = "pay_u"
|
||||||
description = "PayU Rest API wrapper"
|
description = "PayU Rest API wrapper"
|
||||||
version = "0.1.4"
|
version = "0.1.5"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ cargo add pay_u
|
|||||||
async fn usage() {
|
async fn usage() {
|
||||||
let client_id = std::env::var("PAYU_CLIENT_ID").unwrap();
|
let client_id = std::env::var("PAYU_CLIENT_ID").unwrap();
|
||||||
let client_secret = std::env::var("PAYU_CLIENT_SECRET").unwrap();
|
let client_secret = std::env::var("PAYU_CLIENT_SECRET").unwrap();
|
||||||
let merchant_id = std::env::var("PAYU_CLIENT_MERCHANT_ID").unwrap().parse().unwrap();
|
let merchant_id = std::env::var("PAYU_CLIENT_MERCHANT_ID").unwrap().parse::<i32>().map(MerchantPosId::from).unwrap();
|
||||||
let mut client = Client::new(client_id, client_secret, merchant_id);
|
let mut client = Client::new(client_id, client_secret, merchant_id);
|
||||||
client.authorize().await.expect("Invalid credentials");
|
client.authorize().await.expect("Invalid credentials");
|
||||||
|
|
||||||
|
@ -9,6 +9,15 @@ where
|
|||||||
d.deserialize_string(I32Visitor)
|
d.deserialize_string(I32Visitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn deserialize_i32_newtype<'de, N: From<i32>, D>(
|
||||||
|
d: D,
|
||||||
|
) -> std::result::Result<N, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
d.deserialize_string(I32Visitor).map(N::from)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn deserialize_u32<'de, D>(d: D) -> std::result::Result<u32, D::Error>
|
pub(crate) fn deserialize_u32<'de, D>(d: D) -> std::result::Result<u32, D::Error>
|
||||||
where
|
where
|
||||||
D: serde::Deserializer<'de>,
|
D: serde::Deserializer<'de>,
|
||||||
|
346
pay_u/src/lib.rs
346
pay_u/src/lib.rs
@ -6,6 +6,8 @@ use std::sync::Arc;
|
|||||||
use reqwest::redirect;
|
use reqwest::redirect;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::res::OrdersInfo;
|
||||||
|
|
||||||
macro_rules! get_client {
|
macro_rules! get_client {
|
||||||
($self:expr) => {{
|
($self:expr) => {{
|
||||||
#[cfg(feature = "single-client")]
|
#[cfg(feature = "single-client")]
|
||||||
@ -19,6 +21,8 @@ macro_rules! get_client {
|
|||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub static SUCCESS: &str = "SUCCESS";
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Client is not authorized. No bearer token available")]
|
#[error("Client is not authorized. No bearer token available")]
|
||||||
@ -41,7 +45,9 @@ pub enum Error {
|
|||||||
CreateOrder,
|
CreateOrder,
|
||||||
#[error("Failed to fetch order transactions")]
|
#[error("Failed to fetch order transactions")]
|
||||||
OrderTransactions,
|
OrderTransactions,
|
||||||
#[error("PayU rejected to create order. {status_code:?}")]
|
#[error("Failed to fetch order details")]
|
||||||
|
OrderDetails,
|
||||||
|
#[error("PayU rejected to create order with status {status_code:?}")]
|
||||||
CreateFailed {
|
CreateFailed {
|
||||||
status_code: String,
|
status_code: String,
|
||||||
status_desc: Option<String>,
|
status_desc: Option<String>,
|
||||||
@ -49,7 +55,7 @@ pub enum Error {
|
|||||||
severity: Option<String>,
|
severity: Option<String>,
|
||||||
code_literal: Option<CodeLiteral>,
|
code_literal: Option<CodeLiteral>,
|
||||||
},
|
},
|
||||||
#[error("PayU rejected to perform refund. {status_code:?}")]
|
#[error("PayU rejected to perform refund with status {status_code:?}")]
|
||||||
RefundFailed {
|
RefundFailed {
|
||||||
status_code: String,
|
status_code: String,
|
||||||
status_desc: Option<String>,
|
status_desc: Option<String>,
|
||||||
@ -57,6 +63,24 @@ pub enum Error {
|
|||||||
severity: Option<String>,
|
severity: Option<String>,
|
||||||
code_literal: Option<CodeLiteral>,
|
code_literal: Option<CodeLiteral>,
|
||||||
},
|
},
|
||||||
|
#[error("PayU rejected order details request with status {status_code:?}")]
|
||||||
|
OrderDetailsFailed {
|
||||||
|
status_code: String,
|
||||||
|
status_desc: Option<String>,
|
||||||
|
code: Option<String>,
|
||||||
|
severity: Option<String>,
|
||||||
|
code_literal: Option<CodeLiteral>,
|
||||||
|
},
|
||||||
|
#[error("PayU rejected order transactions details request with status {status_code:?}")]
|
||||||
|
OrderTransactionsFailed {
|
||||||
|
status_code: String,
|
||||||
|
status_desc: Option<String>,
|
||||||
|
code: Option<String>,
|
||||||
|
severity: Option<String>,
|
||||||
|
code_literal: Option<CodeLiteral>,
|
||||||
|
},
|
||||||
|
#[error("PayU returned order details but without any order")]
|
||||||
|
NoOrderInDetails,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
@ -80,6 +104,57 @@ impl OrderId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// PayU internal order id
|
||||||
|
#[derive(
|
||||||
|
Debug,
|
||||||
|
serde::Deserialize,
|
||||||
|
serde::Serialize,
|
||||||
|
Copy,
|
||||||
|
Clone,
|
||||||
|
derive_more::Display,
|
||||||
|
derive_more::From,
|
||||||
|
derive_more::Deref,
|
||||||
|
derive_more::Constructor,
|
||||||
|
)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub struct MerchantPosId(pub i32);
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Debug,
|
||||||
|
Clone,
|
||||||
|
serde::Deserialize,
|
||||||
|
serde::Serialize,
|
||||||
|
derive_more::Display,
|
||||||
|
derive_more::From,
|
||||||
|
derive_more::Deref,
|
||||||
|
)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub struct ClientId(pub String);
|
||||||
|
|
||||||
|
impl ClientId {
|
||||||
|
pub fn new<S: Into<String>>(id: S) -> Self {
|
||||||
|
Self(id.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Debug,
|
||||||
|
Clone,
|
||||||
|
serde::Deserialize,
|
||||||
|
serde::Serialize,
|
||||||
|
derive_more::Display,
|
||||||
|
derive_more::From,
|
||||||
|
derive_more::Deref,
|
||||||
|
)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub struct ClientSecret(pub String);
|
||||||
|
|
||||||
|
impl ClientSecret {
|
||||||
|
pub fn new<S: Into<String>>(id: S) -> Self {
|
||||||
|
Self(id.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// PayU payment status.
|
/// PayU payment status.
|
||||||
///
|
///
|
||||||
/// Each payment is initially Pending and can change according to following
|
/// Each payment is initially Pending and can change according to following
|
||||||
@ -212,7 +287,6 @@ impl Buyer {
|
|||||||
|
|
||||||
pub type Price = i32;
|
pub type Price = i32;
|
||||||
pub type Quantity = u32;
|
pub type Quantity = u32;
|
||||||
pub type MerchantPosId = i32;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@ -280,8 +354,8 @@ pub struct OrderCreateRequest {
|
|||||||
customer_ip: String,
|
customer_ip: String,
|
||||||
/// Secret pos ip. This is connected to PayU account
|
/// Secret pos ip. This is connected to PayU account
|
||||||
#[serde(
|
#[serde(
|
||||||
serialize_with = "serialize::serialize_i32",
|
serialize_with = "serialize::serialize_newtype",
|
||||||
deserialize_with = "deserialize::deserialize_i32"
|
deserialize_with = "deserialize::deserialize_i32_newtype"
|
||||||
)]
|
)]
|
||||||
merchant_pos_id: MerchantPosId,
|
merchant_pos_id: MerchantPosId,
|
||||||
/// Transaction description
|
/// Transaction description
|
||||||
@ -316,7 +390,7 @@ impl OrderCreateRequest {
|
|||||||
Self {
|
Self {
|
||||||
notify_url: None,
|
notify_url: None,
|
||||||
customer_ip: customer_ip.into(),
|
customer_ip: customer_ip.into(),
|
||||||
merchant_pos_id: 0,
|
merchant_pos_id: 0.into(),
|
||||||
description: String::from(""),
|
description: String::from(""),
|
||||||
currency_code: currency.into(),
|
currency_code: currency.into(),
|
||||||
total_amount: 0,
|
total_amount: 0,
|
||||||
@ -544,7 +618,7 @@ impl Status {
|
|||||||
/// assert_eq!(status.is_success(), true);
|
/// assert_eq!(status.is_success(), true);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn is_success(&self) -> bool {
|
pub fn is_success(&self) -> bool {
|
||||||
self.status_code.as_str() == "SUCCESS"
|
self.status_code.as_str() == SUCCESS
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns http status
|
/// Returns http status
|
||||||
@ -565,6 +639,13 @@ impl Status {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Prop {
|
||||||
|
pub name: String,
|
||||||
|
pub value: String,
|
||||||
|
}
|
||||||
|
|
||||||
pub mod res {
|
pub mod res {
|
||||||
use crate::{OrderId, Refund, Status};
|
use crate::{OrderId, Refund, Status};
|
||||||
|
|
||||||
@ -588,27 +669,27 @@ pub mod res {
|
|||||||
pub refund: Option<Refund>,
|
pub refund: Option<Refund>,
|
||||||
pub status: Status,
|
pub status: Status,
|
||||||
}
|
}
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct TransactionPayMethod {
|
pub struct TransactionPayMethod {
|
||||||
pub value: String,
|
pub value: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize, Debug)]
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
pub enum CardProfile {
|
pub enum CardProfile {
|
||||||
Consumer,
|
Consumer,
|
||||||
Business,
|
Business,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize, Debug)]
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
pub enum CardClassification {
|
pub enum CardClassification {
|
||||||
Debit,
|
Debit,
|
||||||
Credit,
|
Credit,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct TransactionCartData {
|
pub struct TransactionCartData {
|
||||||
/// // "543402******4014",
|
/// // "543402******4014",
|
||||||
@ -632,40 +713,89 @@ pub mod res {
|
|||||||
pub first_transaction_id: String,
|
pub first_transaction_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
/// > Installment proposal on the Sandbox environment is not related to the
|
||||||
|
/// > order amount and always returns data for 480 PLN.
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct TransactionCardInstallmentProposal {}
|
pub struct TransactionCardInstallmentProposal {
|
||||||
|
/// Example: "5aff3ba8-0c37-4da1-ba4a-4ff24bcc2eed"
|
||||||
|
pub proposal_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct TransactionCart {
|
pub struct TransactionCart {
|
||||||
pub cart_data: TransactionCartData,
|
pub cart_data: TransactionCartData,
|
||||||
pub card_installment_proposal: TransactionCardInstallmentProposal,
|
pub card_installment_proposal: TransactionCardInstallmentProposal,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Transaction {
|
pub struct Transaction {
|
||||||
pub pay_method: TransactionPayMethod,
|
pub pay_method: TransactionPayMethod,
|
||||||
pub payment_flow: String,
|
pub payment_flow: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Transactions {
|
pub struct Transactions {
|
||||||
pub transactions: Vec<Transaction>,
|
pub transactions: Vec<Transaction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Order {
|
||||||
|
/// Example: "{orderId}",
|
||||||
|
pub order_id: super::OrderId,
|
||||||
|
/// Example: "358766",
|
||||||
|
pub ext_order_id: Option<String>,
|
||||||
|
/// Example: "2014-10-27T14:58:17.443+01:00",
|
||||||
|
pub order_create_date: String,
|
||||||
|
/// Example: "http://localhost/OrderNotify/",
|
||||||
|
pub notify_url: Option<String>,
|
||||||
|
/// Example: "127.0.0.1",
|
||||||
|
pub customer_ip: String,
|
||||||
|
/// Example: "145227",
|
||||||
|
pub merchant_pos_id: String,
|
||||||
|
/// Example: "New order",
|
||||||
|
pub description: String,
|
||||||
|
/// Example: "PLN",
|
||||||
|
pub currency_code: String,
|
||||||
|
/// Example: "3200",
|
||||||
|
pub total_amount: String,
|
||||||
|
/// Example: "NEW",
|
||||||
|
pub status: String,
|
||||||
|
/// Example: `[{"name":"Product1","unitPrice":"1000","quantity":"1"}]`
|
||||||
|
pub products: Vec<super::Product>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct OrdersInfo {
|
||||||
|
pub orders: Vec<Order>,
|
||||||
|
pub status: super::Status,
|
||||||
|
pub properties: Option<Vec<crate::Prop>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct OrderInfo {
|
||||||
|
pub order: Order,
|
||||||
|
pub status: super::Status,
|
||||||
|
pub properties: Option<Vec<crate::Prop>>,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct RefundRequest {
|
pub struct RefundRequest {
|
||||||
description: String,
|
description: String,
|
||||||
amount: Price,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
amount: Option<Price>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RefundRequest {
|
impl RefundRequest {
|
||||||
pub fn new<Description>(description: Description, amount: Price) -> Self
|
pub fn new<Description>(description: Description, amount: Option<Price>) -> Self
|
||||||
where
|
where
|
||||||
Description: Into<String>,
|
Description: Into<String>,
|
||||||
{
|
{
|
||||||
@ -679,7 +809,7 @@ impl RefundRequest {
|
|||||||
&self.description
|
&self.description
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn amount(&self) -> Price {
|
pub fn amount(&self) -> Option<Price> {
|
||||||
self.amount
|
self.amount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -709,7 +839,7 @@ pub mod notify {
|
|||||||
pub struct StatusUpdate {
|
pub struct StatusUpdate {
|
||||||
pub order: Order,
|
pub order: Order,
|
||||||
pub local_receipt_date_time: Option<String>,
|
pub local_receipt_date_time: Option<String>,
|
||||||
pub properties: Option<Vec<Prop>>,
|
pub properties: Option<Vec<super::Prop>>,
|
||||||
pub status: Option<super::Status>,
|
pub status: Option<super::Status>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -748,7 +878,7 @@ pub mod notify {
|
|||||||
/// Customer client IP address
|
/// Customer client IP address
|
||||||
pub customer_ip: String,
|
pub customer_ip: String,
|
||||||
/// Secret pos ip. This is connected to PayU account
|
/// Secret pos ip. This is connected to PayU account
|
||||||
#[serde(deserialize_with = "deserialize::deserialize_i32")]
|
#[serde(deserialize_with = "deserialize::deserialize_i32_newtype")]
|
||||||
pub merchant_pos_id: super::MerchantPosId,
|
pub merchant_pos_id: super::MerchantPosId,
|
||||||
/// Transaction description
|
/// Transaction description
|
||||||
pub description: String,
|
pub description: String,
|
||||||
@ -768,20 +898,13 @@ pub mod notify {
|
|||||||
pub pay_method: Option<super::PayMethod>,
|
pub pay_method: Option<super::PayMethod>,
|
||||||
pub status: super::PaymentStatus,
|
pub status: super::PaymentStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct Prop {
|
|
||||||
pub name: String,
|
|
||||||
pub value: String,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
sandbox: bool,
|
sandbox: bool,
|
||||||
merchant_pos_id: MerchantPosId,
|
merchant_pos_id: MerchantPosId,
|
||||||
client_id: String,
|
client_id: ClientId,
|
||||||
client_secret: String,
|
client_secret: ClientSecret,
|
||||||
bearer: Option<String>,
|
bearer: Option<String>,
|
||||||
bearer_expires_at: chrono::DateTime<chrono::Utc>,
|
bearer_expires_at: chrono::DateTime<chrono::Utc>,
|
||||||
#[cfg(feature = "single-client")]
|
#[cfg(feature = "single-client")]
|
||||||
@ -790,23 +913,19 @@ pub struct Client {
|
|||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
/// Create new PayU client
|
/// Create new PayU client
|
||||||
pub fn new<ClientId, ClientSecret>(
|
pub fn new(
|
||||||
client_id: ClientId,
|
client_id: ClientId,
|
||||||
client_secret: ClientSecret,
|
client_secret: ClientSecret,
|
||||||
merchant_pos_id: MerchantPosId,
|
merchant_pos_id: MerchantPosId,
|
||||||
) -> Self
|
) -> Self {
|
||||||
where
|
|
||||||
ClientId: Into<String>,
|
|
||||||
ClientSecret: Into<String>,
|
|
||||||
{
|
|
||||||
#[cfg(feature = "single-client")]
|
#[cfg(feature = "single-client")]
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
bearer: None,
|
bearer: None,
|
||||||
sandbox: false,
|
sandbox: false,
|
||||||
merchant_pos_id,
|
merchant_pos_id,
|
||||||
client_id: client_id.into(),
|
client_id,
|
||||||
client_secret: client_secret.into(),
|
client_secret,
|
||||||
bearer_expires_at: chrono::Utc::now(),
|
bearer_expires_at: chrono::Utc::now(),
|
||||||
client: Arc::new(Self::build_client()),
|
client: Arc::new(Self::build_client()),
|
||||||
}
|
}
|
||||||
@ -817,8 +936,8 @@ impl Client {
|
|||||||
bearer: None,
|
bearer: None,
|
||||||
sandbox: false,
|
sandbox: false,
|
||||||
merchant_pos_id,
|
merchant_pos_id,
|
||||||
client_id: client_id.into(),
|
client_id,
|
||||||
client_secret: client_secret.into(),
|
client_secret,
|
||||||
bearer_expires_at: chrono::Utc::now(),
|
bearer_expires_at: chrono::Utc::now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -849,9 +968,9 @@ impl Client {
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use pay_u::{Client, OrderCreateRequest, Product, Buyer};
|
/// # use pay_u::*;
|
||||||
/// async fn pay() {
|
/// async fn pay() {
|
||||||
/// let mut client = Client::new("145227", "12f071174cb7eb79d4aac5bc2f07563f", 300746)
|
/// let mut client = Client::new(ClientId::new("145227"), ClientSecret::new("12f071174cb7eb79d4aac5bc2f07563f"), MerchantPosId::new(300746))
|
||||||
/// .sandbox()
|
/// .sandbox()
|
||||||
/// .with_bearer("d9a4536e-62ba-4f60-8017-6053211d3f47", 2000);
|
/// .with_bearer("d9a4536e-62ba-4f60-8017-6053211d3f47", 2000);
|
||||||
/// let res = client
|
/// let res = client
|
||||||
@ -906,7 +1025,7 @@ impl Client {
|
|||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
Error::CreateOrder
|
Error::CreateOrder
|
||||||
})?;
|
})?;
|
||||||
if res.status.status_code != "Success" {
|
if !res.status.is_success() {
|
||||||
let Status {
|
let Status {
|
||||||
status_code,
|
status_code,
|
||||||
status_desc,
|
status_desc,
|
||||||
@ -950,18 +1069,29 @@ impl Client {
|
|||||||
/// ```
|
/// ```
|
||||||
/// # use pay_u::*;
|
/// # use pay_u::*;
|
||||||
/// async fn perform_refund() {
|
/// async fn perform_refund() {
|
||||||
/// let mut client = Client::new("145227", "12f071174cb7eb79d4aac5bc2f07563f", 300746)
|
/// let mut client = Client::new(ClientId::new("145227"), ClientSecret::new("12f071174cb7eb79d4aac5bc2f07563f"), MerchantPosId::new(300746))
|
||||||
/// .with_bearer("d9a4536e-62ba-4f60-8017-6053211d3f47", 2000)
|
/// .with_bearer("d9a4536e-62ba-4f60-8017-6053211d3f47", 2000)
|
||||||
/// .sandbox();
|
/// .sandbox();
|
||||||
/// let res = client
|
/// let res = client
|
||||||
/// .partial_refund(
|
/// .refund(
|
||||||
/// OrderId::new("H9LL64F37H160126GUEST000P01"),
|
/// OrderId::new("H9LL64F37H160126GUEST000P01"),
|
||||||
/// RefundRequest::new("Refund", 1000),
|
/// RefundRequest::new("Refund", Some(1000)),
|
||||||
|
/// )
|
||||||
|
/// .await;
|
||||||
|
/// }
|
||||||
|
/// async fn full_refund() {
|
||||||
|
/// let mut client = Client::new(ClientId::new("145227"), ClientSecret::new("12f071174cb7eb79d4aac5bc2f07563f"), MerchantPosId::new(300746))
|
||||||
|
/// .with_bearer("d9a4536e-62ba-4f60-8017-6053211d3f47", 2000)
|
||||||
|
/// .sandbox();
|
||||||
|
/// let res = client
|
||||||
|
/// .refund(
|
||||||
|
/// OrderId::new("H9LL64F37H160126GUEST000P01"),
|
||||||
|
/// RefundRequest::new("Refund", None),
|
||||||
/// )
|
/// )
|
||||||
/// .await;
|
/// .await;
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub async fn partial_refund(
|
pub async fn refund(
|
||||||
&mut self,
|
&mut self,
|
||||||
order_id: OrderId,
|
order_id: OrderId,
|
||||||
refund: RefundRequest,
|
refund: RefundRequest,
|
||||||
@ -987,7 +1117,7 @@ impl Client {
|
|||||||
log::error!("Invalid PayU response {e:?}");
|
log::error!("Invalid PayU response {e:?}");
|
||||||
Error::Refund
|
Error::Refund
|
||||||
})?;
|
})?;
|
||||||
if res.status.status_code.as_str() != "Success" {
|
if !res.status.is_success() {
|
||||||
let Status {
|
let Status {
|
||||||
status_code,
|
status_code,
|
||||||
status_desc,
|
status_desc,
|
||||||
@ -1006,6 +1136,66 @@ impl Client {
|
|||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Order details request. You may use it to completely remove `Order`
|
||||||
|
/// persistence and use extOrderId to connect your data with PayU data.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use pay_u::*;
|
||||||
|
/// async fn order_details() {
|
||||||
|
/// let mut client = Client::new(ClientId::new("145227"), ClientSecret::new("12f071174cb7eb79d4aac5bc2f07563f"), MerchantPosId::new(300746))
|
||||||
|
/// .with_bearer("d9a4536e-62ba-4f60-8017-6053211d3f47", 2000)
|
||||||
|
/// .sandbox();
|
||||||
|
/// let res = client
|
||||||
|
/// .order_details(OrderId::new("H9LL64F37H160126GUEST000P01"))
|
||||||
|
/// .await;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub async fn order_details(&mut self, order_id: OrderId) -> Result<res::OrderInfo> {
|
||||||
|
self.authorize().await?;
|
||||||
|
let bearer = self.bearer.as_ref().cloned().unwrap_or_default();
|
||||||
|
let path = format!("{}/orders/{}", self.base_url(), order_id);
|
||||||
|
let client = get_client!(self);
|
||||||
|
let text = client
|
||||||
|
.post(path)
|
||||||
|
.bearer_auth(bearer)
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.text()
|
||||||
|
.await?;
|
||||||
|
log::trace!("Response: {}", text);
|
||||||
|
let mut res: OrdersInfo = serde_json::from_str(&text).map_err(|e| {
|
||||||
|
log::error!("{e:?}");
|
||||||
|
Error::OrderDetails
|
||||||
|
})?;
|
||||||
|
if !res.status.is_success() {
|
||||||
|
let Status {
|
||||||
|
status_code,
|
||||||
|
status_desc,
|
||||||
|
code,
|
||||||
|
severity,
|
||||||
|
code_literal,
|
||||||
|
} = res.status;
|
||||||
|
return Err(Error::OrderDetailsFailed {
|
||||||
|
status_code,
|
||||||
|
status_desc,
|
||||||
|
code,
|
||||||
|
severity,
|
||||||
|
code_literal,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(res::OrderInfo {
|
||||||
|
order: if res.orders.is_empty() {
|
||||||
|
return Err(Error::NoOrderInDetails);
|
||||||
|
} else {
|
||||||
|
res.orders.remove(0)
|
||||||
|
},
|
||||||
|
status: res.status,
|
||||||
|
properties: res.properties,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// The transaction retrieve request message enables you to retrieve the
|
/// The transaction retrieve request message enables you to retrieve the
|
||||||
/// details of transactions created for an order.
|
/// details of transactions created for an order.
|
||||||
///
|
///
|
||||||
@ -1016,12 +1206,13 @@ impl Client {
|
|||||||
/// > transaction has been processed, the bank details may be available
|
/// > transaction has been processed, the bank details may be available
|
||||||
/// > either after few minutes or on the next business day, depending on the
|
/// > either after few minutes or on the next business day, depending on the
|
||||||
/// > bank.
|
/// > bank.
|
||||||
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use pay_u::*;
|
/// # use pay_u::*;
|
||||||
/// async fn order_transactions() {
|
/// async fn order_transactions() {
|
||||||
/// let mut client = Client::new("145227", "12f071174cb7eb79d4aac5bc2f07563f", 300746)
|
/// let mut client = Client::new(ClientId::new("145227"), ClientSecret::new("12f071174cb7eb79d4aac5bc2f07563f"), MerchantPosId::new(300746))
|
||||||
/// .with_bearer("d9a4536e-62ba-4f60-8017-6053211d3f47", 2000)
|
/// .with_bearer("d9a4536e-62ba-4f60-8017-6053211d3f47", 2000)
|
||||||
/// .sandbox();
|
/// .sandbox();
|
||||||
/// let res = client
|
/// let res = client
|
||||||
@ -1104,7 +1295,11 @@ mod tests {
|
|||||||
|
|
||||||
fn build_client() -> Client {
|
fn build_client() -> Client {
|
||||||
dotenv::dotenv().ok();
|
dotenv::dotenv().ok();
|
||||||
Client::new("145227", "12f071174cb7eb79d4aac5bc2f07563f", 300746)
|
Client::new(
|
||||||
|
ClientId::new("145227"),
|
||||||
|
ClientSecret::new("12f071174cb7eb79d4aac5bc2f07563f"),
|
||||||
|
MerchantPosId::new(300746),
|
||||||
|
)
|
||||||
.sandbox()
|
.sandbox()
|
||||||
.with_bearer("d9a4536e-62ba-4f60-8017-6053211d3f47", 999999)
|
.with_bearer("d9a4536e-62ba-4f60-8017-6053211d3f47", 999999)
|
||||||
}
|
}
|
||||||
@ -1129,22 +1324,65 @@ mod tests {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
if res.is_err() {
|
||||||
|
eprintln!("create_order res is {res:?}");
|
||||||
|
}
|
||||||
assert!(res.is_ok());
|
assert!(res.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn partial_refund() {
|
async fn partial_refund() {
|
||||||
let res = build_client()
|
let res = build_client()
|
||||||
.partial_refund(
|
.refund(
|
||||||
OrderId::new("H9LL64F37H160126GUEST000P01"),
|
OrderId::new("H9LL64F37H160126GUEST000P01"),
|
||||||
RefundRequest::new("Refund", 1000),
|
RefundRequest::new("Refund", Some(1000)),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
assert!(matches!(res, Ok(_)));
|
|
||||||
|
if res.is_err() {
|
||||||
|
eprintln!("partial_refund res is {res:?}");
|
||||||
|
}
|
||||||
|
assert!(matches!(res, Err(Error::RefundFailed { .. })));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn check_refund() {}
|
async fn full_refund() {
|
||||||
|
let res = build_client()
|
||||||
|
.refund(
|
||||||
|
OrderId::new("H9LL64F37H160126GUEST000P01"),
|
||||||
|
RefundRequest::new("Refund", None),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if res.is_err() {
|
||||||
|
eprintln!("full_refund res is {res:?}");
|
||||||
|
}
|
||||||
|
assert!(matches!(res, Err(Error::RefundFailed { .. })));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn order_details() {
|
||||||
|
let res = build_client()
|
||||||
|
.order_details(OrderId::new("H9LL64F37H160126GUEST000P01"))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if res.is_err() {
|
||||||
|
eprintln!("order_details res is {res:?}");
|
||||||
|
}
|
||||||
|
assert!(matches!(res, Err(Error::OrderDetails)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn order_transactions() {
|
||||||
|
let res = build_client()
|
||||||
|
.order_transactions(OrderId::new("H9LL64F37H160126GUEST000P01"))
|
||||||
|
.await;
|
||||||
|
if res.is_err() {
|
||||||
|
eprintln!("order_transactions res is {res:?}");
|
||||||
|
}
|
||||||
|
assert!(matches!(res, Err(Error::OrderTransactions)));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_accepted_refund_json() {
|
fn check_accepted_refund_json() {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
pub(crate) fn serialize_i32<S>(v: &i32, ser: S) -> std::result::Result<S::Ok, S::Error>
|
pub(crate) fn serialize_i32<S>(v: &i32, ser: S) -> std::result::Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
S: serde::Serializer,
|
S: serde::Serializer,
|
||||||
@ -5,6 +7,16 @@ where
|
|||||||
ser.serialize_str(&format!("{v}"))
|
ser.serialize_str(&format!("{v}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn serialize_newtype<N: Display, S>(
|
||||||
|
v: &N,
|
||||||
|
ser: S,
|
||||||
|
) -> std::result::Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
ser.serialize_str(&format!("{v}"))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn serialize_u32<S>(v: &u32, ser: S) -> std::result::Result<S::Ok, S::Error>
|
pub(crate) fn serialize_u32<S>(v: &u32, ser: S) -> std::result::Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
S: serde::Serializer,
|
S: serde::Serializer,
|
||||||
|
Loading…
Reference in New Issue
Block a user