Add payment

This commit is contained in:
eraden 2022-04-25 08:19:20 +02:00
parent 58414f3fe2
commit cbc2a30e0e
5 changed files with 121 additions and 24 deletions

4
.env
View File

@ -9,4 +9,6 @@ SENDGRID_SECRET=SG.CUWRM-eoQfGJNqSU2bbwkg.NW5aBy5vZueCSOwIIyWUBqq5USChGiwAFrWzre
SENDGRID_API_KEY=CUWRM-eoQfGJNqSU2bbwkg SENDGRID_API_KEY=CUWRM-eoQfGJNqSU2bbwkg
SMTP_FROM=adrian.wozniak@ita-prog.pl SMTP_FROM=adrian.wozniak@ita-prog.pl
PAY_U_BEARER=d9a4536e-62ba-4f60-8017-6053211d3f47 PAYU_CLIENT_ID="145227"
PAYU_CLIENT_SECRET="12f071174cb7eb79d4aac5bc2f07563f"
PAYU_CLIENT_MERCHANT_ID=300746

16
Cargo.lock generated
View File

@ -613,7 +613,7 @@ dependencies = [
"oauth2", "oauth2",
"parking_lot 0.12.0", "parking_lot 0.12.0",
"password-hash", "password-hash",
"pay_u 0.1.3", "pay_u",
"pretty_env_logger", "pretty_env_logger",
"rand_core", "rand_core",
"sendgrid", "sendgrid",
@ -2133,20 +2133,6 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc"
[[package]]
name = "pay_u"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eca87ee08eda0742042079ef48c3eb478a3f43dd535b499c88c72e532f6cfb6"
dependencies = [
"chrono",
"log",
"reqwest",
"serde",
"serde_json",
"thiserror",
]
[[package]] [[package]]
name = "pay_u" name = "pay_u"
version = "0.1.4" version = "0.1.4"

View File

@ -65,4 +65,7 @@ jemallocator = { version = "0.3.2", features = [] }
sendgrid = { version = "0.17.4", features = ["async"] } sendgrid = { version = "0.17.4", features = ["async"] }
pay_u = { version = '0.1.3', features = ["single-client"] } pay_u = { path = "../pay_u", version = '0.1', features = ["single-client"] }
[dev-dependencies]
pay_u = { path = "../pay_u", version = "0.1", features = ["single-client"] }

View File

@ -1,21 +1,113 @@
use pay_u::MerchantPosId; use std::sync::Arc;
use parking_lot::Mutex;
use pay_u::{MerchantPosId, OrderCreateRequest};
use crate::model;
use crate::model::Product;
#[macro_export]
macro_rules! pay_async_handler {
($msg: ty, $async: ident, $res: ty) => {
impl actix::Handler<$msg> for PaymentManager {
type Result = actix::ResponseActFuture<Self, Result<$res>>;
fn handle(&mut self, msg: $msg, _ctx: &mut Self::Context) -> Self::Result {
use actix::WrapFuture;
let db = self.client.clone();
Box::pin(async { $async(msg, db).await }.into_actor(self))
}
}
};
}
pub type PayUClient = Arc<Mutex<pay_u::Client>>;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("{0}")]
PayU(#[from] pay_u::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Clone)]
pub struct PaymentManager { pub struct PaymentManager {
client: pay_u::Client, client: PayUClient,
} }
impl PaymentManager { impl PaymentManager {
pub fn new<ClientId, ClientSecret>( pub async fn build<ClientId, ClientSecret>(
client_id: ClientId, client_id: ClientId,
client_secret: ClientSecret, client_secret: ClientSecret,
merchant_pos_id: MerchantPosId, merchant_pos_id: MerchantPosId,
) -> Self ) -> Result<Self>
where where
ClientId: Into<String>, ClientId: Into<String>,
ClientSecret: Into<String>, ClientSecret: Into<String>,
{ {
let mut client = pay_u::Client::new(client_id, client_secret, merchant_pos_id); let mut client = pay_u::Client::new(client_id, client_secret, merchant_pos_id);
client client.authorize().await?;
Self { client } Ok(Self {
client: Arc::new(Mutex::new(client)),
})
} }
} }
impl actix::Actor for PaymentManager {
type Context = actix::Context<Self>;
}
pub struct PaymentResult {
pub redirect_uri: String,
pub payu_order_id: String,
}
pub struct Buyer {
/// Required customer e-mail
pub email: String,
/// Required customer phone number
pub phone: String,
/// Required customer first name
pub first_name: String,
/// Required customer last name
pub last_name: String,
/// Required customer language
pub language: String,
}
impl From<model::Buyer> for pay_u::Buyer {
fn from(b: Buyer) -> Self {
pay_u::Buyer::new(b.email, b.phone, b.first_name, b.last_name, b.language)
}
}
impl From<model::Product> for pay_u::Product {
fn from(p: Product) -> Self {
todo!()
}
}
#[derive(Debug, actix::Message)]
#[rtype(result = "Result<PaymentResult>")]
pub struct RequestPayment {
pub products: Vec<model::Product>,
pub redirect_uri: String,
pub currency: String,
pub description: String,
pub buyer: Buyer,
pub customer_ip: String,
}
pub(crate) async fn request_payment(
msg: RequestPayment,
client: PayUClient,
) -> Result<PaymentResult> {
let mut client = &mut *client.lock();
client.create_order(
OrderCreateRequest::new(msg.buyer.into(), msg.customer_ip, msg.currency)
.with_description(msg.description)
.with_notify_url(msg.redirect_uri)
.with_products(msg.products.into_iter().map(Into::into)),
)
}

View File

@ -13,7 +13,7 @@ use jemallocator::Jemalloc;
use password_hash::SaltString; use password_hash::SaltString;
use validator::{validate_email, validate_length}; use validator::{validate_email, validate_length};
use crate::actors::{database, email_manager, order_manager, token_manager}; use crate::actors::{database, email_manager, order_manager, payment_manager, token_manager};
use crate::email_manager::TestMail; use crate::email_manager::TestMail;
use crate::logic::encrypt_password; use crate::logic::encrypt_password;
use crate::model::{Email, Login, PassHash, Password, Role}; use crate::model::{Email, Login, PassHash, Password, Role};
@ -183,6 +183,19 @@ async fn server(opts: ServerOpts) -> Result<()> {
let db = database::Database::build(&opts.db_url()).await?.start(); let db = database::Database::build(&opts.db_url()).await?.start();
let token_manager = token_manager::TokenManager::new(db.clone()).start(); let token_manager = token_manager::TokenManager::new(db.clone()).start();
let order_manager = order_manager::OrderManager::new(db.clone()).start(); let order_manager = order_manager::OrderManager::new(db.clone()).start();
let payment_manager = {
let client_id = std::env::var("PAYU_CLIENT_ID").expect("Missing PAYU_CLIENT_ID env");
let client_secret =
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()
.expect("Variable PAYU_CLIENT_MERCHANT_ID must be number");
payment_manager::PaymentManager::build(client_id, client_secret, merchant_id)
.await
.expect("Failed to start payment manager")
.start()
};
HttpServer::new(move || { HttpServer::new(move || {
App::new() App::new()
@ -197,6 +210,7 @@ async fn server(opts: ServerOpts) -> Result<()> {
.app_data(Data::new(db.clone())) .app_data(Data::new(db.clone()))
.app_data(Data::new(token_manager.clone())) .app_data(Data::new(token_manager.clone()))
.app_data(Data::new(order_manager.clone())) .app_data(Data::new(order_manager.clone()))
.app_data(Data::new(payment_manager.clone()))
.configure(routes::configure) .configure(routes::configure)
// .default_service(web::to(HttpResponse::Ok)) // .default_service(web::to(HttpResponse::Ok))
}) })