More docs, implement payment
This commit is contained in:
parent
94e7e7eb1e
commit
bf7c942fed
@ -31,7 +31,7 @@ pub(crate) async fn all_account_orders(
|
|||||||
) -> Result<Vec<AccountOrder>> {
|
) -> Result<Vec<AccountOrder>> {
|
||||||
sqlx::query_as(
|
sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
SELECT id, buyer_id, status, order_id
|
SELECT id, buyer_id, status, order_id, order_ext_id
|
||||||
FROM account_orders
|
FROM account_orders
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
@ -73,7 +73,7 @@ pub(crate) async fn create_account_order(
|
|||||||
r#"
|
r#"
|
||||||
INSERT INTO account_orders (buyer_id, status)
|
INSERT INTO account_orders (buyer_id, status)
|
||||||
VALUES ($1, $2, $3)
|
VALUES ($1, $2, $3)
|
||||||
RETURNING id, buyer_id, status
|
RETURNING id, buyer_id, status, order_ext_id
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.bind(msg.buyer_id)
|
.bind(msg.buyer_id)
|
||||||
@ -147,7 +147,7 @@ pub(crate) async fn update_account_order(
|
|||||||
UPDATE account_orders
|
UPDATE account_orders
|
||||||
SET buyer_id = $2 AND status = $3 AND order_id = $4
|
SET buyer_id = $2 AND status = $3 AND order_id = $4
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
RETURNING id, buyer_id, status, order_id
|
RETURNING id, buyer_id, status, order_id, order_ext_id
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.bind(msg.id)
|
.bind(msg.id)
|
||||||
@ -162,6 +162,41 @@ RETURNING id, buyer_id, status, order_id
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(actix::Message)]
|
||||||
|
#[rtype(result = "Result<AccountOrder>")]
|
||||||
|
pub struct UpdateAccountOrderByExt {
|
||||||
|
pub order_ext_id: String,
|
||||||
|
pub status: OrderStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
db_async_handler!(
|
||||||
|
UpdateAccountOrderByExt,
|
||||||
|
update_account_order_by_ext,
|
||||||
|
AccountOrder
|
||||||
|
);
|
||||||
|
|
||||||
|
pub(crate) async fn update_account_order_by_ext(
|
||||||
|
msg: UpdateAccountOrderByExt,
|
||||||
|
db: PgPool,
|
||||||
|
) -> Result<AccountOrder> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
UPDATE account_orders
|
||||||
|
SET status = $2
|
||||||
|
WHERE order_ext_id = $1
|
||||||
|
RETURNING id, buyer_id, status, order_id, order_ext_id
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(msg.order_ext_id)
|
||||||
|
.bind(msg.status)
|
||||||
|
.fetch_one(&db)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
log::error!("{e:?}");
|
||||||
|
super::Error::AccountOrder(Error::CantCreate)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(actix::Message)]
|
#[derive(actix::Message)]
|
||||||
#[rtype(result = "Result<AccountOrder>")]
|
#[rtype(result = "Result<AccountOrder>")]
|
||||||
pub struct FindAccountOrder {
|
pub struct FindAccountOrder {
|
||||||
@ -173,7 +208,7 @@ db_async_handler!(FindAccountOrder, find_account_order, AccountOrder);
|
|||||||
pub(crate) async fn find_account_order(msg: FindAccountOrder, db: PgPool) -> Result<AccountOrder> {
|
pub(crate) async fn find_account_order(msg: FindAccountOrder, db: PgPool) -> Result<AccountOrder> {
|
||||||
sqlx::query_as(
|
sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
SELECT id, buyer_id, status, order_id
|
SELECT id, buyer_id, status, order_id, order_ext_id
|
||||||
FROM account_orders
|
FROM account_orders
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
"#,
|
"#,
|
||||||
|
@ -2,12 +2,13 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use actix::Addr;
|
use actix::Addr;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use pay_u::OrderCreateRequest;
|
|
||||||
|
|
||||||
use crate::config::SharedAppConfig;
|
use crate::config::SharedAppConfig;
|
||||||
use crate::database;
|
|
||||||
use crate::database::Database;
|
use crate::database::Database;
|
||||||
use crate::model::{AccountId, Price, ProductId, Quantity, QuantityUnit, ShoppingCartId};
|
use crate::model::{
|
||||||
|
AccountId, OrderStatus, Price, ProductId, Quantity, QuantityUnit, ShoppingCartId,
|
||||||
|
};
|
||||||
|
use crate::{database, model};
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! pay_async_handler {
|
macro_rules! pay_async_handler {
|
||||||
@ -106,13 +107,14 @@ impl From<Product> for pay_u::Product {
|
|||||||
#[rtype(result = "Result<pay_u::OrderId>")]
|
#[rtype(result = "Result<pay_u::OrderId>")]
|
||||||
pub struct RequestPayment {
|
pub struct RequestPayment {
|
||||||
pub products: Vec<Product>,
|
pub products: Vec<Product>,
|
||||||
pub redirect_uri: String,
|
|
||||||
pub currency: String,
|
pub currency: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub buyer: Buyer,
|
pub buyer: Buyer,
|
||||||
pub customer_ip: String,
|
pub customer_ip: String,
|
||||||
pub buyer_id: AccountId,
|
pub buyer_id: AccountId,
|
||||||
pub shopping_cart_id: ShoppingCartId,
|
pub shopping_cart_id: ShoppingCartId,
|
||||||
|
pub redirect_uri: String,
|
||||||
|
pub continue_uri: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pay_async_handler!(RequestPayment, request_payment, pay_u::OrderId);
|
pay_async_handler!(RequestPayment, request_payment, pay_u::OrderId);
|
||||||
@ -122,7 +124,7 @@ pub(crate) async fn request_payment(
|
|||||||
client: PayUClient,
|
client: PayUClient,
|
||||||
db: Addr<Database>,
|
db: Addr<Database>,
|
||||||
) -> Result<pay_u::OrderId> {
|
) -> Result<pay_u::OrderId> {
|
||||||
let db_order = match db
|
let db_order: model::AccountOrder = match db
|
||||||
.send(database::CreateAccountOrder {
|
.send(database::CreateAccountOrder {
|
||||||
buyer_id: msg.buyer_id,
|
buyer_id: msg.buyer_id,
|
||||||
items: msg
|
items: msg
|
||||||
@ -153,16 +155,55 @@ pub(crate) async fn request_payment(
|
|||||||
client
|
client
|
||||||
.lock()
|
.lock()
|
||||||
.create_order(
|
.create_order(
|
||||||
OrderCreateRequest::build(
|
pay_u::req::OrderCreate::build(
|
||||||
msg.buyer.into(),
|
msg.buyer.into(),
|
||||||
msg.customer_ip,
|
msg.customer_ip,
|
||||||
msg.currency,
|
msg.currency,
|
||||||
msg.description,
|
msg.description,
|
||||||
)?
|
)?
|
||||||
|
.with_products(msg.products.into_iter().map(Into::into))
|
||||||
|
.with_ext_order_id(db_order.order_ext_id.to_string())
|
||||||
.with_notify_url(msg.redirect_uri)
|
.with_notify_url(msg.redirect_uri)
|
||||||
.with_products(msg.products.into_iter().map(Into::into)),
|
.with_continue_url(msg.continue_uri),
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
};
|
};
|
||||||
Ok(order.order_id)
|
Ok(order.order_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub struct PaymentNotification(pay_u::notify::StatusUpdate);
|
||||||
|
|
||||||
|
#[derive(Debug, actix::Message)]
|
||||||
|
#[rtype(result = "Result<()>")]
|
||||||
|
pub struct UpdatePayment {
|
||||||
|
pub notification: PaymentNotification,
|
||||||
|
}
|
||||||
|
|
||||||
|
pay_async_handler!(UpdatePayment, update_payment, ());
|
||||||
|
|
||||||
|
pub(crate) async fn update_payment(
|
||||||
|
msg: UpdatePayment,
|
||||||
|
_client: PayUClient,
|
||||||
|
db: Addr<Database>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let status = msg.notification.0.status();
|
||||||
|
let order_ext_id = match msg.notification.0.order.ext_order_id {
|
||||||
|
Some(id) => id,
|
||||||
|
_ => return Ok(()),
|
||||||
|
};
|
||||||
|
let status = match status {
|
||||||
|
pay_u::PaymentStatus::Pending => return Ok(()),
|
||||||
|
pay_u::PaymentStatus::WaitingForConfirmation => return Ok(()),
|
||||||
|
pay_u::PaymentStatus::Completed => OrderStatus::Payed,
|
||||||
|
pay_u::PaymentStatus::Canceled => OrderStatus::Cancelled,
|
||||||
|
};
|
||||||
|
let _ = db
|
||||||
|
.send(database::UpdateAccountOrderByExt {
|
||||||
|
status,
|
||||||
|
order_ext_id,
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
@ -503,12 +503,12 @@ pub struct Stock {
|
|||||||
pub quantity_unit: QuantityUnit,
|
pub quantity_unit: QuantityUnit,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(sqlx::Type, Serialize, Deserialize, Copy, Clone, Debug, Deref)]
|
#[derive(sqlx::Type, Serialize, Deserialize, Copy, Clone, Debug, Display, Deref)]
|
||||||
#[sqlx(transparent)]
|
#[sqlx(transparent)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct AccountOrderId(RecordId);
|
pub struct AccountOrderId(RecordId);
|
||||||
|
|
||||||
#[derive(sqlx::Type, Serialize, Deserialize, Deref)]
|
#[derive(sqlx::Type, Serialize, Deserialize, Display, Deref)]
|
||||||
#[sqlx(transparent)]
|
#[sqlx(transparent)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct OrderId(String);
|
pub struct OrderId(String);
|
||||||
@ -519,6 +519,34 @@ pub struct AccountOrder {
|
|||||||
pub buyer_id: AccountId,
|
pub buyer_id: AccountId,
|
||||||
pub status: OrderStatus,
|
pub status: OrderStatus,
|
||||||
pub order_id: Option<OrderId>,
|
pub order_id: Option<OrderId>,
|
||||||
|
pub order_ext_id: uuid::Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(sqlx::FromRow, Serialize, Deserialize)]
|
||||||
|
pub struct PublicAccountOrder {
|
||||||
|
pub id: AccountOrderId,
|
||||||
|
pub buyer_id: AccountId,
|
||||||
|
pub status: OrderStatus,
|
||||||
|
pub order_id: Option<OrderId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AccountOrder> for PublicAccountOrder {
|
||||||
|
fn from(
|
||||||
|
AccountOrder {
|
||||||
|
id,
|
||||||
|
buyer_id,
|
||||||
|
status,
|
||||||
|
order_id,
|
||||||
|
order_ext_id: _,
|
||||||
|
}: AccountOrder,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
buyer_id,
|
||||||
|
status,
|
||||||
|
order_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(sqlx::Type, Serialize, Deserialize, Copy, Clone, Debug, Deref)]
|
#[derive(sqlx::Type, Serialize, Deserialize, Copy, Clone, Debug, Deref)]
|
||||||
|
@ -5,10 +5,11 @@ use actix_web::{get, post, HttpResponse};
|
|||||||
use crate::database::{self, Database};
|
use crate::database::{self, Database};
|
||||||
use crate::logic::validate_password;
|
use crate::logic::validate_password;
|
||||||
use crate::model::{Audience, FullAccount, Token, TokenString};
|
use crate::model::{Audience, FullAccount, Token, TokenString};
|
||||||
|
use crate::payment_manager::{PaymentManager, PaymentNotification};
|
||||||
use crate::routes::public::Error as PublicError;
|
use crate::routes::public::Error as PublicError;
|
||||||
use crate::routes::{self, Result};
|
use crate::routes::{self, Result};
|
||||||
use crate::token_manager::TokenManager;
|
use crate::token_manager::TokenManager;
|
||||||
use crate::{public_send_db, token_manager, Login, Password};
|
use crate::{payment_manager, public_send_db, token_manager, Login, Password};
|
||||||
|
|
||||||
#[get("/products")]
|
#[get("/products")]
|
||||||
async fn products(db: Data<Addr<Database>>) -> Result<HttpResponse> {
|
async fn products(db: Data<Addr<Database>>) -> Result<HttpResponse> {
|
||||||
@ -84,6 +85,19 @@ async fn sign_in(
|
|||||||
Ok(HttpResponse::Created().json(SignInOutput { token: string }))
|
Ok(HttpResponse::Created().json(SignInOutput { token: string }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[post("/pay_u/notify")]
|
||||||
|
async fn handle_notification(
|
||||||
|
Json(notify): Json<PaymentNotification>,
|
||||||
|
payment: Data<Addr<PaymentManager>>,
|
||||||
|
) -> HttpResponse {
|
||||||
|
{
|
||||||
|
payment.do_send(payment_manager::UpdatePayment {
|
||||||
|
notification: notify,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
HttpResponse::Ok().body("")
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn configure(config: &mut ServiceConfig) {
|
pub(crate) fn configure(config: &mut ServiceConfig) {
|
||||||
config.service(products).service(stocks).service(sign_in);
|
config.service(products).service(stocks).service(sign_in);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
# Payu REST API
|
# Unofficial Payu client
|
||||||
|
|
||||||
|
<img src="https://poland.payu.com/wp-content/themes/global-website/assets/src/images/payu-logo.svg">
|
||||||
|
|
||||||
|
This client support integration with the REST API 2.1 protocol.
|
||||||
|
It presents various methods of implementing online payments via different PayU services and is dedicated primarily to developers wanting to implement the PayU payment services.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
@ -10,6 +15,8 @@ cargo add pay_u
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
async fn usage() {
|
async fn usage() {
|
||||||
|
use pay_u::*;
|
||||||
|
|
||||||
let client_id = ClientId::new(std::env::var("PAYU_CLIENT_ID").unwrap());
|
let client_id = ClientId::new(std::env::var("PAYU_CLIENT_ID").unwrap());
|
||||||
let client_secret = ClientSecret::new(std::env::var("PAYU_CLIENT_SECRET").unwrap());
|
let client_secret = ClientSecret::new(std::env::var("PAYU_CLIENT_SECRET").unwrap());
|
||||||
let merchant_id = std::env::var("PAYU_CLIENT_MERCHANT_ID").unwrap().parse::<i32>().map(MerchantPosId::from).unwrap();
|
let merchant_id = std::env::var("PAYU_CLIENT_MERCHANT_ID").unwrap().parse::<i32>().map(MerchantPosId::from).unwrap();
|
||||||
@ -17,7 +24,7 @@ async fn usage() {
|
|||||||
client.authorize().await.expect("Invalid credentials");
|
client.authorize().await.expect("Invalid credentials");
|
||||||
|
|
||||||
let _res = client.create_order(
|
let _res = client.create_order(
|
||||||
OrderCreateRequest::build(
|
req::OrderCreate::build(
|
||||||
Buyer::new("john.doe@example.com", "654111654", "John", "Doe", "pl"),
|
Buyer::new("john.doe@example.com", "654111654", "John", "Doe", "pl"),
|
||||||
"127.0.0.1",
|
"127.0.0.1",
|
||||||
"PLN",
|
"PLN",
|
||||||
@ -75,30 +82,36 @@ async fn checkout(session: Data<Session>, db: Data<Database>, payu: Data<Arc<Mut
|
|||||||
let payu = payu.into_inner();
|
let payu = payu.into_inner();
|
||||||
let shopping_cart = db.send(LoadShoppingCart { user_id }).await??;
|
let shopping_cart = db.send(LoadShoppingCart { user_id }).await??;
|
||||||
let shopping_cart_id = shopping_cart.id;
|
let shopping_cart_id = shopping_cart.id;
|
||||||
let create_order_req: pay_u::OrderCreateRequest = shopping_cart.into();
|
let create_order_req: pay_u::req::OrderCreate = shopping_cart.into();
|
||||||
let pay_u::res::CreateOrder { redirect_uri, order_id, .. } = payu.create_order(create_order_req).await?;
|
let pay_u::res::CreateOrder { redirect_uri, order_id, .. } = payu.create_order(create_order_req).await?;
|
||||||
db.send(database::CreateOrder { shopping_cart_id, order_id }).await??;
|
db.send(database::CreateOrder { shopping_cart_id, order_id }).await??;
|
||||||
HttpResponse::SeeOther().append_header((actix_web::http::header::LOCATION, redirect_uri)).body("")
|
HttpResponse::SeeOther().append_header((actix_web::http::header::LOCATION, redirect_uri)).body("")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/pay_u/{own_order_id}/notify")]
|
#[post("/pay_u/{own_order_id}/notify")]
|
||||||
async fn handle_notification(path: Path<i32>, Json(notify): Json<pay_u::notify::StatusUpdate>) -> HttpResponse {
|
async fn handle_notification(
|
||||||
|
path: Path<i32>,
|
||||||
|
Json(notify): Json<pay_u::notify::StatusUpdate>,
|
||||||
|
payment: Data<Addr<PaymentManager>>
|
||||||
|
) -> HttpResponse {
|
||||||
let status = notify.status();
|
let status = notify.status();
|
||||||
|
// Create additional field which will always be unique like UUID
|
||||||
|
// Do not use record primary key!
|
||||||
|
let ext_order_id = String::from(notify.ext_order_id());
|
||||||
|
|
||||||
let order_id = path.into_inner();
|
let order_id = path.into_inner();
|
||||||
match handle_update(order_id, status, notify) {
|
payment.do_send(payment_manager::Update {
|
||||||
Ok(_) => (),
|
status,
|
||||||
Err(e) => {
|
ext_order_id
|
||||||
// ALWAYS SEND OK!
|
});
|
||||||
log::error!("{e:?}");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
HttpResponse::Ok().body("")
|
HttpResponse::Ok().body("")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Releases
|
### Releases
|
||||||
|
|
||||||
0.1.7 - Added credit and more create order request options like additional description, visible description.
|
0.1.8 - Additional documentation, move requests to module
|
||||||
|
0.1.7 - Added credit and more create order request options like additional description, visible description.
|
||||||
|
|
||||||
## Bugs
|
## Bugs
|
||||||
|
|
||||||
|
77
pay_u/src/credit.rs
Normal file
77
pay_u/src/credit.rs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
//! This module allow to create credit request during create order request
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{Address, ShoppingCart};
|
||||||
|
|
||||||
|
/// Describe customer during credit request
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Applicant {
|
||||||
|
/// Applicant’s email address
|
||||||
|
/// Recommended
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
email: Option<String>,
|
||||||
|
/// Applicant’s phone number
|
||||||
|
/// Recommended
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
phone: Option<String>,
|
||||||
|
/// Applicant’s first name
|
||||||
|
/// Recommended
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
first_name: Option<String>,
|
||||||
|
/// Applicant’s last name
|
||||||
|
/// Recommended
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
last_name: Option<String>,
|
||||||
|
/// Language code, ISO-639-1 compliant. Denotes the language version of
|
||||||
|
/// PayU hosted payment page and of e-mail messages sent from PayU to the
|
||||||
|
/// payer (supported values are here).
|
||||||
|
/// Recommended
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
language: Option<String>,
|
||||||
|
/// National Identification Number
|
||||||
|
/// Recommended
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
nin: Option<String>,
|
||||||
|
/// Section containing data about applicant’s address.
|
||||||
|
/// Recommended
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
address: Option<Address>,
|
||||||
|
/// Additional information about person applying for credit.
|
||||||
|
/// Recommended
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
additional_info: Option<ApplicantAdditionalInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allow to create credit request
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Default)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Credit {
|
||||||
|
/// Section containing data of the ordered products
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
shopping_carts: Option<Vec<ShoppingCart>>,
|
||||||
|
/// Section containing data of person applying for a credit
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
applicant: Option<Applicant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Credit {
|
||||||
|
pub fn with_shopping_carts<ShoppingCarts>(mut self, shopping_carts: ShoppingCarts) -> Self
|
||||||
|
where
|
||||||
|
ShoppingCarts: Iterator<Item = ShoppingCart>,
|
||||||
|
{
|
||||||
|
self.shopping_carts = Some(shopping_carts.collect());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ApplicantAdditionalInfo {
|
||||||
|
/// Information whether there were previous, successfully completed orders
|
||||||
|
/// for applicant.
|
||||||
|
/// Recommended
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
has_successfully_finished_order_in_shop: Option<String>,
|
||||||
|
}
|
816
pay_u/src/lib.rs
816
pay_u/src/lib.rs
@ -1,4 +1,13 @@
|
|||||||
|
//! This is unofficial client support integration with the REST API 2.1
|
||||||
|
//! protocol. It presents various methods of implementing online payments via
|
||||||
|
//! different PayU services and is dedicated primarily to developers wanting to
|
||||||
|
//! implement the PayU payment services.
|
||||||
|
|
||||||
|
pub mod credit;
|
||||||
mod deserialize;
|
mod deserialize;
|
||||||
|
pub mod notify;
|
||||||
|
pub mod req;
|
||||||
|
pub mod res;
|
||||||
mod serialize;
|
mod serialize;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -90,6 +99,8 @@ pub enum Error {
|
|||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
/// PayU internal order id
|
/// PayU internal order id
|
||||||
|
///
|
||||||
|
/// Unique order identifier
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
Clone,
|
Clone,
|
||||||
@ -108,7 +119,9 @@ impl OrderId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PayU internal order id
|
/// PayU internal merchant id
|
||||||
|
///
|
||||||
|
/// This value is customer identifier
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
serde::Deserialize,
|
serde::Deserialize,
|
||||||
@ -123,6 +136,7 @@ impl OrderId {
|
|||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct MerchantPosId(pub i32);
|
pub struct MerchantPosId(pub i32);
|
||||||
|
|
||||||
|
/// Public payu OAuth client identifier
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
Clone,
|
Clone,
|
||||||
@ -141,6 +155,7 @@ impl ClientId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Secret payu OAuth client identifier
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug,
|
||||||
Clone,
|
Clone,
|
||||||
@ -509,6 +524,7 @@ pub enum ShoppingMethodType {
|
|||||||
StorePickup,
|
StorePickup,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Delivery address
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Address {
|
pub struct Address {
|
||||||
@ -559,76 +575,6 @@ pub struct ShoppingMethod {
|
|||||||
pub address: Option<Address>,
|
pub address: Option<Address>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct ApplicantAdditionalInfo {
|
|
||||||
/// Information whether there were previous, successfully completed orders
|
|
||||||
/// for applicant.
|
|
||||||
/// Recommended
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
has_successfully_finished_order_in_shop: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct Applicant {
|
|
||||||
/// Applicant’s email address
|
|
||||||
/// Recommended
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
email: Option<String>,
|
|
||||||
/// Applicant’s phone number
|
|
||||||
/// Recommended
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
phone: Option<String>,
|
|
||||||
/// Applicant’s first name
|
|
||||||
/// Recommended
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
first_name: Option<String>,
|
|
||||||
/// Applicant’s last name
|
|
||||||
/// Recommended
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
last_name: Option<String>,
|
|
||||||
/// Language code, ISO-639-1 compliant. Denotes the language version of
|
|
||||||
/// PayU hosted payment page and of e-mail messages sent from PayU to the
|
|
||||||
/// payer (supported values are here).
|
|
||||||
/// Recommended
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
language: Option<String>,
|
|
||||||
/// National Identification Number
|
|
||||||
/// Recommended
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
nin: Option<String>,
|
|
||||||
/// Section containing data about applicant’s address.
|
|
||||||
/// Recommended
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
address: Option<Address>,
|
|
||||||
/// Additional information about person applying for credit.
|
|
||||||
/// Recommended
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
additional_info: Option<ApplicantAdditionalInfo>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Default)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct Credit {
|
|
||||||
/// Section containing data of the ordered products
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
shopping_carts: Option<Vec<ShoppingCart>>,
|
|
||||||
/// Section containing data of person applying for a credit
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
applicant: Option<Applicant>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Credit {
|
|
||||||
pub fn with_shopping_carts<ShoppingCarts>(mut self, shopping_carts: ShoppingCarts) -> Self
|
|
||||||
where
|
|
||||||
ShoppingCarts: Iterator<Item = ShoppingCart>,
|
|
||||||
{
|
|
||||||
self.shopping_carts = Some(shopping_carts.collect());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// MultiUseCartToken
|
/// MultiUseCartToken
|
||||||
pub mod muct {
|
pub mod muct {
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -721,346 +667,6 @@ pub mod muct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct OrderCreateRequest {
|
|
||||||
/// 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.
|
|
||||||
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")]
|
|
||||||
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")]
|
|
||||||
continue_url: Option<String>,
|
|
||||||
/// Payer’s IP address, e.g. 123.123.123.123. Note: 0.0.0.0 is not accepted.
|
|
||||||
customer_ip: String,
|
|
||||||
/// Secret pos ip. This is connected to PayU account
|
|
||||||
#[serde(
|
|
||||||
serialize_with = "serialize::serialize_newtype",
|
|
||||||
deserialize_with = "deserialize::deserialize_i32_newtype"
|
|
||||||
)]
|
|
||||||
merchant_pos_id: MerchantPosId,
|
|
||||||
/// Transaction description
|
|
||||||
description: String,
|
|
||||||
/// 3 characters currency identifier, ex. PLN
|
|
||||||
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 = "serialize::serialize_i32",
|
|
||||||
deserialize_with = "deserialize::deserialize_i32"
|
|
||||||
)]
|
|
||||||
total_amount: Price,
|
|
||||||
/// @see [Buyer]
|
|
||||||
buyer: Option<Buyer>,
|
|
||||||
/// List of products
|
|
||||||
products: Vec<Product>,
|
|
||||||
#[serde(skip_serializing)]
|
|
||||||
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")]
|
|
||||||
validity_time: Option<u16>,
|
|
||||||
/// Additional description of the order.
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
additional_description: Option<String>,
|
|
||||||
/// Text visible on the PayU payment page (max. 80 chars).
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
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")]
|
|
||||||
statement_description: Option<String>,
|
|
||||||
#[serde(flatten, skip_serializing_if = "Option::is_none")]
|
|
||||||
muct: Option<muct::MultiUseCartToken>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
credit: Option<Credit>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OrderCreateRequest {
|
|
||||||
pub fn build<CustomerIp, Currency, Description>(
|
|
||||||
buyer: Buyer,
|
|
||||||
customer_ip: CustomerIp,
|
|
||||||
currency: Currency,
|
|
||||||
description: Description,
|
|
||||||
) -> 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(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: muct::Recurring,
|
|
||||||
card_on_file: muct::CardOnFile,
|
|
||||||
) -> Self {
|
|
||||||
self.muct = Some(muct::MultiUseCartToken {
|
|
||||||
recurring,
|
|
||||||
card_on_file,
|
|
||||||
});
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_products<Products>(mut self, products: Products) -> Self
|
|
||||||
where
|
|
||||||
Products: Iterator<Item = 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: 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 [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) -> 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) -> &Price {
|
|
||||||
&self.total_amount
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn buyer(&self) -> &Option<Buyer> {
|
|
||||||
&self.buyer
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn products(&self) -> &[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: MerchantPosId) -> Self {
|
|
||||||
self.merchant_pos_id = merchant_pos_id;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
pub enum PaymentType {
|
pub enum PaymentType {
|
||||||
@ -1091,6 +697,18 @@ pub struct PayMethod {
|
|||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Status {
|
pub struct Status {
|
||||||
|
/// One of
|
||||||
|
/// * `PENDING`: Payment is currently being processed.
|
||||||
|
/// * `WAITING_FOR_CONFIRMATION`: PayU is currently waiting for the merchant
|
||||||
|
/// system to receive (capture) the payment. This status is set if
|
||||||
|
/// auto-receive is disabled on the merchant system.
|
||||||
|
/// * `COMPLETED`: Payment has been accepted. PayU will pay out the funds
|
||||||
|
/// shortly.
|
||||||
|
/// * `CANCELED`: Payment has been cancelled and the buyer has not been
|
||||||
|
/// charged (no money was taken from buyer's account).
|
||||||
|
///
|
||||||
|
/// > Too prevent sending wrong status from server to PayU this field
|
||||||
|
/// > remains String
|
||||||
status_code: String,
|
status_code: String,
|
||||||
status_desc: Option<String>,
|
status_desc: Option<String>,
|
||||||
code: Option<String>,
|
code: Option<String>,
|
||||||
@ -1098,6 +716,50 @@ pub struct Status {
|
|||||||
code_literal: Option<CodeLiteral>,
|
code_literal: Option<CodeLiteral>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Status {
|
||||||
|
/// Check if http request was successful
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use pay_u::Status;
|
||||||
|
/// let status: Status = serde_json::from_str("{\"statusCode\":\"SUCCESS\"}").unwrap();
|
||||||
|
/// assert_eq!(status.is_success(), true);
|
||||||
|
/// ```
|
||||||
|
pub fn is_success(&self) -> bool {
|
||||||
|
self.status_code.as_str() == SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns http status
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use pay_u::Status;
|
||||||
|
/// let status: Status = serde_json::from_str("{\"statusCode\":\"SUCCESS\"}").unwrap();
|
||||||
|
/// assert_eq!(status.status_code(), "SUCCESS");
|
||||||
|
/// ```
|
||||||
|
pub fn status_code(&self) -> &str {
|
||||||
|
&self.status_code
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn status_desc(&self) -> Option<&str> {
|
||||||
|
self.status_desc.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn code(&self) -> Option<&str> {
|
||||||
|
self.code.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn severity(&self) -> Option<&str> {
|
||||||
|
self.severity.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn code_literal(&self) -> Option<&CodeLiteral> {
|
||||||
|
self.code_literal.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
pub enum StatusCode {
|
pub enum StatusCode {
|
||||||
@ -1156,38 +818,6 @@ pub enum CodeLiteral {
|
|||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Status {
|
|
||||||
/// Check if http request was successful
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # use pay_u::Status;
|
|
||||||
/// let status: Status = serde_json::from_str("{\"statusCode\":\"SUCCESS\"}").unwrap();
|
|
||||||
/// assert_eq!(status.is_success(), true);
|
|
||||||
/// ```
|
|
||||||
pub fn is_success(&self) -> bool {
|
|
||||||
self.status_code.as_str() == SUCCESS
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns http status
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # use pay_u::Status;
|
|
||||||
/// let status: Status = serde_json::from_str("{\"statusCode\":\"SUCCESS\"}").unwrap();
|
|
||||||
/// assert_eq!(status.status_code(), "SUCCESS");
|
|
||||||
/// ```
|
|
||||||
pub fn status_code(&self) -> &str {
|
|
||||||
&self.status_code
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn status_desc(&self) -> Option<&str> {
|
|
||||||
self.status_desc.as_deref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Prop {
|
pub struct Prop {
|
||||||
@ -1195,181 +825,6 @@ pub struct Prop {
|
|||||||
pub value: String,
|
pub value: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod res {
|
|
||||||
use crate::{OrderId, Refund, Status};
|
|
||||||
|
|
||||||
#[derive(serde::Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct CreateOrder {
|
|
||||||
/// Http status as a text
|
|
||||||
pub status: Status,
|
|
||||||
/// Client should be redirected to this URI
|
|
||||||
pub redirect_uri: String,
|
|
||||||
/// This should be connected to your own order
|
|
||||||
pub order_id: OrderId,
|
|
||||||
/// This is YOUR_EXT_ORDER_ID
|
|
||||||
pub ext_order_id: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(serde::Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct RefundDetails {
|
|
||||||
pub order_id: Option<String>,
|
|
||||||
pub refund: Option<Refund>,
|
|
||||||
pub status: Status,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(serde::Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct Refunds {
|
|
||||||
pub refunds: Vec<Refund>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(serde::Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct TransactionPayMethod {
|
|
||||||
pub value: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(serde::Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
|
||||||
pub enum CardProfile {
|
|
||||||
Consumer,
|
|
||||||
Business,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(serde::Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
|
||||||
pub enum CardClassification {
|
|
||||||
Debit,
|
|
||||||
Credit,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(serde::Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct TransactionCartData {
|
|
||||||
/// // "543402******4014",
|
|
||||||
pub card_number_masked: String,
|
|
||||||
/// MC (MasterCard/Maestro), VS (Visa)
|
|
||||||
/// Example; "MC"
|
|
||||||
pub card_scheme: String,
|
|
||||||
pub card_profile: CardProfile,
|
|
||||||
pub card_classification: CardClassification,
|
|
||||||
/// Example: "000"
|
|
||||||
pub card_response_code: String,
|
|
||||||
/// Example: "000 - OK"
|
|
||||||
pub card_response_code_desc: String,
|
|
||||||
/// Example: "5"
|
|
||||||
pub card_eci_code: String,
|
|
||||||
/// Example: "AY",
|
|
||||||
pub card3ds_status: String,
|
|
||||||
/// Example: "PL",
|
|
||||||
pub card_bin_country: String,
|
|
||||||
/// Example: "MCC0111LL1121"
|
|
||||||
pub first_transaction_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// > 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 {
|
|
||||||
/// Example: "5aff3ba8-0c37-4da1-ba4a-4ff24bcc2eed"
|
|
||||||
pub proposal_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(serde::Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct TransactionCart {
|
|
||||||
pub cart_data: TransactionCartData,
|
|
||||||
pub card_installment_proposal: TransactionCardInstallmentProposal,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(serde::Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct Transaction {
|
|
||||||
pub pay_method: TransactionPayMethod,
|
|
||||||
pub payment_flow: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
amount: Option<Price>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RefundRequest {
|
|
||||||
pub fn new<Description>(description: Description, amount: Option<Price>) -> Self
|
|
||||||
where
|
|
||||||
Description: Into<String>,
|
|
||||||
{
|
|
||||||
Self {
|
|
||||||
description: description.into(),
|
|
||||||
amount,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn description(&self) -> &str {
|
|
||||||
&self.description
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn amount(&self) -> Option<Price> {
|
|
||||||
self.amount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Refund {
|
pub struct Refund {
|
||||||
@ -1383,79 +838,6 @@ pub struct Refund {
|
|||||||
pub status_date_time: String,
|
pub status_date_time: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod notify {
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
use super::deserialize;
|
|
||||||
use crate::OrderId;
|
|
||||||
|
|
||||||
/// Payment notification object received on [super::Order].[notify_url]
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct StatusUpdate {
|
|
||||||
pub order: Order,
|
|
||||||
pub local_receipt_date_time: Option<String>,
|
|
||||||
pub properties: Option<Vec<super::Prop>>,
|
|
||||||
pub status: Option<super::Status>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StatusUpdate {
|
|
||||||
pub fn status(&self) -> super::PaymentStatus {
|
|
||||||
self.order.status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Refund notification object
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct RefundUpdate {
|
|
||||||
pub ext_order_id: String,
|
|
||||||
pub order_id: OrderId,
|
|
||||||
pub refund: Refund,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct Refund {
|
|
||||||
pub refund_id: String,
|
|
||||||
pub amount: String,
|
|
||||||
pub currency_code: String,
|
|
||||||
pub status: super::RefundStatus,
|
|
||||||
pub status_date_time: String,
|
|
||||||
pub reason: String,
|
|
||||||
pub reason_description: String,
|
|
||||||
pub refund_date: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct Order {
|
|
||||||
pub notify_url: Option<String>,
|
|
||||||
/// Customer client IP address
|
|
||||||
pub customer_ip: String,
|
|
||||||
/// Secret pos ip. This is connected to PayU account
|
|
||||||
#[serde(deserialize_with = "deserialize::deserialize_i32_newtype")]
|
|
||||||
pub merchant_pos_id: super::MerchantPosId,
|
|
||||||
/// Transaction description
|
|
||||||
pub description: String,
|
|
||||||
/// 3 characters currency identifier, ex. PLN
|
|
||||||
pub 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(deserialize_with = "deserialize::deserialize_i32")]
|
|
||||||
pub total_amount: super::Price,
|
|
||||||
/// @see [Buyer]
|
|
||||||
pub buyer: Option<super::Buyer>,
|
|
||||||
/// List of products
|
|
||||||
pub products: Vec<super::Product>,
|
|
||||||
#[serde(skip_serializing)]
|
|
||||||
pub order_create_date: Option<String>,
|
|
||||||
pub pay_method: Option<super::PayMethod>,
|
|
||||||
pub status: super::PaymentStatus,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
sandbox: bool,
|
sandbox: bool,
|
||||||
merchant_pos_id: MerchantPosId,
|
merchant_pos_id: MerchantPosId,
|
||||||
@ -1500,12 +882,38 @@ impl Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// All operation will be performed in sandbox PayU environment
|
/// All operation will be performed in sandbox PayU environment
|
||||||
|
///
|
||||||
|
/// Examples:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use pay_u::*;
|
||||||
|
///
|
||||||
|
/// async fn test() {
|
||||||
|
/// let client_id = ClientId::new(std::env::var("PAYU_CLIENT_ID").unwrap());
|
||||||
|
/// let client_secret = ClientSecret::new(std::env::var("PAYU_CLIENT_SECRET").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).sandbox();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub fn sandbox(mut self) -> Self {
|
pub fn sandbox(mut self) -> Self {
|
||||||
self.sandbox = true;
|
self.sandbox = true;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set your own bearer key
|
/// Set your own bearer key
|
||||||
|
///
|
||||||
|
/// Examples:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use pay_u::*;
|
||||||
|
///
|
||||||
|
/// async fn test() {
|
||||||
|
/// let client_id = ClientId::new(std::env::var("PAYU_CLIENT_ID").unwrap());
|
||||||
|
/// let client_secret = ClientSecret::new(std::env::var("PAYU_CLIENT_SECRET").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).with_bearer("a89sdhas9d8yasd8", 9_999_999);
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub fn with_bearer<Bearer: Into<String>>(mut self, bearer: Bearer, expires_in: i64) -> Self {
|
pub fn with_bearer<Bearer: Into<String>>(mut self, bearer: Bearer, expires_in: i64) -> Self {
|
||||||
self.bearer = Some(bearer.into());
|
self.bearer = Some(bearer.into());
|
||||||
self.bearer_expires_at = chrono::Utc::now() + chrono::Duration::seconds(expires_in);
|
self.bearer_expires_at = chrono::Utc::now() + chrono::Duration::seconds(expires_in);
|
||||||
@ -1531,13 +939,14 @@ impl Client {
|
|||||||
/// .with_bearer("d9a4536e-62ba-4f60-8017-6053211d3f47", 2000);
|
/// .with_bearer("d9a4536e-62ba-4f60-8017-6053211d3f47", 2000);
|
||||||
/// let res = client
|
/// let res = client
|
||||||
/// .create_order(
|
/// .create_order(
|
||||||
/// OrderCreateRequest::new(
|
/// req::OrderCreate::build(
|
||||||
/// Buyer::new("john.doe@example.com", "654111654", "John", "Doe", "pl"),
|
/// Buyer::new("john.doe@example.com", "654111654", "John", "Doe", "pl"),
|
||||||
/// "127.0.0.1",
|
/// "127.0.0.1",
|
||||||
/// "PLN",
|
/// "PLN",
|
||||||
/// )
|
/// "Some description"
|
||||||
|
/// ).expect("All fields must be valid")
|
||||||
/// .with_notify_url("https://your.eshop.com/notify")
|
/// .with_notify_url("https://your.eshop.com/notify")
|
||||||
/// .with_description("RTV market")
|
/// .with_description("Replace description")
|
||||||
/// .with_products([
|
/// .with_products([
|
||||||
/// Product::new("Wireless Mouse for Laptop", 15000, 1),
|
/// Product::new("Wireless Mouse for Laptop", 15000, 1),
|
||||||
/// Product::new("HDMI cable", 6000, 1),
|
/// Product::new("HDMI cable", 6000, 1),
|
||||||
@ -1546,7 +955,7 @@ impl Client {
|
|||||||
/// .await;
|
/// .await;
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub async fn create_order(&mut self, order: OrderCreateRequest) -> Result<res::CreateOrder> {
|
pub async fn create_order(&mut self, order: req::OrderCreate) -> Result<res::CreateOrder> {
|
||||||
self.authorize().await?;
|
self.authorize().await?;
|
||||||
if order.total_amount
|
if order.total_amount
|
||||||
!= order
|
!= order
|
||||||
@ -1631,7 +1040,7 @@ impl Client {
|
|||||||
/// let res = client
|
/// let res = client
|
||||||
/// .refund(
|
/// .refund(
|
||||||
/// OrderId::new("H9LL64F37H160126GUEST000P01"),
|
/// OrderId::new("H9LL64F37H160126GUEST000P01"),
|
||||||
/// RefundRequest::new("Refund", Some(1000)),
|
/// req::Refund::new("Refund", Some(1000)),
|
||||||
/// )
|
/// )
|
||||||
/// .await;
|
/// .await;
|
||||||
/// }
|
/// }
|
||||||
@ -1642,7 +1051,7 @@ impl Client {
|
|||||||
/// let res = client
|
/// let res = client
|
||||||
/// .refund(
|
/// .refund(
|
||||||
/// OrderId::new("H9LL64F37H160126GUEST000P01"),
|
/// OrderId::new("H9LL64F37H160126GUEST000P01"),
|
||||||
/// RefundRequest::new("Refund", None),
|
/// req::Refund::new("Refund", None),
|
||||||
/// )
|
/// )
|
||||||
/// .await;
|
/// .await;
|
||||||
/// }
|
/// }
|
||||||
@ -1650,12 +1059,12 @@ impl Client {
|
|||||||
pub async fn refund(
|
pub async fn refund(
|
||||||
&mut self,
|
&mut self,
|
||||||
order_id: OrderId,
|
order_id: OrderId,
|
||||||
refund: RefundRequest,
|
refund: req::Refund,
|
||||||
) -> Result<res::RefundDetails> {
|
) -> Result<res::RefundDetails> {
|
||||||
#[derive(Serialize, Debug)]
|
#[derive(Serialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct RefundWrapper {
|
struct RefundWrapper {
|
||||||
refund: RefundRequest,
|
refund: req::Refund,
|
||||||
}
|
}
|
||||||
|
|
||||||
self.authorize().await?;
|
self.authorize().await?;
|
||||||
@ -1918,11 +1327,12 @@ mod tests {
|
|||||||
async fn perform_create_order(client: &mut Client) -> Result<CreateOrder> {
|
async fn perform_create_order(client: &mut Client) -> Result<CreateOrder> {
|
||||||
client
|
client
|
||||||
.create_order(
|
.create_order(
|
||||||
OrderCreateRequest::new(
|
req::OrderCreate::build(
|
||||||
Buyer::new("john.doe@example.com", "654111654", "John", "Doe", "pl"),
|
Buyer::new("john.doe@example.com", "654111654", "John", "Doe", "pl"),
|
||||||
"127.0.0.1",
|
"127.0.0.1",
|
||||||
"PLN",
|
"PLN",
|
||||||
)
|
"Desc",
|
||||||
|
)?
|
||||||
.with_notify_url("https://your.eshop.com/notify")
|
.with_notify_url("https://your.eshop.com/notify")
|
||||||
.with_description("RTV market")
|
.with_description("RTV market")
|
||||||
.with_products(
|
.with_products(
|
||||||
@ -1954,7 +1364,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.expect("Failed to create");
|
.expect("Failed to create");
|
||||||
let res = client
|
let res = client
|
||||||
.refund(order_id, RefundRequest::new("Refund", Some(10)))
|
.refund(order_id, req::Refund::new("Refund", Some(10)))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if res.is_err() {
|
if res.is_err() {
|
||||||
@ -1970,7 +1380,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.expect("Failed to create");
|
.expect("Failed to create");
|
||||||
let res = client
|
let res = client
|
||||||
.refund(order_id, RefundRequest::new("Refund", None))
|
.refund(order_id, req::Refund::new("Refund", None))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if res.is_err() {
|
if res.is_err() {
|
||||||
|
161
pay_u/src/notify.rs
Normal file
161
pay_u/src/notify.rs
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
//! Notification objects. Those objects will be send on notify_url if was given.
|
||||||
|
//!
|
||||||
|
//! To enable notifications for a specific payment, specify the notifyUrl
|
||||||
|
//! parameter in the payment form. Each payment can receive a different URL to
|
||||||
|
//! which notifications will be sent.
|
||||||
|
//!
|
||||||
|
//! Every notification is sent asynchronously. After your system receives a
|
||||||
|
//! notification with the status COMPLETED, instruct it to ignore any further
|
||||||
|
//! notifications.
|
||||||
|
//!
|
||||||
|
//! After sending a notification, PayU system requires a 200 HTTP status code in
|
||||||
|
//! response. If it receives a different status code, it will resend the
|
||||||
|
//! notification. Your system should also take account of situations where a
|
||||||
|
//! notification is sent several times with the same status. For each repeated
|
||||||
|
//! notification, response with code 200 should be sent as well.
|
||||||
|
//!
|
||||||
|
//! To ensure trusted communication between PayU and your shop, you must verify
|
||||||
|
//! the signature value available in the OpenPayu-Signature header each time
|
||||||
|
//! your system receives any notification from a PayU server. Refer to the
|
||||||
|
//! Verification of notifications signature for more information.
|
||||||
|
//!
|
||||||
|
//! Notifications are sent for orders in the following statuses: PENDING,
|
||||||
|
//! WAITING_FOR_CONFIRMATION, COMPLETED, CANCELED.
|
||||||
|
//!
|
||||||
|
//! Note: if you filter IP addresses, remember to allow IPs used by PayU to send
|
||||||
|
//! the notifications. These are:
|
||||||
|
//!
|
||||||
|
//! ### PRODUCTION
|
||||||
|
//!
|
||||||
|
//! > 185.68.12.10, 185.68.12.11, 185.68.12.12, 185.68.12.26, 185.68.12.27,
|
||||||
|
//! > 185.68.12.28
|
||||||
|
//!
|
||||||
|
//! ### SANDBOX
|
||||||
|
//!
|
||||||
|
//! > 185.68.14.10, 185.68.14.11, 185.68.14.12, 185.68.14.26, 185.68.14.27,
|
||||||
|
//! > 185.68.14.28
|
||||||
|
//!
|
||||||
|
//! ## Payment lifecycle
|
||||||
|
//!
|
||||||
|
//! You can configure a separate auto-receive / automatic collection parameter
|
||||||
|
//! for each payment method via the Management Panel.
|
||||||
|
//!
|
||||||
|
//! By default, auto-receive is enabled. The basic payment sequence is as
|
||||||
|
//! follows:
|
||||||
|
//!
|
||||||
|
//! 1) Each successfully authorized payment for an order is captured.
|
||||||
|
//! 2) The buyer is charged with the order amount.
|
||||||
|
//! 3) The shop balance is increased by the order amount.
|
||||||
|
//! 4) PayU calculates its commission to the order.
|
||||||
|
//!
|
||||||
|
//! If the auto-receive is turned off, you should capture each order using a PUT
|
||||||
|
//! method (Order capture) or cancel using DELETE method (Cancellation).
|
||||||
|
//!
|
||||||
|
//! If no such action is taken the order is auto-canceled. Automatic
|
||||||
|
//! cancellation occurs after a number of days indicated for the payment method.
|
||||||
|
//!
|
||||||
|
//! <img src="https://developers.payu.com/images/order_statusesV2-en.png" />
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use super::deserialize;
|
||||||
|
use crate::OrderId;
|
||||||
|
|
||||||
|
/// Payment notification object received on notify_url
|
||||||
|
///
|
||||||
|
/// See [crate::req::CreateOrder::notify_url]
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct StatusUpdate {
|
||||||
|
/// Section containing order object
|
||||||
|
pub order: Order,
|
||||||
|
/// Moment of accepting the transaction and adding funds from the
|
||||||
|
/// transaction to the Shop balance. Format: "%Y-%M-%DT%h:%m:%s%z."
|
||||||
|
/// Example: "2020-06-09T17:52:04.644+02:00". If the millisecond counter
|
||||||
|
/// is "000" then milliseconds are not sent and the format changes to:
|
||||||
|
/// "%Y-%M-%DT%h:%m:%s". Is present only for the status "COMPLETED".
|
||||||
|
pub local_receipt_date_time: Option<String>,
|
||||||
|
/// Array of objects related to transaction identification. In case of
|
||||||
|
/// statuses:
|
||||||
|
/// * `"WAITING_FOR_CONFIRMATION"` and `"COMPLETED"` - Contains one element
|
||||||
|
/// with two parameters: name and value,
|
||||||
|
/// * `"PENDING"` - may contain object with aforementioned parameters or it
|
||||||
|
/// can be empty.
|
||||||
|
///
|
||||||
|
/// Also properties `name`
|
||||||
|
///
|
||||||
|
/// Static value. The payment identifier, displayed on transaction
|
||||||
|
/// statements as "Trans ID" and within the transaction search option in
|
||||||
|
/// the Management Panel.
|
||||||
|
///
|
||||||
|
/// Also properties `value`
|
||||||
|
///
|
||||||
|
/// Transaction ID in PayU system (data type - string).
|
||||||
|
pub properties: Option<Vec<super::Prop>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StatusUpdate {
|
||||||
|
pub fn status(&self) -> super::PaymentStatus {
|
||||||
|
self.order.status
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [crate::req::OrderCreate::ext_order_id]
|
||||||
|
pub fn ext_order_id(&self) -> &str {
|
||||||
|
self.order.ext_order_id.as_deref().unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_ext_order_id(&self) -> bool {
|
||||||
|
self.order.ext_order_id.is_some()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Refund notification object
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct RefundUpdate {
|
||||||
|
pub ext_order_id: String,
|
||||||
|
pub order_id: OrderId,
|
||||||
|
pub refund: Refund,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Refund {
|
||||||
|
pub refund_id: String,
|
||||||
|
pub amount: String,
|
||||||
|
pub currency_code: String,
|
||||||
|
pub status: super::RefundStatus,
|
||||||
|
pub status_date_time: String,
|
||||||
|
pub reason: String,
|
||||||
|
pub reason_description: String,
|
||||||
|
pub refund_date: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Order {
|
||||||
|
pub notify_url: Option<String>,
|
||||||
|
/// Customer client IP address
|
||||||
|
pub customer_ip: String,
|
||||||
|
/// Secret pos ip. This is connected to PayU account
|
||||||
|
#[serde(deserialize_with = "deserialize::deserialize_i32_newtype")]
|
||||||
|
pub merchant_pos_id: super::MerchantPosId,
|
||||||
|
/// Transaction description
|
||||||
|
pub description: String,
|
||||||
|
/// 3 characters currency identifier, ex. PLN
|
||||||
|
pub 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(deserialize_with = "deserialize::deserialize_i32")]
|
||||||
|
pub total_amount: super::Price,
|
||||||
|
/// @see [crate::Buyer]
|
||||||
|
pub buyer: Option<super::Buyer>,
|
||||||
|
/// List of products
|
||||||
|
pub products: Vec<super::Product>,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
pub order_create_date: Option<String>,
|
||||||
|
pub pay_method: Option<super::PayMethod>,
|
||||||
|
pub status: super::PaymentStatus,
|
||||||
|
pub ext_order_id: Option<String>,
|
||||||
|
}
|
381
pay_u/src/req.rs
Normal file
381
pay_u/src/req.rs
Normal file
@ -0,0 +1,381 @@
|
|||||||
|
//! 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
|
||||||
|
}
|
||||||
|
}
|
144
pay_u/src/res.rs
Normal file
144
pay_u/src/res.rs
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
use crate::{OrderId, Refund, Status};
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CreateOrder {
|
||||||
|
/// Http status as a text
|
||||||
|
pub status: Status,
|
||||||
|
/// Client should be redirected to this URI
|
||||||
|
pub redirect_uri: String,
|
||||||
|
/// This should be connected to your own order
|
||||||
|
pub order_id: OrderId,
|
||||||
|
/// This is YOUR_EXT_ORDER_ID
|
||||||
|
pub ext_order_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct RefundDetails {
|
||||||
|
pub order_id: Option<String>,
|
||||||
|
pub refund: Option<Refund>,
|
||||||
|
pub status: Status,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Refunds {
|
||||||
|
pub refunds: Vec<Refund>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct TransactionPayMethod {
|
||||||
|
pub value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
pub enum CardProfile {
|
||||||
|
Consumer,
|
||||||
|
Business,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
pub enum CardClassification {
|
||||||
|
Debit,
|
||||||
|
Credit,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct TransactionCartData {
|
||||||
|
/// // "543402******4014",
|
||||||
|
pub card_number_masked: String,
|
||||||
|
/// MC (MasterCard/Maestro), VS (Visa)
|
||||||
|
/// Example; "MC"
|
||||||
|
pub card_scheme: String,
|
||||||
|
pub card_profile: CardProfile,
|
||||||
|
pub card_classification: CardClassification,
|
||||||
|
/// Example: "000"
|
||||||
|
pub card_response_code: String,
|
||||||
|
/// Example: "000 - OK"
|
||||||
|
pub card_response_code_desc: String,
|
||||||
|
/// Example: "5"
|
||||||
|
pub card_eci_code: String,
|
||||||
|
/// Example: "AY",
|
||||||
|
pub card3ds_status: String,
|
||||||
|
/// Example: "PL",
|
||||||
|
pub card_bin_country: String,
|
||||||
|
/// Example: "MCC0111LL1121"
|
||||||
|
pub first_transaction_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// > 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 {
|
||||||
|
/// Example: "5aff3ba8-0c37-4da1-ba4a-4ff24bcc2eed"
|
||||||
|
pub proposal_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct TransactionCart {
|
||||||
|
pub cart_data: TransactionCartData,
|
||||||
|
pub card_installment_proposal: TransactionCardInstallmentProposal,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Transaction {
|
||||||
|
pub pay_method: TransactionPayMethod,
|
||||||
|
pub payment_flow: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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: Status,
|
||||||
|
pub properties: Option<Vec<crate::Prop>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct OrderInfo {
|
||||||
|
pub order: Order,
|
||||||
|
pub status: Status,
|
||||||
|
pub properties: Option<Vec<crate::Prop>>,
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user