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,
|
||||
) -> Result<Self>
|
||||
where
|
||||
ClientId: Into<String>,
|
||||
ClientSecret: Into<String>,
|
||||
ClientId: Into<pay_u::ClientId>,
|
||||
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?;
|
||||
Ok(Self {
|
||||
client: Arc::new(Mutex::new(client)),
|
||||
|
@ -11,6 +11,7 @@ use actix_web::{App, HttpServer};
|
||||
use gumdrop::Options;
|
||||
use jemallocator::Jemalloc;
|
||||
use password_hash::SaltString;
|
||||
use pay_u::MerchantPosId;
|
||||
use validator::{validate_email, validate_length};
|
||||
|
||||
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");
|
||||
let merchant_id = std::env::var("PAYU_CLIENT_MERCHANT_ID")
|
||||
.expect("Missing PAYU_CLIENT_MERCHANT_ID env")
|
||||
.parse()
|
||||
.parse::<i32>()
|
||||
.map(MerchantPosId::from)
|
||||
.expect("Variable PAYU_CLIENT_MERCHANT_ID must be number");
|
||||
payment_manager::PaymentManager::build(client_id, client_secret, merchant_id)
|
||||
.await
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "pay_u"
|
||||
description = "PayU Rest API wrapper"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
|
@ -12,7 +12,7 @@ cargo add pay_u
|
||||
async fn usage() {
|
||||
let client_id = std::env::var("PAYU_CLIENT_ID").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);
|
||||
client.authorize().await.expect("Invalid credentials");
|
||||
|
||||
|
@ -9,6 +9,15 @@ where
|
||||
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>
|
||||
where
|
||||
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 serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::res::OrdersInfo;
|
||||
|
||||
macro_rules! get_client {
|
||||
($self:expr) => {{
|
||||
#[cfg(feature = "single-client")]
|
||||
@ -19,6 +21,8 @@ macro_rules! get_client {
|
||||
}};
|
||||
}
|
||||
|
||||
pub static SUCCESS: &str = "SUCCESS";
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("Client is not authorized. No bearer token available")]
|
||||
@ -41,7 +45,9 @@ pub enum Error {
|
||||
CreateOrder,
|
||||
#[error("Failed to fetch order transactions")]
|
||||
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 {
|
||||
status_code: String,
|
||||
status_desc: Option<String>,
|
||||
@ -49,7 +55,7 @@ pub enum Error {
|
||||
severity: Option<String>,
|
||||
code_literal: Option<CodeLiteral>,
|
||||
},
|
||||
#[error("PayU rejected to perform refund. {status_code:?}")]
|
||||
#[error("PayU rejected to perform refund with status {status_code:?}")]
|
||||
RefundFailed {
|
||||
status_code: String,
|
||||
status_desc: Option<String>,
|
||||
@ -57,6 +63,24 @@ pub enum Error {
|
||||
severity: Option<String>,
|
||||
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>;
|
||||
@ -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.
|
||||
///
|
||||
/// Each payment is initially Pending and can change according to following
|
||||
@ -212,7 +287,6 @@ impl Buyer {
|
||||
|
||||
pub type Price = i32;
|
||||
pub type Quantity = u32;
|
||||
pub type MerchantPosId = i32;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@ -280,8 +354,8 @@ pub struct OrderCreateRequest {
|
||||
customer_ip: String,
|
||||
/// Secret pos ip. This is connected to PayU account
|
||||
#[serde(
|
||||
serialize_with = "serialize::serialize_i32",
|
||||
deserialize_with = "deserialize::deserialize_i32"
|
||||
serialize_with = "serialize::serialize_newtype",
|
||||
deserialize_with = "deserialize::deserialize_i32_newtype"
|
||||
)]
|
||||
merchant_pos_id: MerchantPosId,
|
||||
/// Transaction description
|
||||
@ -316,7 +390,7 @@ impl OrderCreateRequest {
|
||||
Self {
|
||||
notify_url: None,
|
||||
customer_ip: customer_ip.into(),
|
||||
merchant_pos_id: 0,
|
||||
merchant_pos_id: 0.into(),
|
||||
description: String::from(""),
|
||||
currency_code: currency.into(),
|
||||
total_amount: 0,
|
||||
@ -544,7 +618,7 @@ impl Status {
|
||||
/// assert_eq!(status.is_success(), true);
|
||||
/// ```
|
||||
pub fn is_success(&self) -> bool {
|
||||
self.status_code.as_str() == "SUCCESS"
|
||||
self.status_code.as_str() == SUCCESS
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
use crate::{OrderId, Refund, Status};
|
||||
|
||||
@ -588,27 +669,27 @@ pub mod res {
|
||||
pub refund: Option<Refund>,
|
||||
pub status: Status,
|
||||
}
|
||||
#[derive(serde::Deserialize)]
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TransactionPayMethod {
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub enum CardProfile {
|
||||
Consumer,
|
||||
Business,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub enum CardClassification {
|
||||
Debit,
|
||||
Credit,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TransactionCartData {
|
||||
/// // "543402******4014",
|
||||
@ -632,40 +713,89 @@ pub mod res {
|
||||
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")]
|
||||
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")]
|
||||
pub struct TransactionCart {
|
||||
pub cart_data: TransactionCartData,
|
||||
pub card_installment_proposal: TransactionCardInstallmentProposal,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Transaction {
|
||||
pub pay_method: TransactionPayMethod,
|
||||
pub payment_flow: String,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Transactions {
|
||||
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)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RefundRequest {
|
||||
description: String,
|
||||
amount: Price,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
amount: Option<Price>,
|
||||
}
|
||||
|
||||
impl RefundRequest {
|
||||
pub fn new<Description>(description: Description, amount: Price) -> Self
|
||||
pub fn new<Description>(description: Description, amount: Option<Price>) -> Self
|
||||
where
|
||||
Description: Into<String>,
|
||||
{
|
||||
@ -679,7 +809,7 @@ impl RefundRequest {
|
||||
&self.description
|
||||
}
|
||||
|
||||
pub fn amount(&self) -> Price {
|
||||
pub fn amount(&self) -> Option<Price> {
|
||||
self.amount
|
||||
}
|
||||
}
|
||||
@ -709,7 +839,7 @@ pub mod notify {
|
||||
pub struct StatusUpdate {
|
||||
pub order: Order,
|
||||
pub local_receipt_date_time: Option<String>,
|
||||
pub properties: Option<Vec<Prop>>,
|
||||
pub properties: Option<Vec<super::Prop>>,
|
||||
pub status: Option<super::Status>,
|
||||
}
|
||||
|
||||
@ -748,7 +878,7 @@ pub mod notify {
|
||||
/// Customer client IP address
|
||||
pub customer_ip: String,
|
||||
/// 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,
|
||||
/// Transaction description
|
||||
pub description: String,
|
||||
@ -768,20 +898,13 @@ pub mod notify {
|
||||
pub pay_method: Option<super::PayMethod>,
|
||||
pub status: super::PaymentStatus,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Prop {
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Client {
|
||||
sandbox: bool,
|
||||
merchant_pos_id: MerchantPosId,
|
||||
client_id: String,
|
||||
client_secret: String,
|
||||
client_id: ClientId,
|
||||
client_secret: ClientSecret,
|
||||
bearer: Option<String>,
|
||||
bearer_expires_at: chrono::DateTime<chrono::Utc>,
|
||||
#[cfg(feature = "single-client")]
|
||||
@ -790,23 +913,19 @@ pub struct Client {
|
||||
|
||||
impl Client {
|
||||
/// Create new PayU client
|
||||
pub fn new<ClientId, ClientSecret>(
|
||||
pub fn new(
|
||||
client_id: ClientId,
|
||||
client_secret: ClientSecret,
|
||||
merchant_pos_id: MerchantPosId,
|
||||
) -> Self
|
||||
where
|
||||
ClientId: Into<String>,
|
||||
ClientSecret: Into<String>,
|
||||
{
|
||||
) -> Self {
|
||||
#[cfg(feature = "single-client")]
|
||||
{
|
||||
Self {
|
||||
bearer: None,
|
||||
sandbox: false,
|
||||
merchant_pos_id,
|
||||
client_id: client_id.into(),
|
||||
client_secret: client_secret.into(),
|
||||
client_id,
|
||||
client_secret,
|
||||
bearer_expires_at: chrono::Utc::now(),
|
||||
client: Arc::new(Self::build_client()),
|
||||
}
|
||||
@ -817,8 +936,8 @@ impl Client {
|
||||
bearer: None,
|
||||
sandbox: false,
|
||||
merchant_pos_id,
|
||||
client_id: client_id.into(),
|
||||
client_secret: client_secret.into(),
|
||||
client_id,
|
||||
client_secret,
|
||||
bearer_expires_at: chrono::Utc::now(),
|
||||
}
|
||||
}
|
||||
@ -849,9 +968,9 @@ impl Client {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use pay_u::{Client, OrderCreateRequest, Product, Buyer};
|
||||
/// # use pay_u::*;
|
||||
/// 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()
|
||||
/// .with_bearer("d9a4536e-62ba-4f60-8017-6053211d3f47", 2000);
|
||||
/// let res = client
|
||||
@ -906,7 +1025,7 @@ impl Client {
|
||||
log::error!("{e:?}");
|
||||
Error::CreateOrder
|
||||
})?;
|
||||
if res.status.status_code != "Success" {
|
||||
if !res.status.is_success() {
|
||||
let Status {
|
||||
status_code,
|
||||
status_desc,
|
||||
@ -950,18 +1069,29 @@ impl Client {
|
||||
/// ```
|
||||
/// # use pay_u::*;
|
||||
/// 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)
|
||||
/// .sandbox();
|
||||
/// let res = client
|
||||
/// .partial_refund(
|
||||
/// .refund(
|
||||
/// 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;
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn partial_refund(
|
||||
pub async fn refund(
|
||||
&mut self,
|
||||
order_id: OrderId,
|
||||
refund: RefundRequest,
|
||||
@ -987,7 +1117,7 @@ impl Client {
|
||||
log::error!("Invalid PayU response {e:?}");
|
||||
Error::Refund
|
||||
})?;
|
||||
if res.status.status_code.as_str() != "Success" {
|
||||
if !res.status.is_success() {
|
||||
let Status {
|
||||
status_code,
|
||||
status_desc,
|
||||
@ -1006,6 +1136,66 @@ impl Client {
|
||||
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
|
||||
/// details of transactions created for an order.
|
||||
///
|
||||
@ -1016,12 +1206,13 @@ impl Client {
|
||||
/// > transaction has been processed, the bank details may be available
|
||||
/// > either after few minutes or on the next business day, depending on the
|
||||
/// > bank.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use pay_u::*;
|
||||
/// 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)
|
||||
/// .sandbox();
|
||||
/// let res = client
|
||||
@ -1104,7 +1295,11 @@ mod tests {
|
||||
|
||||
fn build_client() -> Client {
|
||||
dotenv::dotenv().ok();
|
||||
Client::new("145227", "12f071174cb7eb79d4aac5bc2f07563f", 300746)
|
||||
Client::new(
|
||||
ClientId::new("145227"),
|
||||
ClientSecret::new("12f071174cb7eb79d4aac5bc2f07563f"),
|
||||
MerchantPosId::new(300746),
|
||||
)
|
||||
.sandbox()
|
||||
.with_bearer("d9a4536e-62ba-4f60-8017-6053211d3f47", 999999)
|
||||
}
|
||||
@ -1129,22 +1324,65 @@ mod tests {
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
if res.is_err() {
|
||||
eprintln!("create_order res is {res:?}");
|
||||
}
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn partial_refund() {
|
||||
let res = build_client()
|
||||
.partial_refund(
|
||||
.refund(
|
||||
OrderId::new("H9LL64F37H160126GUEST000P01"),
|
||||
RefundRequest::new("Refund", 1000),
|
||||
RefundRequest::new("Refund", Some(1000)),
|
||||
)
|
||||
.await;
|
||||
assert!(matches!(res, Ok(_)));
|
||||
|
||||
if res.is_err() {
|
||||
eprintln!("partial_refund res is {res:?}");
|
||||
}
|
||||
assert!(matches!(res, Err(Error::RefundFailed { .. })));
|
||||
}
|
||||
|
||||
#[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]
|
||||
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>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
@ -5,6 +7,16 @@ where
|
||||
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>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
|
Loading…
Reference in New Issue
Block a user