From 9775274d715d494498003d77c5cd6c7fe92145c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Wo=C5=BAniak?= Date: Fri, 9 Dec 2022 16:33:35 +0100 Subject: [PATCH] Init & create payment (not working) --- Cargo.lock | 270 ++----- Cargo.toml | 4 +- README.md | 12 + crates/config/src/lib.rs | 2 +- crates/payment_adapter/Readme.adoc | 31 + crates/payment_adapter/src/lib.rs | 29 +- crates/payment_adapter_pay_u/Cargo.lock | 380 +--------- crates/payment_adapter_pay_u/Cargo.toml | 5 +- crates/payment_adapter_pay_u/src/credit.rs | 77 ++ .../payment_adapter_pay_u/src/deserialize.rs | 162 ++++ crates/payment_adapter_pay_u/src/lib.rs | 294 +++++--- crates/payment_adapter_pay_u/src/model.rs | 700 ++++++++++++++++++ crates/payment_adapter_pay_u/src/req.rs | 382 ++++++++++ crates/payment_adapter_pay_u/src/res.rs | 144 ++++ crates/payment_adapter_pay_u/src/serialize.rs | 25 + crates/payment_manager/Cargo.toml | 2 + crates/payment_manager/src/main.rs | 146 +++- .../payment_manager/src/pay_u_adapter/mod.rs | 109 --- .../payment_manager/src/t_pay_adapter/mod.rs | 0 vendor/pay_u/src/lib.rs | 11 - 20 files changed, 1982 insertions(+), 803 deletions(-) create mode 100644 crates/payment_adapter/Readme.adoc create mode 100644 crates/payment_adapter_pay_u/src/credit.rs create mode 100644 crates/payment_adapter_pay_u/src/deserialize.rs create mode 100644 crates/payment_adapter_pay_u/src/model.rs create mode 100644 crates/payment_adapter_pay_u/src/req.rs create mode 100644 crates/payment_adapter_pay_u/src/res.rs create mode 100644 crates/payment_adapter_pay_u/src/serialize.rs delete mode 100644 crates/payment_manager/src/pay_u_adapter/mod.rs delete mode 100644 crates/payment_manager/src/t_pay_adapter/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 65b2550..a025d91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,7 +25,7 @@ dependencies = [ "tarpc", "testx", "thiserror", - "tokio 1.22.0", + "tokio 1.23.0", "tracing", "tracing-opentelemetry", "tracing-subscriber", @@ -51,7 +51,7 @@ dependencies = [ "parking_lot 0.12.1", "pin-project-lite 0.2.9", "smallvec", - "tokio 1.22.0", + "tokio 1.23.0", "tokio-util 0.7.4", ] @@ -68,7 +68,7 @@ dependencies = [ "log", "memchr", "pin-project-lite 0.2.9", - "tokio 1.22.0", + "tokio 1.23.0", "tokio-util 0.7.4", ] @@ -140,7 +140,7 @@ checksum = "7ea16c295198e958ef31930a6ef37d0fb64e9ca3b6116e6b93a8bdae96ee1000" dependencies = [ "actix-macros", "futures-core", - "tokio 1.22.0", + "tokio 1.23.0", ] [[package]] @@ -157,7 +157,7 @@ dependencies = [ "mio 0.8.5", "num_cpus", "socket2 0.4.7", - "tokio 1.22.0", + "tokio 1.23.0", "tracing", ] @@ -692,7 +692,7 @@ dependencies = [ "tarpc", "testx", "thiserror", - "tokio 1.22.0", + "tokio 1.23.0", "tracing", "tracing-opentelemetry", "tracing-subscriber", @@ -734,7 +734,7 @@ dependencies = [ "strum", "tarpc", "thiserror", - "tokio 1.22.0", + "tokio 1.23.0", "tracing", "whatlang", ] @@ -1099,9 +1099,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a41a86530d0fe7f5d9ea779916b7cadd2d4f9add748b99c2c029cbbdfaf453" +checksum = "bdf07d07d6531bfcdbe9b8b739b104610c6508dcc4d63b410585faf338241daf" dependencies = [ "cc", "cxxbridge-flags", @@ -1111,9 +1111,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06416d667ff3e3ad2df1cd8cd8afae5da26cf9cec4d0825040f88b5ca659a2f0" +checksum = "d2eb5b96ecdc99f72657332953d4d9c50135af1bac34277801cc3937906ebd39" dependencies = [ "cc", "codespan-reporting", @@ -1126,15 +1126,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "820a9a2af1669deeef27cb271f476ffd196a2c4b6731336011e0ba63e2c7cf71" +checksum = "ac040a39517fd1674e0f32177648334b0f4074625b5588a64519804ba0553b12" [[package]] name = "cxxbridge-macro" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08a6e2fcc370a089ad3b4aaf54db3b1b4cee38ddabce5896b33eb693275f470" +checksum = "1362b0ddcfc4eb0a1f57b68bd77dd99f0e826958a96abd0ae9bd092e114ffed6" dependencies = [ "proc-macro2", "quote", @@ -1178,9 +1178,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.3.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" +checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" [[package]] name = "database_manager" @@ -1373,7 +1373,7 @@ dependencies = [ "tarpc", "testx", "thiserror", - "tokio 1.22.0", + "tokio 1.23.0", "tracing", "tracing-opentelemetry", "tracing-subscriber", @@ -1570,9 +1570,9 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b9663d381d07ae25dc88dbdf27df458faa83a9b25336bcac83d5e452b5fc9d3" +checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9" dependencies = [ "cfg-if 1.0.0", "libc", @@ -1710,7 +1710,7 @@ dependencies = [ "rumqttc", "serde", "thiserror", - "tokio 1.22.0", + "tokio 1.23.0", "tracing", "uuid 1.2.2", ] @@ -2007,30 +2007,11 @@ dependencies = [ "http", "indexmap", "slab", - "tokio 1.22.0", + "tokio 1.23.0", "tokio-util 0.7.4", "tracing", ] -[[package]] -name = "h2_wasi" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756e9dfbaf90887b69ea8113ef9d04a9fec0ec4ac17b84a95006381f6f7966f9" -dependencies = [ - "bytes 1.3.0", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio-util_wasi", - "tokio_wasi", - "tracing", -] - [[package]] name = "handlebars" version = "4.3.5" @@ -2239,7 +2220,7 @@ dependencies = [ "itoa 1.0.4", "pin-project-lite 0.2.9", "socket2 0.4.7", - "tokio 1.22.0", + "tokio 1.23.0", "tower-service", "tracing", "want", @@ -2267,34 +2248,10 @@ dependencies = [ "bytes 1.3.0", "hyper 0.14.23", "native-tls", - "tokio 1.22.0", + "tokio 1.23.0", "tokio-native-tls", ] -[[package]] -name = "hyper_wasi" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b903a973bd98c326103ea94336a9ffe44c57c71517d6be7639438021053dc6e" -dependencies = [ - "bytes 1.3.0", - "futures-channel", - "futures-core", - "futures-util", - "h2_wasi", - "http", - "http-body 0.4.5", - "httparse", - "httpdate 1.0.2", - "itoa 1.0.4", - "pin-project-lite 0.2.9", - "tokio_wasi", - "tower-service", - "tracing", - "want", - "wasmedge_wasi_socket", -] - [[package]] name = "iana-time-zone" version = "0.1.53" @@ -2371,9 +2328,9 @@ dependencies = [ [[package]] name = "insta" -version = "1.21.2" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261bf85ed492cd1c47c9ba675e48649682a9d2d2e77f515c5386d7726fb0ba76" +checksum = "197f4e300af8b23664d4077bf5c40e0afa9ba66a567bb5a51d3def3c7b287d1c" dependencies = [ "console", "lazy_static", @@ -2457,9 +2414,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.5.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745" +checksum = "ec947b7a4ce12e3b87e353abae7ce124d025b6c7d6c5aea5cc0bcf92e9510ded" [[package]] name = "ipnetwork" @@ -2614,9 +2571,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.137" +version = "0.2.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" [[package]] name = "libgit2-sys" @@ -2904,19 +2861,6 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "mio_wasi" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e260e68b7a4187e52caf00a035aaf7166391c2c2529123fe8229d4d8f86e15a2" -dependencies = [ - "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "wasmedge_wasi_socket", - "windows-sys 0.36.1", -] - [[package]] name = "miow" version = "0.2.2" @@ -2994,9 +2938,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" +checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" dependencies = [ "bitflags", "cc", @@ -3095,22 +3039,11 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" -[[package]] -name = "openapi" -version = "1.0.0" -dependencies = [ - "reqwest 0.11.13", - "serde", - "serde_derive", - "serde_json", - "url", -] - [[package]] name = "openssl" -version = "0.10.43" +version = "0.10.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020433887e44c27ff16365eaa2d380547a94544ad509aff6eb5b6e3e0b27b376" +checksum = "29d971fd5722fec23977260f6e81aa67d2f22cadbdc2aa049f1022d9a3be1566" dependencies = [ "bitflags", "cfg-if 1.0.0", @@ -3140,9 +3073,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.78" +version = "0.9.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07d5c8cb6e57b3a3612064d7b18b117912b4ce70955c2504d4b741c9e244b132" +checksum = "5454462c0eced1e97f2ec09036abc8da362e66802f66fd20f86854d9d8cbcbc4" dependencies = [ "autocfg 1.1.0", "cc", @@ -3257,7 +3190,7 @@ dependencies = [ "tarpc", "testx", "thiserror", - "tokio 1.22.0", + "tokio 1.23.0", "tracing", "tracing-opentelemetry", "tracing-subscriber", @@ -3344,21 +3277,6 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" -[[package]] -name = "pay_u" -version = "0.1.9" -dependencies = [ - "chrono", - "derive_more", - "dotenv", - "log", - "reqwest_wasi", - "serde", - "serde_json", - "thiserror", - "tokio 1.22.0", -] - [[package]] name = "payment_adapter" version = "0.1.0" @@ -3388,6 +3306,7 @@ dependencies = [ "opentelemetry 0.17.0", "opentelemetry-jaeger", "payment_adapter", + "reqwest 0.11.13", "rumqttc", "serde", "sqlx", @@ -3395,7 +3314,7 @@ dependencies = [ "tarpc", "testx", "thiserror", - "tokio 1.22.0", + "tokio 1.23.0", "tracing", "tracing-opentelemetry", "tracing-subscriber", @@ -3955,7 +3874,6 @@ dependencies = [ "js-sys", "log", "mime", - "mime_guess", "native-tls", "once_cell", "percent-encoding", @@ -3963,7 +3881,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "tokio 1.22.0", + "tokio 1.23.0", "tokio-native-tls", "tower-service", "url", @@ -3973,41 +3891,6 @@ dependencies = [ "winreg 0.10.1", ] -[[package]] -name = "reqwest_wasi" -version = "0.11.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c1c0524070b75dc4a924bdaac56d5c874205497fb3132f09f3270e4efb188d" -dependencies = [ - "base64", - "bytes 1.3.0", - "encoding_rs", - "futures-core", - "futures-util", - "h2_wasi", - "http", - "http-body 0.4.5", - "hyper_wasi", - "ipnet", - "js-sys", - "lazy_static", - "log", - "mime", - "once_cell", - "percent-encoding", - "pin-project-lite 0.2.9", - "serde", - "serde_json", - "serde_urlencoded", - "tokio_wasi", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winreg 0.10.1", -] - [[package]] name = "ring" version = "0.16.20" @@ -4084,7 +3967,7 @@ dependencies = [ "rustls-native-certs", "rustls-pemfile 0.3.0", "thiserror", - "tokio 1.22.0", + "tokio 1.23.0", "tokio-rustls", ] @@ -4315,7 +4198,7 @@ dependencies = [ "tarpc", "testx", "thiserror", - "tokio 1.22.0", + "tokio 1.23.0", "tracing", "tracing-opentelemetry", "tracing-subscriber", @@ -4758,7 +4641,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24c5b2d25fa654cc5f841750b8e1cdedbe21189bf9a9382ee90bfa9dd3562396" dependencies = [ "once_cell", - "tokio 1.22.0", + "tokio 1.23.0", "tokio-rustls", ] @@ -4798,7 +4681,7 @@ dependencies = [ "tarpc", "testx", "thiserror", - "tokio 1.22.0", + "tokio 1.23.0", "tracing", "tracing-opentelemetry", "tracing-subscriber", @@ -4940,7 +4823,7 @@ dependencies = [ "static_assertions", "tarpc-plugins", "thiserror", - "tokio 1.22.0", + "tokio 1.23.0", "tokio-serde", "tokio-util 0.7.4", "tracing", @@ -5153,7 +5036,7 @@ dependencies = [ "sha2", "testx", "thiserror", - "tokio 1.22.0", + "tokio 1.23.0", "tracing", "uuid 1.2.2", ] @@ -5178,9 +5061,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.22.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" +checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" dependencies = [ "autocfg 1.1.0", "bytes 1.3.0", @@ -5193,7 +5076,7 @@ dependencies = [ "signal-hook-registry", "socket2 0.4.7", "tokio-macros", - "winapi 0.3.9", + "windows-sys 0.42.0", ] [[package]] @@ -5214,7 +5097,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" dependencies = [ "native-tls", - "tokio 1.22.0", + "tokio 1.23.0", ] [[package]] @@ -5224,7 +5107,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ "rustls", - "tokio 1.22.0", + "tokio 1.23.0", "webpki", ] @@ -5252,7 +5135,7 @@ checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" dependencies = [ "futures-core", "pin-project-lite 0.2.9", - "tokio 1.22.0", + "tokio 1.23.0", ] [[package]] @@ -5290,42 +5173,10 @@ dependencies = [ "futures-sink", "pin-project-lite 0.2.9", "slab", - "tokio 1.22.0", + "tokio 1.23.0", "tracing", ] -[[package]] -name = "tokio-util_wasi" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e7e921c33dc681c0b01aa4c5aa1bf619f46a7040a1565ddb1f84bc4666c1bd" -dependencies = [ - "bytes 1.3.0", - "futures-core", - "futures-sink", - "pin-project-lite 0.2.9", - "tokio_wasi", - "tracing", -] - -[[package]] -name = "tokio_wasi" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68e9a04f238fa1676caa7c897bf1dbe7a82269b675aca14176a028af7a2167d2" -dependencies = [ - "autocfg 1.1.0", - "bytes 1.3.0", - "libc", - "memchr", - "mio_wasi", - "num_cpus", - "pin-project-lite 0.2.9", - "socket2 0.4.7", - "wasmedge_wasi_socket", - "winapi 0.3.9", -] - [[package]] name = "toml" version = "0.5.9" @@ -5474,9 +5325,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "ucd-trie" @@ -5676,7 +5527,7 @@ dependencies = [ "log", "rusty_pool", "thiserror", - "tokio 1.22.0", + "tokio 1.23.0", "wapc", ] @@ -5812,15 +5663,6 @@ dependencies = [ "leb128", ] -[[package]] -name = "wasmedge_wasi_socket" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc4e93009d7e2c2e3d29b65d1f56ef606466673e85fe417808beca50cb608bf" -dependencies = [ - "libc", -] - [[package]] name = "wasmparser" version = "0.93.0" diff --git a/Cargo.toml b/Cargo.toml index 40ea4ce..604d253 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,8 +25,8 @@ members = [ # "crates/api", "crates/web", # vendor - "vendor/t_pay", - "vendor/pay_u", +# "vendor/t_pay", +# "vendor/pay_u", ] exclude = [ "crates/payment_adapter_pay_u", diff --git a/README.md b/README.md index 65fe02d..e104487 100644 --- a/README.md +++ b/README.md @@ -20,3 +20,15 @@ This script will treat all crates which starts with `payment_adapter_` as a paym mkdir -p adapters ./scripts/build-adapters ``` + +##### `init` + +| Argument | Result | +|-----------------------------------------|--------------------------| +| payment_argument::PaymentProviderConfig | payment_argument::Status | + +* `create_payment` + +| Argument | Result | +|--------------------------------|-------------------------------| +| payment_adapter::CreatePayment | payment_adapter::OrderCreated | diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index a1d679a..e9b8e55 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -531,7 +531,7 @@ pub struct PaymentConfig { #[serde(default)] optional_payment: bool, #[serde(flatten)] - providers: HashMap, + pub providers: HashMap, } impl Example for PaymentConfig { diff --git a/crates/payment_adapter/Readme.adoc b/crates/payment_adapter/Readme.adoc new file mode 100644 index 0000000..db7897c --- /dev/null +++ b/crates/payment_adapter/Readme.adoc @@ -0,0 +1,31 @@ += Payment Adapter Implementation + +== Extention Lifetime + +Payment Adapter extension is loaded as WASI file and initialize as a module. +Then to store it in runtime `name` function is called to load extension unique `name` (`String`). + +Using this `name` extension configuration from application configuration, names must match! In config is missing is missing application will print error message is stop. + +Then configuration is passed to extension to setup client. If everything goes as expected it should return `Status::Success`, in any other situation it must return `Status::Failure`, this also will cause to print error message and stop application. + +Lastly if extension will be be dropped for some reason it will be detached from store and `teardown` function will be called. + +1. `name` +2. `init` +3. `teardown` + +== Example + +[rust] +``` +/// Mandatory for WASM runtime +#[no_mangle] +pub fn wapc_init() { + wapc::register_function("name", name); + wapc::register_function("init", init); + wapc::register_function("teardown", teardown); + wapc::register_function("create_payment", create_payment); + wapc::register_function("cancel_order", cancel_order); +} +``` diff --git a/crates/payment_adapter/src/lib.rs b/crates/payment_adapter/src/lib.rs index 440743a..bf78267 100644 --- a/crates/payment_adapter/src/lib.rs +++ b/crates/payment_adapter/src/lib.rs @@ -9,6 +9,10 @@ pub const CONFIG_POS: u32 = 3; pub enum Error { #[error("Malformed create payment message")] MalformedCreatePayment, + #[error("Provider rejected order")] + PaymentFailed, + #[error("HTTP request failed")] + HttpFailed, } #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] @@ -54,7 +58,6 @@ pub struct Item { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[repr(C)] pub struct CreatePayment { - // pub client: PayUClient, pub buyer: Buyer, pub customer_ip: String, pub currency: String, @@ -66,6 +69,30 @@ pub struct CreatePayment { pub continue_uri: String, } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[repr(C)] +pub struct OrderCreated { + pub ext_order_id: ExtOrderId, + pub redirect_uri: String, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[repr(C)] +pub enum HttpMethod { + Get, + Post, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[repr(C)] +pub struct HttpRequest { + pub method: HttpMethod, + pub url: String, + pub headers: Vec<(String, String)>, + pub bearer_auth: Option, + pub body: Option>, +} + // #[tarpc::service] // pub trait PaymentAdapter { // async fn create_payment(msg: CreatePayment) -> Status; diff --git a/crates/payment_adapter_pay_u/Cargo.lock b/crates/payment_adapter_pay_u/Cargo.lock index 4043874..434579a 100644 --- a/crates/payment_adapter_pay_u/Cargo.lock +++ b/crates/payment_adapter_pay_u/Cargo.lock @@ -38,12 +38,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - [[package]] name = "base64ct" version = "1.5.3" @@ -95,12 +89,6 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" -[[package]] -name = "bytes" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" - [[package]] name = "cc" version = "1.0.77" @@ -290,21 +278,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" -[[package]] -name = "encoding_rs" -version = "0.8.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "form_urlencoded" version = "1.1.0" @@ -424,31 +397,6 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] -[[package]] -name = "h2_wasi" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756e9dfbaf90887b69ea8113ef9d04a9fec0ec4ac17b84a95006381f6f7966f9" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio-util_wasi", - "tokio_wasi", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hermit-abi" version = "0.1.19" @@ -458,64 +406,6 @@ dependencies = [ "libc", ] -[[package]] -name = "http" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" - -[[package]] -name = "httpdate" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" - -[[package]] -name = "hyper_wasi" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b903a973bd98c326103ea94336a9ffe44c57c71517d6be7639438021053dc6e" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2_wasi", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "tokio_wasi", - "tower-service", - "tracing", - "want", - "wasmedge_wasi_socket", -] - [[package]] name = "iana-time-zone" version = "0.1.53" @@ -561,16 +451,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "indexmap" -version = "1.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" -dependencies = [ - "autocfg", - "hashbrown", -] - [[package]] name = "instant" version = "0.1.12" @@ -580,12 +460,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "ipnet" -version = "2.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745" - [[package]] name = "itoa" version = "1.0.4" @@ -662,25 +536,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "mime" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" - -[[package]] -name = "mio_wasi" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e260e68b7a4187e52caf00a035aaf7166391c2c2529123fe8229d4d8f86e15a2" -dependencies = [ - "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "wasmedge_wasi_socket", - "windows-sys 0.36.1", -] - [[package]] name = "model" version = "0.1.0" @@ -776,7 +631,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -796,19 +651,6 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" -[[package]] -name = "pay_u" -version = "0.1.9" -dependencies = [ - "chrono", - "derive_more", - "log", - "reqwest_wasi", - "serde", - "serde_json", - "thiserror", -] - [[package]] name = "payment_adapter" version = "0.1.0" @@ -827,9 +669,10 @@ name = "payment_adapter_pay_u" version = "0.1.0" dependencies = [ "bincode", - "pay_u", + "chrono", "payment_adapter", - "tokio", + "serde", + "thiserror", "tracing", "wapc-codec", "wapc-guest", @@ -929,41 +772,6 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" -[[package]] -name = "reqwest_wasi" -version = "0.11.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c1c0524070b75dc4a924bdaac56d5c874205497fb3132f09f3270e4efb188d" -dependencies = [ - "base64", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2_wasi", - "http", - "http-body", - "hyper_wasi", - "ipnet", - "js-sys", - "lazy_static", - "log", - "mime", - "once_cell", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", - "tokio_wasi", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winreg", -] - [[package]] name = "rmp" version = "0.8.11" @@ -1050,18 +858,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - [[package]] name = "slab" version = "0.4.7" @@ -1077,16 +873,6 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" -[[package]] -name = "socket2" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "subtle" version = "2.4.1" @@ -1159,49 +945,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" -[[package]] -name = "tokio" -version = "1.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" -dependencies = [ - "autocfg", - "pin-project-lite", - "windows-sys 0.42.0", -] - -[[package]] -name = "tokio-util_wasi" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e7e921c33dc681c0b01aa4c5aa1bf619f46a7040a1565ddb1f84bc4666c1bd" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio_wasi", - "tracing", -] - -[[package]] -name = "tokio_wasi" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68e9a04f238fa1676caa7c897bf1dbe7a82269b675aca14176a028af7a2167d2" -dependencies = [ - "autocfg", - "bytes", - "libc", - "memchr", - "mio_wasi", - "num_cpus", - "pin-project-lite", - "socket2", - "wasmedge_wasi_socket", - "winapi", -] - [[package]] name = "toml" version = "0.5.9" @@ -1211,12 +954,6 @@ dependencies = [ "serde", ] -[[package]] -name = "tower-service" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" - [[package]] name = "tracing" version = "0.1.37" @@ -1249,12 +986,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "try-lock" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" - [[package]] name = "typenum" version = "1.15.0" @@ -1330,16 +1061,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "want" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -dependencies = [ - "log", - "try-lock", -] - [[package]] name = "wapc-codec" version = "1.0.0" @@ -1397,18 +1118,6 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "wasm-bindgen-macro" version = "0.2.83" @@ -1438,25 +1147,6 @@ version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" -[[package]] -name = "wasmedge_wasi_socket" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc4e93009d7e2c2e3d29b65d1f56ef606466673e85fe417808beca50cb608bf" -dependencies = [ - "libc", -] - -[[package]] -name = "web-sys" -version = "0.3.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "winapi" version = "0.3.9" @@ -1488,19 +1178,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", -] - [[package]] name = "windows-sys" version = "0.42.0" @@ -1508,12 +1185,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.0", + "windows_x86_64_msvc", ] [[package]] @@ -1522,48 +1199,24 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - [[package]] name = "windows_aarch64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - [[package]] name = "windows_i686_gnu" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - [[package]] name = "windows_i686_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - [[package]] name = "windows_x86_64_gnu" version = "0.42.0" @@ -1576,23 +1229,8 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - [[package]] name = "windows_x86_64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" - -[[package]] -name = "winreg" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" -dependencies = [ - "winapi", -] diff --git a/crates/payment_adapter_pay_u/Cargo.toml b/crates/payment_adapter_pay_u/Cargo.toml index 174f328..2f16e58 100644 --- a/crates/payment_adapter_pay_u/Cargo.toml +++ b/crates/payment_adapter_pay_u/Cargo.toml @@ -7,10 +7,11 @@ edition = "2021" crate-type = ['cdylib'] [dependencies] -pay_u = { path = "../../vendor/pay_u" } payment_adapter = { path = "../payment_adapter" } bincode = { version = "1.3.3" } wapc-codec = { version = "1.0.0" } wapc-guest = { version = "1.0.0" } -tokio = { version="1.21.1", features=["rt","sync","time"] } 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" } diff --git a/crates/payment_adapter_pay_u/src/credit.rs b/crates/payment_adapter_pay_u/src/credit.rs new file mode 100644 index 0000000..2aebfef --- /dev/null +++ b/crates/payment_adapter_pay_u/src/credit.rs @@ -0,0 +1,77 @@ +//! This module allow to create credit request during create order request + +use serde::{Deserialize, Serialize}; + +use crate::model::{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, + /// Applicant’s phone number + /// Recommended + #[serde(skip_serializing_if = "Option::is_none")] + phone: Option, + /// Applicant’s first name + /// Recommended + #[serde(skip_serializing_if = "Option::is_none")] + first_name: Option, + /// Applicant’s last name + /// Recommended + #[serde(skip_serializing_if = "Option::is_none")] + last_name: Option, + /// 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, + /// National Identification Number + /// Recommended + #[serde(skip_serializing_if = "Option::is_none")] + nin: Option, + /// Section containing data about applicant’s address. + /// Recommended + #[serde(skip_serializing_if = "Option::is_none")] + address: Option
, + /// Additional information about person applying for credit. + /// Recommended + #[serde(skip_serializing_if = "Option::is_none")] + additional_info: Option, +} + +/// 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>, + /// Section containing data of person applying for a credit + #[serde(skip_serializing_if = "Option::is_none")] + applicant: Option, +} + +impl Credit { + pub fn with_shopping_carts(mut self, shopping_carts: ShoppingCarts) -> Self + where + ShoppingCarts: Iterator, + { + 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, +} diff --git a/crates/payment_adapter_pay_u/src/deserialize.rs b/crates/payment_adapter_pay_u/src/deserialize.rs new file mode 100644 index 0000000..9aa1736 --- /dev/null +++ b/crates/payment_adapter_pay_u/src/deserialize.rs @@ -0,0 +1,162 @@ +use std::fmt; + +use serde::de::{self, Error, Visitor}; + +pub(crate) fn deserialize_i32<'de, D>(d: D) -> std::result::Result +where + D: serde::Deserializer<'de>, +{ + d.deserialize_string(I32Visitor) +} + +pub(crate) fn deserialize_i32_newtype<'de, N: From, D>( + d: D, +) -> std::result::Result +where + D: serde::Deserializer<'de>, +{ + d.deserialize_string(I32Visitor).map(N::from) +} + +pub(crate) fn deserialize_u32<'de, D>(d: D) -> std::result::Result +where + D: serde::Deserializer<'de>, +{ + d.deserialize_string(U32Visitor) +} + +struct I32Visitor; + +impl<'de> Visitor<'de> for I32Visitor { + type Value = i32; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("an integer between -2^31 and 2^31") + } + + fn visit_i8(self, value: i8) -> Result + where + E: de::Error, + { + Ok(i32::from(value)) + } + + fn visit_i32(self, value: i32) -> Result + where + E: de::Error, + { + Ok(value) + } + + fn visit_i64(self, value: i64) -> Result + where + E: de::Error, + { + use std::i32; + if value >= i64::from(i32::MIN) && value <= i64::from(i32::MAX) { + Ok(value as i32) + } else { + Err(E::custom(format!("i32 out of range: {}", value))) + } + } + + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + if let Ok(value) = v.parse::() { + Ok(value) + } else { + Err(E::custom(format!("str does not contains valid i32: {}", v))) + } + } + + fn visit_string(self, v: String) -> Result + where + E: Error, + { + if let Ok(value) = v.parse::() { + Ok(value) + } else { + Err(E::custom(format!( + "string does not contains valid i32: {}", + v + ))) + } + } + + // Similar for other methods: + // - visit_i16 + // - visit_u8 + // - visit_u16 + // - visit_u32 + // - visit_u64 +} + +struct U32Visitor; + +impl<'de> Visitor<'de> for U32Visitor { + type Value = u32; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("an integer between -2^31 and 2^31") + } + + fn visit_u8(self, value: u8) -> Result + where + E: de::Error, + { + Ok(u32::from(value)) + } + + fn visit_u32(self, value: u32) -> Result + where + E: de::Error, + { + Ok(value) + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + use std::u32; + if value >= u64::from(u32::MIN) && value <= u64::from(u32::MAX) { + Ok(value as u32) + } else { + Err(E::custom(format!("i32 out of range: {}", value))) + } + } + + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + if let Ok(value) = v.parse::() { + Ok(value) + } else { + Err(E::custom(format!("str does not contains valid i32: {}", v))) + } + } + + fn visit_string(self, v: String) -> Result + where + E: Error, + { + if let Ok(value) = v.parse::() { + Ok(value) + } else { + Err(E::custom(format!( + "string does not contains valid i32: {}", + v + ))) + } + } + + // Similar for other methods: + // - visit_i16 + // - visit_u8 + // - visit_u16 + // - visit_u32 + // - visit_u64 +} diff --git a/crates/payment_adapter_pay_u/src/lib.rs b/crates/payment_adapter_pay_u/src/lib.rs index 9fed1ce..8b52aee 100644 --- a/crates/payment_adapter_pay_u/src/lib.rs +++ b/crates/payment_adapter_pay_u/src/lib.rs @@ -1,133 +1,247 @@ +mod credit; +mod deserialize; +mod model; +mod req; +mod res; +mod serialize; + +use std::collections::HashMap; use std::sync::{Arc, Mutex}; -use pay_u::{ClientId, ClientSecret, MerchantPosId}; use payment_adapter::*; use wapc_codec::messagepack::*; use wapc_guest as wapc; +use crate::req::OrderCreate; + struct PayU { - client: Arc>, + sandbox: bool, + merchant_pos_id: i32, + client_id: String, + client_secret: String, + bearer: Option, + bearer_expires_at: chrono::DateTime, } -static mut CLIENT: Option = None; -static mut RUNTIME: Option = None; +impl PayU { + /// Create new PayU client + pub fn new(client_id: String, client_secret: String, merchant_pos_id: i32) -> Self { + Self { + bearer: None, + sandbox: false, + merchant_pos_id, + client_id, + client_secret, + bearer_expires_at: chrono::Utc::now(), + } + } + + fn base_url(&self) -> &str { + if self.sandbox { + "https://secure.snd.payu.com/api/v2_1" + } else { + "https://secure.payu.com/api/v2_1" + } + } +} + +static mut CONFIG: Option>> = None; + +fn client() -> Arc> { + unsafe { CONFIG.as_ref().unwrap().clone() } +} impl PayU { fn mount() { + wapc::register_function("teardown", Self::teardown); + wapc::register_function("name", Self::name); wapc::register_function("init", Self::init); wapc::register_function("create_payment", Self::create_payment); + wapc::register_function("cancel_order", Self::cancel_order); + } + + fn teardown(_msg: &[u8]) -> wapc::CallResult { + Ok(vec![]) + } + + fn name(_msg: &[u8]) -> wapc::CallResult { + Ok(b"pay_u".to_vec()) } fn init(msg: &[u8]) -> wapc::CallResult { - let runtime = tokio::runtime::Builder::new_current_thread() - .enable_all() - .enable_time() - .build() - .unwrap(); - unsafe { - RUNTIME = Some(runtime); - } - let config: PaymentProviderConfig = match deserialize(msg) { Ok(c) => c, - _ => return Err(Box::new(payment_adapter::Error::MalformedCreatePayment)), + _ => return Err(Box::new(Error::MalformedCreatePayment)), }; - let res_client = Arc::new(Mutex::new({ - pay_u::Client::new( - ClientId::new(config.client_id.unwrap()), - ClientSecret::new(config.client_secret.unwrap()), - MerchantPosId::new(config.merchant_id.unwrap().parse().unwrap()), - ) - })); - - let c = res_client.clone(); - unsafe { RUNTIME.as_ref().unwrap() }.block_on(async { - c.lock().unwrap().authorize().await.unwrap_or_else(|e| { - // tracing::error!("{e}"); - dbg!(e); - std::process::exit(1); - }); - }); + let client = PayU::new( + config.client_id.unwrap(), + config.client_secret.unwrap(), + config.merchant_id.unwrap().parse().unwrap(), + ); unsafe { - CLIENT = Some(Self { client: res_client }); + CONFIG = Some(Arc::new(Mutex::new(client))); } - Ok(vec![]) + Ok(serialize(Status::Success).unwrap_or_default()) } fn create_payment(msg: &[u8]) -> wapc::CallResult { let c: CreatePayment = match deserialize(msg) { Ok(c) => c, - _ => return Err(Box::new(payment_adapter::Error::MalformedCreatePayment)), + _ => return Err(Box::new(Error::MalformedCreatePayment)), }; - let CreatePayment { - client, - buyer, - customer_ip, - currency, - description, - cart_products, - mut items, - order_ext_id, - notify_uri, - continue_uri, - } = c; - - let pay_u::res::CreateOrder { - status: _, - redirect_uri, - order_id, - ext_order_id: _, - } = unsafe { RUNTIME.as_ref().unwrap() }.block_on(async { - let client = unsafe { CLIENT.as_ref().unwrap() }; - client - .create_order( - pay_u::req::OrderCreate::build( - buyer.into(), - customer_ip, - currency, - description, - ) - .map_err(|e| { - tracing::error!("{}", e); - Error::InvalidOrder - })? - .with_products(cart_products.into_iter().map(|p| { - pay_u::Product::new( - p.name.to_string(), - **p.price, - items - .remove(&p.id) - .map(|(quantity, _)| **quantity as u32) - .unwrap_or_default(), - ) - })) - .with_ext_order_id(order_ext_id) - .with_notify_url(notify_uri) - .with_continue_url(continue_uri), - ) - .await - .map_err(|e| { - tracing::error!("{}", e); - Error::PaymentFailed - }) - .unwrap() - }); - let _ = Ok((redirect_uri, ExtOrderId::new(order_id.0))); - wapc::console_log(&format!( "IN_WASM: Received request for `ping` operation with payload : {:?}", c )); eprintln!("{:?}", c); - // let _res = wapc::host_call("binding", "sample:namespace", "pong", msg)?; - Ok(serialize(Status::Success).unwrap()) + + let res::CreateOrder { + status: _, + redirect_uri, + order_id, + ext_order_id: _, + } = create_payment(c)?; + Ok(serialize(OrderCreated { + redirect_uri, + ext_order_id: ExtOrderId::new(order_id), + }) + .unwrap()) } + + fn cancel_order(_msg: &[u8]) -> wapc::CallResult { + Ok(vec![]) + } +} + +fn create_payment(c: CreatePayment) -> Result { + let config = client(); + let pay_u = config.lock().unwrap(); + + let CreatePayment { + buyer, + customer_ip, + currency, + description, + cart_products, + items, + order_ext_id, + notify_uri, + continue_uri, + } = c; + + let quantities = { + let len = items.len(); + items + .iter() + .fold(HashMap::with_capacity(len), |mut h, item| { + h.insert(item.product_id, item.quantity); + h + }) + }; + let create_order = OrderCreate::build( + { + let Buyer { + email, + phone, + first_name, + last_name, + language, + } = buyer; + + model::Buyer::new(email, phone, first_name, last_name, language) + }, + customer_ip, + currency, + description, + ) + .map_err(|e| { + tracing::error!("{}", e); + Error::MalformedCreatePayment + })? + .with_products(cart_products.into_iter().map(|p| { + model::Product::new( + p.name.to_string(), + p.unit_price, + 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); + + authorize()?; + let res = wapc::host_call( + "binding", + "http:req", + "http_req", + &serialize(HttpRequest { + method: HttpMethod::Post, + url: format!("{}/orders", pay_u.base_url()), + headers: vec![], + bearer_auth: pay_u.bearer.clone(), + body: serialize(create_order).ok(), + }) + .unwrap(), + ) + .map_err(|e| { + tracing::error!("{}", e); + Error::PaymentFailed + }) + .and_then(|v| { + deserialize(&v).map_err(|e| { + tracing::error!("{}", e); + Error::PaymentFailed + }) + })?; + Ok(res) } #[no_mangle] pub fn wapc_init() { PayU::mount(); } + +fn authorize() -> Result { + use chrono::{Duration, Utc}; + + let config = client(); + let mut pay_u = config.lock().unwrap(); + + if Utc::now() - Duration::seconds(1) < pay_u.bearer_expires_at { + return Ok(true); + } + #[derive(serde::Deserialize)] + struct BearerResult { + access_token: String, + expires_in: i64, + } + + 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| { + tracing::error!("{}", e); + Error::PaymentFailed + })?).map_err(|e| { + tracing::error!("{}", e); + Error::PaymentFailed + })?; + let res: BearerResult = deserialize(&res).map_err(|e| { + tracing::error!("{}", e); + Error::PaymentFailed + })?; + tracing::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) +} diff --git a/crates/payment_adapter_pay_u/src/model.rs b/crates/payment_adapter_pay_u/src/model.rs new file mode 100644 index 0000000..0c8add5 --- /dev/null +++ b/crates/payment_adapter_pay_u/src/model.rs @@ -0,0 +1,700 @@ +use serde::{Deserialize, Serialize}; + +use crate::{deserialize, serialize}; + +pub static SUCCESS: &str = "SUCCESS"; + +pub type OrderId = String; +pub type MerchantPosId = i32; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Client is not authorized. No bearer token available")] + NoToken, + #[error("Invalid customer ip. IP 0.0.0.0 is not acceptable")] + CustomerIp, + #[error("{0}")] + Io(#[from] std::io::Error), + #[error("Total value is not sum of products price")] + IncorrectTotal, + #[error("Buyer is required to place an order")] + NoBuyer, + #[error("Description is required to place an order")] + NoDescription, + #[error("Client is not authorized")] + Unauthorized, + #[error("Refund returned invalid response")] + Refund, + #[error("Create order returned invalid response")] + CreateOrder, + #[error("Failed to fetch order transactions")] + OrderTransactions, + #[error("Failed to fetch order details")] + OrderDetails, + #[error("Failed to fetch order refunds")] + OrderRefunds, + #[error("PayU rejected to create order with status {status_code:?}")] + CreateFailed { + status_code: String, + status_desc: Option, + code: Option, + severity: Option, + code_literal: Option, + }, + #[error("PayU rejected to perform refund with status {status_code:?}")] + RefundFailed { + status_code: String, + status_desc: Option, + code: Option, + severity: Option, + code_literal: Option, + }, + #[error("PayU rejected order details request with status {status_code:?}")] + OrderDetailsFailed { + status_code: String, + status_desc: Option, + code: Option, + severity: Option, + code_literal: Option, + }, + #[error("PayU rejected order transactions details request with status {status_code:?}")] + OrderTransactionsFailed { + status_code: String, + status_desc: Option, + code: Option, + severity: Option, + code_literal: Option, + }, + #[error("PayU returned order details but without any order")] + NoOrderInDetails, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum CodeLiteral { + /// Request lacks "refund" object. + MissingRefundSection, + + /// Transaction has not been finalized + TransNotEnded, + + /// Lack of funds in account + NoBalance, + + /// Refund amount exceeds transaction amount + AmountToBig, + + /// Refund value is too small + AmountToSmall, + + /// Refunds have been disabled + RefundDisabled, + + /// Too many refund attempts have been made + RefundToOften, + + /// Refund was already created + Paid, + + /// Unknown error + UnknownError, + + /// extRefundId was re-used and other params do not match the values + /// sent during the first call. + RefundIdempotencyMismatch, + + /// Shop billing has not yet been completed + TransBillingEntriesNotCompleted, + + /// The available time for refund has passed. + TransTooOld, + + /// Transaction amount that remains after refund creation will be too + /// small to make another refund. + RemainingTransAmountTooSmall, + + #[serde(other)] + /// Implementation changed + Unknown, +} + +pub type Result = std::result::Result; + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Prop { + pub name: String, + pub value: String, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Refund { + pub refund_id: String, + pub ext_refund_id: Option, + pub amount: String, + pub currency_code: String, + pub description: String, + pub creation_date_time: String, + pub status: String, + pub status_date_time: String, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +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_desc: Option, + code: Option, + severity: Option, + code_literal: Option, +} + +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() + } +} + +/// PayU payment status. +/// +/// Each payment is initially Pending and can change according to following +/// graph: +/// +/// +#[derive(Serialize, Deserialize, Copy, Clone, Debug)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum PaymentStatus { + /// Payment is currently being processed. + Pending, + /// 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. + WaitingForConfirmation, + /// Payment has been accepted. PayU will pay out the funds shortly. + Completed, + /// Payment has been cancelled and the buyer has not been charged (no money + /// was taken from buyer's account). + Canceled, +} + +#[derive(Serialize, Deserialize, Copy, Clone, Debug)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum RefundStatus { + /// refund was completed successfully + Finalized, + /// refund was cancelled + Canceled, + /// refund in progress + Pending, + /// 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. + WaitingForConfirmation, + /// Payment has been accepted. PayU will pay out the funds shortly. + Completed, +} + +#[derive(Serialize, Deserialize, Debug, Default)] +#[serde(rename_all = "camelCase")] +pub struct Delivery { + #[serde(skip_serializing_if = "Option::is_none")] + /// Street name + street: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + /// Postal box number + postal_box: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + /// Postal code + postal_code: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + /// City + city: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + /// Province + state: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + /// Two-letter country code compliant with ISO-3166. + country_code: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + /// Address description + name: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + /// Recipient’s name + recipient_name: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + /// Recipient’s e-mail address + recipient_email: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + /// Recipient’s phone number + recipient_phone: Option, +} + +#[derive(Serialize, Deserialize, Debug, Default)] +#[serde(rename_all = "camelCase")] +pub struct BuyerShippingAddress { + #[serde(skip_serializing_if = "Option::is_none")] + /// stores the shipping address + delivery: Option, +} + +impl BuyerShippingAddress { + pub fn new_with_delivery(delivery: Delivery) -> Self { + Self { + delivery: Some(delivery), + } + } +} + +#[derive(Serialize, Deserialize, Debug, Default)] +#[serde(rename_all = "camelCase")] +pub struct Buyer { + /// Required customer e-mail + #[serde(skip_serializing_if = "Option::is_none")] + email: Option, + /// Required customer phone number + #[serde(skip_serializing_if = "Option::is_none")] + phone: Option, + /// Required customer first name + #[serde(skip_serializing_if = "Option::is_none")] + first_name: Option, + /// Required customer last name + #[serde(skip_serializing_if = "Option::is_none")] + last_name: Option, + /// Required customer language + #[serde(skip_serializing_if = "Option::is_none")] + language: Option, + #[serde(skip_serializing_if = "Option::is_none")] + delivery: Option, +} + +impl Buyer { + pub fn new( + email: Email, + phone: Phone, + first_name: FirstName, + last_name: LastName, + lang: Language, + ) -> Self + where + Email: Into, + Phone: Into, + FirstName: Into, + LastName: Into, + Language: Into, + { + Self { + email: Some(email.into()), + phone: Some(phone.into()), + first_name: Some(first_name.into()), + last_name: Some(last_name.into()), + language: Some(lang.into()), + delivery: None, + } + } + + pub fn email(&self) -> &str { + self.email.as_deref().unwrap_or_default() + } + pub fn with_email(mut self, email: S) -> Self + where + S: Into, + { + self.email = Some(email.into()); + self + } + pub fn phone(&self) -> &str { + self.phone.as_deref().unwrap_or_default() + } + pub fn with_phone(mut self, phone: S) -> Self + where + S: Into, + { + self.phone = Some(phone.into()); + self + } + pub fn first_name(&self) -> &str { + self.first_name.as_deref().unwrap_or_default() + } + pub fn with_first_name(mut self, first_name: S) -> Self + where + S: Into, + { + self.first_name = Some(first_name.into()); + self + } + pub fn last_name(&self) -> &str { + self.last_name.as_deref().unwrap_or_default() + } + pub fn with_last_name(mut self, last_name: S) -> Self + where + S: Into, + { + self.last_name = Some(last_name.into()); + self + } + pub fn language(&self) -> &str { + self.language.as_deref().unwrap_or_default() + } + pub fn with_language(mut self, language: S) -> Self + where + S: Into, + { + self.language = Some(language.into()); + self + } + + pub fn with_delivery(mut self, delivery: Delivery) -> Self { + self.delivery = Some(BuyerShippingAddress::new_with_delivery(delivery)); + self + } +} + +pub type Price = i32; +pub type Quantity = u32; + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Product { + pub name: String, + #[serde( + serialize_with = "serialize::serialize_i32", + deserialize_with = "deserialize::deserialize_i32" + )] + pub unit_price: Price, + #[serde( + serialize_with = "serialize::serialize_u32", + deserialize_with = "deserialize::deserialize_u32" + )] + pub quantity: Quantity, + /// Product type, which can be virtual or material; (possible values true or + /// false). + #[serde(rename = "virtual", skip_serializing_if = "Option::is_none")] + pub virtual_product: Option, + /// Marketplace date from which the product (or offer) is available, ISO + /// format applies, e.g. "2019-03-27T10:57:59.000+01:00". + #[serde(skip_serializing_if = "Option::is_none")] + pub listing_date: Option, +} + +impl Product { + pub fn new>(name: Name, unit_price: Price, quantity: Quantity) -> Self { + Self { + name: name.into(), + unit_price, + quantity, + virtual_product: None, + listing_date: None, + } + } + + /// Product type, which can be virtual or material; (possible values true or + /// false). + pub fn into_virtual(mut self) -> Self { + self.virtual_product = Some(true); + self + } + + /// Product type, which can be virtual or material; (possible values true or + /// false). + pub fn non_virtual(mut self) -> Self { + self.virtual_product = Some(false); + self + } + + /// Marketplace date from which the product (or offer) is available, ISO + /// format applies, e.g. "2019-03-27T10:57:59.000+01:00". + pub fn with_listing_date(mut self, listing_date: chrono::NaiveDateTime) -> Self { + self.listing_date = Some(listing_date); + self + } + + fn erase_listing_date(mut self) -> Self { + self.listing_date = None; + self + } +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ShoppingCart { + /// Section containing data of shipping method. + #[serde(skip_serializing_if = "Option::is_none")] + shopping_method: Option, + /// Section containing data about ordered products. + /// > Note: product objects in the section do not + /// > have a listingDate field + #[serde(skip_serializing_if = "Option::is_none")] + products: Option>, + /// Submerchant identifier. This field should be consistent with field + /// extCustomerId in shoppingCarts section when order is placed in + /// marketplace. + ext_customer_id: String, +} + +impl ShoppingCart { + pub fn new(ext_customer_id: ExtCustomerId) -> Self + where + ExtCustomerId: Into, + { + Self { + shopping_method: None, + ext_customer_id: ext_customer_id.into(), + products: None, + } + } + + pub fn new_with_products( + ext_customer_id: ExtCustomerId, + products: Products, + ) -> Self + where + ExtCustomerId: Into, + Products: Iterator, + { + Self { + shopping_method: None, + ext_customer_id: ext_customer_id.into(), + products: Some(products.map(Product::erase_listing_date).collect()), + } + } + + pub fn with_products(mut self, products: Products) -> Self + where + Products: Iterator, + { + self.products = Some(products.map(Product::erase_listing_date).collect()); + self + } + + /// Section containing data of shipping method. + pub fn shopping_method(&self) -> &Option { + &self.shopping_method + } + + /// Section containing data about ordered products. + /// > Note: product objects in the section do not + /// > have a listingDate field + pub fn products(&self) -> &Option> { + &self.products + } + + /// Submerchant identifier. This field should be consistent with field + /// extCustomerId in shoppingCarts section when order is placed in + /// marketplace. + pub fn ext_customer_id(&self) -> &String { + &self.ext_customer_id + } +} + +/// Type of shipment +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum ShoppingMethodType { + Courier, + CollectionPointPickup, + ParcelLocker, + StorePickup, +} + +/// Delivery address +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Address { + /// The full name of the pickup point, including its unique identifier, e.g. + /// „Parcel locker POZ29A”. + #[serde(skip_serializing_if = "Option::is_none")] + pub point_id: Option, + /// Street name, possibly including house and flat number. + /// Recommended + #[serde(skip_serializing_if = "Option::is_none")] + pub street: Option, + /// Street number + /// Recommended + #[serde(skip_serializing_if = "Option::is_none")] + pub street_no: Option, + /// Flat number + /// Recommended + #[serde(skip_serializing_if = "Option::is_none")] + pub flat_no: Option, + /// Postal code + /// Recommended + #[serde(skip_serializing_if = "Option::is_none")] + pub postal_code: Option, + /// City + /// Recommended + #[serde(skip_serializing_if = "Option::is_none")] + pub city: Option, + /// Two-letter country code compliant with ISO-3166 + /// Recommended + #[serde(skip_serializing_if = "Option::is_none")] + pub country_code: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ShoppingMethod { + /// Shipping type + /// Recommended + #[serde(rename = "type", skip_serializing_if = "Option::is_none")] + pub shopping_type: Option, + /// Shipping cost + /// Recommended + #[serde(skip_serializing_if = "Option::is_none")] + pub price: Option, + /// Section containing data about shipping address. + /// Recommended + #[serde(skip_serializing_if = "Option::is_none")] + pub address: Option
, +} + +/// MultiUseCartToken +pub mod muct { + use serde::{Deserialize, Serialize}; + + #[derive(Serialize, Deserialize, Debug)] + #[serde(rename_all = "SCREAMING_SNAKE_CASE")] + pub enum CardOnFile { + /// Payment initialized by the card owner who agreed to save card for + /// future use. You can expect full authentication (3D Secure + /// and/or CVV). If you want to use multi-use token (TOKC_) + /// later, you have to be confident, that first payment was + /// successful. Default value for single-use token (TOK_). + /// + /// In case of plain card data payments you should retrieve transaction + /// data to obtain first TransactionId. It should be passed in + /// payMethods.payMethod.card section for transactions marked as + /// STANDARD, STANDARD_CARDHOLDER and STANDARD_MERCHANT; + /// STANDARD_CARDHOLDER - payment with already saved card, + /// initialized by the card owner. This transaction has + /// multi-use token (TOKC_). Depending of payment parameters + /// (e.g. high transaction amount) strong authentication can be + /// expected (3D Secure and/or CVV). Default value for multi-use token + /// (TOKC_); + First, + /// Payment with already saved card, initialized by the shop without the + /// card owner participation. This transaction has multi-use token + /// (TOKC_). By the definition, this payment type does not + /// require strong authentication. You cannot use it if FIRST + /// card-on-file payment failed. + StandardMerchant, + } + + #[derive(Serialize, Deserialize, Debug)] + #[serde(rename_all = "SCREAMING_SNAKE_CASE")] + pub enum Recurring { + /// Payment initialized by the card owner who agreed to save card for + /// future use in recurring plan. You can expect full authentication (3D + /// Secure and/or CVV). If you want to use multi-use token (TOKC_) + /// later, you have to be confident, that first recurring + /// payment was successful. + First, + /// Subsequent recurring payment (user is not present). This transaction + /// has multi use token (TOKC_). You cannot use it if FIRST recurring + /// payment failed. + Standard, + } + + #[derive(Serialize, Deserialize, Debug)] + #[serde(rename_all = "camelCase")] + pub struct MultiUseCartToken { + /// Information about party initializing order: + /// + /// * `FIRST` - payment initialized by the card owner who agreed to save + /// card for future use. You can expect full authentication (3D Secure + /// and/or CVV). If you want to use multi-use token (TOKC_) later, you + /// have to be confident, that first payment was successful. Default + /// value for single-use token (TOK_). + /// + /// In case of plain card data payments you should retrieve + /// transaction data to obtain first TransactionId. It should + /// be passed in payMethods.payMethod.card section for + /// transactions marked as STANDARD, STANDARD_CARDHOLDER and + /// STANDARD_MERCHANT; STANDARD_CARDHOLDER - payment with + /// already saved card, initialized by the card owner. This + /// transaction has multi-use token (TOKC_). Depending of payment + /// parameters (e.g. high transaction amount) strong authentication + /// can be expected (3D Secure and/or CVV). Default value for + /// multi-use token (TOKC_); + /// * `STANDARD_MERCHANT` - payment with already saved card, initialized + /// by the shop without the card owner participation. This transaction + /// has multi-use token (TOKC_). By the definition, this payment type + /// does not require strong authentication. You cannot use it if FIRST + /// card-on-file payment failed. + /// + /// `cardOnFile` parameter cannot be used with recurring parameter. + pub card_on_file: CardOnFile, + /// Marks the order as recurring payment. + /// + /// * `FIRST` - payment initialized by the card owner who agreed to save + /// card for future use in recurring plan. You can expect full + /// authentication (3D Secure and/or CVV). If you want to use + /// multi-use token (TOKC_) later, you have to be confident, that + /// first recurring payment was successful. + /// * `STANDARD` - subsequent recurring payment (user is not present). + /// This transaction has multi use token (TOKC_). You cannot use it if + /// FIRST recurring payment failed. + /// + /// `recurring` parameter cannot be used with cardOnFile parameter. + pub recurring: Recurring, + } +} diff --git a/crates/payment_adapter_pay_u/src/req.rs b/crates/payment_adapter_pay_u/src/req.rs new file mode 100644 index 0000000..18da7fa --- /dev/null +++ b/crates/payment_adapter_pay_u/src/req.rs @@ -0,0 +1,382 @@ +//! Objects used to send requests to PayU + +use serde::{Deserialize, Serialize}; + +use crate::credit::Credit; +pub use crate::model::*; + +#[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, +} + +impl Refund { + pub fn new(description: Description, amount: Option) -> Self + where + Description: Into, + { + Self { + description: description.into(), + amount, + } + } + + pub fn description(&self) -> &str { + &self.description + } + + pub fn amount(&self) -> Option { + 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, + /// 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, + /// 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: + /// + /// + /// 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, + /// 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: 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: Price, + /// @see [crate::Buyer] + pub(crate) buyer: Option, + /// List of products + pub(crate) products: Vec, + #[serde(skip_serializing)] + pub(crate) order_create_date: Option, + /// 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, + /// Additional description of the order. + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) additional_description: Option, + /// Text visible on the PayU payment page (max. 80 chars). + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) visible_description: Option, + /// 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, + #[serde(flatten, skip_serializing_if = "Option::is_none")] + pub(crate) muct: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) credit: Option, +} + +impl OrderCreate { + pub fn build( + buyer: Buyer, + customer_ip: CustomerIp, + currency: Currency, + description: Description, + ) -> Result + where + CustomerIp: Into, + Currency: Into, + Description: Into, + { + 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, + 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>(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(mut self, products: Products) -> Self + where + Products: Iterator, + { + 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(mut self, desc: Description) -> Self + where + Description: Into, + { + self.description = String::from(desc.into().trim()); + self + } + + /// Additional description of the order. + pub fn with_additional_description>( + 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(mut self, desc: Description) -> Self + where + Description: Into, + { + 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(mut self, notify_url: NotifyUrl) -> Self + where + NotifyUrl: Into, + { + 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(mut self, continue_url: ContinueUrl) -> Self + where + ContinueUrl: Into, + { + 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 { + &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 { + &self.buyer + } + + pub fn products(&self) -> &[Product] { + &self.products + } + + pub fn order_create_date(&self) -> &Option { + &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 + } +} diff --git a/crates/payment_adapter_pay_u/src/res.rs b/crates/payment_adapter_pay_u/src/res.rs new file mode 100644 index 0000000..614afec --- /dev/null +++ b/crates/payment_adapter_pay_u/src/res.rs @@ -0,0 +1,144 @@ +use crate::model::*; + +#[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, +} + +#[derive(serde::Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct RefundDetails { + pub order_id: Option, + pub refund: Option, + pub status: Status, +} + +#[derive(serde::Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Refunds { + pub refunds: Vec, +} + +#[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, +} + +#[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, + /// Example: "2014-10-27T14:58:17.443+01:00", + pub order_create_date: String, + /// Example: "http://localhost/OrderNotify/", + pub notify_url: Option, + /// 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, +} + +#[derive(serde::Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct OrdersInfo { + pub orders: Vec, + pub status: Status, + pub properties: Option>, +} + +#[derive(serde::Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct OrderInfo { + pub order: Order, + pub status: Status, + pub properties: Option>, +} diff --git a/crates/payment_adapter_pay_u/src/serialize.rs b/crates/payment_adapter_pay_u/src/serialize.rs new file mode 100644 index 0000000..54682ad --- /dev/null +++ b/crates/payment_adapter_pay_u/src/serialize.rs @@ -0,0 +1,25 @@ +use std::fmt::Display; + +pub(crate) fn serialize_i32(v: &i32, ser: S) -> std::result::Result +where + S: serde::Serializer, +{ + ser.serialize_str(&format!("{v}")) +} + +pub(crate) fn serialize_newtype( + v: &N, + ser: S, +) -> std::result::Result +where + S: serde::Serializer, +{ + ser.serialize_str(&format!("{v}")) +} + +pub(crate) fn serialize_u32(v: &u32, ser: S) -> std::result::Result +where + S: serde::Serializer, +{ + ser.serialize_str(&format!("{v}")) +} diff --git a/crates/payment_manager/Cargo.toml b/crates/payment_manager/Cargo.toml index 9be79c7..f6dd983 100644 --- a/crates/payment_manager/Cargo.toml +++ b/crates/payment_manager/Cargo.toml @@ -34,6 +34,8 @@ 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 = [] } +reqwest = { version = "0.11.13", features = ["default", "json", "blocking"] } +#pay_u = { path = "../../vendor/pay_u" } [dev-dependencies] fake = { version = "2.5.0" } diff --git a/crates/payment_manager/src/main.rs b/crates/payment_manager/src/main.rs index 1114ecc..9b0e79b 100644 --- a/crates/payment_manager/src/main.rs +++ b/crates/payment_manager/src/main.rs @@ -1,7 +1,12 @@ 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 reqwest::header::{HeaderMap, HeaderName, HeaderValue}; +use wapc::WasiParams; use wapc_pool::HostPoolBuilder; // use payment_adapter::{CreatePayment, PaymentAdapter, Status}; @@ -43,7 +48,7 @@ async fn main() { ) }); for file in dir.filter_map(|r| r.map(|r| r.path()).ok()) { - eprintln!("{:?}", file.extension()); + eprintln!("{:?}", file); if file.extension().and_then(|s| s.to_str()) != Some("wasm") { continue; } @@ -51,6 +56,7 @@ async fn main() { let module = std::fs::read(&file).unwrap(); let engine = wasmtime_provider::WasmtimeEngineProviderBuilder::new() .module_bytes(&module) + .wasi_params(WasiParams::default()) .build() .unwrap(); @@ -59,7 +65,11 @@ async fn main() { .factory(move || { wapc::WapcHost::new( Box::new(engine.clone()), - Some(Box::new(move |_a, _b, _c, _d, _e| Ok(vec![]))), + Some(Box::new( + move |_a, _binding, _namespace, msg_name, payload| { + Ok(host_call(msg_name, payload)?) + }, + )), ) .unwrap() }) @@ -67,6 +77,18 @@ async fn main() { .build(); { + let msg = config + .lock() + .payment() + .providers + .get("pay_u") + .cloned() + .unwrap_or_default(); + + pool.call("init", wapc_codec::messagepack::serialize(msg).unwrap()) + .await + .unwrap(); + let msg = payment_adapter::CreatePayment { buyer: payment_adapter::Buyer { email: "email".to_string(), @@ -105,3 +127,123 @@ async fn main() { // let mqtt_client = mqtt::start(config.clone(), db.clone()).await; // rpc::start(config, db, mqtt_client).await; } + +fn host_call(name: &str, payload: &[u8]) -> Result, payment_adapter::Error> { + match name { + "http_req" => { + let req: HttpRequest = match deserialize(payload) { + Ok(req) => req, + _ => return Ok(vec![]), + }; + http_request(req) + } + _ => Ok(vec![]), + } +} + +fn http_request(req: HttpRequest) -> Result, payment_adapter::Error> { + let HttpRequest { + method, + url, + headers, + bearer_auth, + body, + } = 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) + .build() + .expect("Failed to create client"); + + let headers = { + let len = headers.len(); + headers + .into_iter() + .fold(HeaderMap::with_capacity(len), |mut m, (k, v)| { + if let (Ok(v), Ok(k)) = + (HeaderValue::from_str(v.as_str()), HeaderName::from_str(&k)) + { + m.insert(k, v); + } + m + }) + }; + + match (method, bearer_auth) { + (HttpMethod::Get, Some(bearer)) => { + let text = client + .get(url) + .headers(headers) + .bearer_auth(bearer) + .send() + .map_err(|e| { + tracing::error!("{}", e); + payment_adapter::Error::HttpFailed + })? + .text() + .map_err(|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| { + tracing::error!("{}", e); + payment_adapter::Error::HttpFailed + })? + .text() + .map_err(|e| { + tracing::error!("{}", e); + payment_adapter::Error::HttpFailed + })?; + Ok(text.into_bytes()) + } + (HttpMethod::Post, Some(bearer)) => { + let body = body.unwrap_or_default(); + let len = body.len(); + let body = std::io::Cursor::new(body); + let body = reqwest::blocking::Body::sized(body, len as u64); + let text = client + .post(url) + .headers(headers) + .bearer_auth(bearer) + .body(body) + .send() + .map_err(|e| { + tracing::error!("{}", e); + payment_adapter::Error::HttpFailed + })? + .text() + .map_err(|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| { + tracing::error!("{}", e); + payment_adapter::Error::HttpFailed + })? + .text() + .map_err(|e| { + tracing::error!("{}", e); + payment_adapter::Error::HttpFailed + })?; + Ok(text.into_bytes()) + } + } +} diff --git a/crates/payment_manager/src/pay_u_adapter/mod.rs b/crates/payment_manager/src/pay_u_adapter/mod.rs deleted file mode 100644 index aca4543..0000000 --- a/crates/payment_manager/src/pay_u_adapter/mod.rs +++ /dev/null @@ -1,109 +0,0 @@ -use std::collections::HashMap; - -use model::*; - -// use crate::{Buyer, Error, PayUClient, Result}; - -#[derive(Debug, thiserror::Error)] -pub enum Error {} - -pub type Result = std::result::Result; - -#[derive(Debug)] -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 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) - } -} - -#[derive(Debug)] -pub struct Product { - pub id: ProductId, - pub name: String, - pub unit_price: Price, - pub quantity_unit: QuantityUnit, - pub quantity: Quantity, -} - -impl From for pay_u::Product { - fn from(p: Product) -> Self { - pay_u::Product::new(p.name, **p.unit_price, **p.quantity as u32) - } -} - -pub struct CreatePayment { - pub client: PayUClient, - pub buyer: Buyer, - pub customer_ip: String, - pub currency: String, - pub description: String, - pub cart_products: Vec, - pub items: HashMap, - pub order_ext_id: String, - pub notify_uri: String, - pub continue_uri: String, -} - -impl CreatePayment { - pub(crate) async fn create_payment(self) -> Result<(String, ExtOrderId)> { - let CreatePayment { - client, - buyer, - customer_ip, - currency, - description, - cart_products, - mut items, - order_ext_id, - notify_uri, - continue_uri, - } = self; - - let pay_u::res::CreateOrder { - status: _, - redirect_uri, - order_id, - ext_order_id: _, - } = client - .lock() - .create_order( - pay_u::req::OrderCreate::build(buyer.into(), customer_ip, currency, description) - .map_err(|e| { - tracing::error!("{}", e); - Error::InvalidOrder - })? - .with_products(cart_products.into_iter().map(|p| { - pay_u::Product::new( - p.name.to_string(), - **p.price, - items - .remove(&p.id) - .map(|(quantity, _)| **quantity as u32) - .unwrap_or_default(), - ) - })) - .with_ext_order_id(order_ext_id) - .with_notify_url(notify_uri) - .with_continue_url(continue_uri), - ) - .await - .map_err(|e| { - tracing::error!("{}", e); - Error::PaymentFailed - })?; - Ok((redirect_uri, ExtOrderId::new(order_id.0))) - } -} diff --git a/crates/payment_manager/src/t_pay_adapter/mod.rs b/crates/payment_manager/src/t_pay_adapter/mod.rs deleted file mode 100644 index e69de29..0000000 diff --git a/vendor/pay_u/src/lib.rs b/vendor/pay_u/src/lib.rs index 8e4a74f..5c35b7b 100644 --- a/vendor/pay_u/src/lib.rs +++ b/vendor/pay_u/src/lib.rs @@ -1295,17 +1295,6 @@ impl Client { "https://secure.payu.com/api/v2_1" } } - - fn build_client() -> reqwest::Client { - reqwest::ClientBuilder::default() - .user_agent("curl/7.82.0") - // .use_native_tls() - // Do not follow redirect! - .redirect(redirect::Policy::none()) - .connection_verbose(true) - .build() - .expect("Failed to create client") - } } #[cfg(test)]