mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 19:46:48 +08:00
feature(connector): add support for worldpay connector (#272)
This commit is contained in:
279
Cargo.lock
generated
279
Cargo.lock
generated
@ -88,7 +88,7 @@ dependencies = [
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"sha1",
|
||||
"smallvec",
|
||||
"tracing",
|
||||
@ -263,7 +263,7 @@ version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.2.8",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
@ -334,6 +334,16 @@ version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f907281554a3d0312bb7aab855a8e0ef6cbf1614d06de54105039ca8b34460e"
|
||||
|
||||
[[package]]
|
||||
name = "assert-json-diff"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-bb8-diesel"
|
||||
version = "0.1.0"
|
||||
@ -346,6 +356,17 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"event-listener",
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-stream"
|
||||
version = "0.3.3"
|
||||
@ -422,7 +443,7 @@ dependencies = [
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"rustls 0.20.7",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@ -936,6 +957,15 @@ dependencies = [
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd7bef69dc86e3c610e4e7aed41035e2a7ed12e72dd7530f61327a6579a4390b"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "config"
|
||||
version = "0.13.3"
|
||||
@ -1069,6 +1099,25 @@ dependencies = [
|
||||
"parking_lot_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deadpool"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "421fe0f90f2ab22016f32a9881be5134fdd71c65298917084b0c7477cbc3856e"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"deadpool-runtime",
|
||||
"num_cpus",
|
||||
"retain_mut",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deadpool-runtime"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eaa37046cc0f6c3cc6090fbdbf73ef0b8ef4cfcc37f6befc0020f63e8cf121e1"
|
||||
|
||||
[[package]]
|
||||
name = "derive_deref"
|
||||
version = "1.1.1"
|
||||
@ -1231,13 +1280,19 @@ dependencies = [
|
||||
"rustc_version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "2.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||
|
||||
[[package]]
|
||||
name = "fake"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d68f517805463f3a896a9d29c1d6ff09d3579ded64a7201b4069f8f9c0d52fd"
|
||||
dependencies = [
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1317,7 +1372,7 @@ dependencies = [
|
||||
"native-tls",
|
||||
"parking_lot 0.11.2",
|
||||
"pretty_env_logger",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"redis-protocol",
|
||||
"semver",
|
||||
"sha-1",
|
||||
@ -1442,6 +1497,21 @@ version = "0.3.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"memchr",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
"waker-fn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.25"
|
||||
@ -1465,6 +1535,12 @@ version = "0.3.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea"
|
||||
|
||||
[[package]]
|
||||
name = "futures-timer"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.25"
|
||||
@ -1503,6 +1579,17 @@ dependencies = [
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.9.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.8"
|
||||
@ -1511,7 +1598,7 @@ checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1625,6 +1712,27 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29"
|
||||
|
||||
[[package]]
|
||||
name = "http-types"
|
||||
version = "2.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e9b187a72d63adbfba487f48095306ac823049cb504ee195541e91c7775f5ad"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-channel",
|
||||
"base64 0.13.1",
|
||||
"futures-lite",
|
||||
"http",
|
||||
"infer",
|
||||
"pin-project-lite",
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_qs 0.8.5",
|
||||
"serde_urlencoded",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.8.0"
|
||||
@ -1732,6 +1840,12 @@ dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "infer"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac"
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
@ -2017,7 +2131,7 @@ checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
@ -2027,7 +2141,7 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8"
|
||||
dependencies = [
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2231,7 +2345,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"opentelemetry_api",
|
||||
"percent-encoding",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
@ -2262,6 +2376,12 @@ dependencies = [
|
||||
"supports-color",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.2"
|
||||
@ -2486,8 +2606,8 @@ dependencies = [
|
||||
"lazy_static",
|
||||
"num-traits",
|
||||
"quick-error 2.0.1",
|
||||
"rand",
|
||||
"rand_chacha",
|
||||
"rand 0.8.5",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_xorshift",
|
||||
"regex-syntax",
|
||||
"rusty-fork",
|
||||
@ -2549,6 +2669,19 @@ dependencies = [
|
||||
"scheduled-thread-pool",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
|
||||
dependencies = [
|
||||
"getrandom 0.1.16",
|
||||
"libc",
|
||||
"rand_chacha 0.2.2",
|
||||
"rand_core 0.5.1",
|
||||
"rand_hc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
@ -2556,8 +2689,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2567,7 +2710,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
||||
dependencies = [
|
||||
"getrandom 0.1.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2576,7 +2728,16 @@ version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.2.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
|
||||
dependencies = [
|
||||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2585,7 +2746,7 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2697,6 +2858,12 @@ dependencies = [
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "retain_mut"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0"
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.16.20"
|
||||
@ -2763,7 +2930,7 @@ dependencies = [
|
||||
"nanoid",
|
||||
"num_cpus",
|
||||
"once_cell",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"redis_interface",
|
||||
"reqwest",
|
||||
"ring",
|
||||
@ -2772,8 +2939,9 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_qs",
|
||||
"serde_qs 0.10.1",
|
||||
"serde_urlencoded",
|
||||
"serial_test",
|
||||
"storage_models",
|
||||
"structopt",
|
||||
"strum",
|
||||
@ -2783,6 +2951,7 @@ dependencies = [
|
||||
"toml",
|
||||
"url",
|
||||
"uuid",
|
||||
"wiremock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3024,6 +3193,17 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_qs"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_qs"
|
||||
version = "0.10.1"
|
||||
@ -3047,6 +3227,31 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serial_test"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c789ec87f4687d022a2405cf46e0cd6284889f1839de292cadeb6c6019506f2"
|
||||
dependencies = [
|
||||
"dashmap",
|
||||
"futures",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"parking_lot 0.12.1",
|
||||
"serial_test_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serial_test_derive"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b64f9e531ce97c88b4778aad0ceee079216071cffec6ac9b904277f8f92e7fe3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha-1"
|
||||
version = "0.9.8"
|
||||
@ -3508,7 +3713,7 @@ dependencies = [
|
||||
"indexmap",
|
||||
"pin-project",
|
||||
"pin-project-lite",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util 0.7.4",
|
||||
@ -3754,7 +3959,7 @@ version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.2.8",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@ -3809,6 +4014,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "waker-fn"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.0"
|
||||
@ -3819,6 +4030,12 @@ dependencies = [
|
||||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.9.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
@ -4085,6 +4302,28 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wiremock"
|
||||
version = "0.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "249dc68542861d17eae4b4e5e8fb381c2f9e8f255a84f6771d5fdf8b6c03ce3c"
|
||||
dependencies = [
|
||||
"assert-json-diff",
|
||||
"async-trait",
|
||||
"base64 0.13.1",
|
||||
"deadpool",
|
||||
"futures",
|
||||
"futures-timer",
|
||||
"http-types",
|
||||
"hyper",
|
||||
"log",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xmlparser"
|
||||
version = "0.13.3"
|
||||
|
||||
@ -45,36 +45,29 @@ Below is a step-by-step tutorial for integrating a new connector.
|
||||
|
||||
### **Generate the template**
|
||||
|
||||
Install cargo generate.
|
||||
|
||||
```bash
|
||||
cargo install cargo-generate
|
||||
```
|
||||
|
||||
Under `crates/router/src/connector/` run the following commands
|
||||
|
||||
```bash
|
||||
cargo gen-pg <connector-name>
|
||||
cd scripts
|
||||
sh add_connector.sh <connector-name>
|
||||
```
|
||||
|
||||
For this tutorial `<connector-name>` would be `checkout`.
|
||||
|
||||
The folder structure will be modified this way
|
||||
The folder structure will be modified as below
|
||||
```
|
||||
crates/router/src/connector
|
||||
├── checkout
|
||||
│ └── transformers.rs
|
||||
└── checkout.rs
|
||||
crates/router/tests/connectors
|
||||
└── checkout.rs
|
||||
```
|
||||
|
||||

|
||||
|
||||
`transformers.rs` will contain connectors API Request and Response types, and conversion between the router and connector API types.
|
||||
`mod.rs` will contain the trait implementations for the connector.
|
||||
`crates/router/src/connector/checkout/transformers.rs` will contain connectors API Request and Response types, and conversion between the router and connector API types.
|
||||
`crates/router/src/connector/checkout.rs` will contain the trait implementations for the connector.
|
||||
`crates/router/tests/connectors/checkout.rs` will contain the basic tests for the payments flows.
|
||||
|
||||
There is boiler plate code with `todo!()` in the above mentioned files. Go through the rest of the guide and fill in code wherever necessary.
|
||||
|
||||
Add the below lines in `src/connector/mod.rs`
|
||||
|
||||
```rust
|
||||
pub mod checkout;
|
||||
pub use checkout::Checkout;
|
||||
```
|
||||
|
||||
### **Implementing Request and Response types**
|
||||
|
||||
Adding new Connector is all about implementing the data transformation from Router's core to Connector's API request format.
|
||||
@ -287,13 +280,64 @@ Don’t forget to add logs lines in appropriate places.
|
||||
Refer to other conector code for trait implementations. mostly tThe rust compiler will guide you to do it easily.
|
||||
Feel free to connect with us in case of any queries and if you want to confirm the status mapping.
|
||||
|
||||
Feel free to connect with us in case of queries and also if you want to confirm the status mapping.
|
||||
### **Test the connector**
|
||||
Try running the tests in `crates/router/tests/connectors/{{connector-name}}.rs`.
|
||||
All tests should pass and add appropiate tests for connector specific payment flows.
|
||||
|
||||
### After implementing the above
|
||||
### **Build payment request and response from json schema**
|
||||
Some connectors will provide [json schema](https://developer.worldpay.com/docs/access-worldpay/api/references/payments) for each request and response supported. We can directly convert that schema to rust code by using below script. On running the script a `temp.rs` file will be created in `src/connector/<connector-name>` folder
|
||||
|
||||
Add connector name in :
|
||||
*Note: The code generated may not be production ready and might fail for some case, we have to clean up the code as per our standards.*
|
||||
|
||||
- `crates/api_models/src/enums.rs` in Connector enum (in alphabetical order)
|
||||
- `crates/router/src/types/api/mod.rs` convert_connector function
|
||||
```bash
|
||||
brew install openapi-generator
|
||||
export CONNECTOR_NAME="<CONNECTOR-NAME>" #Change it to appropriate connector name
|
||||
export SCHEMA_PATH="<PATH-TO-JSON-SCHEMA-FILE>" #it can be json or yaml, Refer samples below
|
||||
openapi-generator generate -g rust -i ${SCHEMA_PATH} -o temp && cat temp/src/models/* > crates/router/src/connector/${CONNECTOR_NAME}/temp.rs && rm -rf temp && sed -i'' -r "s/^pub use.*//;s/^pub mod.*//;s/^\/.*//;s/^.\*.*//;s/crate::models:://g;" crates/router/src/connector/${CONNECTOR_NAME}/temp.rs && cargo +nightly fmt
|
||||
```
|
||||
|
||||
Configure the Connectors API credentials using the PaymentConnectors API.
|
||||
JSON example
|
||||
```json
|
||||
{
|
||||
"openapi": "3.0.1",
|
||||
"paths": {},
|
||||
"info": {
|
||||
"title": "",
|
||||
"version": ""
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"PaymentsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"outcome": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"outcome"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
YAML example
|
||||
```yaml
|
||||
---
|
||||
openapi: 3.0.1
|
||||
paths: {}
|
||||
info:
|
||||
title: ""
|
||||
version: ""
|
||||
components:
|
||||
schemas:
|
||||
PaymentsResponse:
|
||||
type: object
|
||||
properties:
|
||||
outcome:
|
||||
type: string
|
||||
required:
|
||||
- outcome
|
||||
```
|
||||
|
||||
@ -38,7 +38,7 @@ locker_decryption_key2 = ""
|
||||
|
||||
[connectors.supported]
|
||||
wallets = ["klarna", "braintree", "applepay"]
|
||||
cards = ["stripe", "adyen", "authorizedotnet", "checkout", "braintree", "aci", "shift4", "cybersource"]
|
||||
cards = ["stripe", "adyen", "authorizedotnet", "checkout", "braintree", "aci", "shift4", "cybersource", "worldpay"]
|
||||
|
||||
[eph_key]
|
||||
validity = 1
|
||||
@ -73,6 +73,9 @@ base_url = "https://apitest.cybersource.com/"
|
||||
[connectors.shift4]
|
||||
base_url = "https://api.shift4.com/"
|
||||
|
||||
[connectors.worldpay]
|
||||
base_url = "http://localhost:9090/"
|
||||
|
||||
[scheduler]
|
||||
stream = "SCHEDULER_STREAM"
|
||||
consumer_group = "SCHEDULER_GROUP"
|
||||
|
||||
@ -125,6 +125,9 @@ base_url = "https://apitest.cybersource.com/"
|
||||
[connectors.shift4]
|
||||
base_url = "https://api.shift4.com/"
|
||||
|
||||
[connectors.worldpay]
|
||||
base_url = "https://try.access.worldpay.com/"
|
||||
|
||||
# This data is used to call respective connectors for wallets and cards
|
||||
[connectors.supported]
|
||||
wallets = ["klarna", "braintree", "applepay"]
|
||||
|
||||
@ -80,6 +80,9 @@ base_url = "https://apitest.cybersource.com/"
|
||||
[connectors.shift4]
|
||||
base_url = "https://api.shift4.com/"
|
||||
|
||||
[connectors.worldpay]
|
||||
base_url = "https://try.access.worldpay.com/"
|
||||
|
||||
[connectors.supported]
|
||||
wallets = ["klarna", "braintree", "applepay"]
|
||||
cards = ["stripe", "adyen", "authorizedotnet", "checkout", "braintree", "shift4", "cybersource"]
|
||||
cards = ["stripe", "adyen", "authorizedotnet", "checkout", "braintree", "shift4", "cybersource", "worldpay"]
|
||||
|
||||
@ -3,7 +3,7 @@ mod transformers;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use bytes::Bytes;
|
||||
use error_stack::ResultExt;
|
||||
use error_stack::{ResultExt, IntoReport};
|
||||
|
||||
use crate::{
|
||||
configs::settings,
|
||||
@ -12,7 +12,7 @@ use crate::{
|
||||
errors::{self, CustomResult},
|
||||
payments,
|
||||
},
|
||||
headers, logger, services,
|
||||
headers, logger, services::{self, ConnectorIntegration},
|
||||
types::{
|
||||
self,
|
||||
api::{self, ConnectorCommon, ConnectorCommonExt},
|
||||
@ -26,16 +26,19 @@ use transformers as {{project-name | downcase}};
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct {{project-name | downcase | pascal_case}};
|
||||
|
||||
impl api::ConnectorCommonExt for {{project-name | downcase | pascal_case}} {
|
||||
fn build_headers<Flow, Request, Response>(
|
||||
impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for {{project-name | downcase | pascal_case}}
|
||||
where
|
||||
Self: ConnectorIntegration<Flow, Request, Response>,{
|
||||
fn build_headers(
|
||||
&self,
|
||||
req: &types::RouterData<Flow, Request, Response>,
|
||||
_req: &types::RouterData<Flow, Request, Response>,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl api::ConnectorCommon for {{project-name | downcase | pascal_case}} {
|
||||
impl ConnectorCommon for {{project-name | downcase | pascal_case}} {
|
||||
fn id(&self) -> &'static str {
|
||||
"{{project-name | downcase}}"
|
||||
}
|
||||
@ -58,7 +61,7 @@ impl api::Payment for {{project-name | downcase | pascal_case}} {}
|
||||
|
||||
impl api::PreVerify for {{project-name | downcase | pascal_case}} {}
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
ConnectorIntegration<
|
||||
api::Verify,
|
||||
types::VerifyRequestData,
|
||||
types::PaymentsResponseData,
|
||||
@ -69,7 +72,7 @@ impl
|
||||
impl api::PaymentVoid for {{project-name | downcase | pascal_case}} {}
|
||||
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
ConnectorIntegration<
|
||||
api::Void,
|
||||
types::PaymentsCancelData,
|
||||
types::PaymentsResponseData,
|
||||
@ -78,12 +81,13 @@ impl
|
||||
|
||||
impl api::PaymentSync for {{project-name | downcase | pascal_case}} {}
|
||||
impl
|
||||
services::ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
|
||||
ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
|
||||
for {{project-name | downcase | pascal_case}}
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::PaymentsSyncRouterData,
|
||||
_req: &types::PaymentsSyncRouterData,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
todo!()
|
||||
}
|
||||
@ -94,31 +98,31 @@ impl
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
req: &types::PaymentsSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
_req: &types::PaymentsSyncRouterData,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::PaymentsSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
_req: &types::PaymentsSyncRouterData,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: Bytes,
|
||||
_res: Bytes,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::PaymentsSyncRouterData,
|
||||
res: Response,
|
||||
_data: &types::PaymentsSyncRouterData,
|
||||
_res: Response,
|
||||
) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> {
|
||||
todo!()
|
||||
}
|
||||
@ -127,7 +131,7 @@ impl
|
||||
|
||||
impl api::PaymentCapture for {{project-name | downcase | pascal_case}} {}
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
ConnectorIntegration<
|
||||
api::Capture,
|
||||
types::PaymentsCaptureData,
|
||||
types::PaymentsResponseData,
|
||||
@ -135,7 +139,8 @@ impl
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::PaymentsCaptureRouterData,
|
||||
_req: &types::PaymentsCaptureRouterData,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
todo!()
|
||||
}
|
||||
@ -153,31 +158,31 @@ impl
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::PaymentsCaptureRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
_req: &types::PaymentsCaptureRouterData,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::PaymentsCaptureRouterData,
|
||||
res: Response,
|
||||
_data: &types::PaymentsCaptureRouterData,
|
||||
_res: Response,
|
||||
) -> CustomResult<types::PaymentsCaptureRouterData, errors::ConnectorError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
req: &types::PaymentsCaptureRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
_req: &types::PaymentsCaptureRouterData,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: Bytes,
|
||||
_res: Bytes,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
todo!()
|
||||
}
|
||||
@ -186,7 +191,7 @@ impl
|
||||
impl api::PaymentSession for {{project-name | downcase | pascal_case}} {}
|
||||
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
ConnectorIntegration<
|
||||
api::Session,
|
||||
types::PaymentsSessionData,
|
||||
types::PaymentsResponseData,
|
||||
@ -198,12 +203,12 @@ impl
|
||||
impl api::PaymentAuthorize for {{project-name | downcase | pascal_case}} {}
|
||||
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
ConnectorIntegration<
|
||||
api::Authorize,
|
||||
types::PaymentsAuthorizeData,
|
||||
types::PaymentsResponseData,
|
||||
> for {{project-name | downcase | pascal_case}} {
|
||||
fn get_headers(&self, _req: &types::PaymentsAuthorizeRouterData) -> CustomResult<Vec<(String, String)>,errors::ConnectorError> {
|
||||
fn get_headers(&self, _req: &types::PaymentsAuthorizeRouterData,_connectors: &settings::Connectors,) -> CustomResult<Vec<(String, String)>,errors::ConnectorError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
@ -211,13 +216,13 @@ impl
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_url(&self, _req: &types::PaymentsAuthorizeRouterData, connectors: &settings::Connectors,) -> CustomResult<String,errors::ConnectorError> {
|
||||
fn get_url(&self, _req: &types::PaymentsAuthorizeRouterData, _connectors: &settings::Connectors,) -> CustomResult<String,errors::ConnectorError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_request_body(&self, req: &types::PaymentsAuthorizeRouterData) -> CustomResult<Option<String>,errors::ConnectorError> {
|
||||
let {{project-name | downcase}}_req =
|
||||
utils::Encode::<{{project-name | downcase}}::{{project-name | downcase | pascal_case}}PaymentsRequest>::convert_and_url_encode(req).change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
utils::Encode::<{{project-name | downcase}}::{{project-name | downcase | pascal_case}}PaymentsRequest>::convert_and_encode(req).change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
Ok(Some({{project-name | downcase}}_req))
|
||||
}
|
||||
|
||||
@ -247,12 +252,12 @@ impl api::RefundExecute for {{project-name | downcase | pascal_case}} {}
|
||||
impl api::RefundSync for {{project-name | downcase | pascal_case}} {}
|
||||
|
||||
impl
|
||||
services::ConnectorIntegration<
|
||||
ConnectorIntegration<
|
||||
api::Execute,
|
||||
types::RefundsData,
|
||||
types::RefundsResponseData,
|
||||
> for {{project-name | downcase | pascal_case}} {
|
||||
fn get_headers(&self, _req: &types::RefundsRouterData<api::Execute>) -> CustomResult<Vec<(String,String)>,errors::ConnectorError> {
|
||||
fn get_headers(&self, _req: &types::RefundsRouterData<api::Execute>,_connectors: &settings::Connectors,) -> CustomResult<Vec<(String,String)>,errors::ConnectorError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
@ -260,12 +265,12 @@ impl
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_url(&self, _req: &types::RefundsRouterData<api::Execute>, connectors: &settings::Connectors,) -> CustomResult<String,errors::ConnectorError> {
|
||||
fn get_url(&self, _req: &types::RefundsRouterData<api::Execute>, _connectors: &settings::Connectors,) -> CustomResult<String,errors::ConnectorError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_request_body(&self, req: &types::RefundsRouterData<api::Execute>) -> CustomResult<Option<String>,errors::ConnectorError> {
|
||||
let {{project-name | downcase}}_req = utils::Encode::<{{project-name| downcase}}::{{project-name | downcase | pascal_case}}RefundRequest>::convert_and_url_encode(req).change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
let {{project-name | downcase}}_req = utils::Encode::<{{project-name| downcase}}::{{project-name | downcase | pascal_case}}RefundRequest>::convert_and_encode(req).change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
Ok(Some({{project-name | downcase}}_req))
|
||||
}
|
||||
|
||||
@ -273,7 +278,7 @@ impl
|
||||
let request = services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.url(&types::RefundExecuteType::get_url(self, req, connectors)?)
|
||||
.headers(types::RefundExecuteType::get_headers(self, req)?)
|
||||
.headers(types::RefundExecuteType::get_headers(self, req, connectors)?)
|
||||
.body(types::RefundExecuteType::get_request_body(self, req)?)
|
||||
.build();
|
||||
Ok(Some(request))
|
||||
@ -301,8 +306,8 @@ impl
|
||||
}
|
||||
|
||||
impl
|
||||
services::ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData> for {{project-name | downcase | pascal_case}} {
|
||||
fn get_headers(&self, _req: &types::RefundSyncRouterData) -> CustomResult<Vec<(String, String)>,errors::ConnectorError> {
|
||||
ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData> for {{project-name | downcase | pascal_case}} {
|
||||
fn get_headers(&self, _req: &types::RefundSyncRouterData,_connectors: &settings::Connectors,) -> CustomResult<Vec<(String, String)>,errors::ConnectorError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
@ -341,21 +346,21 @@ impl api::IncomingWebhook for {{project-name | downcase | pascal_case}} {
|
||||
&self,
|
||||
_body: &[u8],
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
todo!()
|
||||
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
|
||||
}
|
||||
|
||||
fn get_webhook_event_type(
|
||||
&self,
|
||||
_body: &[u8],
|
||||
) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> {
|
||||
todo!()
|
||||
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
|
||||
}
|
||||
|
||||
fn get_webhook_resource_object(
|
||||
&self,
|
||||
_body: &[u8],
|
||||
) -> CustomResult<serde_json::Value, errors::ConnectorError> {
|
||||
todo!()
|
||||
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ use crate::{
|
||||
};
|
||||
|
||||
struct {{project-name | downcase | pascal_case}};
|
||||
impl utils::ConnectorActions for {{project-name | downcase | pascal_case}} {}
|
||||
impl ConnectorActions for {{project-name | downcase | pascal_case}} {}
|
||||
impl utils::Connector for {{project-name | downcase | pascal_case}} {
|
||||
fn get_data(&self) -> types::api::ConnectorData {
|
||||
use router::connector::{{project-name | downcase | pascal_case}};
|
||||
|
||||
@ -497,6 +497,7 @@ pub enum Connector {
|
||||
Klarna,
|
||||
Shift4,
|
||||
Stripe,
|
||||
Worldpay,
|
||||
}
|
||||
|
||||
impl From<AttemptStatus> for IntentStatus {
|
||||
|
||||
@ -87,6 +87,8 @@ rand = "0.8.5"
|
||||
time = { version = "0.3.17", features = ["macros"] }
|
||||
tokio = "1.23.0"
|
||||
toml = "0.5.9"
|
||||
serial_test = "0.10.0"
|
||||
wiremock = "0.5"
|
||||
|
||||
[[bin]]
|
||||
name = "router"
|
||||
|
||||
@ -117,6 +117,7 @@ pub struct Connectors {
|
||||
pub shift4: ConnectorParams,
|
||||
pub stripe: ConnectorParams,
|
||||
pub supported: SupportedConnectors,
|
||||
pub worldpay: ConnectorParams,
|
||||
pub applepay: ConnectorParams,
|
||||
}
|
||||
|
||||
|
||||
@ -6,12 +6,12 @@ pub mod braintree;
|
||||
pub mod checkout;
|
||||
pub mod cybersource;
|
||||
pub mod klarna;
|
||||
pub mod stripe;
|
||||
|
||||
pub mod shift4;
|
||||
pub mod stripe;
|
||||
pub mod worldpay;
|
||||
|
||||
pub use self::{
|
||||
aci::Aci, adyen::Adyen, applepay::Applepay, authorizedotnet::Authorizedotnet,
|
||||
braintree::Braintree, checkout::Checkout, cybersource::Cybersource, klarna::Klarna,
|
||||
shift4::Shift4, stripe::Stripe,
|
||||
shift4::Shift4, stripe::Stripe, worldpay::Worldpay,
|
||||
};
|
||||
|
||||
@ -439,6 +439,21 @@ impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponse
|
||||
Ok(format!("{}refunds", self.base_url(connectors),))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::RefundSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
Ok(Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Get)
|
||||
.url(&types::RefundSyncType::get_url(self, req, connectors)?)
|
||||
.headers(types::RefundSyncType::get_headers(self, req, connectors)?)
|
||||
.body(types::RefundSyncType::get_request_body(self, req)?)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::RefundSyncRouterData,
|
||||
|
||||
613
crates/router/src/connector/worldpay.rs
Normal file
613
crates/router/src/connector/worldpay.rs
Normal file
@ -0,0 +1,613 @@
|
||||
mod requests;
|
||||
mod response;
|
||||
mod transformers;
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
use bytes::Bytes;
|
||||
use error_stack::{IntoReport, ResultExt};
|
||||
use storage_models::enums;
|
||||
use transformers as worldpay;
|
||||
|
||||
use self::{requests::*, response::*};
|
||||
use crate::{
|
||||
configs::settings,
|
||||
core::{
|
||||
errors::{self, CustomResult},
|
||||
payments,
|
||||
},
|
||||
headers, logger,
|
||||
services::{self, ConnectorIntegration},
|
||||
types::{
|
||||
self,
|
||||
api::{self, ConnectorCommon, ConnectorCommonExt},
|
||||
ErrorResponse, Response,
|
||||
},
|
||||
utils::{self, BytesExt},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Worldpay;
|
||||
|
||||
impl<Flow, Request, Response> ConnectorCommonExt<Flow, Request, Response> for Worldpay
|
||||
where
|
||||
Self: ConnectorIntegration<Flow, Request, Response>,
|
||||
{
|
||||
fn build_headers(
|
||||
&self,
|
||||
req: &types::RouterData<Flow, Request, Response>,
|
||||
_connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
let mut headers = vec![(
|
||||
headers::CONTENT_TYPE.to_string(),
|
||||
self.get_content_type().to_string(),
|
||||
)];
|
||||
let mut api_key = self.get_auth_header(&req.connector_auth_type)?;
|
||||
headers.append(&mut api_key);
|
||||
Ok(headers)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectorCommon for Worldpay {
|
||||
fn id(&self) -> &'static str {
|
||||
"worldpay"
|
||||
}
|
||||
|
||||
fn common_get_content_type(&self) -> &'static str {
|
||||
"application/vnd.worldpay.payments-v6+json"
|
||||
}
|
||||
|
||||
fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str {
|
||||
connectors.worldpay.base_url.as_ref()
|
||||
}
|
||||
|
||||
fn get_auth_header(
|
||||
&self,
|
||||
auth_type: &types::ConnectorAuthType,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
let auth: worldpay::WorldpayAuthType = auth_type
|
||||
.try_into()
|
||||
.change_context(errors::ConnectorError::FailedToObtainAuthType)?;
|
||||
Ok(vec![(headers::AUTHORIZATION.to_string(), auth.api_key)])
|
||||
}
|
||||
|
||||
fn build_error_response(
|
||||
&self,
|
||||
res: Bytes,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
let response: WorldpayErrorResponse = res
|
||||
.parse_struct("WorldpayErrorResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
Ok(ErrorResponse {
|
||||
code: response.error_name,
|
||||
message: response.message,
|
||||
reason: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl api::Payment for Worldpay {}
|
||||
|
||||
impl api::PreVerify for Worldpay {}
|
||||
impl ConnectorIntegration<api::Verify, types::VerifyRequestData, types::PaymentsResponseData>
|
||||
for Worldpay
|
||||
{
|
||||
}
|
||||
|
||||
impl api::PaymentVoid for Worldpay {}
|
||||
|
||||
impl ConnectorIntegration<api::Void, types::PaymentsCancelData, types::PaymentsResponseData>
|
||||
for Worldpay
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::PaymentsCancelRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
self.build_headers(req, connectors)
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
self.common_get_content_type()
|
||||
}
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
req: &types::PaymentsCancelRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
let connector_payment_id = req.request.connector_transaction_id.clone();
|
||||
Ok(format!(
|
||||
"{}payments/settlements/{}",
|
||||
self.base_url(connectors),
|
||||
connector_payment_id
|
||||
))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::PaymentsCancelRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
Ok(Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.url(&types::PaymentsVoidType::get_url(self, req, connectors)?)
|
||||
.headers(types::PaymentsVoidType::get_headers(self, req, connectors)?)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::PaymentsCancelRouterData,
|
||||
res: Response,
|
||||
) -> CustomResult<types::PaymentsCancelRouterData, errors::ConnectorError>
|
||||
where
|
||||
api::Void: Clone,
|
||||
types::PaymentsCancelData: Clone,
|
||||
types::PaymentsResponseData: Clone,
|
||||
{
|
||||
match res.status_code {
|
||||
202 => {
|
||||
let response: WorldpayPaymentsResponse = res
|
||||
.response
|
||||
.parse_struct("Worldpay PaymentsResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
Ok(types::PaymentsCancelRouterData {
|
||||
status: enums::AttemptStatus::Voided,
|
||||
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||
resource_id: types::ResponseId::try_from(response.links)?,
|
||||
redirection_data: None,
|
||||
redirect: false,
|
||||
mandate_reference: None,
|
||||
}),
|
||||
..data.clone()
|
||||
})
|
||||
}
|
||||
_ => Err(errors::ConnectorError::ResponseHandlingFailed)?,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: Bytes,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl api::PaymentSync for Worldpay {}
|
||||
impl ConnectorIntegration<api::PSync, types::PaymentsSyncData, types::PaymentsResponseData>
|
||||
for Worldpay
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::PaymentsSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
self.build_headers(req, connectors)
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
self.common_get_content_type()
|
||||
}
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
req: &types::PaymentsSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
let connector_payment_id = req
|
||||
.request
|
||||
.connector_transaction_id
|
||||
.get_connector_transaction_id()
|
||||
.change_context(errors::ConnectorError::MissingConnectorTransactionID)?;
|
||||
Ok(format!(
|
||||
"{}payments/events/{}",
|
||||
self.base_url(connectors),
|
||||
connector_payment_id
|
||||
))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::PaymentsSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
Ok(Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Get)
|
||||
.url(&types::PaymentsSyncType::get_url(self, req, connectors)?)
|
||||
.headers(types::PaymentsSyncType::get_headers(self, req, connectors)?)
|
||||
.body(types::PaymentsSyncType::get_request_body(self, req)?)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: Bytes,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res)
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::PaymentsSyncRouterData,
|
||||
res: Response,
|
||||
) -> CustomResult<types::PaymentsSyncRouterData, errors::ConnectorError> {
|
||||
let response: WorldpayEventResponse =
|
||||
res.response
|
||||
.parse_struct("Worldpay EventResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
|
||||
Ok(types::PaymentsSyncRouterData {
|
||||
status: enums::AttemptStatus::from(response.last_event),
|
||||
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||
resource_id: data.request.connector_transaction_id.clone(),
|
||||
redirection_data: None,
|
||||
redirect: false,
|
||||
mandate_reference: None,
|
||||
}),
|
||||
..data.clone()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl api::PaymentCapture for Worldpay {}
|
||||
impl ConnectorIntegration<api::Capture, types::PaymentsCaptureData, types::PaymentsResponseData>
|
||||
for Worldpay
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::PaymentsCaptureRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
self.build_headers(req, connectors)
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
self.common_get_content_type()
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::PaymentsCaptureRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
Ok(Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.url(&types::PaymentsCaptureType::get_url(self, req, connectors)?)
|
||||
.headers(types::PaymentsCaptureType::get_headers(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::PaymentsCaptureRouterData,
|
||||
res: Response,
|
||||
) -> CustomResult<types::PaymentsCaptureRouterData, errors::ConnectorError> {
|
||||
logger::debug!(worldpaypayments_capture_response=?res);
|
||||
match res.status_code {
|
||||
202 => {
|
||||
let response: WorldpayPaymentsResponse = res
|
||||
.response
|
||||
.parse_struct("Worldpay PaymentsResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
Ok(types::PaymentsCaptureRouterData {
|
||||
status: enums::AttemptStatus::Charged,
|
||||
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||
resource_id: types::ResponseId::try_from(response.links)?,
|
||||
redirection_data: None,
|
||||
redirect: false,
|
||||
mandate_reference: None,
|
||||
}),
|
||||
..data.clone()
|
||||
})
|
||||
}
|
||||
_ => Err(errors::ConnectorError::ResponseHandlingFailed)?,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
req: &types::PaymentsCaptureRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
let connector_payment_id = req.request.connector_transaction_id.clone();
|
||||
Ok(format!(
|
||||
"{}payments/settlements/{}",
|
||||
self.base_url(connectors),
|
||||
connector_payment_id
|
||||
))
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: Bytes,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl api::PaymentSession for Worldpay {}
|
||||
|
||||
impl ConnectorIntegration<api::Session, types::PaymentsSessionData, types::PaymentsResponseData>
|
||||
for Worldpay
|
||||
{
|
||||
}
|
||||
|
||||
impl api::PaymentAuthorize for Worldpay {}
|
||||
|
||||
impl ConnectorIntegration<api::Authorize, types::PaymentsAuthorizeData, types::PaymentsResponseData>
|
||||
for Worldpay
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::PaymentsAuthorizeRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
self.build_headers(req, connectors)
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
self.common_get_content_type()
|
||||
}
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
_req: &types::PaymentsAuthorizeRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(format!(
|
||||
"{}payments/authorizations",
|
||||
self.base_url(connectors)
|
||||
))
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
&self,
|
||||
req: &types::PaymentsAuthorizeRouterData,
|
||||
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||
let worldpay_req = utils::Encode::<WorldpayPaymentsRequest>::convert_and_encode(req)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
Ok(Some(worldpay_req))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::PaymentsAuthorizeRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
Ok(Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.url(&types::PaymentsAuthorizeType::get_url(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.headers(types::PaymentsAuthorizeType::get_headers(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.body(types::PaymentsAuthorizeType::get_request_body(self, req)?)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::PaymentsAuthorizeRouterData,
|
||||
res: Response,
|
||||
) -> CustomResult<types::PaymentsAuthorizeRouterData, errors::ConnectorError> {
|
||||
let response: WorldpayPaymentsResponse = res
|
||||
.response
|
||||
.parse_struct("Worldpay PaymentsResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
logger::debug!(worldpaypayments_create_response=?response);
|
||||
types::RouterData::try_from(types::ResponseRouterData {
|
||||
response,
|
||||
data: data.clone(),
|
||||
http_code: res.status_code,
|
||||
})
|
||||
.change_context(errors::ConnectorError::ResponseHandlingFailed)
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: Bytes,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl api::Refund for Worldpay {}
|
||||
impl api::RefundExecute for Worldpay {}
|
||||
impl api::RefundSync for Worldpay {}
|
||||
|
||||
impl ConnectorIntegration<api::Execute, types::RefundsData, types::RefundsResponseData>
|
||||
for Worldpay
|
||||
{
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::RefundsRouterData<api::Execute>,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
self.build_headers(req, connectors)
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
self.common_get_content_type()
|
||||
}
|
||||
|
||||
fn get_request_body(
|
||||
&self,
|
||||
req: &types::RefundExecuteRouterData,
|
||||
) -> CustomResult<Option<String>, errors::ConnectorError> {
|
||||
let req = utils::Encode::<WorldpayRefundRequest>::convert_and_encode(req)
|
||||
.change_context(errors::ConnectorError::RequestEncodingFailed)?;
|
||||
Ok(Some(req))
|
||||
}
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
req: &types::RefundsRouterData<api::Execute>,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
let connector_payment_id = req.request.connector_transaction_id.clone();
|
||||
Ok(format!(
|
||||
"{}payments/settlements/refunds/partials/{}",
|
||||
self.base_url(connectors),
|
||||
connector_payment_id
|
||||
))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::RefundsRouterData<api::Execute>,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
let request = services::RequestBuilder::new()
|
||||
.method(services::Method::Post)
|
||||
.url(&types::RefundExecuteType::get_url(self, req, connectors)?)
|
||||
.headers(types::RefundExecuteType::get_headers(
|
||||
self, req, connectors,
|
||||
)?)
|
||||
.body(types::RefundExecuteType::get_request_body(self, req)?)
|
||||
.build();
|
||||
Ok(Some(request))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::RefundsRouterData<api::Execute>,
|
||||
res: Response,
|
||||
) -> CustomResult<types::RefundsRouterData<api::Execute>, errors::ConnectorError> {
|
||||
logger::debug!(target: "router::connector::worldpay", response=?res);
|
||||
match res.status_code {
|
||||
202 => {
|
||||
let response: WorldpayPaymentsResponse = res
|
||||
.response
|
||||
.parse_struct("Worldpay PaymentsResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
Ok(types::RefundExecuteRouterData {
|
||||
response: Ok(types::RefundsResponseData {
|
||||
connector_refund_id: ResponseIdStr::try_from(response.links)?.id,
|
||||
refund_status: enums::RefundStatus::Success,
|
||||
}),
|
||||
..data.clone()
|
||||
})
|
||||
}
|
||||
_ => Err(errors::ConnectorError::ResponseHandlingFailed)?,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: Bytes,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConnectorIntegration<api::RSync, types::RefundsData, types::RefundsResponseData> for Worldpay {
|
||||
fn get_headers(
|
||||
&self,
|
||||
req: &types::RefundSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Vec<(String, String)>, errors::ConnectorError> {
|
||||
self.build_headers(req, connectors)
|
||||
}
|
||||
|
||||
fn get_content_type(&self) -> &'static str {
|
||||
self.common_get_content_type()
|
||||
}
|
||||
|
||||
fn get_url(
|
||||
&self,
|
||||
req: &types::RefundSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Ok(format!(
|
||||
"{}payments/events/{}",
|
||||
self.base_url(connectors),
|
||||
req.request.connector_transaction_id
|
||||
))
|
||||
}
|
||||
|
||||
fn build_request(
|
||||
&self,
|
||||
req: &types::RefundSyncRouterData,
|
||||
connectors: &settings::Connectors,
|
||||
) -> CustomResult<Option<services::Request>, errors::ConnectorError> {
|
||||
Ok(Some(
|
||||
services::RequestBuilder::new()
|
||||
.method(services::Method::Get)
|
||||
.url(&types::RefundSyncType::get_url(self, req, connectors)?)
|
||||
.headers(types::RefundSyncType::get_headers(self, req, connectors)?)
|
||||
.body(types::RefundSyncType::get_request_body(self, req)?)
|
||||
.build(),
|
||||
))
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
data: &types::RefundSyncRouterData,
|
||||
res: Response,
|
||||
) -> CustomResult<types::RefundSyncRouterData, errors::ConnectorError> {
|
||||
let response: WorldpayEventResponse =
|
||||
res.response
|
||||
.parse_struct("Worldpay EventResponse")
|
||||
.change_context(errors::ConnectorError::ResponseDeserializationFailed)?;
|
||||
Ok(types::RefundSyncRouterData {
|
||||
response: Ok(types::RefundsResponseData {
|
||||
connector_refund_id: data.request.refund_id.clone(),
|
||||
refund_status: enums::RefundStatus::from(response.last_event),
|
||||
}),
|
||||
..data.clone()
|
||||
})
|
||||
}
|
||||
|
||||
fn get_error_response(
|
||||
&self,
|
||||
res: Bytes,
|
||||
) -> CustomResult<ErrorResponse, errors::ConnectorError> {
|
||||
self.build_error_response(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl api::IncomingWebhook for Worldpay {
|
||||
fn get_webhook_object_reference_id(
|
||||
&self,
|
||||
_body: &[u8],
|
||||
) -> CustomResult<String, errors::ConnectorError> {
|
||||
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
|
||||
}
|
||||
|
||||
fn get_webhook_event_type(
|
||||
&self,
|
||||
_body: &[u8],
|
||||
) -> CustomResult<api::IncomingWebhookEvent, errors::ConnectorError> {
|
||||
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
|
||||
}
|
||||
|
||||
fn get_webhook_resource_object(
|
||||
&self,
|
||||
_body: &[u8],
|
||||
) -> CustomResult<serde_json::Value, errors::ConnectorError> {
|
||||
Err(errors::ConnectorError::WebhooksNotImplemented).into_report()
|
||||
}
|
||||
}
|
||||
|
||||
impl services::ConnectorRedirectResponse for Worldpay {
|
||||
fn get_flow_type(
|
||||
&self,
|
||||
_query_params: &str,
|
||||
) -> CustomResult<payments::CallConnectorAction, errors::ConnectorError> {
|
||||
Ok(payments::CallConnectorAction::Trigger)
|
||||
}
|
||||
}
|
||||
225
crates/router/src/connector/worldpay/requests.rs
Normal file
225
crates/router/src/connector/worldpay/requests.rs
Normal file
@ -0,0 +1,225 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BillingAddress {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub city: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub address2: Option<String>,
|
||||
pub postal_code: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub state: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub address3: Option<String>,
|
||||
pub country_code: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub address1: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorldpayPaymentsRequest {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub channel: Option<Channel>,
|
||||
pub instruction: Instruction,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub customer: Option<Customer>,
|
||||
pub merchant: Merchant,
|
||||
pub transaction_reference: String,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize,
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum Channel {
|
||||
#[default]
|
||||
Moto,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Customer {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub risk_profile: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub authentication: Option<CustomerAuthentication>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum CustomerAuthentication {
|
||||
ThreeDS(ThreeDS),
|
||||
Token(NetworkToken),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ThreeDS {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub authentication_value: Option<String>,
|
||||
pub version: ThreeDSVersion,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub transaction_id: Option<String>,
|
||||
pub eci: String,
|
||||
#[serde(rename = "type")]
|
||||
pub auth_type: CustomerAuthType,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize,
|
||||
)]
|
||||
pub enum ThreeDSVersion {
|
||||
#[default]
|
||||
#[serde(rename = "1")]
|
||||
One,
|
||||
#[serde(rename = "2")]
|
||||
Two,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize,
|
||||
)]
|
||||
pub enum CustomerAuthType {
|
||||
#[serde(rename = "3DS")]
|
||||
#[default]
|
||||
Variant3Ds,
|
||||
#[serde(rename = "card/networkToken")]
|
||||
NetworkToken,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NetworkToken {
|
||||
#[serde(rename = "type")]
|
||||
pub auth_type: CustomerAuthType,
|
||||
pub authentication_value: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub eci: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Instruction {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub debt_repayment: Option<bool>,
|
||||
pub value: PaymentValue,
|
||||
pub narrative: InstructionNarrative,
|
||||
pub payment_instrument: PaymentInstrument,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InstructionNarrative {
|
||||
pub line1: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub line2: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum PaymentInstrument {
|
||||
Card(CardPayment),
|
||||
CardToken(CardToken),
|
||||
Googlepay(WalletPayment),
|
||||
Applepay(WalletPayment),
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Eq, Default, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize,
|
||||
)]
|
||||
pub enum PaymentType {
|
||||
#[default]
|
||||
#[serde(rename = "card/plain")]
|
||||
Card,
|
||||
#[serde(rename = "card/token")]
|
||||
CardToken,
|
||||
#[serde(rename = "card/wallet+googlepay")]
|
||||
Googlepay,
|
||||
#[serde(rename = "card/wallet+applepay")]
|
||||
Applepay,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CardPayment {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub billing_address: Option<BillingAddress>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub card_holder_name: Option<String>,
|
||||
pub card_expiry_date: CardExpiryDate,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub cvc: Option<String>,
|
||||
#[serde(rename = "type")]
|
||||
pub payment_type: PaymentType,
|
||||
pub card_number: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CardToken {
|
||||
#[serde(rename = "type")]
|
||||
pub payment_type: PaymentType,
|
||||
pub href: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WalletPayment {
|
||||
#[serde(rename = "type")]
|
||||
pub payment_type: PaymentType,
|
||||
pub wallet_token: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub billing_address: Option<BillingAddress>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct CardExpiryDate {
|
||||
pub month: u8,
|
||||
pub year: u16,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct PaymentValue {
|
||||
pub amount: i64,
|
||||
pub currency: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Merchant {
|
||||
pub entity: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub mcc: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub payment_facilitator: Option<PaymentFacilitator>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PaymentFacilitator {
|
||||
pub pf_id: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub iso_id: Option<String>,
|
||||
pub sub_merchant: SubMerchant,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SubMerchant {
|
||||
pub city: String,
|
||||
pub name: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub state: Option<String>,
|
||||
pub postal_code: String,
|
||||
pub merchant_id: String,
|
||||
pub country_code: String,
|
||||
pub street: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tax_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize)]
|
||||
pub struct WorldpayRefundRequest {
|
||||
pub value: PaymentValue,
|
||||
pub reference: String,
|
||||
}
|
||||
306
crates/router/src/connector/worldpay/response.rs
Normal file
306
crates/router/src/connector/worldpay/response.rs
Normal file
@ -0,0 +1,306 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{core::errors, types};
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorldpayPaymentsResponse {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub exemption: Option<Exemption>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub issuer: Option<Issuer>,
|
||||
pub outcome: Option<Outcome>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub payment_instrument: Option<PaymentsResPaymentInstrument>,
|
||||
/// Any risk factors which have been identified for the authorization. This section will not appear if no risks are identified.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub risk_factors: Option<Vec<RiskFactorsInner>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub scheme: Option<PaymentsResponseScheme>,
|
||||
#[serde(rename = "_links", skip_serializing_if = "Option::is_none")]
|
||||
pub links: Option<PaymentLinks>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum Outcome {
|
||||
Authorized,
|
||||
Refused,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorldpayEventResponse {
|
||||
pub last_event: EventType,
|
||||
#[serde(rename = "_links", skip_serializing_if = "Option::is_none")]
|
||||
pub links: Option<EventLinks>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum EventType {
|
||||
Authorized,
|
||||
Cancelled,
|
||||
Charged,
|
||||
SentForRefund,
|
||||
RefundFailed,
|
||||
Refused,
|
||||
Refunded,
|
||||
Error,
|
||||
CaptureFailed,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct Exemption {
|
||||
pub result: String,
|
||||
pub reason: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct PaymentLinks {
|
||||
#[serde(rename = "payments:events", skip_serializing_if = "Option::is_none")]
|
||||
pub events: Option<PaymentLink>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct EventLinks {
|
||||
#[serde(rename = "payments:events", skip_serializing_if = "Option::is_none")]
|
||||
pub events: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct PaymentLink {
|
||||
pub href: String,
|
||||
}
|
||||
|
||||
fn get_resource_id<T, F>(
|
||||
links: Option<PaymentLinks>,
|
||||
transform_fn: F,
|
||||
) -> Result<T, error_stack::Report<errors::ConnectorError>>
|
||||
where
|
||||
F: Fn(String) -> T,
|
||||
{
|
||||
let reference_id = links
|
||||
.and_then(|l| l.events)
|
||||
.and_then(|e| e.href.rsplit_once('/').map(|h| h.1.to_string()))
|
||||
.map(transform_fn);
|
||||
reference_id.ok_or_else(|| {
|
||||
errors::ConnectorError::MissingRequiredField {
|
||||
field_name: "links.events".to_string(),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
pub struct ResponseIdStr {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
impl TryFrom<Option<PaymentLinks>> for ResponseIdStr {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(links: Option<PaymentLinks>) -> Result<Self, Self::Error> {
|
||||
get_resource_id(links, |id| Self { id })
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Option<PaymentLinks>> for types::ResponseId {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(links: Option<PaymentLinks>) -> Result<Self, Self::Error> {
|
||||
get_resource_id(links, Self::ConnectorTransactionId)
|
||||
}
|
||||
}
|
||||
|
||||
impl Exemption {
|
||||
pub fn new(result: String, reason: String) -> Self {
|
||||
Self { result, reason }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Issuer {
|
||||
pub authorization_code: String,
|
||||
}
|
||||
|
||||
impl Issuer {
|
||||
pub fn new(authorization_code: String) -> Self {
|
||||
Self { authorization_code }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct PaymentsResPaymentInstrument {
|
||||
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
|
||||
pub risk_type: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub card: Option<PaymentInstrumentCard>,
|
||||
}
|
||||
|
||||
impl PaymentsResPaymentInstrument {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
risk_type: None,
|
||||
card: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PaymentInstrumentCard {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub number: Option<PaymentInstrumentCardNumber>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub issuer: Option<PaymentInstrumentCardIssuer>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub payment_account_reference: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub country_code: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub funding_type: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub brand: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub expiry_date: Option<PaymentInstrumentCardExpiryDate>,
|
||||
}
|
||||
|
||||
impl PaymentInstrumentCard {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
number: None,
|
||||
issuer: None,
|
||||
payment_account_reference: None,
|
||||
country_code: None,
|
||||
funding_type: None,
|
||||
brand: None,
|
||||
expiry_date: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PaymentInstrumentCardExpiryDate {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub month: Option<i32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub year: Option<i32>,
|
||||
}
|
||||
|
||||
impl PaymentInstrumentCardExpiryDate {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
month: None,
|
||||
year: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PaymentInstrumentCardIssuer {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
impl PaymentInstrumentCardIssuer {
|
||||
pub fn new() -> Self {
|
||||
Self { name: None }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PaymentInstrumentCardNumber {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub bin: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub last4_digits: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub dpan: Option<String>,
|
||||
}
|
||||
|
||||
impl PaymentInstrumentCardNumber {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
bin: None,
|
||||
last4_digits: None,
|
||||
dpan: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RiskFactorsInner {
|
||||
#[serde(rename = "type")]
|
||||
pub risk_type: RiskType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub detail: Option<Detail>,
|
||||
pub risk: Risk,
|
||||
}
|
||||
|
||||
impl RiskFactorsInner {
|
||||
pub fn new(risk_type: RiskType, risk: Risk) -> Self {
|
||||
Self {
|
||||
risk_type,
|
||||
detail: None,
|
||||
risk,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize,
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum RiskType {
|
||||
#[default]
|
||||
Avs,
|
||||
Cvc,
|
||||
RiskProfile,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize,
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum Detail {
|
||||
#[default]
|
||||
Address,
|
||||
Postcode,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize,
|
||||
)]
|
||||
pub enum Risk {
|
||||
#[default]
|
||||
#[serde(rename = "not_checked")]
|
||||
NotChecked,
|
||||
#[serde(rename = "not_matched")]
|
||||
NotMatched,
|
||||
#[serde(rename = "not_supplied")]
|
||||
NotSupplied,
|
||||
#[serde(rename = "verificationFailed")]
|
||||
VerificationFailed,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||
pub struct PaymentsResponseScheme {
|
||||
pub reference: String,
|
||||
}
|
||||
|
||||
impl PaymentsResponseScheme {
|
||||
pub fn new(reference: String) -> Self {
|
||||
Self { reference }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorldpayErrorResponse {
|
||||
pub error_name: String,
|
||||
pub message: String,
|
||||
}
|
||||
179
crates/router/src/connector/worldpay/transformers.rs
Normal file
179
crates/router/src/connector/worldpay/transformers.rs
Normal file
@ -0,0 +1,179 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use common_utils::errors::CustomResult;
|
||||
use masking::PeekInterface;
|
||||
use storage_models::enums;
|
||||
|
||||
use super::{requests::*, response::*};
|
||||
use crate::{
|
||||
core::errors,
|
||||
types::{self, api},
|
||||
};
|
||||
|
||||
fn parse_int<T: FromStr>(
|
||||
val: masking::Secret<String, masking::WithType>,
|
||||
) -> CustomResult<T, errors::ConnectorError>
|
||||
where
|
||||
<T as FromStr>::Err: Sync,
|
||||
{
|
||||
let res = val.peek().parse::<T>();
|
||||
if let Ok(val) = res {
|
||||
Ok(val)
|
||||
} else {
|
||||
Err(errors::ConnectorError::RequestEncodingFailed)?
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch_payment_instrument(
|
||||
payment_method: api::PaymentMethod,
|
||||
) -> CustomResult<PaymentInstrument, errors::ConnectorError> {
|
||||
match payment_method {
|
||||
api::PaymentMethod::Card(card) => Ok(PaymentInstrument::Card(CardPayment {
|
||||
card_expiry_date: CardExpiryDate {
|
||||
month: parse_int::<u8>(card.card_exp_month)?,
|
||||
year: parse_int::<u16>(card.card_exp_year)?,
|
||||
},
|
||||
card_number: card.card_number.peek().to_string(),
|
||||
..CardPayment::default()
|
||||
})),
|
||||
api::PaymentMethod::Wallet(wallet) => match wallet.issuer_name {
|
||||
api_models::enums::WalletIssuer::ApplePay => {
|
||||
Ok(PaymentInstrument::Applepay(WalletPayment {
|
||||
payment_type: PaymentType::Applepay,
|
||||
wallet_token: wallet.token,
|
||||
..WalletPayment::default()
|
||||
}))
|
||||
}
|
||||
api_models::enums::WalletIssuer::GooglePay => {
|
||||
Ok(PaymentInstrument::Googlepay(WalletPayment {
|
||||
payment_type: PaymentType::Googlepay,
|
||||
wallet_token: wallet.token,
|
||||
..WalletPayment::default()
|
||||
}))
|
||||
}
|
||||
_ => Err(errors::ConnectorError::NotImplemented("Wallet Type".to_string()).into()),
|
||||
},
|
||||
_ => {
|
||||
Err(errors::ConnectorError::NotImplemented("Current Payment Method".to_string()).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&types::PaymentsAuthorizeRouterData> for WorldpayPaymentsRequest {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
instruction: Instruction {
|
||||
value: PaymentValue {
|
||||
amount: item.request.amount,
|
||||
currency: item.request.currency.to_string(),
|
||||
},
|
||||
narrative: InstructionNarrative {
|
||||
line1: item.merchant_id.clone(),
|
||||
..Default::default()
|
||||
},
|
||||
payment_instrument: fetch_payment_instrument(
|
||||
item.request.payment_method_data.clone(),
|
||||
)?,
|
||||
debt_repayment: None,
|
||||
},
|
||||
merchant: Merchant {
|
||||
entity: item.payment_id.clone(),
|
||||
..Default::default()
|
||||
},
|
||||
transaction_reference: item.attempt_id.clone().ok_or(
|
||||
errors::ConnectorError::MissingRequiredField {
|
||||
field_name: "attempt_id".to_string(),
|
||||
},
|
||||
)?,
|
||||
channel: None,
|
||||
customer: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WorldpayAuthType {
|
||||
pub(super) api_key: String,
|
||||
}
|
||||
|
||||
impl TryFrom<&types::ConnectorAuthType> for WorldpayAuthType {
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(auth_type: &types::ConnectorAuthType) -> Result<Self, Self::Error> {
|
||||
match auth_type {
|
||||
types::ConnectorAuthType::HeaderKey { api_key } => Ok(Self {
|
||||
api_key: api_key.to_string(),
|
||||
}),
|
||||
_ => Err(errors::ConnectorError::FailedToObtainAuthType)?,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Outcome> for enums::AttemptStatus {
|
||||
fn from(item: Outcome) -> Self {
|
||||
match item {
|
||||
Outcome::Authorized => Self::Authorized,
|
||||
Outcome::Refused => Self::Failure,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EventType> for enums::AttemptStatus {
|
||||
fn from(value: EventType) -> Self {
|
||||
match value {
|
||||
EventType::Authorized => Self::Authorized,
|
||||
EventType::CaptureFailed => Self::CaptureFailed,
|
||||
EventType::Refused => Self::Failure,
|
||||
EventType::Charged => Self::Charged,
|
||||
_ => Self::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EventType> for enums::RefundStatus {
|
||||
fn from(value: EventType) -> Self {
|
||||
match value {
|
||||
EventType::Refunded => Self::Success,
|
||||
EventType::RefundFailed => Self::Failure,
|
||||
_ => Self::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<types::PaymentsResponseRouterData<WorldpayPaymentsResponse>>
|
||||
for types::PaymentsAuthorizeRouterData
|
||||
{
|
||||
type Error = error_stack::Report<errors::ConnectorError>;
|
||||
fn try_from(
|
||||
item: types::PaymentsResponseRouterData<WorldpayPaymentsResponse>,
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
status: match item.response.outcome {
|
||||
Some(outcome) => enums::AttemptStatus::from(outcome),
|
||||
None => Err(errors::ConnectorError::MissingRequiredField {
|
||||
field_name: "outcome".to_string(),
|
||||
})?,
|
||||
},
|
||||
description: item.response.description,
|
||||
response: Ok(types::PaymentsResponseData::TransactionResponse {
|
||||
resource_id: types::ResponseId::try_from(item.response.links)?,
|
||||
redirection_data: None,
|
||||
redirect: false,
|
||||
mandate_reference: None,
|
||||
}),
|
||||
..item.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> TryFrom<&types::RefundsRouterData<F>> for WorldpayRefundRequest {
|
||||
type Error = error_stack::Report<errors::ParsingError>;
|
||||
fn try_from(item: &types::RefundsRouterData<F>) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
reference: item.request.connector_transaction_id.clone(),
|
||||
value: PaymentValue {
|
||||
amount: item.request.amount,
|
||||
currency: item.request.currency.to_string(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -241,8 +241,8 @@ pub enum ApiClientError {
|
||||
|
||||
#[error("URL encoding of request payload failed")]
|
||||
UrlEncodingFailed,
|
||||
#[error("Failed to send request to connector")]
|
||||
RequestNotSent,
|
||||
#[error("Failed to send request to connector {0}")]
|
||||
RequestNotSent(String),
|
||||
#[error("Failed to decode response")]
|
||||
ResponseDecodingFailed,
|
||||
|
||||
|
||||
@ -79,6 +79,7 @@ where
|
||||
merchant_id: merchant_account.merchant_id.clone(),
|
||||
connector: merchant_connector_account.connector_name,
|
||||
payment_id: payment_data.payment_attempt.payment_id.clone(),
|
||||
attempt_id: Some(payment_data.payment_attempt.attempt_id.clone()),
|
||||
status: payment_data.payment_attempt.status,
|
||||
payment_method,
|
||||
connector_auth_type: auth_type,
|
||||
|
||||
@ -66,6 +66,7 @@ pub async fn construct_refund_router_data<'a, F>(
|
||||
merchant_id: merchant_account.merchant_id.clone(),
|
||||
connector: merchant_connector_account.connector_name,
|
||||
payment_id: payment_attempt.payment_id.clone(),
|
||||
attempt_id: Some(payment_attempt.attempt_id.clone()),
|
||||
status,
|
||||
payment_method: payment_method_type,
|
||||
connector_auth_type: auth_type,
|
||||
|
||||
@ -245,7 +245,7 @@ async fn send_request(
|
||||
}
|
||||
.map_err(|error| match error {
|
||||
error if error.is_timeout() => errors::ApiClientError::RequestTimeoutReceived,
|
||||
_ => errors::ApiClientError::RequestNotSent,
|
||||
_ => errors::ApiClientError::RequestNotSent(error.to_string()),
|
||||
})
|
||||
.into_report()
|
||||
.attach_printable("Unable to send request to connector")
|
||||
|
||||
@ -69,6 +69,7 @@ pub struct RouterData<Flow, Request, Response> {
|
||||
pub merchant_id: String,
|
||||
pub connector: String,
|
||||
pub payment_id: String,
|
||||
pub attempt_id: Option<String>,
|
||||
pub status: storage_enums::AttemptStatus,
|
||||
pub payment_method: storage_enums::PaymentMethodType,
|
||||
pub connector_auth_type: ConnectorAuthType,
|
||||
|
||||
@ -149,6 +149,7 @@ impl ConnectorData {
|
||||
"applepay" => Ok(Box::new(&connector::Applepay)),
|
||||
"cybersource" => Ok(Box::new(&connector::Cybersource)),
|
||||
"shift4" => Ok(Box::new(&connector::Shift4)),
|
||||
"worldpay" => Ok(Box::new(&connector::Worldpay)),
|
||||
_ => Err(report!(errors::UnexpectedError)
|
||||
.attach_printable(format!("invalid connector name: {connector_name}")))
|
||||
.change_context(errors::ConnectorError::InvalidConnectorName)
|
||||
|
||||
@ -22,6 +22,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData {
|
||||
merchant_id: String::from("aci"),
|
||||
connector: "aci".to_string(),
|
||||
payment_id: uuid::Uuid::new_v4().to_string(),
|
||||
attempt_id: None,
|
||||
status: enums::AttemptStatus::default(),
|
||||
auth_type: enums::AuthenticationType::NoThreeDs,
|
||||
payment_method: enums::PaymentMethodType::Card,
|
||||
@ -68,6 +69,7 @@ fn construct_refund_router_data<F>() -> types::RefundsRouterData<F> {
|
||||
merchant_id: String::from("aci"),
|
||||
connector: "aci".to_string(),
|
||||
payment_id: uuid::Uuid::new_v4().to_string(),
|
||||
attempt_id: None,
|
||||
status: enums::AttemptStatus::default(),
|
||||
router_return_url: None,
|
||||
payment_method: enums::PaymentMethodType::Card,
|
||||
|
||||
@ -22,6 +22,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData {
|
||||
merchant_id: String::from("authorizedotnet"),
|
||||
connector: "authorizedotnet".to_string(),
|
||||
payment_id: uuid::Uuid::new_v4().to_string(),
|
||||
attempt_id: None,
|
||||
status: enums::AttemptStatus::default(),
|
||||
router_return_url: None,
|
||||
payment_method: enums::PaymentMethodType::Card,
|
||||
@ -69,6 +70,7 @@ fn construct_refund_router_data<F>() -> types::RefundsRouterData<F> {
|
||||
merchant_id: String::from("authorizedotnet"),
|
||||
connector: "authorizedotnet".to_string(),
|
||||
payment_id: uuid::Uuid::new_v4().to_string(),
|
||||
attempt_id: None,
|
||||
status: enums::AttemptStatus::default(),
|
||||
router_return_url: None,
|
||||
auth_type: enums::AuthenticationType::NoThreeDs,
|
||||
|
||||
@ -19,6 +19,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData {
|
||||
merchant_id: "checkout".to_string(),
|
||||
connector: "checkout".to_string(),
|
||||
payment_id: uuid::Uuid::new_v4().to_string(),
|
||||
attempt_id: None,
|
||||
status: enums::AttemptStatus::default(),
|
||||
router_return_url: None,
|
||||
auth_type: enums::AuthenticationType::NoThreeDs,
|
||||
@ -66,6 +67,7 @@ fn construct_refund_router_data<F>() -> types::RefundsRouterData<F> {
|
||||
merchant_id: "checkout".to_string(),
|
||||
connector: "checkout".to_string(),
|
||||
payment_id: uuid::Uuid::new_v4().to_string(),
|
||||
attempt_id: None,
|
||||
status: enums::AttemptStatus::default(),
|
||||
router_return_url: None,
|
||||
payment_method: enums::PaymentMethodType::Card,
|
||||
|
||||
@ -7,6 +7,7 @@ pub(crate) struct ConnectorAuthentication {
|
||||
pub authorizedotnet: Option<BodyKey>,
|
||||
pub checkout: Option<BodyKey>,
|
||||
pub shift4: Option<HeaderKey>,
|
||||
pub worldpay: Option<HeaderKey>,
|
||||
}
|
||||
|
||||
impl ConnectorAuthentication {
|
||||
|
||||
@ -6,3 +6,4 @@ mod checkout;
|
||||
mod connector_auth;
|
||||
mod shift4;
|
||||
mod utils;
|
||||
mod worldpay;
|
||||
|
||||
@ -15,3 +15,6 @@ key1 = "MyProcessingChannelId"
|
||||
|
||||
[shift4]
|
||||
api_key = "Bearer MyApiKey"
|
||||
|
||||
[worldpay]
|
||||
api_key = "Bearer MyApiKey"
|
||||
@ -8,6 +8,7 @@ use router::{
|
||||
routes, services,
|
||||
types::{self, api, storage::enums, PaymentAddress},
|
||||
};
|
||||
use wiremock::{Mock, MockServer};
|
||||
|
||||
pub trait Connector {
|
||||
fn get_data(&self) -> types::api::ConnectorData;
|
||||
@ -25,6 +26,7 @@ pub trait ConnectorActions: Connector {
|
||||
let request = generate_data(
|
||||
self.get_name(),
|
||||
self.get_auth_token(),
|
||||
enums::AuthenticationType::NoThreeDs,
|
||||
payment_data.unwrap_or_else(|| types::PaymentsAuthorizeData {
|
||||
capture_method: Some(storage_models::enums::CaptureMethod::Manual),
|
||||
..PaymentAuthorizeType::default().0
|
||||
@ -40,10 +42,26 @@ pub trait ConnectorActions: Connector {
|
||||
let request = generate_data(
|
||||
self.get_name(),
|
||||
self.get_auth_token(),
|
||||
enums::AuthenticationType::NoThreeDs,
|
||||
payment_data.unwrap_or_else(|| PaymentAuthorizeType::default().0),
|
||||
);
|
||||
call_connector(request, integration).await
|
||||
}
|
||||
|
||||
async fn sync_payment(
|
||||
&self,
|
||||
payment_data: Option<types::PaymentsSyncData>,
|
||||
) -> types::PaymentsSyncRouterData {
|
||||
let integration = self.get_data().connector.get_connector_integration();
|
||||
let request = generate_data(
|
||||
self.get_name(),
|
||||
self.get_auth_token(),
|
||||
enums::AuthenticationType::NoThreeDs,
|
||||
payment_data.unwrap_or_else(|| PaymentSyncType::default().0),
|
||||
);
|
||||
call_connector(request, integration).await
|
||||
}
|
||||
|
||||
async fn capture_payment(
|
||||
&self,
|
||||
transaction_id: String,
|
||||
@ -53,6 +71,7 @@ pub trait ConnectorActions: Connector {
|
||||
let request = generate_data(
|
||||
self.get_name(),
|
||||
self.get_auth_token(),
|
||||
enums::AuthenticationType::NoThreeDs,
|
||||
payment_data.unwrap_or(types::PaymentsCaptureData {
|
||||
amount_to_capture: Some(100),
|
||||
connector_transaction_id: transaction_id,
|
||||
@ -62,6 +81,25 @@ pub trait ConnectorActions: Connector {
|
||||
);
|
||||
call_connector(request, integration).await
|
||||
}
|
||||
|
||||
async fn void_payment(
|
||||
&self,
|
||||
transaction_id: String,
|
||||
payment_data: Option<types::PaymentsCancelData>,
|
||||
) -> types::PaymentsCancelRouterData {
|
||||
let integration = self.get_data().connector.get_connector_integration();
|
||||
let request = generate_data(
|
||||
self.get_name(),
|
||||
self.get_auth_token(),
|
||||
enums::AuthenticationType::NoThreeDs,
|
||||
payment_data.unwrap_or(types::PaymentsCancelData {
|
||||
connector_transaction_id: transaction_id,
|
||||
cancellation_reason: Some("Test cancellation".to_string()),
|
||||
}),
|
||||
);
|
||||
call_connector(request, integration).await
|
||||
}
|
||||
|
||||
async fn refund_payment(
|
||||
&self,
|
||||
transaction_id: String,
|
||||
@ -71,6 +109,29 @@ pub trait ConnectorActions: Connector {
|
||||
let request = generate_data(
|
||||
self.get_name(),
|
||||
self.get_auth_token(),
|
||||
enums::AuthenticationType::NoThreeDs,
|
||||
payment_data.unwrap_or_else(|| types::RefundsData {
|
||||
amount: 100,
|
||||
currency: enums::Currency::USD,
|
||||
refund_id: uuid::Uuid::new_v4().to_string(),
|
||||
payment_method_data: types::api::PaymentMethod::Card(CCardType::default().0),
|
||||
connector_transaction_id: transaction_id,
|
||||
refund_amount: 100,
|
||||
}),
|
||||
);
|
||||
call_connector(request, integration).await
|
||||
}
|
||||
|
||||
async fn sync_refund(
|
||||
&self,
|
||||
transaction_id: String,
|
||||
payment_data: Option<types::RefundsData>,
|
||||
) -> types::RefundSyncRouterData {
|
||||
let integration = self.get_data().connector.get_connector_integration();
|
||||
let request = generate_data(
|
||||
self.get_name(),
|
||||
self.get_auth_token(),
|
||||
enums::AuthenticationType::NoThreeDs,
|
||||
payment_data.unwrap_or_else(|| types::RefundsData {
|
||||
amount: 100,
|
||||
currency: enums::Currency::USD,
|
||||
@ -105,7 +166,32 @@ async fn call_connector<
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub struct MockConfig {
|
||||
pub address: Option<String>,
|
||||
pub mocks: Vec<Mock>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait LocalMock {
|
||||
async fn start_server(&self, config: MockConfig) -> MockServer {
|
||||
let address = config
|
||||
.address
|
||||
.unwrap_or_else(|| "127.0.0.1:9090".to_string());
|
||||
let listener = std::net::TcpListener::bind(address).unwrap();
|
||||
let expected_server_address = listener
|
||||
.local_addr()
|
||||
.expect("Failed to get server address.");
|
||||
let mock_server = MockServer::builder().listener(listener).start().await;
|
||||
assert_eq!(&expected_server_address, mock_server.address());
|
||||
for mock in config.mocks {
|
||||
mock_server.register(mock).await;
|
||||
}
|
||||
mock_server
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PaymentAuthorizeType(pub types::PaymentsAuthorizeData);
|
||||
pub struct PaymentSyncType(pub types::PaymentsSyncData);
|
||||
pub struct PaymentRefundType(pub types::RefundsData);
|
||||
pub struct CCardType(pub api::CCard);
|
||||
|
||||
@ -142,6 +228,18 @@ impl Default for PaymentAuthorizeType {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PaymentSyncType {
|
||||
fn default() -> Self {
|
||||
let data = types::PaymentsSyncData {
|
||||
connector_transaction_id: types::ResponseId::ConnectorTransactionId(
|
||||
"12345".to_string(),
|
||||
),
|
||||
encoded_data: None,
|
||||
};
|
||||
Self(data)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PaymentRefundType {
|
||||
fn default() -> Self {
|
||||
let data = types::RefundsData {
|
||||
@ -171,6 +269,7 @@ pub fn get_connector_transaction_id(
|
||||
fn generate_data<Flow, Req: From<Req>, Res>(
|
||||
connector: String,
|
||||
connector_auth_type: types::ConnectorAuthType,
|
||||
auth_type: enums::AuthenticationType,
|
||||
req: Req,
|
||||
) -> types::RouterData<Flow, Req, Res> {
|
||||
types::RouterData {
|
||||
@ -178,9 +277,10 @@ fn generate_data<Flow, Req: From<Req>, Res>(
|
||||
merchant_id: connector.clone(),
|
||||
connector,
|
||||
payment_id: uuid::Uuid::new_v4().to_string(),
|
||||
attempt_id: Some(uuid::Uuid::new_v4().to_string()),
|
||||
status: enums::AttemptStatus::default(),
|
||||
router_return_url: None,
|
||||
auth_type: enums::AuthenticationType::NoThreeDs,
|
||||
auth_type,
|
||||
payment_method: enums::PaymentMethodType::Card,
|
||||
connector_auth_type,
|
||||
description: Some("This is a test".to_string()),
|
||||
|
||||
320
crates/router/tests/connectors/worldpay.rs
Normal file
320
crates/router/tests/connectors/worldpay.rs
Normal file
@ -0,0 +1,320 @@
|
||||
use futures::future::OptionFuture;
|
||||
use router::types::{
|
||||
self,
|
||||
api::{self, enums as api_enums},
|
||||
storage::enums,
|
||||
};
|
||||
use serde_json::json;
|
||||
use serial_test::serial;
|
||||
use wiremock::{
|
||||
matchers::{body_json, method, path},
|
||||
Mock, ResponseTemplate,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
connector_auth,
|
||||
utils::{self, ConnectorActions, LocalMock, MockConfig},
|
||||
};
|
||||
|
||||
struct Worldpay;
|
||||
|
||||
impl LocalMock for Worldpay {}
|
||||
impl ConnectorActions for Worldpay {}
|
||||
impl utils::Connector for Worldpay {
|
||||
fn get_data(&self) -> types::api::ConnectorData {
|
||||
use router::connector::Worldpay;
|
||||
types::api::ConnectorData {
|
||||
connector: Box::new(&Worldpay),
|
||||
connector_name: types::Connector::Worldpay,
|
||||
get_token: types::api::GetToken::Connector,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_auth_token(&self) -> types::ConnectorAuthType {
|
||||
types::ConnectorAuthType::from(
|
||||
connector_auth::ConnectorAuthentication::new()
|
||||
.worldpay
|
||||
.expect("Missing connector authentication configuration"),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_name(&self) -> String {
|
||||
"worldpay".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
#[serial]
|
||||
async fn should_authorize_card_payment() {
|
||||
let conn = Worldpay {};
|
||||
let _mock = conn.start_server(get_mock_config()).await;
|
||||
let response = conn.authorize_payment(None).await;
|
||||
assert_eq!(response.status, enums::AttemptStatus::Authorized);
|
||||
assert_eq!(
|
||||
utils::get_connector_transaction_id(response),
|
||||
Some("123456".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
#[serial]
|
||||
async fn should_authorize_gpay_payment() {
|
||||
let conn = Worldpay {};
|
||||
let _mock = conn.start_server(get_mock_config()).await;
|
||||
let response = conn
|
||||
.authorize_payment(Some(types::PaymentsAuthorizeData {
|
||||
payment_method_data: types::api::PaymentMethod::Wallet(api::WalletData {
|
||||
issuer_name: api_enums::WalletIssuer::GooglePay,
|
||||
token: "someToken".to_string(),
|
||||
}),
|
||||
..utils::PaymentAuthorizeType::default().0
|
||||
}))
|
||||
.await;
|
||||
assert_eq!(response.status, enums::AttemptStatus::Authorized);
|
||||
assert_eq!(
|
||||
utils::get_connector_transaction_id(response),
|
||||
Some("123456".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
#[serial]
|
||||
async fn should_authorize_applepay_payment() {
|
||||
let conn = Worldpay {};
|
||||
let _mock = conn.start_server(get_mock_config()).await;
|
||||
let response = conn
|
||||
.authorize_payment(Some(types::PaymentsAuthorizeData {
|
||||
payment_method_data: types::api::PaymentMethod::Wallet(api::WalletData {
|
||||
issuer_name: api_enums::WalletIssuer::ApplePay,
|
||||
token: "someToken".to_string(),
|
||||
}),
|
||||
..utils::PaymentAuthorizeType::default().0
|
||||
}))
|
||||
.await;
|
||||
assert_eq!(response.status, enums::AttemptStatus::Authorized);
|
||||
assert_eq!(
|
||||
utils::get_connector_transaction_id(response),
|
||||
Some("123456".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
#[serial]
|
||||
async fn should_capture_already_authorized_payment() {
|
||||
let connector = Worldpay {};
|
||||
let _mock = connector.start_server(get_mock_config()).await;
|
||||
let authorize_response = connector.authorize_payment(None).await;
|
||||
assert_eq!(authorize_response.status, enums::AttemptStatus::Authorized);
|
||||
let txn_id = utils::get_connector_transaction_id(authorize_response);
|
||||
let response: OptionFuture<_> = txn_id
|
||||
.map(|transaction_id| async move {
|
||||
connector.capture_payment(transaction_id, None).await.status
|
||||
})
|
||||
.into();
|
||||
assert_eq!(response.await, Some(enums::AttemptStatus::Charged));
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
#[serial]
|
||||
async fn should_sync_payment() {
|
||||
let connector = Worldpay {};
|
||||
let _mock = connector.start_server(get_mock_config()).await;
|
||||
let response = connector
|
||||
.sync_payment(Some(types::PaymentsSyncData {
|
||||
connector_transaction_id: router::types::ResponseId::ConnectorTransactionId(
|
||||
"112233".to_string(),
|
||||
),
|
||||
encoded_data: None,
|
||||
}))
|
||||
.await;
|
||||
assert_eq!(response.status, enums::AttemptStatus::Authorized,);
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
#[serial]
|
||||
async fn should_void_already_authorized_payment() {
|
||||
let connector = Worldpay {};
|
||||
let _mock = connector.start_server(get_mock_config()).await;
|
||||
let authorize_response = connector.authorize_payment(None).await;
|
||||
assert_eq!(authorize_response.status, enums::AttemptStatus::Authorized);
|
||||
let txn_id = utils::get_connector_transaction_id(authorize_response);
|
||||
let response: OptionFuture<_> =
|
||||
txn_id
|
||||
.map(|transaction_id| async move {
|
||||
connector.void_payment(transaction_id, None).await.status
|
||||
})
|
||||
.into();
|
||||
assert_eq!(response.await, Some(enums::AttemptStatus::Voided));
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
#[serial]
|
||||
async fn should_fail_capture_for_invalid_payment() {
|
||||
let connector = Worldpay {};
|
||||
let _mock = connector.start_server(get_mock_config()).await;
|
||||
let authorize_response = connector.authorize_payment(None).await;
|
||||
assert_eq!(authorize_response.status, enums::AttemptStatus::Authorized);
|
||||
let response = connector.capture_payment("12345".to_string(), None).await;
|
||||
let err = response.response.unwrap_err();
|
||||
assert_eq!(
|
||||
err.message,
|
||||
"You must provide valid transaction id to capture payment".to_string()
|
||||
);
|
||||
assert_eq!(err.code, "invalid-id".to_string());
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
#[serial]
|
||||
async fn should_refund_succeeded_payment() {
|
||||
let connector = Worldpay {};
|
||||
let _mock = connector.start_server(get_mock_config()).await;
|
||||
//make a successful payment
|
||||
let response = connector.make_payment(None).await;
|
||||
|
||||
//try refund for previous payment
|
||||
let transaction_id = utils::get_connector_transaction_id(response).unwrap();
|
||||
let response = connector.refund_payment(transaction_id, None).await;
|
||||
assert_eq!(
|
||||
response.response.unwrap().refund_status,
|
||||
enums::RefundStatus::Success,
|
||||
);
|
||||
}
|
||||
|
||||
#[actix_web::test]
|
||||
#[serial]
|
||||
async fn should_sync_refund() {
|
||||
let connector = Worldpay {};
|
||||
let _mock = connector.start_server(get_mock_config()).await;
|
||||
let response = connector.sync_refund("654321".to_string(), None).await;
|
||||
assert_eq!(
|
||||
response.response.unwrap().refund_status,
|
||||
enums::RefundStatus::Success,
|
||||
);
|
||||
}
|
||||
|
||||
fn get_mock_config() -> MockConfig {
|
||||
let authorized = json!({
|
||||
"outcome": "authorized",
|
||||
"_links": {
|
||||
"payments:cancel": {
|
||||
"href": "/payments/authorizations/cancellations/123456"
|
||||
},
|
||||
"payments:settle": {
|
||||
"href": "/payments/settlements/123456"
|
||||
},
|
||||
"payments:partialSettle": {
|
||||
"href": "/payments/settlements/partials/123456"
|
||||
},
|
||||
"payments:events": {
|
||||
"href": "/payments/events/123456"
|
||||
},
|
||||
"curies": [
|
||||
{
|
||||
"name": "payments",
|
||||
"href": "/rels/payments/{rel}",
|
||||
"templated": true
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
let settled = json!({
|
||||
"_links": {
|
||||
"payments:refund": {
|
||||
"href": "/payments/settlements/refunds/full/654321"
|
||||
},
|
||||
"payments:partialRefund": {
|
||||
"href": "/payments/settlements/refunds/partials/654321"
|
||||
},
|
||||
"payments:events": {
|
||||
"href": "/payments/events/654321"
|
||||
},
|
||||
"curies": [
|
||||
{
|
||||
"name": "payments",
|
||||
"href": "/rels/payments/{rel}",
|
||||
"templated": true
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
let error_resp = json!({
|
||||
"errorName": "invalid-id",
|
||||
"message": "You must provide valid transaction id to capture payment"
|
||||
});
|
||||
let partial_refund = json!({
|
||||
"_links": {
|
||||
"payments:events": {
|
||||
"href": "https://try.access.worldpay.com/payments/events/eyJrIjoiazNhYjYzMiJ9"
|
||||
},
|
||||
"curies": [{
|
||||
"name": "payments",
|
||||
"href": "https://try.access.worldpay.com/rels/payments/{rel}",
|
||||
"templated": true
|
||||
}]
|
||||
}
|
||||
});
|
||||
let partial_refund_req_body = json!({
|
||||
"value": {
|
||||
"amount": 100,
|
||||
"currency": "USD"
|
||||
},
|
||||
"reference": "123456"
|
||||
});
|
||||
let refunded = json!({
|
||||
"lastEvent": "refunded",
|
||||
"_links": {
|
||||
"payments:cancel": "/payments/authorizations/cancellations/654321",
|
||||
"payments:settle": "/payments/settlements/full/654321",
|
||||
"payments:partialSettle": "/payments/settlements/partials/654321",
|
||||
"curies": [
|
||||
{
|
||||
"name": "payments",
|
||||
"href": "/rels/payments/{rel}",
|
||||
"templated": true
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
let sync_payment = json!({
|
||||
"lastEvent": "authorized",
|
||||
"_links": {
|
||||
"payments:events": "/payments/authorizations/events/654321",
|
||||
"payments:settle": "/payments/settlements/full/654321",
|
||||
"payments:partialSettle": "/payments/settlements/partials/654321",
|
||||
"curies": [
|
||||
{
|
||||
"name": "payments",
|
||||
"href": "/rels/payments/{rel}",
|
||||
"templated": true
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
MockConfig {
|
||||
address: Some("127.0.0.1:9090".to_string()),
|
||||
mocks: vec![
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/payments/authorizations".to_string()))
|
||||
.respond_with(ResponseTemplate::new(201).set_body_json(authorized)),
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/payments/settlements/123456".to_string()))
|
||||
.respond_with(ResponseTemplate::new(202).set_body_json(settled)),
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/payments/events/112233".to_string()))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(sync_payment)),
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/payments/settlements/12345".to_string()))
|
||||
.respond_with(ResponseTemplate::new(400).set_body_json(error_resp)),
|
||||
Mock::given(method("POST"))
|
||||
.and(path(
|
||||
"/payments/settlements/refunds/partials/123456".to_string(),
|
||||
))
|
||||
.and(body_json(partial_refund_req_body))
|
||||
.respond_with(ResponseTemplate::new(202).set_body_json(partial_refund)),
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/payments/events/654321".to_string()))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(refunded)),
|
||||
],
|
||||
}
|
||||
}
|
||||
@ -2,23 +2,43 @@ pg=$1;
|
||||
pgc="$(tr '[:lower:]' '[:upper:]' <<< ${pg:0:1})${pg:1}"
|
||||
src="crates/router/src"
|
||||
conn="$src/connector"
|
||||
tests="../../tests/connectors/"
|
||||
SCRIPT="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
|
||||
if [[ -z "$pg" ]]; then
|
||||
echo 'Connector name not present: try "sh add_connector.sh <adyen>"'
|
||||
exit
|
||||
fi
|
||||
cd $SCRIPT/..
|
||||
# remove template files if already created for this connector
|
||||
rm -rf $conn/$pg $conn/$pg.rs
|
||||
git checkout $conn.rs $src/types/api.rs $src/configs/settings.rs
|
||||
git checkout $conn.rs $src/types/api.rs $src/configs/settings.rs config/Development.toml config/docker_compose.toml crates/api_models/src/enums.rs
|
||||
# add enum for this connector in required places
|
||||
sed -i'' -e "s/pub use self::{/pub mod ${pg};\n\npub use self::{/" $conn.rs
|
||||
sed -i'' -e "s/};/${pg}::${pgc},\n};/" $conn.rs
|
||||
sed -i'' -e "s/_ => Err/\"${pg}\" => Ok(Box::new(\&connector::${pgc})),\n\t\t\t_ => Err/" $src/types/api.rs
|
||||
sed -i'' -e "s/pub supported: SupportedConnectors,/pub supported: SupportedConnectors,\n\tpub ${pg}: ConnectorParams,/" $src/configs/settings.rs
|
||||
rm $conn.rs-e $src/types/api.rs-e $src/configs/settings.rs-e
|
||||
sed -i'' -e "s/\[scheduler\]/[connectors.${pg}]\nbase_url = \"\"\n\n[scheduler]/" config/Development.toml
|
||||
sed -r -i'' -e "s/cards = \[(.*)\]/cards = [\1, \"${pg}\"]/" config/Development.toml
|
||||
sed -i'' -e "s/\[connectors.supported\]/[connectors.${pg}]\nbase_url = ""\n\n[connectors.supported]/" config/docker_compose.toml
|
||||
sed -r -i'' -e "s/cards = \[(.*)\]/cards = [\1, \"${pg}\"]/" config/docker_compose.toml
|
||||
sed -i'' -e "s/Dummy,/Dummy,\n\t${pgc},/" crates/api_models/src/enums.rs
|
||||
# remove temporary files created in above step
|
||||
rm $conn.rs-e $src/types/api.rs-e $src/configs/settings.rs-e config/Development.toml-e config/docker_compose.toml-e crates/api_models/src/enums.rs-e
|
||||
cd $conn/
|
||||
# generate template files for the connector
|
||||
cargo install cargo-generate
|
||||
cargo gen-pg $pg
|
||||
# move sub files and test files to appropiate folder
|
||||
mv $pg/mod.rs $pg.rs
|
||||
mv $pg/test.rs ../../tests/connectors/$pg.rs
|
||||
sed -i'' -e "s/mod utils;/mod ${pg};\nmod utils;/" ../../tests/connectors/main.rs
|
||||
rm ../../tests/connectors/main.rs-e
|
||||
echo "Successfully created connector: try running the tests of "$pg.rs
|
||||
mv $pg/test.rs ${tests}/$pg.rs
|
||||
# remove changes from tests if already done for this connector
|
||||
git checkout ${tests}/main.rs ${tests}/connector_auth.rs
|
||||
# add enum for this connector in test folder
|
||||
sed -i'' -e "s/mod utils;/mod ${pg};\nmod utils;/" ${tests}/main.rs
|
||||
sed -i'' -e "s/struct ConnectorAuthentication {/struct ConnectorAuthentication {\n\tpub ${pg}: Option<HeaderKey>,/" ${tests}/connector_auth.rs
|
||||
# remove temporary files created in above step
|
||||
rm ${tests}/main.rs-e ${tests}/connector_auth.rs-e
|
||||
cargo build
|
||||
echo "Successfully created connector. Running the tests of "$pg.rs
|
||||
# runs tests for the new connector
|
||||
cargo test --package router --test connectors -- $pg
|
||||
Reference in New Issue
Block a user