Working create order with Pay U

This commit is contained in:
Adrian Woźniak 2022-12-15 14:42:08 +01:00
parent 8d17fb9309
commit 9b61b79483
7 changed files with 221 additions and 177 deletions

1
Cargo.lock generated
View File

@ -3322,6 +3322,7 @@ dependencies = [
"wapc",
"wapc-codec",
"wapc-pool",
"wasmtime",
"wasmtime-provider",
]

View File

@ -4,15 +4,23 @@ pub use uuid;
pub const CONFIG_POS: u32 = 3;
#[derive(Debug, Clone, Copy, thiserror::Error, serde::Serialize, serde::Deserialize)]
#[derive(Debug, Clone, thiserror::Error, serde::Serialize, serde::Deserialize)]
#[repr(C)]
pub enum Error {
#[error("Malformed create payment message")]
MalformedCreatePayment,
#[error("Malformed authorize response")]
MalformedAuthorize,
#[error("Message pack: malformed create payment message")]
MsgPackDeserializationFailed,
#[error("Provider rejected authorization")]
AuthorizeFailed,
#[error("Provider rejected order")]
PaymentFailed,
#[error("HTTP request failed")]
HttpFailed,
#[error("Unknown host operation {0:?}")]
UnknownOperation(String),
}
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
@ -64,7 +72,7 @@ pub struct CreatePayment {
pub description: String,
pub cart_products: Vec<Product>,
pub items: Vec<Item>,
pub order_ext_id: String,
pub order_ext_id: Option<String>,
pub notify_uri: String,
pub continue_uri: String,
}
@ -72,8 +80,8 @@ pub struct CreatePayment {
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[repr(C)]
pub struct OrderCreated {
pub ext_order_id: ExtOrderId,
pub redirect_uri: String,
pub ext_order_id: Option<ExtOrderId>,
pub redirect_uri: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
@ -93,6 +101,34 @@ pub struct HttpRequest {
pub body: Option<Vec<u8>>,
}
pub mod tracing {
macro_rules! log_levels {
($($lvl: ident),+) => {
$(
#[macro_export]
macro_rules! $lvl {
($fmt: expr) => {
wapc::console_log($fmt);
};
($fmt: expr, $arg1: expr) => {
wapc::console_log(&format!($fmt, $arg1));
};
($fmt: expr, $arg1: expr, $arg2: expr) => {
wapc::console_log(&format!($fmt, $arg1, $arg2));
};
($fmt: expr, $arg1: expr, $arg2: expr, $arg3: expr) => {
wapc::console_log(&format!($fmt, $arg1, $arg2, $arg3));
};
($fmt: expr, $arg1: expr, $arg2: expr, $arg3: expr, $arg4: expr) => {
wapc::console_log(&format!($fmt, $arg1, $arg2, $arg3, $arg4));
};
}
)+
};
}
log_levels!(error, warn, debug, info, trace, log);
}
// #[tarpc::service]
// pub trait PaymentAdapter {
// async fn create_payment(msg: CreatePayment) -> Status;

View File

@ -127,6 +127,12 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "common_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3f6d59c71e7dc3af60f0af9db32364d96a16e9310f3f5db2b55ed642162dd35"
[[package]]
name = "config"
version = "0.1.0"
@ -670,10 +676,11 @@ version = "0.1.0"
dependencies = [
"bincode",
"chrono",
"common_macros",
"payment_adapter",
"serde",
"serde_json",
"thiserror",
"tracing",
"wapc-codec",
"wapc-guest",
]

View File

@ -11,7 +11,9 @@ payment_adapter = { path = "../payment_adapter" }
bincode = { version = "1.3.3" }
wapc-codec = { version = "1.0.0" }
wapc-guest = { version = "1.0.0" }
tracing = { version = "0.1.37" }
#tracing = { version = "0.1.37" }
chrono = { version = "0.4.23", features = ['alloc', 'wasmbind'] }
serde = { version = "1.0.149", features = ['derive'] }
thiserror = { version = "1.0.37" }
serde_json = { version = "1.0.89" }
common_macros = { version = "0.1.1" }

View File

@ -28,11 +28,11 @@ impl PayU {
pub fn new(client_id: String, client_secret: String, merchant_pos_id: i32) -> Self {
Self {
bearer: None,
sandbox: false,
sandbox: true,
merchant_pos_id,
client_id,
client_secret,
bearer_expires_at: chrono::Utc::now(),
bearer_expires_at: chrono::DateTime::default(),
}
}
@ -43,6 +43,14 @@ impl PayU {
"https://secure.payu.com/api/v2_1"
}
}
fn auth_url(&self) -> &str {
if self.sandbox {
"https://secure.snd.payu.com/pl/standard/user/oauth/authorize"
} else {
"https://secure.payu.com/pl/standard/user/oauth/authorize"
}
}
}
static mut CONFIG: Option<Arc<Mutex<PayU>>> = None;
@ -92,11 +100,7 @@ impl PayU {
_ => return Err(Box::new(Error::MalformedCreatePayment)),
};
wapc::console_log(&format!(
"IN_WASM: Received request for `ping` operation with payload : {:?}",
c
));
eprintln!("{:?}", c);
info!("{:?}", c);
let res::CreateOrder {
status: _,
@ -105,8 +109,8 @@ impl PayU {
ext_order_id: _,
} = create_payment(c)?;
Ok(serialize(OrderCreated {
redirect_uri,
ext_order_id: ExtOrderId::new(order_id),
redirect_uri: Some(redirect_uri),
ext_order_id: Some(ExtOrderId::new(order_id)),
})
.unwrap())
}
@ -116,10 +120,17 @@ impl PayU {
}
}
fn json_header() -> Vec<(String, String)> {
vec![
("Content-Type".into(), "application/json".into()),
("Accept".into(), "application/json".into()),
]
}
fn create_payment(c: CreatePayment) -> Result<res::CreateOrder, Error> {
eprintln!("Authorizing...");
info!("Authorizing...");
authorize()?;
eprintln!("Authorized!");
info!("Authorized!");
let config = client();
let pay_u = config.lock().unwrap();
@ -145,7 +156,7 @@ fn create_payment(c: CreatePayment) -> Result<res::CreateOrder, Error> {
h
})
};
let create_order = OrderCreate::build(
let mut create_order = OrderCreate::build(
{
let Buyer {
email,
@ -162,10 +173,10 @@ fn create_payment(c: CreatePayment) -> Result<res::CreateOrder, Error> {
description,
)
.map_err(|e| {
eprintln!("{}", e);
tracing::error!("{}", e);
error!("{}", e);
Error::MalformedCreatePayment
})?
.with_merchant_pos_id(pay_u.merchant_pos_id)
.with_products(cart_products.into_iter().map(|p| {
model::Product::new(
p.name.to_string(),
@ -173,35 +184,37 @@ fn create_payment(c: CreatePayment) -> Result<res::CreateOrder, Error> {
quantities.get(&p.id).copied().unwrap_or_default() as model::Quantity,
)
}))
.with_ext_order_id(order_ext_id)
.with_notify_url(notify_uri)
.with_continue_url(continue_uri);
eprintln!("calling host http 'create order'!");
let res = wapc::host_call(
"binding",
"http:req",
"http_req",
&serialize(HttpRequest {
if let Some(order_ext_id) = order_ext_id {
create_order = create_order.with_ext_order_id(order_ext_id);
}
info!("calling host http 'create order'!");
let v = {
let msg = serialize(HttpRequest {
method: HttpMethod::Post,
url: format!("{}/orders", pay_u.base_url()),
headers: vec![],
headers: json_header(),
bearer_auth: pay_u.bearer.clone(),
body: serialize(create_order).ok(),
body: Some(
serde_json::to_string_pretty(&create_order)
.unwrap()
.into_bytes(),
),
})
.unwrap(),
)
.map_err(|e| {
eprintln!("{}", e);
tracing::error!("{}", e);
Error::PaymentFailed
})
.and_then(|v| {
deserialize(&v).map_err(|e| {
eprintln!("{}", e);
tracing::error!("{}", e);
.unwrap();
info!("create order msg {:?}", msg);
wapc::host_call("create:order", "http:req", "http_req", &msg).map_err(|e| {
error!("{}", e);
Error::PaymentFailed
})
})?
};
info!("host_call deserialize buffer {:?}", v);
let res: res::CreateOrder = serde_json::from_slice(&v).map_err(|e| {
error!("{}", e);
Error::PaymentFailed
})?;
Ok(res)
}
@ -217,7 +230,10 @@ fn authorize() -> Result<bool, Error> {
let config = client();
let mut pay_u = config.lock().unwrap();
if Utc::now() - Duration::seconds(1) < pay_u.bearer_expires_at {
let now = Utc::now() - Duration::seconds(1);
info!("Now is {:?}", now);
if now < pay_u.bearer_expires_at && pay_u.bearer.is_some() {
info!("Bearer ok");
return Ok(true);
}
#[derive(serde::Deserialize)]
@ -226,33 +242,33 @@ fn authorize() -> Result<bool, Error> {
expires_in: i64,
}
eprintln!("calling host http 'authorize'!");
let res = wapc::host_call("binding", "http:req", "http_req", &serialize(HttpRequest {
method: HttpMethod::Post,
url: format!(
"https://secure.payu.com/pl/standard/user/oauth/authorize?grant_type=client_credentials&client_id={}&client_secret={}",
pay_u.client_id,
pay_u.client_secret
),
headers: vec![],
bearer_auth: None,
body: None,
}).map_err(|e| {
eprintln!("{}", e);
tracing::error!("{}", e);
Error::PaymentFailed
})?).map_err(|e| {
eprintln!("{}", e);
tracing::error!("{}", e);
Error::PaymentFailed
})?;
let res: BearerResult = deserialize(&res).map_err(|e| {
eprintln!("{}", e);
tracing::error!("{}", e);
Error::PaymentFailed
info!("calling host http 'authorize'!");
let res = {
let msg = serialize(HttpRequest {
method: HttpMethod::Post,
url: format!(
"{}?grant_type=client_credentials&client_id={}&client_secret={}",
pay_u.auth_url(),
pay_u.client_id,
pay_u.client_secret,
),
headers: json_header(),
bearer_auth: None,
body: None,
})
.unwrap();
info!("authorize msg {:?}", msg);
wapc::host_call("authorize", "http:req", "http_req", &msg).map_err(|e| {
error!("{}", e);
Error::AuthorizeFailed
})?
};
let res: BearerResult = serde_json::from_slice(&res).map_err(|e| {
error!("{}", e);
Error::MalformedAuthorize
})?;
eprintln!("Authorized with calling host");
tracing::trace!("Bearer is {}", res.access_token);
info!("Authorized with calling host");
trace!("Bearer is {}", res.access_token);
pay_u.bearer_expires_at = Utc::now() + Duration::seconds(res.expires_in);
pay_u.bearer = Some(res.access_token);
Ok(true)

View File

@ -31,9 +31,10 @@ tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
uuid = { version = "1.2.1", features = ["serde", "v4"] }
bincode = { version = "1.3.3" }
wapc = { version = "1.0.0", features = [] }
wapc-codec = { version = "1.0.0" }
wapc-pool = { version = "1.0.0" }
wasmtime-provider = { version = "1.3.2", features = [] }
wapc-codec = { version = "1.0.0", features = [] }
wapc-pool = { version = "1.0.0", features = [] }
wasmtime = { version = "3.0.1", features = ['parallel-compilation', 'async'] }
wasmtime-provider = { version = "1.3.2", features = ['wasmtime-wasi', 'wasi-common', 'wasi', 'cache'] }
reqwest = { version = "0.11.13", features = ["default", "json", "blocking"] }
#pay_u = { path = "../../vendor/pay_u" }

View File

@ -2,13 +2,11 @@ use std::fs::read_dir;
use std::path::PathBuf;
use std::str::FromStr;
use bincode::deserialize;
use config::{AppConfig, UpdateConfig};
use payment_adapter::{HttpMethod, HttpRequest};
use payment_adapter::{HttpMethod, HttpRequest, Item, Product};
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
use wapc::WasiParams;
use wapc_pool::HostPoolBuilder;
// use payment_adapter::{CreatePayment, PaymentAdapter, Status};
// mod actions;
// mod context;
@ -33,7 +31,10 @@ impl UpdateConfig for Opts {
#[tokio::main]
async fn main() {
std::env::set_var("RUST_LOG", "debug");
tracing_subscriber::fmt::init();
let opts: Opts = gumdrop::parse_args_default_or_exit();
let config = config::default_load(&opts);
@ -49,7 +50,7 @@ async fn main() {
)
});
for file in dir.filter_map(|r| r.map(|r| r.path()).ok()) {
eprintln!("{:?}", file);
tracing::info!("{:?}", file);
if file.extension().and_then(|s| s.to_str()) != Some("wasm") {
continue;
}
@ -67,8 +68,8 @@ async fn main() {
wapc::WapcHost::new(
Box::new(engine.clone()),
Some(Box::new(
move |_a, _binding, _namespace, msg_name, payload| {
Ok(host_call(msg_name, payload)?)
move |_a, binding, _namespace, msg_name, payload| {
Ok(host_call(binding, msg_name, payload)?)
},
)),
)
@ -86,28 +87,40 @@ async fn main() {
.cloned()
.unwrap_or_default();
tracing::info!("Start init");
pool.call("init", wapc_codec::messagepack::serialize(msg).unwrap())
.await
.unwrap();
let msg = payment_adapter::CreatePayment {
buyer: payment_adapter::Buyer {
email: "email".to_string(),
phone: "phone".to_string(),
first_name: "first_name".to_string(),
last_name: "last_name".to_string(),
language: "language".to_string(),
email: "hello@example.com".to_string(),
phone: "530698478".to_string(),
first_name: "Joe".to_string(),
last_name: "Doe".to_string(),
language: "pl".to_string(),
},
customer_ip: "customer_ip".to_string(),
currency: "currency".to_string(),
description: "description".to_string(),
cart_products: vec![],
items: Default::default(),
order_ext_id: "order_ext_id".to_string(),
notify_uri: "notify_uri".to_string(),
continue_uri: "continue_uri".to_string(),
customer_ip: "12.22.34.54".to_string(),
currency: "PLN".to_string(),
description: "Nesciunt fugit libero quis dolorum quo. Tempore aut nisi voluptatem. Odio et aspernatur est. Sint vel molestias sunt cumque quibusdam reprehenderit est.".to_string(),
cart_products: vec![Product {
id: 23,
name: "Socks".to_string(),
unit_price: 1542,
quantity_unit: "Unit".to_string(),
quantity: 2,
}],
items: vec![Item {
product_id: 23,
quantity: 2,
quantity_unit: "Unit".to_string(),
}],
order_ext_id: None,
notify_uri: "https://localhost:3030/notify_uri".to_string(),
continue_uri: "https://localhost:3030/continue_uri".to_string(),
};
tracing::info!("Start create_payment");
let call_result = pool
.call(
"create_payment",
@ -115,10 +128,10 @@ async fn main() {
)
.await
.unwrap();
let result: payment_adapter::Status =
let result: payment_adapter::OrderCreated =
wapc_codec::messagepack::deserialize(&call_result).unwrap();
eprintln!("create payment res {:?}", result)
tracing::info!("create payment res {:?}", result)
}
}
}
@ -129,20 +142,27 @@ async fn main() {
// rpc::start(config, db, mqtt_client).await;
}
fn host_call(name: &str, payload: &[u8]) -> Result<Vec<u8>, payment_adapter::Error> {
// #[tracing::instrument]
fn host_call(binding: &str, name: &str, payload: &[u8]) -> Result<Vec<u8>, payment_adapter::Error> {
match name {
"http_req" => {
let req: HttpRequest = match deserialize(payload) {
let req: HttpRequest = match wapc_codec::messagepack::deserialize(payload) {
Ok(req) => req,
_ => return Ok(vec![]),
Err(e) => {
tracing::error!("{:?} payload {:?}", binding, payload);
tracing::error!("Failed to deserialize. {}", e);
return Err(payment_adapter::Error::MsgPackDeserializationFailed);
}
};
http_request(req)
}
_ => Ok(vec![]),
_ => Err(payment_adapter::Error::UnknownOperation(name.to_string())),
}
}
#[tracing::instrument]
fn http_request(req: HttpRequest) -> Result<Vec<u8>, payment_adapter::Error> {
tracing::info!("{:?}", req);
let HttpRequest {
method,
url,
@ -152,7 +172,6 @@ fn http_request(req: HttpRequest) -> Result<Vec<u8>, payment_adapter::Error> {
} = req;
let client = reqwest::blocking::ClientBuilder::default()
.user_agent("curl/7.82.0")
// .use_native_tls()
// Do not follow redirect!
.redirect(reqwest::redirect::Policy::none())
.connection_verbose(true)
@ -173,84 +192,46 @@ fn http_request(req: HttpRequest) -> Result<Vec<u8>, payment_adapter::Error> {
})
};
match (method, bearer_auth) {
(HttpMethod::Get, Some(bearer)) => {
let text = client
.get(url)
.headers(headers)
.bearer_auth(bearer)
.send()
.map_err(|e| {
eprintln!("{}", e);
tracing::error!("{}", e);
payment_adapter::Error::HttpFailed
})?
.text()
.map_err(|e| {
eprintln!("{}", e);
tracing::error!("{}", e);
payment_adapter::Error::HttpFailed
})?;
Ok(text.into_bytes())
}
(HttpMethod::Get, _) => {
let text = client
.get(url)
.headers(headers)
.send()
.map_err(|e| {
eprintln!("{}", e);
tracing::error!("{}", e);
payment_adapter::Error::HttpFailed
})?
.text()
.map_err(|e| {
eprintln!("{}", e);
tracing::error!("{}", e);
payment_adapter::Error::HttpFailed
})?;
Ok(text.into_bytes())
}
(HttpMethod::Post, Some(bearer)) => {
let body = body.unwrap_or_default();
let body = reqwest::blocking::Body::from(body);
let text = client
.post(url)
.headers(headers)
.bearer_auth(bearer)
.body(body)
.send()
.map_err(|e| {
eprintln!("{}", e);
tracing::error!("{}", e);
payment_adapter::Error::HttpFailed
})?
.text()
.map_err(|e| {
eprintln!("{}", e);
tracing::error!("{}", e);
payment_adapter::Error::HttpFailed
})?;
Ok(text.into_bytes())
}
(HttpMethod::Post, None) => {
let text = client
.post(url)
.headers(headers)
.body(body.unwrap_or_default())
.send()
.map_err(|e| {
eprintln!("{}", e);
tracing::error!("{}", e);
payment_adapter::Error::HttpFailed
})?
.text()
.map_err(|e| {
eprintln!("{}", e);
tracing::error!("{}", e);
payment_adapter::Error::HttpFailed
})?;
Ok(text.into_bytes())
}
}
let response = match (method, bearer_auth) {
(HttpMethod::Get, Some(bearer)) => client
.get(url)
.headers(headers)
.bearer_auth(bearer)
.send()
.map_err(|e| {
tracing::error!("{}", e);
payment_adapter::Error::HttpFailed
})?,
(HttpMethod::Get, _) => client.get(url).headers(headers).send().map_err(|e| {
tracing::error!("{}", e);
payment_adapter::Error::HttpFailed
})?,
(HttpMethod::Post, Some(bearer)) => client
.post(url)
.headers(headers)
.bearer_auth(bearer)
.body(body.unwrap_or_default())
.send()
.map_err(|e| {
tracing::error!("{}", e);
payment_adapter::Error::HttpFailed
})?,
(HttpMethod::Post, None) => client
.post(url)
.headers(headers)
.body(body.unwrap_or_default())
.send()
.map_err(|e| {
eprintln!("POST NONE {}", e);
tracing::error!("{}", e);
payment_adapter::Error::HttpFailed
})?,
};
tracing::info!("HTTP response status {:?}", response.status());
let text = response.bytes().map_err(|e| {
tracing::error!("{}", e);
payment_adapter::Error::HttpFailed
})?;
tracing::info!("HTTP Result {:?}", text);
Ok(text.to_vec())
}