diff --git a/.deepsource.toml b/.deepsource.toml index 8d0b3c501afa..3cac181614e5 100644 --- a/.deepsource.toml +++ b/.deepsource.toml @@ -13,4 +13,4 @@ name = "rust" enabled = true [analyzers.meta] -msrv = "1.80.0" +msrv = "1.78.0" diff --git a/.github/workflows/CI-pr.yml b/.github/workflows/CI-pr.yml index 722e0d22b168..a31d04500101 100644 --- a/.github/workflows/CI-pr.yml +++ b/.github/workflows/CI-pr.yml @@ -297,12 +297,10 @@ jobs: uses: dtolnay/rust-toolchain@master with: toolchain: stable 2 weeks ago - components: clippy - name: Install rust cache uses: Swatinem/rust-cache@v2.7.0 - - name: Install just uses: taiki-e/install-action@v2.41.10 with: @@ -316,3 +314,7 @@ jobs: # # We only want to ensure successful compilation for now. # RUSTFLAGS: "-D warnings -A clippy::todo" run: just check_v2 + + - name: Run cargo check enabling only the release and v2 features + shell: bash + run: cargo check --no-default-features --features "release,v2" diff --git a/.github/workflows/CI-push.yml b/.github/workflows/CI-push.yml index ace8d7eaf0a5..0f92fc36c314 100644 --- a/.github/workflows/CI-push.yml +++ b/.github/workflows/CI-push.yml @@ -197,3 +197,29 @@ jobs: - name: Spell check uses: crate-ci/typos@master + + check-v2: + name: Check compilation for V2 features + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install mold linker + uses: rui314/setup-mold@v1 + if: ${{ runner.os == 'Linux' }} + with: + make-default: true + + - name: Install Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: stable 2 weeks ago + + - name: Install rust cache + uses: Swatinem/rust-cache@v2.7.0 + + - name: Run cargo check enabling only the release and v2 features + shell: bash + run: cargo check --no-default-features --features "release,v2" diff --git a/CHANGELOG.md b/CHANGELOG.md index cc49ea76d60b..b8729392ac8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,136 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.09.13.0 + +### Features + +- **connector:** [Novalnet] add Payment flows for cards ([#5726](https://github.com/juspay/hyperswitch/pull/5726)) ([`246fdc8`](https://github.com/juspay/hyperswitch/commit/246fdc84064367885596b33f5e0e66af78a97a3c)) +- **refunds:** Refunds aggregate api ([#5795](https://github.com/juspay/hyperswitch/pull/5795)) ([`00386f3`](https://github.com/juspay/hyperswitch/commit/00386f32954a6f9a3f71388e9cad7fbdb8824206)) + +### Bug Fixes + +- **connector:** [ZSL] compare consr_paid_amt with the total amount for identifying partial payments ([#5873](https://github.com/juspay/hyperswitch/pull/5873)) ([`985761c`](https://github.com/juspay/hyperswitch/commit/985761cfb31539ca51c66832a7f2cca458640c78)) +- **router:** Add payment_method check in `get_mandate_type` ([#5828](https://github.com/juspay/hyperswitch/pull/5828)) ([`1929f56`](https://github.com/juspay/hyperswitch/commit/1929f56e2ab65fc8570eb492ebc4f254bd5092d0)) + +### Refactors + +- **core:** Update shipping_cost and order_tax_amount to net_amount of payment_attempt ([#5844](https://github.com/juspay/hyperswitch/pull/5844)) ([`840609a`](https://github.com/juspay/hyperswitch/commit/840609af61ad1441ca95ebb7294196b35e7b875b)) +- **payment_links:** Update API contract for dynamic transaction details and upgrade UI ([#5849](https://github.com/juspay/hyperswitch/pull/5849)) ([`a96e9f3`](https://github.com/juspay/hyperswitch/commit/a96e9f3e22ca24ffb96af3e469cace543dc03796)) +- Handle redirections for iframed content ([#5591](https://github.com/juspay/hyperswitch/pull/5591)) ([`1bb8f5e`](https://github.com/juspay/hyperswitch/commit/1bb8f5e8ebd817ac7e04e2e9dd2ae9e422313bc3)) +- Return optional request body from build_request_v2 in ConnectorIntegrationV2 trait ([#5865](https://github.com/juspay/hyperswitch/pull/5865)) ([`608676c`](https://github.com/juspay/hyperswitch/commit/608676c8e26d73b191914960dbcc8b84effb6c7d)) + +**Full Changelog:** [`2024.09.12.0...2024.09.13.0`](https://github.com/juspay/hyperswitch/compare/2024.09.12.0...2024.09.13.0) + +- - - + +## 2024.09.12.0 + +### Features + +- **connector:** [Fiuu] Add DuitNow/FPX PaymentMethod ([#5841](https://github.com/juspay/hyperswitch/pull/5841)) ([`8c0fec9`](https://github.com/juspay/hyperswitch/commit/8c0fec9dc317ed8753d0341f01e0174f5a701edc)) +- **opensearch:** Add profile_id and organization_id to /search APIs ([#5705](https://github.com/juspay/hyperswitch/pull/5705)) ([`7eb5354`](https://github.com/juspay/hyperswitch/commit/7eb5354efe7b4790cb8e2a6ebac23126f3233add)) +- **payment_method_data:** Populate additional payment method data fields across all the methods in payments response ([#5788](https://github.com/juspay/hyperswitch/pull/5788)) ([`034f736`](https://github.com/juspay/hyperswitch/commit/034f736ea6e25ae2c75e74dedc12ac54f5979d68)) +- **payments:** Add support for profile aggregates ([#5845](https://github.com/juspay/hyperswitch/pull/5845)) ([`b7b0a08`](https://github.com/juspay/hyperswitch/commit/b7b0a08114eb73b167b07c297ca5422e43a51e21)) + +### Bug Fixes + +- **router:** Return `collect_billing_details_from_wallet_connector` if `always_collect_billing_details_from_wallet_connector ` is false in merchant payment method list ([#5854](https://github.com/juspay/hyperswitch/pull/5854)) ([`a90e406`](https://github.com/juspay/hyperswitch/commit/a90e4062761cb8b9b71daa5bb2f07901cbb2f8f9)) +- Fix `status_code` being logged as string instead of number in logs ([#5850](https://github.com/juspay/hyperswitch/pull/5850)) ([`75e8f35`](https://github.com/juspay/hyperswitch/commit/75e8f354314edb5d35fc5036e39a7aec4ec23485)) + +### Refactors + +- Add a GAT Data to Operation trait ([#5825](https://github.com/juspay/hyperswitch/pull/5825)) ([`418ea4e`](https://github.com/juspay/hyperswitch/commit/418ea4e2c6ce49323dc63ccf311a606ca6eec73f)) +- Remove unwanted commented lines ([#5851](https://github.com/juspay/hyperswitch/pull/5851)) ([`4a9fd7e`](https://github.com/juspay/hyperswitch/commit/4a9fd7e0bf46a05d27e58e0febde8442f94acba6)) + +### Documentation + +- Correction for JPY in API Ref ([#5853](https://github.com/juspay/hyperswitch/pull/5853)) ([`be346e5`](https://github.com/juspay/hyperswitch/commit/be346e5d963925ecbe1bbb77aa024f7eed66019e)) + +### Miscellaneous Tasks + +- Remove Connectors enum dependency from ConnectorIntegrationV2 trait ([#5840](https://github.com/juspay/hyperswitch/pull/5840)) ([`296ca31`](https://github.com/juspay/hyperswitch/commit/296ca311c96cc032aaa9ad846299db24bacaeb56)) + +### Build System / Dependencies + +- **deps:** Bump `sqlx` to `0.8.2` ([#5859](https://github.com/juspay/hyperswitch/pull/5859)) ([`b72c770`](https://github.com/juspay/hyperswitch/commit/b72c77075e6847d5cce3066b9bc88df1127fbfb2)) + +**Full Changelog:** [`2024.09.11.0...2024.09.12.0`](https://github.com/juspay/hyperswitch/compare/2024.09.11.0...2024.09.12.0) + +- - - + +## 2024.09.11.0 + +### Features + +- **analytics:** + - Revert api_event metrics and filters back to merchant_id authentication ([#5821](https://github.com/juspay/hyperswitch/pull/5821)) ([`283154d`](https://github.com/juspay/hyperswitch/commit/283154d3f66ae26a0d9644ed50547ad1407bd924)) + - Add card_network as a field in payment_attempts clickhouse table ([#5807](https://github.com/juspay/hyperswitch/pull/5807)) ([`bf1797f`](https://github.com/juspay/hyperswitch/commit/bf1797fe7cf769eb6e89f75b132a35a6cd9003df)) +- **connector:** [THUNES] Add template code ([#5775](https://github.com/juspay/hyperswitch/pull/5775)) ([`9b508a8`](https://github.com/juspay/hyperswitch/commit/9b508a838d68d14517477d41f2fc60168703d001)) +- **core:** [Payouts] Add billing address to payout list ([#5004](https://github.com/juspay/hyperswitch/pull/5004)) ([`49a60bf`](https://github.com/juspay/hyperswitch/commit/49a60bf14725d67bc4f8c5814dc5ca27b7f57712)) +- **payment_methods_list:** Add is_tax_connector_enabled boolean value in payment_methods_list call response ([#5707](https://github.com/juspay/hyperswitch/pull/5707)) ([`3a5fb53`](https://github.com/juspay/hyperswitch/commit/3a5fb532de39e033feff7e8de126c5e5bf12eb36)) +- **payout:** Add dynamic fields for payout links ([#5764](https://github.com/juspay/hyperswitch/pull/5764)) ([`f4ad657`](https://github.com/juspay/hyperswitch/commit/f4ad6579cc317b32599d28dbb6164be6e20804fa)) +- **router:** Mask keys in `connector_account_details` for merchant_connector_response in mca retrieve flow ([#5848](https://github.com/juspay/hyperswitch/pull/5848)) ([`71b5202`](https://github.com/juspay/hyperswitch/commit/71b52024c296548156cd80950010a2f1266906fb)) +- **users:** New profile level roles ([#5843](https://github.com/juspay/hyperswitch/pull/5843)) ([`3cb0f24`](https://github.com/juspay/hyperswitch/commit/3cb0f2405303f9abbf15fff2b31ad617c1a6ca91)) + +### Bug Fixes + +- **router:** [Stripe/Itau/Paypal/Bambora/Cybersource] prevent partial submission of billing address and add required fields for all payment methods ([#5752](https://github.com/juspay/hyperswitch/pull/5752)) ([`ad40ced`](https://github.com/juspay/hyperswitch/commit/ad40cedf5cf70337c0ec49e3fa6306fe25badc39)) + +### Refactors + +- Add hgetall command to redis interface ([#5727](https://github.com/juspay/hyperswitch/pull/5727)) ([`74ec3f3`](https://github.com/juspay/hyperswitch/commit/74ec3f3df33de8c418efa859789365627c12b93d)) + +### Miscellaneous Tasks + +- Address Rust 1.81.0 clippy lints ([#5832](https://github.com/juspay/hyperswitch/pull/5832)) ([`933cef4`](https://github.com/juspay/hyperswitch/commit/933cef425fed2e324474f4015dd728bde2612cb2)) +- Make kms decryption optional if partial auth not enabled ([#5779](https://github.com/juspay/hyperswitch/pull/5779)) ([`aaeb192`](https://github.com/juspay/hyperswitch/commit/aaeb1925767b08e35decb913e3867cd6415f0abd)) + +**Full Changelog:** [`2024.09.10.0...2024.09.11.0`](https://github.com/juspay/hyperswitch/compare/2024.09.10.0...2024.09.11.0) + +- - - + +## 2024.09.10.0 + +### Features + +- Enable payment and refund filter at DB query level ([#5827](https://github.com/juspay/hyperswitch/pull/5827)) ([`21352cf`](https://github.com/juspay/hyperswitch/commit/21352cf875e360c808562a15fcbb8d8c6a27ae50)) + +**Full Changelog:** [`2024.09.09.0...2024.09.10.0`](https://github.com/juspay/hyperswitch/compare/2024.09.09.0...2024.09.10.0) + +- - - + +## 2024.09.09.0 + +### Features + +- **core:** Add Support for Payments Dynamic Tax Calculation Based on Shipping Address ([#5619](https://github.com/juspay/hyperswitch/pull/5619)) ([`a03ad53`](https://github.com/juspay/hyperswitch/commit/a03ad53e437efa30528c9b28f0d0328b6d0d1bc2)) +- **recon:** Add merchant and profile IDs in auth tokens ([#5643](https://github.com/juspay/hyperswitch/pull/5643)) ([`d9485a5`](https://github.com/juspay/hyperswitch/commit/d9485a5f360f78f308f4e70c361f33873c63b686)) +- Add support to forward x-request-id to keymanager service ([#5803](https://github.com/juspay/hyperswitch/pull/5803)) ([`36cd5c1`](https://github.com/juspay/hyperswitch/commit/36cd5c1c41ff4948d52f1b8f1dbe21af200fc618)) + +**Full Changelog:** [`2024.09.06.0...2024.09.09.0`](https://github.com/juspay/hyperswitch/compare/2024.09.06.0...2024.09.09.0) + +- - - + +## 2024.09.06.0 + +### Features + +- **customer_v2:** Add customer V2 delete api ([#5518](https://github.com/juspay/hyperswitch/pull/5518)) ([`a901d67`](https://github.com/juspay/hyperswitch/commit/a901d67108d2053727fff433da6ef74b61353b11)) +- **payouts:** Add profile level payout filter API ([#5808](https://github.com/juspay/hyperswitch/pull/5808)) ([`d93f8a1`](https://github.com/juspay/hyperswitch/commit/d93f8a12bbd35f9069e19d08b64ba87cd4bb623d)) +- **user:** Implement entity level authorization ([#5819](https://github.com/juspay/hyperswitch/pull/5819)) ([`e15ea18`](https://github.com/juspay/hyperswitch/commit/e15ea184d9c3b057549afe8d245bb69a4d5afcdc)) +- **users:** Send profile_id in JWT and user_info APIs ([#5817](https://github.com/juspay/hyperswitch/pull/5817)) ([`4d49903`](https://github.com/juspay/hyperswitch/commit/4d499038c03986a6f3ecee742c5add1c55789b01)) + +### Bug Fixes + +- **docker:** Add `version_feature_set` build arg with default as `v1` in wasm build dockerfile ([#5813](https://github.com/juspay/hyperswitch/pull/5813)) ([`402652e`](https://github.com/juspay/hyperswitch/commit/402652eeb76c5706adbf789a09da7b7e3b18d9f7)) +- Fix errors on payment_methods_v2 ([#5800](https://github.com/juspay/hyperswitch/pull/5800)) ([`dfebc29`](https://github.com/juspay/hyperswitch/commit/dfebc29c2b1398ac8934bd350eefcd4fa4f10d84)) +- Move profile level connector list endpoint to separate scope ([#5814](https://github.com/juspay/hyperswitch/pull/5814)) ([`9dd1511`](https://github.com/juspay/hyperswitch/commit/9dd1511b4d5568bd0cf3e24b0f73c4fa2b45e0d9)) + +**Full Changelog:** [`2024.09.05.0...2024.09.06.0`](https://github.com/juspay/hyperswitch/compare/2024.09.05.0...2024.09.06.0) + +- - - + ## 2024.09.05.0 ### Features diff --git a/Cargo.lock b/Cargo.lock index de688a61f79d..ac324d9c310e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1837,9 +1837,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.1.15" +version = "1.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" +checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" dependencies = [ "jobserver", "libc", @@ -2208,9 +2208,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -3921,8 +3921,10 @@ dependencies = [ "http 0.2.12", "hyperswitch_domain_models", "hyperswitch_interfaces", + "image", "masking", "once_cell", + "qrcode", "rand", "regex", "reqwest 0.11.27", @@ -3930,6 +3932,7 @@ dependencies = [ "router_env", "serde", "serde_json", + "serde_urlencoded", "strum 0.26.3", "time", "url", @@ -6226,7 +6229,6 @@ dependencies = [ "hyperswitch_constraint_graph", "hyperswitch_domain_models", "hyperswitch_interfaces", - "image", "infer", "iso_currency", "isocountry", @@ -6244,7 +6246,6 @@ dependencies = [ "openidconnect", "openssl", "pm_auth", - "qrcode", "quick-xml", "rand", "rand_chacha", @@ -6512,9 +6513,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.35" +version = "0.38.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" +checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" dependencies = [ "bitflags 2.6.0", "errno", @@ -6804,9 +6805,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] @@ -6845,9 +6846,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", @@ -6856,9 +6857,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.127" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "indexmap 2.5.0", "itoa", @@ -7199,9 +7200,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcfa89bea9500db4a0d038513d7a060566bfc51d46d1c014847049a45cce85e8" +checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e" dependencies = [ "sqlx-core", "sqlx-macros", @@ -7212,9 +7213,9 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d06e2f2bd861719b1f3f0c7dbe1d80c30bf59e76cf019f07d9014ed7eefb8e08" +checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e" dependencies = [ "atoi", "bigdecimal", @@ -7254,9 +7255,9 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f998a9defdbd48ed005a89362bd40dd2117502f15294f61c8d47034107dbbdc" +checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657" dependencies = [ "proc-macro2", "quote", @@ -7267,9 +7268,9 @@ dependencies = [ [[package]] name = "sqlx-macros-core" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d100558134176a2629d46cec0c8891ba0be8910f7896abfdb75ef4ab6f4e7ce" +checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5" dependencies = [ "dotenvy", "either", @@ -7293,9 +7294,9 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cac0ab331b14cb3921c62156d913e4c15b74fb6ec0f3146bd4ef6e4fb3c12" +checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" dependencies = [ "atoi", "base64 0.22.1", @@ -7337,9 +7338,9 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9734dbce698c67ecf67c442f768a5e90a49b2a4d61a9f1d59f73874bd4cf0710" +checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" dependencies = [ "atoi", "base64 0.22.1", @@ -7378,9 +7379,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75b419c3c1b1697833dd927bdc4c6545a620bc1bbafabd44e1efbe9afcd337e" +checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680" dependencies = [ "atoi", "flume", diff --git a/Cargo.toml b/Cargo.toml index c191e0a53ba7..90eb996b82b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ resolver = "2" members = ["crates/*"] package.edition = "2021" -package.rust-version = "1.80.0" +package.rust-version = "1.78.0" package.license = "Apache-2.0" [workspace.dependencies] diff --git a/INSTALL_dependencies.sh b/INSTALL_dependencies.sh index b18c731dafa7..3ec36ccc66e0 100755 --- a/INSTALL_dependencies.sh +++ b/INSTALL_dependencies.sh @@ -9,7 +9,7 @@ if [[ "${TRACE-0}" == "1" ]]; then set -o xtrace fi -RUST_MSRV=1.80.0 +RUST_MSRV="1.78.0" _DB_NAME="hyperswitch_db" _DB_USER="db_user" _DB_PASS="db_password" diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 3ba7097d3a7b..acff517e1072 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -1436,6 +1436,61 @@ "propertyName": "type" } }, + "AchBankDebitAdditionalData": { + "type": "object", + "required": [ + "account_number", + "routing_number" + ], + "properties": { + "account_number": { + "type": "string", + "description": "Partially masked account number for ach bank debit payment", + "example": "0001****3456" + }, + "routing_number": { + "type": "string", + "description": "Partially masked routing number for ach bank debit payment", + "example": "110***000" + }, + "card_holder_name": { + "type": "string", + "description": "Card holder's name", + "example": "John Doe", + "nullable": true + }, + "bank_account_holder_name": { + "type": "string", + "description": "Bank account's owner name", + "example": "John Doe", + "nullable": true + }, + "bank_name": { + "allOf": [ + { + "$ref": "#/components/schemas/BankNames" + } + ], + "nullable": true + }, + "bank_type": { + "allOf": [ + { + "$ref": "#/components/schemas/BankType" + } + ], + "nullable": true + }, + "bank_holder_type": { + "allOf": [ + { + "$ref": "#/components/schemas/BankHolderType" + } + ], + "nullable": true + } + } + }, "AchBankTransfer": { "type": "object", "required": [ @@ -2014,6 +2069,31 @@ "unresolved" ] }, + "BacsBankDebitAdditionalData": { + "type": "object", + "required": [ + "account_number", + "sort_code" + ], + "properties": { + "account_number": { + "type": "string", + "description": "Partially masked account number for Bacs payment method", + "example": "0001****3456" + }, + "sort_code": { + "type": "string", + "description": "Partially masked sort code for Bacs payment method", + "example": "108800" + }, + "bank_account_holder_name": { + "type": "string", + "description": "Bank account's owner name", + "example": "John Doe", + "nullable": true + } + } + }, "BacsBankTransfer": { "type": "object", "required": [ @@ -2075,6 +2155,35 @@ } } }, + "BancontactBankRedirectAdditionalData": { + "type": "object", + "properties": { + "last4": { + "type": "string", + "description": "Last 4 digits of the card number", + "example": "4242", + "nullable": true + }, + "card_exp_month": { + "type": "string", + "description": "The card's expiry month", + "example": "12", + "nullable": true + }, + "card_exp_year": { + "type": "string", + "description": "The card's expiry year", + "example": "24", + "nullable": true + }, + "card_holder_name": { + "type": "string", + "description": "The card holder's name", + "example": "John Test", + "nullable": true + } + } + }, "Bank": { "oneOf": [ { @@ -2091,6 +2200,54 @@ } ] }, + "BankDebitAdditionalData": { + "oneOf": [ + { + "type": "object", + "required": [ + "ach" + ], + "properties": { + "ach": { + "$ref": "#/components/schemas/AchBankDebitAdditionalData" + } + } + }, + { + "type": "object", + "required": [ + "bacs" + ], + "properties": { + "bacs": { + "$ref": "#/components/schemas/BacsBankDebitAdditionalData" + } + } + }, + { + "type": "object", + "required": [ + "becs" + ], + "properties": { + "becs": { + "$ref": "#/components/schemas/BecsBankDebitAdditionalData" + } + } + }, + { + "type": "object", + "required": [ + "sepa" + ], + "properties": { + "sepa": { + "$ref": "#/components/schemas/SepaBankDebitAdditionalData" + } + } + } + ] + }, "BankDebitBilling": { "type": "object", "properties": { @@ -2298,6 +2455,28 @@ } ] }, + "BankDebitResponse": { + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/BankDebitAdditionalData" + } + ], + "nullable": true + }, + { + "type": "object" + } + ] + }, + "BankHolderType": { + "type": "string", + "enum": [ + "personal", + "business" + ] + }, "BankNames": { "type": "string", "description": "Name of banks supported by Hyperswitch", @@ -2308,6 +2487,7 @@ "alliance_bank", "am_bank", "bank_of_america", + "bank_of_china", "bank_islam", "bank_muamalat", "bank_rakyat", @@ -2870,30 +3050,250 @@ { "type": "object", "required": [ - "online_banking_thailand" + "online_banking_thailand" + ], + "properties": { + "online_banking_thailand": { + "type": "object", + "required": [ + "issuer" + ], + "properties": { + "issuer": { + "$ref": "#/components/schemas/BankNames" + } + } + } + } + }, + { + "type": "object", + "required": [ + "local_bank_redirect" + ], + "properties": { + "local_bank_redirect": { + "type": "object" + } + } + } + ] + }, + "BankRedirectDetails": { + "oneOf": [ + { + "type": "object", + "required": [ + "BancontactCard" + ], + "properties": { + "BancontactCard": { + "$ref": "#/components/schemas/BancontactBankRedirectAdditionalData" + } + } + }, + { + "type": "object", + "required": [ + "Blik" + ], + "properties": { + "Blik": { + "$ref": "#/components/schemas/BlikBankRedirectAdditionalData" + } + } + }, + { + "type": "object", + "required": [ + "Giropay" + ], + "properties": { + "Giropay": { + "$ref": "#/components/schemas/GiropayBankRedirectAdditionalData" + } + } + } + ] + }, + "BankRedirectResponse": { + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/BankRedirectDetails" + } + ], + "nullable": true + }, + { + "type": "object", + "properties": { + "bank_name": { + "allOf": [ + { + "$ref": "#/components/schemas/BankNames" + } + ], + "nullable": true + } + } + } + ] + }, + "BankTransferAdditionalData": { + "oneOf": [ + { + "type": "object", + "required": [ + "ach" + ], + "properties": { + "ach": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "sepa" + ], + "properties": { + "sepa": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "bacs" + ], + "properties": { + "bacs": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "multibanco" + ], + "properties": { + "multibanco": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "permata" + ], + "properties": { + "permata": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "bca" + ], + "properties": { + "bca": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "bni_va" + ], + "properties": { + "bni_va": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "bri_va" + ], + "properties": { + "bri_va": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "cimb_va" + ], + "properties": { + "cimb_va": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "danamon_va" + ], + "properties": { + "danamon_va": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "mandiri_va" + ], + "properties": { + "mandiri_va": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "pix" + ], + "properties": { + "pix": { + "$ref": "#/components/schemas/PixBankTransferAdditionalData" + } + } + }, + { + "type": "object", + "required": [ + "pse" ], "properties": { - "online_banking_thailand": { - "type": "object", - "required": [ - "issuer" - ], - "properties": { - "issuer": { - "$ref": "#/components/schemas/BankNames" - } - } + "pse": { + "type": "object" } } }, { "type": "object", "required": [ - "local_bank_redirect" + "local_bank_transfer" ], "properties": { - "local_bank_redirect": { - "type": "object" + "local_bank_transfer": { + "$ref": "#/components/schemas/LocalBankTransferAdditionalData" } } } @@ -3278,6 +3678,63 @@ } ] }, + "BankTransferResponse": { + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/BankTransferAdditionalData" + } + ], + "nullable": true + }, + { + "type": "object" + } + ] + }, + "BankType": { + "type": "string", + "enum": [ + "checking", + "savings" + ] + }, + "BecsBankDebitAdditionalData": { + "type": "object", + "required": [ + "account_number", + "bsb_number" + ], + "properties": { + "account_number": { + "type": "string", + "description": "Partially masked account number for Becs payment method", + "example": "0001****3456" + }, + "bsb_number": { + "type": "string", + "description": "Bank-State-Branch (bsb) number", + "example": "000000" + }, + "bank_account_holder_name": { + "type": "string", + "description": "Bank account's owner name", + "example": "John Doe", + "nullable": true + } + } + }, + "BlikBankRedirectAdditionalData": { + "type": "object", + "properties": { + "blik_code": { + "type": "string", + "example": "3GD9MO", + "nullable": true + } + } + }, "BlocklistDataKind": { "type": "string", "enum": [ @@ -3534,6 +3991,14 @@ { "type": "object", "properties": { + "form_layout": { + "allOf": [ + { + "$ref": "#/components/schemas/UIWidgetFormLayout" + } + ], + "nullable": true + }, "payout_test_mode": { "type": "boolean", "description": "Allows for removing any validations / pre-requisites which are necessary in a production environment", @@ -4303,6 +4768,21 @@ } ] }, + "CardRedirectResponse": { + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/CardRedirectData" + } + ], + "nullable": true + }, + { + "type": "object" + } + ] + }, "CardResponse": { "type": "object", "properties": { @@ -4376,6 +4856,34 @@ } } }, + "CardTokenAdditionalData": { + "type": "object", + "required": [ + "card_holder_name" + ], + "properties": { + "card_holder_name": { + "type": "string", + "description": "The card holder's name", + "example": "John Test" + } + } + }, + "CardTokenResponse": { + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/CardTokenAdditionalData" + } + ], + "nullable": true + }, + { + "type": "object" + } + ] + }, "CashappQr": { "type": "object" }, @@ -4471,6 +4979,7 @@ "cryptopay", "cybersource", "datatrans", + "deutschebank", "dlocal", "ebanx", "fiserv", @@ -4492,6 +5001,7 @@ "nexinets", "nmi", "noon", + "novalnet", "nuvei", "opennode", "paybox", @@ -4508,6 +5018,7 @@ "square", "stax", "stripe", + "taxjar", "threedsecureio", "trustpay", "tsys", @@ -4622,7 +5133,8 @@ "non_banking_finance", "payout_processor", "payment_method_auth", - "authentication_processor" + "authentication_processor", + "tax_processor" ] }, "ConnectorVolumeSplit": { @@ -4989,6 +5501,21 @@ } } }, + "CryptoResponse": { + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/CryptoData" + } + ], + "nullable": true + }, + { + "type": "object" + } + ] + }, "Currency": { "type": "string", "description": "The three letter ISO currency code in uppercase. Eg: 'USD' for the United States Dollar.", @@ -6574,6 +7101,24 @@ "enum": [ "user_cnpj" ] + }, + { + "type": "string", + "enum": [ + "user_iban" + ] + }, + { + "type": "string", + "enum": [ + "browser_language" + ] + }, + { + "type": "string", + "enum": [ + "browser_ip" + ] } ], "description": "Possible field type of required fields in payment_method_data" @@ -6742,6 +7287,32 @@ } } }, + "GiftCardAdditionalData": { + "oneOf": [ + { + "type": "object", + "required": [ + "givex" + ], + "properties": { + "givex": { + "$ref": "#/components/schemas/GivexGiftCardAdditionalData" + } + } + }, + { + "type": "object", + "required": [ + "pay_safe_card" + ], + "properties": { + "pay_safe_card": { + "type": "object" + } + } + } + ] + }, "GiftCardData": { "oneOf": [ { @@ -6785,6 +7356,57 @@ } } }, + "GiftCardResponse": { + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/GiftCardAdditionalData" + } + ], + "nullable": true + }, + { + "type": "object" + } + ] + }, + "GiropayBankRedirectAdditionalData": { + "type": "object", + "properties": { + "bic": { + "type": "string", + "description": "Masked bank account bic code", + "nullable": true + }, + "iban": { + "type": "string", + "description": "Partially masked international bank account number (iban) for SEPA", + "nullable": true + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/CountryAlpha2" + } + ], + "nullable": true + } + } + }, + "GivexGiftCardAdditionalData": { + "type": "object", + "required": [ + "last4" + ], + "properties": { + "last4": { + "type": "string", + "description": "Last 4 digits of the gift card number", + "example": "4242" + } + } + }, "GoPayRedirection": { "type": "object" }, @@ -7628,6 +8250,17 @@ } } }, + "LocalBankTransferAdditionalData": { + "type": "object", + "properties": { + "bank_code": { + "type": "string", + "description": "Partially masked bank code", + "example": "**** OA2312", + "nullable": true + } + } + }, "MandateAmountData": { "type": "object", "required": [ @@ -9273,6 +9906,21 @@ } ] }, + "OpenBankingResponse": { + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/OpenBankingData" + } + ], + "nullable": true + }, + { + "type": "object" + } + ] + }, "OpenBankingSessionToken": { "type": "object", "required": [ @@ -9341,6 +9989,11 @@ } ], "nullable": true + }, + "product_tax_code": { + "type": "string", + "description": "The tax code for the product", + "nullable": true } } }, @@ -9406,6 +10059,11 @@ } ], "nullable": true + }, + "product_tax_code": { + "type": "string", + "description": "The tax code for the product", + "nullable": true } } }, @@ -10148,7 +10806,10 @@ "nullable": true }, "transaction_details": { - "type": "string", + "type": "array", + "items": { + "$ref": "#/components/schemas/PaymentLinkTransactionDetails" + }, "description": "Dynamic details related to merchant to be rendered in payment link", "nullable": true } @@ -10200,7 +10861,10 @@ "nullable": true }, "transaction_details": { - "type": "object", + "type": "array", + "items": { + "$ref": "#/components/schemas/PaymentLinkTransactionDetails" + }, "description": "Dynamic details related to merchant to be rendered in payment link", "nullable": true } @@ -10251,6 +10915,35 @@ "expired" ] }, + "PaymentLinkTransactionDetails": { + "type": "object", + "required": [ + "key", + "value" + ], + "properties": { + "key": { + "type": "string", + "description": "Key for the transaction details", + "example": "Policy-Number", + "maxLength": 255 + }, + "value": { + "type": "string", + "description": "Value for the transaction details", + "example": "297472368473924", + "maxLength": 255 + }, + "ui_configuration": { + "allOf": [ + { + "$ref": "#/components/schemas/TransactionDetailsUiConfiguration" + } + ], + "nullable": true + } + } + }, "PaymentListConstraints": { "type": "object", "properties": { @@ -10813,7 +11506,7 @@ ], "properties": { "bank_transfer": { - "type": "object" + "$ref": "#/components/schemas/BankTransferResponse" } } }, @@ -10846,7 +11539,7 @@ ], "properties": { "bank_redirect": { - "type": "object" + "$ref": "#/components/schemas/BankRedirectResponse" } } }, @@ -10857,7 +11550,7 @@ ], "properties": { "crypto": { - "type": "object" + "$ref": "#/components/schemas/CryptoResponse" } } }, @@ -10868,7 +11561,7 @@ ], "properties": { "bank_debit": { - "type": "object" + "$ref": "#/components/schemas/BankDebitResponse" } } }, @@ -10901,7 +11594,7 @@ ], "properties": { "real_time_payment": { - "type": "object" + "$ref": "#/components/schemas/RealTimePaymentDataResponse" } } }, @@ -10912,7 +11605,7 @@ ], "properties": { "upi": { - "type": "object" + "$ref": "#/components/schemas/UpiResponse" } } }, @@ -10923,7 +11616,7 @@ ], "properties": { "voucher": { - "type": "object" + "$ref": "#/components/schemas/VoucherResponse" } } }, @@ -10934,7 +11627,7 @@ ], "properties": { "gift_card": { - "type": "object" + "$ref": "#/components/schemas/GiftCardResponse" } } }, @@ -10945,7 +11638,7 @@ ], "properties": { "card_redirect": { - "type": "object" + "$ref": "#/components/schemas/CardRedirectResponse" } } }, @@ -10956,7 +11649,7 @@ ], "properties": { "card_token": { - "type": "object" + "$ref": "#/components/schemas/CardTokenResponse" } } }, @@ -10967,7 +11660,7 @@ ], "properties": { "open_banking": { - "type": "object" + "$ref": "#/components/schemas/OpenBankingResponse" } } } @@ -11061,7 +11754,8 @@ "payment_methods", "mandate_payment", "show_surcharge_breakup_screen", - "request_external_three_ds_authentication" + "request_external_three_ds_authentication", + "is_tax_calculation_enabled" ], "properties": { "redirect_url": { @@ -11123,6 +11817,10 @@ "type": "boolean", "description": "flag that indicates whether to collect billing details from wallets or from the customer", "nullable": true + }, + "is_tax_calculation_enabled": { + "type": "boolean", + "description": "flag that indicates whether to calculate tax on the order amount" } } }, @@ -11565,7 +12263,7 @@ "amount": { "type": "integer", "format": "int64", - "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and ¥100 since ¥ is a zero-decimal currency", + "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies)", "example": 6540, "nullable": true, "minimum": 0 @@ -11585,6 +12283,13 @@ "example": 6540, "nullable": true }, + "shipping_cost": { + "type": "integer", + "format": "int64", + "description": "The shipping cost for the payment. This is required for tax calculation in some regions.", + "example": 6540, + "nullable": true + }, "payment_id": { "type": "string", "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant. The value for this field can be specified in the request, it will be auto generated otherwise and returned in the API response.", @@ -11904,6 +12609,11 @@ "example": "Custom_Order_id_123", "nullable": true, "maxLength": 255 + }, + "skip_external_tax_calculation": { + "type": "boolean", + "description": "Whether to calculate tax for this payment intent", + "nullable": true } } }, @@ -11917,7 +12627,7 @@ "amount": { "type": "integer", "format": "int64", - "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and ¥100 since ¥ is a zero-decimal currency", + "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies)", "minimum": 0 }, "currency": { @@ -11930,6 +12640,13 @@ "example": 6540, "nullable": true }, + "shipping_cost": { + "type": "integer", + "format": "int64", + "description": "The shipping cost for the payment. This is required for tax calculation in some regions.", + "example": 6540, + "nullable": true + }, "payment_id": { "type": "string", "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant. The value for this field can be specified in the request, it will be auto generated otherwise and returned in the API response.", @@ -12262,6 +12979,11 @@ "example": "Custom_Order_id_123", "nullable": true, "maxLength": 255 + }, + "skip_external_tax_calculation": { + "type": "boolean", + "description": "Whether to calculate tax for this payment intent", + "nullable": true } } }, @@ -12309,7 +13031,7 @@ "net_amount": { "type": "integer", "format": "int64", - "description": "The payment net amount. net_amount = amount + surcharge_details.surcharge_amount + surcharge_details.tax_amount,\nIf no surcharge_details, net_amount = amount", + "description": "The payment net amount. net_amount = amount + surcharge_details.surcharge_amount + surcharge_details.tax_amount + shipping_cost + order_tax_amount,\nIf no surcharge_details, shipping_cost, order_tax_amount, net_amount = amount", "example": 6540 }, "amount_capturable": { @@ -12781,6 +13503,66 @@ "example": "Custom_Order_id_123", "nullable": true, "maxLength": 255 + }, + "order_tax_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + } + } + }, + "PaymentsDynamicTaxCalculationRequest": { + "type": "object", + "required": [ + "shipping", + "client_secret", + "payment_method_type" + ], + "properties": { + "shipping": { + "$ref": "#/components/schemas/Address" + }, + "client_secret": { + "type": "string", + "description": "Client Secret" + }, + "payment_method_type": { + "$ref": "#/components/schemas/PaymentMethodType" + } + } + }, + "PaymentsDynamicTaxCalculationResponse": { + "type": "object", + "required": [ + "payment_id", + "net_amount" + ], + "properties": { + "payment_id": { + "type": "string", + "description": "The identifier for the payment" + }, + "net_amount": { + "$ref": "#/components/schemas/MinorUnit" + }, + "order_tax_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "shipping_cost": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true } } }, @@ -12883,7 +13665,7 @@ "amount": { "type": "integer", "format": "int64", - "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and ¥100 since ¥ is a zero-decimal currency", + "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies)", "example": 6540, "nullable": true, "minimum": 0 @@ -12903,6 +13685,13 @@ "example": 6540, "nullable": true }, + "shipping_cost": { + "type": "integer", + "format": "int64", + "description": "The shipping cost for the payment. This is required for tax calculation in some regions.", + "example": 6540, + "nullable": true + }, "payment_id": { "type": "string", "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant. The value for this field can be specified in the request, it will be auto generated otherwise and returned in the API response.", @@ -13314,6 +14103,11 @@ "example": "Custom_Order_id_123", "nullable": true, "maxLength": 255 + }, + "skip_external_tax_calculation": { + "type": "boolean", + "description": "Whether to calculate tax for this payment intent", + "nullable": true } }, "additionalProperties": false @@ -13362,7 +14156,7 @@ "net_amount": { "type": "integer", "format": "int64", - "description": "The payment net amount. net_amount = amount + surcharge_details.surcharge_amount + surcharge_details.tax_amount,\nIf no surcharge_details, net_amount = amount", + "description": "The payment net amount. net_amount = amount + surcharge_details.surcharge_amount + surcharge_details.tax_amount + shipping_cost + order_tax_amount,\nIf no surcharge_details, shipping_cost, order_tax_amount, net_amount = amount", "example": 6540 }, "amount_capturable": { @@ -13859,6 +14653,14 @@ "example": "Custom_Order_id_123", "nullable": true, "maxLength": 255 + }, + "order_tax_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true } } }, @@ -13981,7 +14783,7 @@ "amount": { "type": "integer", "format": "int64", - "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and ¥100 since ¥ is a zero-decimal currency", + "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies)", "example": 6540, "nullable": true, "minimum": 0 @@ -14001,6 +14803,13 @@ "example": 6540, "nullable": true }, + "shipping_cost": { + "type": "integer", + "format": "int64", + "description": "The shipping cost for the payment. This is required for tax calculation in some regions.", + "example": 6540, + "nullable": true + }, "payment_id": { "type": "string", "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant. The value for this field can be specified in the request, it will be auto generated otherwise and returned in the API response.", @@ -14315,6 +15124,11 @@ "example": "Custom_Order_id_123", "nullable": true, "maxLength": 255 + }, + "skip_external_tax_calculation": { + "type": "boolean", + "description": "Whether to calculate tax for this payment intent", + "nullable": true } } }, @@ -14444,6 +15258,14 @@ "example": "[{\"payment_method\": \"bank_transfer\", \"payment_method_types\": [\"ach\", \"bacs\"]}]", "nullable": true }, + "form_layout": { + "allOf": [ + { + "$ref": "#/components/schemas/UIWidgetFormLayout" + } + ], + "nullable": true + }, "test_mode": { "type": "boolean", "description": "`test_mode` allows for opening payout links without any restrictions. This removes\n- domain name validations\n- check for making sure link is accessed within an iframe", @@ -14747,8 +15569,11 @@ "nullable": true }, "billing": { - "type": "object", - "description": "The billing address for the payout", + "allOf": [ + { + "$ref": "#/components/schemas/Address" + } + ], "nullable": true }, "auto_fulfill": { @@ -15387,6 +16212,29 @@ } } }, + "PixBankTransferAdditionalData": { + "type": "object", + "properties": { + "pix_key": { + "type": "string", + "description": "Partially masked unique key for pix transfer", + "example": "a1f4102e ****** 6fa48899c1d1", + "nullable": true + }, + "cpf": { + "type": "string", + "description": "Partially masked CPF - CPF is a Brazilian tax identification number", + "example": "**** 124689", + "nullable": true + }, + "cnpj": { + "type": "string", + "description": "Partially masked CNPJ - CNPJ is a Brazilian company tax identification number", + "example": "**** 417312", + "nullable": true + } + } + }, "PollConfigResponse": { "type": "object", "required": [ @@ -15566,6 +16414,21 @@ } ] }, + "RealTimePaymentDataResponse": { + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/RealTimePaymentData" + } + ], + "nullable": true + }, + { + "type": "object" + } + ] + }, "ReceiverDetails": { "type": "object", "required": [ @@ -15675,6 +16538,22 @@ } } }, + "RefundAggregateResponse": { + "type": "object", + "required": [ + "status_with_count" + ], + "properties": { + "status_with_count": { + "type": "object", + "description": "The list of refund status with their count", + "additionalProperties": { + "type": "integer", + "format": "int64" + } + } + } + }, "RefundListRequest": { "allOf": [ { @@ -16335,6 +17214,7 @@ "cryptopay", "cybersource", "datatrans", + "deutschebank", "dlocal", "ebanx", "fiserv", @@ -16354,6 +17234,7 @@ "nexinets", "nmi", "noon", + "novalnet", "nuvei", "opennode", "paybox", @@ -16739,6 +17620,25 @@ } } }, + "SepaBankDebitAdditionalData": { + "type": "object", + "required": [ + "iban" + ], + "properties": { + "iban": { + "type": "string", + "description": "Partially masked international bank account number (iban) for SEPA", + "example": "DE8937******013000" + }, + "bank_account_holder_name": { + "type": "string", + "description": "Bank account's owner name", + "example": "John Doe", + "nullable": true + } + } + }, "SepaBankTransfer": { "type": "object", "required": [ @@ -17315,6 +18215,32 @@ "TouchNGoRedirection": { "type": "object" }, + "TransactionDetailsUiConfiguration": { + "type": "object", + "properties": { + "position": { + "type": "integer", + "format": "int32", + "description": "Position of the key-value pair in the UI", + "example": 5, + "nullable": true + }, + "is_key_bold": { + "type": "boolean", + "description": "Whether the key should be bold", + "default": false, + "example": true, + "nullable": true + }, + "is_value_bold": { + "type": "boolean", + "description": "Whether the value should be bold", + "default": false, + "example": true, + "nullable": true + } + } + }, "TransactionStatus": { "type": "string", "description": "Indicates the transaction status", @@ -17336,6 +18262,13 @@ "payout" ] }, + "UIWidgetFormLayout": { + "type": "string", + "enum": [ + "tabs", + "journey" + ] + }, "UpdateApiKeyRequest": { "type": "object", "description": "The request body for updating an API Key.", @@ -17365,6 +18298,43 @@ }, "additionalProperties": false }, + "UpiAdditionalData": { + "oneOf": [ + { + "type": "object", + "required": [ + "upi_collect" + ], + "properties": { + "upi_collect": { + "$ref": "#/components/schemas/UpiCollectAdditionalData" + } + } + }, + { + "type": "object", + "required": [ + "upi_intent" + ], + "properties": { + "upi_intent": { + "$ref": "#/components/schemas/UpiIntentData" + } + } + } + ] + }, + "UpiCollectAdditionalData": { + "type": "object", + "properties": { + "vpa_id": { + "type": "string", + "description": "Masked VPA ID", + "example": "ab********@okhdfcbank", + "nullable": true + } + } + }, "UpiCollectData": { "type": "object", "properties": { @@ -17404,6 +18374,21 @@ "UpiIntentData": { "type": "object" }, + "UpiResponse": { + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/UpiAdditionalData" + } + ], + "nullable": true + }, + { + "type": "object" + } + ] + }, "ValueType": { "oneOf": [ { @@ -17698,6 +18683,21 @@ } ] }, + "VoucherResponse": { + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/VoucherData" + } + ], + "nullable": true + }, + { + "type": "object" + } + ] + }, "Wallet": { "oneOf": [ { diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 24bb66598ef6..e6cb4ede374d 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -5066,6 +5066,61 @@ "propertyName": "type" } }, + "AchBankDebitAdditionalData": { + "type": "object", + "required": [ + "account_number", + "routing_number" + ], + "properties": { + "account_number": { + "type": "string", + "description": "Partially masked account number for ach bank debit payment", + "example": "0001****3456" + }, + "routing_number": { + "type": "string", + "description": "Partially masked routing number for ach bank debit payment", + "example": "110***000" + }, + "card_holder_name": { + "type": "string", + "description": "Card holder's name", + "example": "John Doe", + "nullable": true + }, + "bank_account_holder_name": { + "type": "string", + "description": "Bank account's owner name", + "example": "John Doe", + "nullable": true + }, + "bank_name": { + "allOf": [ + { + "$ref": "#/components/schemas/BankNames" + } + ], + "nullable": true + }, + "bank_type": { + "allOf": [ + { + "$ref": "#/components/schemas/BankType" + } + ], + "nullable": true + }, + "bank_holder_type": { + "allOf": [ + { + "$ref": "#/components/schemas/BankHolderType" + } + ], + "nullable": true + } + } + }, "AchBankTransfer": { "type": "object", "required": [ @@ -5644,6 +5699,31 @@ "unresolved" ] }, + "BacsBankDebitAdditionalData": { + "type": "object", + "required": [ + "account_number", + "sort_code" + ], + "properties": { + "account_number": { + "type": "string", + "description": "Partially masked account number for Bacs payment method", + "example": "0001****3456" + }, + "sort_code": { + "type": "string", + "description": "Partially masked sort code for Bacs payment method", + "example": "108800" + }, + "bank_account_holder_name": { + "type": "string", + "description": "Bank account's owner name", + "example": "John Doe", + "nullable": true + } + } + }, "BacsBankTransfer": { "type": "object", "required": [ @@ -5705,6 +5785,35 @@ } } }, + "BancontactBankRedirectAdditionalData": { + "type": "object", + "properties": { + "last4": { + "type": "string", + "description": "Last 4 digits of the card number", + "example": "4242", + "nullable": true + }, + "card_exp_month": { + "type": "string", + "description": "The card's expiry month", + "example": "12", + "nullable": true + }, + "card_exp_year": { + "type": "string", + "description": "The card's expiry year", + "example": "24", + "nullable": true + }, + "card_holder_name": { + "type": "string", + "description": "The card holder's name", + "example": "John Test", + "nullable": true + } + } + }, "Bank": { "oneOf": [ { @@ -5721,6 +5830,54 @@ } ] }, + "BankDebitAdditionalData": { + "oneOf": [ + { + "type": "object", + "required": [ + "ach" + ], + "properties": { + "ach": { + "$ref": "#/components/schemas/AchBankDebitAdditionalData" + } + } + }, + { + "type": "object", + "required": [ + "bacs" + ], + "properties": { + "bacs": { + "$ref": "#/components/schemas/BacsBankDebitAdditionalData" + } + } + }, + { + "type": "object", + "required": [ + "becs" + ], + "properties": { + "becs": { + "$ref": "#/components/schemas/BecsBankDebitAdditionalData" + } + } + }, + { + "type": "object", + "required": [ + "sepa" + ], + "properties": { + "sepa": { + "$ref": "#/components/schemas/SepaBankDebitAdditionalData" + } + } + } + ] + }, "BankDebitBilling": { "type": "object", "properties": { @@ -5928,6 +6085,28 @@ } ] }, + "BankDebitResponse": { + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/BankDebitAdditionalData" + } + ], + "nullable": true + }, + { + "type": "object" + } + ] + }, + "BankHolderType": { + "type": "string", + "enum": [ + "personal", + "business" + ] + }, "BankNames": { "type": "string", "description": "Name of banks supported by Hyperswitch", @@ -5938,6 +6117,7 @@ "alliance_bank", "am_bank", "bank_of_america", + "bank_of_china", "bank_islam", "bank_muamalat", "bank_rakyat", @@ -6500,30 +6680,250 @@ { "type": "object", "required": [ - "online_banking_thailand" + "online_banking_thailand" + ], + "properties": { + "online_banking_thailand": { + "type": "object", + "required": [ + "issuer" + ], + "properties": { + "issuer": { + "$ref": "#/components/schemas/BankNames" + } + } + } + } + }, + { + "type": "object", + "required": [ + "local_bank_redirect" + ], + "properties": { + "local_bank_redirect": { + "type": "object" + } + } + } + ] + }, + "BankRedirectDetails": { + "oneOf": [ + { + "type": "object", + "required": [ + "BancontactCard" + ], + "properties": { + "BancontactCard": { + "$ref": "#/components/schemas/BancontactBankRedirectAdditionalData" + } + } + }, + { + "type": "object", + "required": [ + "Blik" + ], + "properties": { + "Blik": { + "$ref": "#/components/schemas/BlikBankRedirectAdditionalData" + } + } + }, + { + "type": "object", + "required": [ + "Giropay" + ], + "properties": { + "Giropay": { + "$ref": "#/components/schemas/GiropayBankRedirectAdditionalData" + } + } + } + ] + }, + "BankRedirectResponse": { + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/BankRedirectDetails" + } + ], + "nullable": true + }, + { + "type": "object", + "properties": { + "bank_name": { + "allOf": [ + { + "$ref": "#/components/schemas/BankNames" + } + ], + "nullable": true + } + } + } + ] + }, + "BankTransferAdditionalData": { + "oneOf": [ + { + "type": "object", + "required": [ + "ach" + ], + "properties": { + "ach": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "sepa" + ], + "properties": { + "sepa": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "bacs" + ], + "properties": { + "bacs": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "multibanco" + ], + "properties": { + "multibanco": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "permata" + ], + "properties": { + "permata": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "bca" + ], + "properties": { + "bca": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "bni_va" + ], + "properties": { + "bni_va": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "bri_va" + ], + "properties": { + "bri_va": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "cimb_va" + ], + "properties": { + "cimb_va": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "danamon_va" + ], + "properties": { + "danamon_va": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "mandiri_va" + ], + "properties": { + "mandiri_va": { + "type": "object" + } + } + }, + { + "type": "object", + "required": [ + "pix" + ], + "properties": { + "pix": { + "$ref": "#/components/schemas/PixBankTransferAdditionalData" + } + } + }, + { + "type": "object", + "required": [ + "pse" ], "properties": { - "online_banking_thailand": { - "type": "object", - "required": [ - "issuer" - ], - "properties": { - "issuer": { - "$ref": "#/components/schemas/BankNames" - } - } + "pse": { + "type": "object" } } }, { "type": "object", "required": [ - "local_bank_redirect" + "local_bank_transfer" ], "properties": { - "local_bank_redirect": { - "type": "object" + "local_bank_transfer": { + "$ref": "#/components/schemas/LocalBankTransferAdditionalData" } } } @@ -6908,6 +7308,63 @@ } ] }, + "BankTransferResponse": { + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/BankTransferAdditionalData" + } + ], + "nullable": true + }, + { + "type": "object" + } + ] + }, + "BankType": { + "type": "string", + "enum": [ + "checking", + "savings" + ] + }, + "BecsBankDebitAdditionalData": { + "type": "object", + "required": [ + "account_number", + "bsb_number" + ], + "properties": { + "account_number": { + "type": "string", + "description": "Partially masked account number for Becs payment method", + "example": "0001****3456" + }, + "bsb_number": { + "type": "string", + "description": "Bank-State-Branch (bsb) number", + "example": "000000" + }, + "bank_account_holder_name": { + "type": "string", + "description": "Bank account's owner name", + "example": "John Doe", + "nullable": true + } + } + }, + "BlikBankRedirectAdditionalData": { + "type": "object", + "properties": { + "blik_code": { + "type": "string", + "example": "3GD9MO", + "nullable": true + } + } + }, "BlocklistDataKind": { "type": "string", "enum": [ @@ -7164,6 +7621,14 @@ { "type": "object", "properties": { + "form_layout": { + "allOf": [ + { + "$ref": "#/components/schemas/UIWidgetFormLayout" + } + ], + "nullable": true + }, "payout_test_mode": { "type": "boolean", "description": "Allows for removing any validations / pre-requisites which are necessary in a production environment", @@ -7950,6 +8415,21 @@ } ] }, + "CardRedirectResponse": { + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/CardRedirectData" + } + ], + "nullable": true + }, + { + "type": "object" + } + ] + }, "CardResponse": { "type": "object", "properties": { @@ -8023,6 +8503,34 @@ } } }, + "CardTokenAdditionalData": { + "type": "object", + "required": [ + "card_holder_name" + ], + "properties": { + "card_holder_name": { + "type": "string", + "description": "The card holder's name", + "example": "John Test" + } + } + }, + "CardTokenResponse": { + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/CardTokenAdditionalData" + } + ], + "nullable": true + }, + { + "type": "object" + } + ] + }, "CashappQr": { "type": "object" }, @@ -8118,6 +8626,7 @@ "cryptopay", "cybersource", "datatrans", + "deutschebank", "dlocal", "ebanx", "fiserv", @@ -8139,6 +8648,7 @@ "nexinets", "nmi", "noon", + "novalnet", "nuvei", "opennode", "paybox", @@ -8155,6 +8665,7 @@ "square", "stax", "stripe", + "taxjar", "threedsecureio", "trustpay", "tsys", @@ -8269,7 +8780,8 @@ "non_banking_finance", "payout_processor", "payment_method_auth", - "authentication_processor" + "authentication_processor", + "tax_processor" ] }, "ConnectorVolumeSplit": { @@ -8636,6 +9148,21 @@ } } }, + "CryptoResponse": { + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/CryptoData" + } + ], + "nullable": true + }, + { + "type": "object" + } + ] + }, "Currency": { "type": "string", "description": "The three letter ISO currency code in uppercase. Eg: 'USD' for the United States Dollar.", @@ -10193,6 +10720,24 @@ "enum": [ "user_cnpj" ] + }, + { + "type": "string", + "enum": [ + "user_iban" + ] + }, + { + "type": "string", + "enum": [ + "browser_language" + ] + }, + { + "type": "string", + "enum": [ + "browser_ip" + ] } ], "description": "Possible field type of required fields in payment_method_data" @@ -10361,6 +10906,32 @@ } } }, + "GiftCardAdditionalData": { + "oneOf": [ + { + "type": "object", + "required": [ + "givex" + ], + "properties": { + "givex": { + "$ref": "#/components/schemas/GivexGiftCardAdditionalData" + } + } + }, + { + "type": "object", + "required": [ + "pay_safe_card" + ], + "properties": { + "pay_safe_card": { + "type": "object" + } + } + } + ] + }, "GiftCardData": { "oneOf": [ { @@ -10404,6 +10975,57 @@ } } }, + "GiftCardResponse": { + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/GiftCardAdditionalData" + } + ], + "nullable": true + }, + { + "type": "object" + } + ] + }, + "GiropayBankRedirectAdditionalData": { + "type": "object", + "properties": { + "bic": { + "type": "string", + "description": "Masked bank account bic code", + "nullable": true + }, + "iban": { + "type": "string", + "description": "Partially masked international bank account number (iban) for SEPA", + "nullable": true + }, + "country": { + "allOf": [ + { + "$ref": "#/components/schemas/CountryAlpha2" + } + ], + "nullable": true + } + } + }, + "GivexGiftCardAdditionalData": { + "type": "object", + "required": [ + "last4" + ], + "properties": { + "last4": { + "type": "string", + "description": "Last 4 digits of the gift card number", + "example": "4242" + } + } + }, "GoPayRedirection": { "type": "object" }, @@ -11247,6 +11869,17 @@ } } }, + "LocalBankTransferAdditionalData": { + "type": "object", + "properties": { + "bank_code": { + "type": "string", + "description": "Partially masked bank code", + "example": "**** OA2312", + "nullable": true + } + } + }, "MandateAmountData": { "type": "object", "required": [ @@ -13278,6 +13911,21 @@ } ] }, + "OpenBankingResponse": { + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/OpenBankingData" + } + ], + "nullable": true + }, + { + "type": "object" + } + ] + }, "OpenBankingSessionToken": { "type": "object", "required": [ @@ -13346,6 +13994,11 @@ } ], "nullable": true + }, + "product_tax_code": { + "type": "string", + "description": "The tax code for the product", + "nullable": true } } }, @@ -13411,6 +14064,11 @@ } ], "nullable": true + }, + "product_tax_code": { + "type": "string", + "description": "The tax code for the product", + "nullable": true } } }, @@ -14146,7 +14804,10 @@ "nullable": true }, "transaction_details": { - "type": "string", + "type": "array", + "items": { + "$ref": "#/components/schemas/PaymentLinkTransactionDetails" + }, "description": "Dynamic details related to merchant to be rendered in payment link", "nullable": true } @@ -14198,7 +14859,10 @@ "nullable": true }, "transaction_details": { - "type": "object", + "type": "array", + "items": { + "$ref": "#/components/schemas/PaymentLinkTransactionDetails" + }, "description": "Dynamic details related to merchant to be rendered in payment link", "nullable": true } @@ -14249,6 +14913,35 @@ "expired" ] }, + "PaymentLinkTransactionDetails": { + "type": "object", + "required": [ + "key", + "value" + ], + "properties": { + "key": { + "type": "string", + "description": "Key for the transaction details", + "example": "Policy-Number", + "maxLength": 255 + }, + "value": { + "type": "string", + "description": "Value for the transaction details", + "example": "297472368473924", + "maxLength": 255 + }, + "ui_configuration": { + "allOf": [ + { + "$ref": "#/components/schemas/TransactionDetailsUiConfiguration" + } + ], + "nullable": true + } + } + }, "PaymentListConstraints": { "type": "object", "properties": { @@ -14811,7 +15504,7 @@ ], "properties": { "bank_transfer": { - "type": "object" + "$ref": "#/components/schemas/BankTransferResponse" } } }, @@ -14844,7 +15537,7 @@ ], "properties": { "bank_redirect": { - "type": "object" + "$ref": "#/components/schemas/BankRedirectResponse" } } }, @@ -14855,7 +15548,7 @@ ], "properties": { "crypto": { - "type": "object" + "$ref": "#/components/schemas/CryptoResponse" } } }, @@ -14866,7 +15559,7 @@ ], "properties": { "bank_debit": { - "type": "object" + "$ref": "#/components/schemas/BankDebitResponse" } } }, @@ -14899,7 +15592,7 @@ ], "properties": { "real_time_payment": { - "type": "object" + "$ref": "#/components/schemas/RealTimePaymentDataResponse" } } }, @@ -14910,7 +15603,7 @@ ], "properties": { "upi": { - "type": "object" + "$ref": "#/components/schemas/UpiResponse" } } }, @@ -14921,7 +15614,7 @@ ], "properties": { "voucher": { - "type": "object" + "$ref": "#/components/schemas/VoucherResponse" } } }, @@ -14932,7 +15625,7 @@ ], "properties": { "gift_card": { - "type": "object" + "$ref": "#/components/schemas/GiftCardResponse" } } }, @@ -14943,7 +15636,7 @@ ], "properties": { "card_redirect": { - "type": "object" + "$ref": "#/components/schemas/CardRedirectResponse" } } }, @@ -14954,7 +15647,7 @@ ], "properties": { "card_token": { - "type": "object" + "$ref": "#/components/schemas/CardTokenResponse" } } }, @@ -14965,7 +15658,7 @@ ], "properties": { "open_banking": { - "type": "object" + "$ref": "#/components/schemas/OpenBankingResponse" } } } @@ -15059,7 +15752,8 @@ "payment_methods", "mandate_payment", "show_surcharge_breakup_screen", - "request_external_three_ds_authentication" + "request_external_three_ds_authentication", + "is_tax_calculation_enabled" ], "properties": { "redirect_url": { @@ -15121,6 +15815,10 @@ "type": "boolean", "description": "flag that indicates whether to collect billing details from wallets or from the customer", "nullable": true + }, + "is_tax_calculation_enabled": { + "type": "boolean", + "description": "flag that indicates whether to calculate tax on the order amount" } } }, @@ -15563,7 +16261,7 @@ "amount": { "type": "integer", "format": "int64", - "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and ¥100 since ¥ is a zero-decimal currency", + "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies)", "example": 6540, "nullable": true, "minimum": 0 @@ -15583,6 +16281,13 @@ "example": 6540, "nullable": true }, + "shipping_cost": { + "type": "integer", + "format": "int64", + "description": "The shipping cost for the payment. This is required for tax calculation in some regions.", + "example": 6540, + "nullable": true + }, "payment_id": { "type": "string", "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant. The value for this field can be specified in the request, it will be auto generated otherwise and returned in the API response.", @@ -15902,6 +16607,11 @@ "example": "Custom_Order_id_123", "nullable": true, "maxLength": 255 + }, + "skip_external_tax_calculation": { + "type": "boolean", + "description": "Whether to calculate tax for this payment intent", + "nullable": true } } }, @@ -15915,7 +16625,7 @@ "amount": { "type": "integer", "format": "int64", - "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and ¥100 since ¥ is a zero-decimal currency", + "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies)", "minimum": 0 }, "currency": { @@ -15928,6 +16638,13 @@ "example": 6540, "nullable": true }, + "shipping_cost": { + "type": "integer", + "format": "int64", + "description": "The shipping cost for the payment. This is required for tax calculation in some regions.", + "example": 6540, + "nullable": true + }, "payment_id": { "type": "string", "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant. The value for this field can be specified in the request, it will be auto generated otherwise and returned in the API response.", @@ -16260,6 +16977,11 @@ "example": "Custom_Order_id_123", "nullable": true, "maxLength": 255 + }, + "skip_external_tax_calculation": { + "type": "boolean", + "description": "Whether to calculate tax for this payment intent", + "nullable": true } } }, @@ -16307,7 +17029,7 @@ "net_amount": { "type": "integer", "format": "int64", - "description": "The payment net amount. net_amount = amount + surcharge_details.surcharge_amount + surcharge_details.tax_amount,\nIf no surcharge_details, net_amount = amount", + "description": "The payment net amount. net_amount = amount + surcharge_details.surcharge_amount + surcharge_details.tax_amount + shipping_cost + order_tax_amount,\nIf no surcharge_details, shipping_cost, order_tax_amount, net_amount = amount", "example": 6540 }, "amount_capturable": { @@ -16779,6 +17501,66 @@ "example": "Custom_Order_id_123", "nullable": true, "maxLength": 255 + }, + "order_tax_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + } + } + }, + "PaymentsDynamicTaxCalculationRequest": { + "type": "object", + "required": [ + "shipping", + "client_secret", + "payment_method_type" + ], + "properties": { + "shipping": { + "$ref": "#/components/schemas/Address" + }, + "client_secret": { + "type": "string", + "description": "Client Secret" + }, + "payment_method_type": { + "$ref": "#/components/schemas/PaymentMethodType" + } + } + }, + "PaymentsDynamicTaxCalculationResponse": { + "type": "object", + "required": [ + "payment_id", + "net_amount" + ], + "properties": { + "payment_id": { + "type": "string", + "description": "The identifier for the payment" + }, + "net_amount": { + "$ref": "#/components/schemas/MinorUnit" + }, + "order_tax_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "shipping_cost": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true } } }, @@ -16881,7 +17663,7 @@ "amount": { "type": "integer", "format": "int64", - "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and ¥100 since ¥ is a zero-decimal currency", + "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies)", "example": 6540, "nullable": true, "minimum": 0 @@ -16901,6 +17683,13 @@ "example": 6540, "nullable": true }, + "shipping_cost": { + "type": "integer", + "format": "int64", + "description": "The shipping cost for the payment. This is required for tax calculation in some regions.", + "example": 6540, + "nullable": true + }, "payment_id": { "type": "string", "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant. The value for this field can be specified in the request, it will be auto generated otherwise and returned in the API response.", @@ -17312,6 +18101,11 @@ "example": "Custom_Order_id_123", "nullable": true, "maxLength": 255 + }, + "skip_external_tax_calculation": { + "type": "boolean", + "description": "Whether to calculate tax for this payment intent", + "nullable": true } }, "additionalProperties": false @@ -17360,7 +18154,7 @@ "net_amount": { "type": "integer", "format": "int64", - "description": "The payment net amount. net_amount = amount + surcharge_details.surcharge_amount + surcharge_details.tax_amount,\nIf no surcharge_details, net_amount = amount", + "description": "The payment net amount. net_amount = amount + surcharge_details.surcharge_amount + surcharge_details.tax_amount + shipping_cost + order_tax_amount,\nIf no surcharge_details, shipping_cost, order_tax_amount, net_amount = amount", "example": 6540 }, "amount_capturable": { @@ -17857,6 +18651,14 @@ "example": "Custom_Order_id_123", "nullable": true, "maxLength": 255 + }, + "order_tax_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true } } }, @@ -17979,7 +18781,7 @@ "amount": { "type": "integer", "format": "int64", - "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and ¥100 since ¥ is a zero-decimal currency", + "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies)", "example": 6540, "nullable": true, "minimum": 0 @@ -17999,6 +18801,13 @@ "example": 6540, "nullable": true }, + "shipping_cost": { + "type": "integer", + "format": "int64", + "description": "The shipping cost for the payment. This is required for tax calculation in some regions.", + "example": 6540, + "nullable": true + }, "payment_id": { "type": "string", "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant. The value for this field can be specified in the request, it will be auto generated otherwise and returned in the API response.", @@ -18313,6 +19122,11 @@ "example": "Custom_Order_id_123", "nullable": true, "maxLength": 255 + }, + "skip_external_tax_calculation": { + "type": "boolean", + "description": "Whether to calculate tax for this payment intent", + "nullable": true } } }, @@ -18669,6 +19483,14 @@ "example": "[{\"payment_method\": \"bank_transfer\", \"payment_method_types\": [\"ach\", \"bacs\"]}]", "nullable": true }, + "form_layout": { + "allOf": [ + { + "$ref": "#/components/schemas/UIWidgetFormLayout" + } + ], + "nullable": true + }, "test_mode": { "type": "boolean", "description": "`test_mode` allows for opening payout links without any restrictions. This removes\n- domain name validations\n- check for making sure link is accessed within an iframe", @@ -18735,8 +19557,11 @@ "nullable": true }, "billing": { - "type": "object", - "description": "The billing address for the payout", + "allOf": [ + { + "$ref": "#/components/schemas/Address" + } + ], "nullable": true }, "auto_fulfill": { @@ -19825,6 +20650,29 @@ } } }, + "PixBankTransferAdditionalData": { + "type": "object", + "properties": { + "pix_key": { + "type": "string", + "description": "Partially masked unique key for pix transfer", + "example": "a1f4102e ****** 6fa48899c1d1", + "nullable": true + }, + "cpf": { + "type": "string", + "description": "Partially masked CPF - CPF is a Brazilian tax identification number", + "example": "**** 124689", + "nullable": true + }, + "cnpj": { + "type": "string", + "description": "Partially masked CNPJ - CNPJ is a Brazilian company tax identification number", + "example": "**** 417312", + "nullable": true + } + } + }, "PollConfigResponse": { "type": "object", "required": [ @@ -20004,6 +20852,21 @@ } ] }, + "RealTimePaymentDataResponse": { + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/RealTimePaymentData" + } + ], + "nullable": true + }, + { + "type": "object" + } + ] + }, "ReceiverDetails": { "type": "object", "required": [ @@ -20113,6 +20976,22 @@ } } }, + "RefundAggregateResponse": { + "type": "object", + "required": [ + "status_with_count" + ], + "properties": { + "status_with_count": { + "type": "object", + "description": "The list of refund status with their count", + "additionalProperties": { + "type": "integer", + "format": "int64" + } + } + } + }, "RefundListRequest": { "allOf": [ { @@ -20773,6 +21652,7 @@ "cryptopay", "cybersource", "datatrans", + "deutschebank", "dlocal", "ebanx", "fiserv", @@ -20792,6 +21672,7 @@ "nexinets", "nmi", "noon", + "novalnet", "nuvei", "opennode", "paybox", @@ -21168,6 +22049,25 @@ } } }, + "SepaBankDebitAdditionalData": { + "type": "object", + "required": [ + "iban" + ], + "properties": { + "iban": { + "type": "string", + "description": "Partially masked international bank account number (iban) for SEPA", + "example": "DE8937******013000" + }, + "bank_account_holder_name": { + "type": "string", + "description": "Bank account's owner name", + "example": "John Doe", + "nullable": true + } + } + }, "SepaBankTransfer": { "type": "object", "required": [ @@ -21744,6 +22644,32 @@ "TouchNGoRedirection": { "type": "object" }, + "TransactionDetailsUiConfiguration": { + "type": "object", + "properties": { + "position": { + "type": "integer", + "format": "int32", + "description": "Position of the key-value pair in the UI", + "example": 5, + "nullable": true + }, + "is_key_bold": { + "type": "boolean", + "description": "Whether the key should be bold", + "default": false, + "example": true, + "nullable": true + }, + "is_value_bold": { + "type": "boolean", + "description": "Whether the value should be bold", + "default": false, + "example": true, + "nullable": true + } + } + }, "TransactionStatus": { "type": "string", "description": "Indicates the transaction status", @@ -21765,6 +22691,13 @@ "payout" ] }, + "UIWidgetFormLayout": { + "type": "string", + "enum": [ + "tabs", + "journey" + ] + }, "UpdateApiKeyRequest": { "type": "object", "description": "The request body for updating an API Key.", @@ -21794,6 +22727,43 @@ }, "additionalProperties": false }, + "UpiAdditionalData": { + "oneOf": [ + { + "type": "object", + "required": [ + "upi_collect" + ], + "properties": { + "upi_collect": { + "$ref": "#/components/schemas/UpiCollectAdditionalData" + } + } + }, + { + "type": "object", + "required": [ + "upi_intent" + ], + "properties": { + "upi_intent": { + "$ref": "#/components/schemas/UpiIntentData" + } + } + } + ] + }, + "UpiCollectAdditionalData": { + "type": "object", + "properties": { + "vpa_id": { + "type": "string", + "description": "Masked VPA ID", + "example": "ab********@okhdfcbank", + "nullable": true + } + } + }, "UpiCollectData": { "type": "object", "properties": { @@ -21833,6 +22803,21 @@ "UpiIntentData": { "type": "object" }, + "UpiResponse": { + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/UpiAdditionalData" + } + ], + "nullable": true + }, + { + "type": "object" + } + ] + }, "ValueType": { "oneOf": [ { @@ -22127,6 +23112,21 @@ } ] }, + "VoucherResponse": { + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/VoucherData" + } + ], + "nullable": true + }, + { + "type": "object" + } + ] + }, "Wallet": { "oneOf": [ { diff --git a/config/config.example.toml b/config/config.example.toml index c02402c04713..7059626f8cfd 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -133,7 +133,6 @@ bg_metrics_collection_interval_in_secs = 15 # Interval for collecting master_enc_key = "sample_key" # Master Encryption key used to encrypt merchant wise encryption key. Should be 32-byte long. admin_api_key = "test_admin" # admin API key for admin authentication. jwt_secret = "secret" # JWT secret used for user authentication. -recon_admin_api_key = "recon_test_admin" # recon_admin API key for recon authentication. # Locker settings contain details for accessing a card locker, a # PCI Compliant storage entity which stores payment method information @@ -202,7 +201,7 @@ coinbase.base_url = "https://api.commerce.coinbase.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" -deutschebank.base_url = "https://sandbox.directpos.de/rest-api/services/v2.1" +deutschebank.base_url = "https://testmerch.directpos.de/rest-api" dlocal.base_url = "https://sandbox.dlocal.com/" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" @@ -210,6 +209,7 @@ fiserv.base_url = "https://cert.api.fiservapps.com/" fiservemea.base_url = "https://prod.emea.api.fiservapps.com/sandbox" fiuu.base_url = "https://sandbox.merchant.razer.com/" fiuu.secondary_base_url="https://sandbox.merchant.razer.com/" +fiuu.third_base_url="https://api.merchant.razer.com/" forte.base_url = "https://sandbox.forte.net/api/v3" globalpay.base_url = "https://apis.sandbox.globalpay.com/ucp/" globepay.base_url = "https://pay.globepay.co/" @@ -254,6 +254,7 @@ stax.base_url = "https://apiprod.fattlabs.com/" stripe.base_url = "https://api.stripe.com/" taxjar.base_url = "https://api.sandbox.taxjar.com/v2/" threedsecureio.base_url = "https://service.sandbox.3dsecure.io" +thunes.base_url = "https://api.limonetikqualif.com/" stripe.base_url_file_upload = "https://files.stripe.com/" trustpay.base_url = "https://test-tpgw.trustpay.eu/" trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/" @@ -319,6 +320,7 @@ cards = [ "stax", "stripe", "threedsecureio", + "thunes", "worldpay", "zen", "zsl", @@ -409,6 +411,7 @@ bankofamerica = { payment_method = "card" } cybersource = { payment_method = "card" } nmi = { payment_method = "card" } payme = { payment_method = "card" } +deutschebank = { payment_method = "bank_debit" } [dummy_connector] enabled = true # Whether dummy connector is enabled or not @@ -554,6 +557,7 @@ payout_connector_list = "stripe,wise" [bank_config.online_banking_fpx] adyen.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" +fiuu.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_of_china,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" [bank_config.online_banking_thailand] adyen.banks = "bangkok_bank,krungsri_bank,krung_thai_bank,the_siam_commercial_bank,kasikorn_bank" @@ -722,4 +726,7 @@ public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "p encryption_key = "" # Encryption key used for encrypting data in user_authentication_methods table [locker_based_open_banking_connectors] -connector_list = "" \ No newline at end of file +connector_list = "" + +[recipient_emails] +recon = "test@example.com" diff --git a/config/deployments/env_specific.toml b/config/deployments/env_specific.toml index a7bd116a3228..6fe08509b761 100644 --- a/config/deployments/env_specific.toml +++ b/config/deployments/env_specific.toml @@ -256,7 +256,6 @@ url = "http://localhost:5000" # URL of the encryption service master_enc_key = "sample_key" # Master Encryption key used to encrypt merchant wise encryption key. Should be 32-byte long. admin_api_key = "test_admin" # admin API key for admin authentication. jwt_secret = "secret" # JWT secret used for user authentication. -recon_admin_api_key = "recon_test_admin" # recon_admin API key for recon authentication. # Server configuration [server] @@ -300,3 +299,6 @@ public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "p [user_auth_methods] encryption_key = "user_auth_table_encryption_key" # Encryption key used for encrypting data in user_authentication_methods table + +[recipient_emails] +recon = "recon@example.com" diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index eeec8f31bf43..90d29a6992dc 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -7,6 +7,7 @@ ideal.stripe.banks = "abn_amro,asn_bank,bunq,handelsbanken,ing,knab,moneyou,rabo ideal.multisafepay.banks = "abn_amro, asn_bank, bunq, handelsbanken, nationale_nederlanden, n26, ing, knab, rabobank, regiobank, revolut, sns_bank,triodos_bank, van_lanschot, yoursafe" online_banking_czech_republic.adyen.banks = "ceska_sporitelna,komercni_banka,platnosc_online_karta_platnicza" online_banking_fpx.adyen.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" +online_banking_fpx.fiuu.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_of_china,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" online_banking_poland.adyen.banks = "blik_psp,place_zipko,m_bank,pay_with_ing,santander_przelew24,bank_pekaosa,bank_millennium,pay_with_alior_bank,banki_spoldzielcze,pay_with_inteligo,bnp_paribas_poland,bank_nowy_sa,credit_agricole,pay_with_bos,pay_with_citi_handlowy,pay_with_plus_bank,toyota_bank,velo_bank,e_transfer_pocztowy24" online_banking_slovakia.adyen.banks = "e_platby_vub,postova_banka,sporo_pay,tatra_pay,viamo" online_banking_thailand.adyen.banks = "bangkok_bank,krungsri_bank,krung_thai_bank,the_siam_commercial_bank,kasikorn_bank" @@ -42,7 +43,7 @@ coinbase.base_url = "https://api.commerce.coinbase.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" -deutschebank.base_url = "https://sandbox.directpos.de/rest-api/services/v2.1" +deutschebank.base_url = "https://testmerch.directpos.de/rest-api" dlocal.base_url = "https://sandbox.dlocal.com/" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" @@ -50,6 +51,7 @@ fiserv.base_url = "https://cert.api.fiservapps.com/" fiservemea.base_url = "https://prod.emea.api.fiservapps.com/sandbox" fiuu.base_url = "https://sandbox.merchant.razer.com/" fiuu.secondary_base_url="https://sandbox.merchant.razer.com/" +fiuu.third_base_url="https://api.merchant.razer.com/" forte.base_url = "https://sandbox.forte.net/api/v3" globalpay.base_url = "https://apis.sandbox.globalpay.com/ucp/" globepay.base_url = "https://pay.globepay.co/" @@ -93,6 +95,7 @@ stax.base_url = "https://apiprod.fattlabs.com/" stripe.base_url = "https://api.stripe.com/" stripe.base_url_file_upload = "https://files.stripe.com/" taxjar.base_url = "https://api.sandbox.taxjar.com/v2/" +thunes.base_url = "https://api.limonetikqualif.com/" trustpay.base_url = "https://test-tpgw.trustpay.eu/" trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/" tsys.base_url = "https://stagegw.transnox.com/" @@ -343,6 +346,7 @@ bankofamerica = { payment_method = "card" } cybersource = { payment_method = "card" } nmi.payment_method = "card" payme.payment_method = "card" +deutschebank = { payment_method = "bank_debit" } #tokenization configuration which describe token lifetime and payment method for specific connector [tokenization] @@ -369,4 +373,4 @@ keys = "accept-language,user-agent" sdk_eligible_payment_methods = "card" [locker_based_open_banking_connectors] -connector_list = "" +connector_list = "" \ No newline at end of file diff --git a/config/deployments/production.toml b/config/deployments/production.toml index 3f744891b070..0443d1915427 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -7,6 +7,7 @@ ideal.stripe.banks = "abn_amro,asn_bank,bunq,handelsbanken,ing,knab,moneyou,rabo ideal.multisafepay.banks = "abn_amro, asn_bank, bunq, handelsbanken, nationale_nederlanden, n26, ing, knab, rabobank, regiobank, revolut, sns_bank,triodos_bank, van_lanschot, yoursafe" online_banking_czech_republic.adyen.banks = "ceska_sporitelna,komercni_banka,platnosc_online_karta_platnicza" online_banking_fpx.adyen.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" +online_banking_fpx.fiuu.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_of_china,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" online_banking_poland.adyen.banks = "blik_psp,place_zipko,m_bank,pay_with_ing,santander_przelew24,bank_pekaosa,bank_millennium,pay_with_alior_bank,banki_spoldzielcze,pay_with_inteligo,bnp_paribas_poland,bank_nowy_sa,credit_agricole,pay_with_bos,pay_with_citi_handlowy,pay_with_plus_bank,toyota_bank,velo_bank,e_transfer_pocztowy24" online_banking_slovakia.adyen.banks = "e_platby_vub,postova_banka,sporo_pay,tatra_pay,viamo,volksbank_gruppe,volkskreditbank_ag,vr_bank_braunau" online_banking_thailand.adyen.banks = "bangkok_bank,krungsri_bank,krung_thai_bank,the_siam_commercial_bank,kasikorn_bank" @@ -47,13 +48,14 @@ cryptopay.base_url = "https://business.cryptopay.me/" cybersource.base_url = "https://api.cybersource.com/" datatrans.base_url = "https://api.datatrans.com/" dlocal.base_url = "https://sandbox.dlocal.com/" -deutschebank.base_url = "https://merch.directpos.de/rest-api/services/v2.1" +deutschebank.base_url = "https://merch.directpos.de/rest-api" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" fiserv.base_url = "https://cert.api.fiservapps.com/" fiservemea.base_url = "https://prod.emea.api.fiservapps.com" fiuu.base_url = "https://pay.merchant.razer.com/" fiuu.secondary_base_url="https://api.merchant.razer.com/" +fiuu.third_base_url="https://api.merchant.razer.com/" forte.base_url = "https://sandbox.forte.net/api/v3" globalpay.base_url = "https://apis.sandbox.globalpay.com/ucp/" globepay.base_url = "https://pay.globepay.co/" @@ -97,6 +99,7 @@ stax.base_url = "https://apiprod.fattlabs.com/" stripe.base_url = "https://api.stripe.com/" stripe.base_url_file_upload = "https://files.stripe.com/" taxjar.base_url = "https://api.taxjar.com/v2/" +thunes.base_url = "https://api.limonetik.com/" trustpay.base_url = "https://tpgw.trustpay.eu/" trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/" tsys.base_url = "https://gateway.transit-pass.com/" @@ -356,6 +359,7 @@ bankofamerica = { payment_method = "card" } cybersource = { payment_method = "card" } nmi.payment_method = "card" payme.payment_method = "card" +deutschebank = { payment_method = "bank_debit" } #tokenization configuration which describe token lifetime and payment method for specific connector [tokenization] @@ -382,4 +386,4 @@ keys = "accept-language,user-agent" sdk_eligible_payment_methods = "card" [locker_based_open_banking_connectors] -connector_list = "" +connector_list = "" \ No newline at end of file diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 31514982c2f4..8c6792417296 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -7,6 +7,7 @@ ideal.stripe.banks = "abn_amro,asn_bank,bunq,handelsbanken,ing,knab,moneyou,rabo ideal.multisafepay.banks = "abn_amro, asn_bank, bunq, handelsbanken, nationale_nederlanden, n26, ing, knab, rabobank, regiobank, revolut, sns_bank,triodos_bank, van_lanschot, yoursafe" online_banking_czech_republic.adyen.banks = "ceska_sporitelna,komercni_banka,platnosc_online_karta_platnicza" online_banking_fpx.adyen.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" +online_banking_fpx.fiuu.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_of_china,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" online_banking_poland.adyen.banks = "blik_psp,place_zipko,m_bank,pay_with_ing,santander_przelew24,bank_pekaosa,bank_millennium,pay_with_alior_bank,banki_spoldzielcze,pay_with_inteligo,bnp_paribas_poland,bank_nowy_sa,credit_agricole,pay_with_bos,pay_with_citi_handlowy,pay_with_plus_bank,toyota_bank,velo_bank,e_transfer_pocztowy24" online_banking_slovakia.adyen.banks = "e_platby_vub,postova_banka,sporo_pay,tatra_pay,viamo" online_banking_thailand.adyen.banks = "bangkok_bank,krungsri_bank,krung_thai_bank,the_siam_commercial_bank,kasikorn_bank" @@ -47,13 +48,14 @@ cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" dlocal.base_url = "https://sandbox.dlocal.com/" -deutschebank.base_url = "https://sandbox.directpos.de/rest-api/services/v2.1" +deutschebank.base_url = "https://testmerch.directpos.de/rest-api" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" fiserv.base_url = "https://cert.api.fiservapps.com/" fiservemea.base_url = "https://prod.emea.api.fiservapps.com/sandbox" fiuu.base_url = "https://sandbox.merchant.razer.com/" fiuu.secondary_base_url="https://sandbox.merchant.razer.com/" +fiuu.third_base_url="https://api.merchant.razer.com/" forte.base_url = "https://sandbox.forte.net/api/v3" globalpay.base_url = "https://apis.sandbox.globalpay.com/ucp/" globepay.base_url = "https://pay.globepay.co/" @@ -97,6 +99,7 @@ stax.base_url = "https://apiprod.fattlabs.com/" stripe.base_url = "https://api.stripe.com/" stripe.base_url_file_upload = "https://files.stripe.com/" taxjar.base_url = "https://api.sandbox.taxjar.com/v2/" +thunes.base_url = "https://api.limonetikqualif.com/" trustpay.base_url = "https://test-tpgw.trustpay.eu/" trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/" tsys.base_url = "https://stagegw.transnox.com/" @@ -360,6 +363,7 @@ bankofamerica = { payment_method = "card" } cybersource = { payment_method = "card" } nmi.payment_method = "card" payme.payment_method = "card" +deutschebank = { payment_method = "bank_debit" } #tokenization configuration which describe token lifetime and payment method for specific connector [tokenization] @@ -386,4 +390,4 @@ keys = "accept-language,user-agent" sdk_eligible_payment_methods = "card" [locker_based_open_banking_connectors] -connector_list = "" +connector_list = "" \ No newline at end of file diff --git a/config/development.toml b/config/development.toml index 5c849a284c85..1c6a4a827f31 100644 --- a/config/development.toml +++ b/config/development.toml @@ -61,7 +61,6 @@ request_body_limit = 32768 admin_api_key = "test_admin" master_enc_key = "73ad7bbbbc640c845a150f67d058b279849370cd2c1f3c67c4dd6c869213e13a" jwt_secret = "secret" -recon_admin_api_key = "recon_test_admin" [applepay_merchant_configs] merchant_cert_key = "MERCHANT CERTIFICATE KEY" @@ -156,6 +155,7 @@ cards = [ "stripe", "taxjar", "threedsecureio", + "thunes", "trustpay", "tsys", "volt", @@ -210,7 +210,7 @@ coinbase.base_url = "https://api.commerce.coinbase.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" -deutschebank.base_url = "https://sandbox.directpos.de/rest-api/services/v2.1" +deutschebank.base_url = "https://testmerch.directpos.de/rest-api" dlocal.base_url = "https://sandbox.dlocal.com/" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" @@ -218,6 +218,7 @@ fiserv.base_url = "https://cert.api.fiservapps.com/" fiservemea.base_url = "https://prod.emea.api.fiservapps.com/sandbox" fiuu.base_url = "https://sandbox.merchant.razer.com/" fiuu.secondary_base_url="https://sandbox.merchant.razer.com/" +fiuu.third_base_url="https://api.merchant.razer.com/" forte.base_url = "https://sandbox.forte.net/api/v3" globalpay.base_url = "https://apis.sandbox.globalpay.com/ucp/" globepay.base_url = "https://pay.globepay.co/" @@ -262,6 +263,7 @@ stax.base_url = "https://apiprod.fattlabs.com/" stripe.base_url = "https://api.stripe.com/" taxjar.base_url = "https://api.sandbox.taxjar.com/v2/" threedsecureio.base_url = "https://service.sandbox.3dsecure.io" +thunes.base_url = "https://api.limonetikqualif.com/" stripe.base_url_file_upload = "https://files.stripe.com/" wise.base_url = "https://api.sandbox.transferwise.tech/" worldline.base_url = "https://eu.sandbox.api-ingenico.com/" @@ -336,6 +338,7 @@ adyen = { banks = "aib,bank_of_scotland,danske_bank,first_direct,first_trust,hal [bank_config.online_banking_fpx] adyen.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" +fiuu.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_of_china,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" [bank_config.online_banking_thailand] adyen.banks = "bangkok_bank,krungsri_bank,krung_thai_bank,the_siam_commercial_bank,kasikorn_bank" @@ -535,6 +538,7 @@ bankofamerica = { payment_method = "card" } cybersource = { payment_method = "card" } nmi = { payment_method = "card" } payme = { payment_method = "card" } +deutschebank = { payment_method = "bank_debit" } [connector_customer] connector_list = "gocardless,stax,stripe" @@ -727,3 +731,6 @@ encryption_key = "A8EF32E029BC3342E54BF2E172A4D7AA43E8EF9D2C3A624A9F04E2EF79DC69 [locker_based_open_banking_connectors] connector_list = "" + +[recipient_emails] +recon = "recon@example.com" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 421984a7741e..704acdfee147 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -50,7 +50,6 @@ pool_size = 5 admin_api_key = "test_admin" jwt_secret = "secret" master_enc_key = "73ad7bbbbc640c845a150f67d058b279849370cd2c1f3c67c4dd6c869213e13a" -recon_admin_api_key = "recon_test_admin" [user] password_validity_in_days = 90 @@ -131,7 +130,7 @@ coinbase.base_url = "https://api.commerce.coinbase.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" -deutschebank.base_url = "https://sandbox.directpos.de/rest-api/services/v2.1" +deutschebank.base_url = "https://testmerch.directpos.de/rest-api" dlocal.base_url = "https://sandbox.dlocal.com/" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" @@ -139,6 +138,7 @@ fiserv.base_url = "https://cert.api.fiservapps.com/" fiservemea.base_url = "https://prod.emea.api.fiservapps.com/sandbox" fiuu.base_url = "https://sandbox.merchant.razer.com/" fiuu.secondary_base_url="https://sandbox.merchant.razer.com/" +fiuu.third_base_url="https://api.merchant.razer.com/" forte.base_url = "https://sandbox.forte.net/api/v3" globalpay.base_url = "https://apis.sandbox.globalpay.com/ucp/" globepay.base_url = "https://pay.globepay.co/" @@ -183,6 +183,7 @@ stax.base_url = "https://apiprod.fattlabs.com/" stripe.base_url = "https://api.stripe.com/" taxjar.base_url = "https://api.sandbox.taxjar.com/v2/" threedsecureio.base_url = "https://service.sandbox.3dsecure.io" +thunes.base_url = "https://api.limonetikqualif.com/" stripe.base_url_file_upload = "https://files.stripe.com/" trustpay.base_url = "https://test-tpgw.trustpay.eu/" trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/" @@ -265,6 +266,7 @@ cards = [ "stripe", "taxjar", "threedsecureio", + "thunes", "trustpay", "tsys", "volt", @@ -315,6 +317,7 @@ bankofamerica = { payment_method = "card" } cybersource = { payment_method = "card" } nmi = { payment_method = "card" } payme = { payment_method = "card" } +deutschebank = { payment_method = "bank_debit" } [dummy_connector] enabled = true @@ -429,6 +432,7 @@ ach = { currency = "USD" } [bank_config.online_banking_fpx] adyen.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" +fiuu.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_of_china,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" [bank_config.online_banking_thailand] adyen.banks = "bangkok_bank,krungsri_bank,krung_thai_bank,the_siam_commercial_bank,kasikorn_bank" @@ -586,3 +590,6 @@ ach = { country = "US", currency = "USD" } [locker_based_open_banking_connectors] connector_list = "" + +[recipient_emails] +recon = "recon@example.com" diff --git a/crates/analytics/Cargo.toml b/crates/analytics/Cargo.toml index 68893bf829f3..687d5a2e208a 100644 --- a/crates/analytics/Cargo.toml +++ b/crates/analytics/Cargo.toml @@ -36,7 +36,7 @@ opensearch = { version = "2.2.0", features = ["aws-auth"] } reqwest = { version = "0.11.27", features = ["serde_json"] } serde = { version = "1.0.197", features = ["derive", "rc"] } serde_json = "1.0.115" -sqlx = { version = "0.8.1", features = ["postgres", "runtime-tokio", "runtime-tokio-native-tls", "time", "bigdecimal"] } +sqlx = { version = "0.8.2", features = ["postgres", "runtime-tokio", "runtime-tokio-native-tls", "time", "bigdecimal"] } strum = { version = "0.26.2", features = ["derive"] } thiserror = "1.0.58" time = { version = "0.3.35", features = ["serde", "serde-well-known", "std"] } diff --git a/crates/analytics/docs/clickhouse/scripts/payment_attempts.sql b/crates/analytics/docs/clickhouse/scripts/payment_attempts.sql index 5c1ee8754c96..f5d0ca51fd7c 100644 --- a/crates/analytics/docs/clickhouse/scripts/payment_attempts.sql +++ b/crates/analytics/docs/clickhouse/scripts/payment_attempts.sql @@ -42,6 +42,7 @@ CREATE TABLE payment_attempt_queue ( `client_version` LowCardinality(Nullable(String)), `organization_id` String, `profile_id` String, + `card_network` Nullable(String), `sign_flag` Int8 ) ENGINE = Kafka SETTINGS kafka_broker_list = 'kafka0:29092', kafka_topic_list = 'hyperswitch-payment-attempt-events', @@ -94,6 +95,7 @@ CREATE TABLE payment_attempts ( `client_version` LowCardinality(Nullable(String)), `organization_id` String, `profile_id` String, + `card_network` Nullable(String), `sign_flag` Int8, INDEX connectorIndex connector TYPE bloom_filter GRANULARITY 1, INDEX paymentMethodIndex payment_method TYPE bloom_filter GRANULARITY 1, @@ -149,6 +151,7 @@ CREATE MATERIALIZED VIEW payment_attempt_mv TO payment_attempts ( `client_version` LowCardinality(Nullable(String)), `organization_id` String, `profile_id` String, + `card_network` Nullable(String), `sign_flag` Int8 ) AS SELECT @@ -196,8 +199,9 @@ SELECT client_version, organization_id, profile_id, + card_network, sign_flag FROM payment_attempt_queue WHERE - length(_error) = 0; \ No newline at end of file + length(_error) = 0; diff --git a/crates/analytics/src/api_event/core.rs b/crates/analytics/src/api_event/core.rs index f3a7b154b907..305de7e69c86 100644 --- a/crates/analytics/src/api_event/core.rs +++ b/crates/analytics/src/api_event/core.rs @@ -21,7 +21,6 @@ use super::{ metrics::ApiEventMetricRow, }; use crate::{ - enums::AuthInfo, errors::{AnalyticsError, AnalyticsResult}, metrics, types::FiltersError, @@ -52,7 +51,7 @@ pub async fn api_events_core( pub async fn get_filters( pool: &AnalyticsProvider, req: GetApiEventFiltersRequest, - auth: &AuthInfo, + merchant_id: &common_utils::id_type::MerchantId, ) -> AnalyticsResult { use api_models::analytics::{api_event::ApiEventDimensions, ApiEventFilterValue}; @@ -69,7 +68,8 @@ pub async fn get_filters( AnalyticsProvider::Clickhouse(ckh_pool) | AnalyticsProvider::CombinedSqlx(_, ckh_pool) | AnalyticsProvider::CombinedCkh(_, ckh_pool) => { - get_api_event_filter_for_dimension(dim, auth, &req.time_range, ckh_pool).await + get_api_event_filter_for_dimension(dim, merchant_id, &req.time_range, ckh_pool) + .await } } .switch()? @@ -92,7 +92,7 @@ pub async fn get_filters( #[instrument(skip_all)] pub async fn get_api_event_metrics( pool: &AnalyticsProvider, - auth: &AuthInfo, + merchant_id: &common_utils::id_type::MerchantId, req: GetApiEventMetricRequest, ) -> AnalyticsResult> { let mut metrics_accumulator: HashMap = @@ -109,14 +109,14 @@ pub async fn get_api_event_metrics( // TODO: lifetime issues with joinset, // can be optimized away if joinset lifetime requirements are relaxed - let auth_scoped = auth.to_owned(); + let merchant_id_scoped = merchant_id.to_owned(); set.spawn( async move { let data = pool .get_api_event_metrics( &metric_type, &req.group_by_names.clone(), - &auth_scoped, + &merchant_id_scoped, &req.filters, &req.time_series.map(|t| t.granularity), &req.time_range, diff --git a/crates/analytics/src/api_event/filters.rs b/crates/analytics/src/api_event/filters.rs index 5c8136805c93..62fd87890180 100644 --- a/crates/analytics/src/api_event/filters.rs +++ b/crates/analytics/src/api_event/filters.rs @@ -4,7 +4,6 @@ use error_stack::ResultExt; use time::PrimitiveDateTime; use crate::{ - enums::AuthInfo, query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, ToSql, Window}, types::{AnalyticsCollection, AnalyticsDataSource, FiltersError, FiltersResult, LoadRow}, }; @@ -13,7 +12,7 @@ pub trait ApiEventFilterAnalytics: LoadRow {} pub async fn get_api_event_filter_for_dimension( dimension: ApiEventDimensions, - auth: &AuthInfo, + merchant_id: &common_utils::id_type::MerchantId, time_range: &TimeRange, pool: &T, ) -> FiltersResult> @@ -33,7 +32,9 @@ where .attach_printable("Error filtering time range") .switch()?; - auth.set_filter_clause(&mut query_builder).switch()?; + query_builder + .add_filter_clause("merchant_id", merchant_id) + .switch()?; query_builder.set_distinct(); diff --git a/crates/analytics/src/api_event/metrics.rs b/crates/analytics/src/api_event/metrics.rs index 48e95b6b5cdf..ac29ec15169f 100644 --- a/crates/analytics/src/api_event/metrics.rs +++ b/crates/analytics/src/api_event/metrics.rs @@ -7,7 +7,6 @@ use api_models::analytics::{ use time::PrimitiveDateTime; use crate::{ - enums::AuthInfo, query::{Aggregate, GroupByClause, ToSql, Window}, types::{AnalyticsCollection, AnalyticsDataSource, LoadRow, MetricsResult}, }; @@ -44,7 +43,7 @@ where async fn load_metrics( &self, dimensions: &[ApiEventDimensions], - auth: &AuthInfo, + merchant_id: &common_utils::id_type::MerchantId, filters: &ApiEventFilters, granularity: &Option, time_range: &TimeRange, @@ -65,7 +64,7 @@ where async fn load_metrics( &self, dimensions: &[ApiEventDimensions], - auth: &AuthInfo, + merchant_id: &common_utils::id_type::MerchantId, filters: &ApiEventFilters, granularity: &Option, time_range: &TimeRange, @@ -74,17 +73,38 @@ where match self { Self::Latency => { MaxLatency - .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .load_metrics( + dimensions, + merchant_id, + filters, + granularity, + time_range, + pool, + ) .await } Self::ApiCount => { ApiCount - .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .load_metrics( + dimensions, + merchant_id, + filters, + granularity, + time_range, + pool, + ) .await } Self::StatusCodeCount => { StatusCodeCount - .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .load_metrics( + dimensions, + merchant_id, + filters, + granularity, + time_range, + pool, + ) .await } } diff --git a/crates/analytics/src/api_event/metrics/api_count.rs b/crates/analytics/src/api_event/metrics/api_count.rs index 3973870a4556..f00c01bbf381 100644 --- a/crates/analytics/src/api_event/metrics/api_count.rs +++ b/crates/analytics/src/api_event/metrics/api_count.rs @@ -10,7 +10,6 @@ use time::PrimitiveDateTime; use super::ApiEventMetricRow; use crate::{ - enums::AuthInfo, query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window}, types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, }; @@ -31,7 +30,7 @@ where async fn load_metrics( &self, _dimensions: &[ApiEventDimensions], - auth: &AuthInfo, + merchant_id: &common_utils::id_type::MerchantId, filters: &ApiEventFilters, granularity: &Option, time_range: &TimeRange, @@ -70,7 +69,9 @@ where .switch()?; } - auth.set_filter_clause(&mut query_builder).switch()?; + query_builder + .add_filter_clause("merchant_id", merchant_id) + .switch()?; time_range .set_filter_clause(&mut query_builder) diff --git a/crates/analytics/src/api_event/metrics/latency.rs b/crates/analytics/src/api_event/metrics/latency.rs index c9a62abe6761..5d71da2a0aa1 100644 --- a/crates/analytics/src/api_event/metrics/latency.rs +++ b/crates/analytics/src/api_event/metrics/latency.rs @@ -10,7 +10,6 @@ use time::PrimitiveDateTime; use super::ApiEventMetricRow; use crate::{ - enums::AuthInfo, query::{ Aggregate, FilterTypes, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window, @@ -34,7 +33,7 @@ where async fn load_metrics( &self, _dimensions: &[ApiEventDimensions], - auth: &AuthInfo, + merchant_id: &common_utils::id_type::MerchantId, filters: &ApiEventFilters, granularity: &Option, time_range: &TimeRange, @@ -77,7 +76,9 @@ where filters.set_filter_clause(&mut query_builder).switch()?; - auth.set_filter_clause(&mut query_builder).switch()?; + query_builder + .add_filter_clause("merchant_id", merchant_id) + .switch()?; time_range .set_filter_clause(&mut query_builder) diff --git a/crates/analytics/src/api_event/metrics/status_code_count.rs b/crates/analytics/src/api_event/metrics/status_code_count.rs index 190f38999c93..b4fff367b629 100644 --- a/crates/analytics/src/api_event/metrics/status_code_count.rs +++ b/crates/analytics/src/api_event/metrics/status_code_count.rs @@ -10,7 +10,6 @@ use time::PrimitiveDateTime; use super::ApiEventMetricRow; use crate::{ - enums::AuthInfo, query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window}, types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, }; @@ -31,7 +30,7 @@ where async fn load_metrics( &self, _dimensions: &[ApiEventDimensions], - auth: &AuthInfo, + merchant_id: &common_utils::id_type::MerchantId, filters: &ApiEventFilters, granularity: &Option, time_range: &TimeRange, @@ -48,7 +47,9 @@ where filters.set_filter_clause(&mut query_builder).switch()?; - auth.set_filter_clause(&mut query_builder).switch()?; + query_builder + .add_filter_clause("merchant_id", merchant_id) + .switch()?; time_range .set_filter_clause(&mut query_builder) diff --git a/crates/analytics/src/lib.rs b/crates/analytics/src/lib.rs index 66005a61b754..d5cb3c718dfb 100644 --- a/crates/analytics/src/lib.rs +++ b/crates/analytics/src/lib.rs @@ -828,7 +828,7 @@ impl AnalyticsProvider { &self, metric: &ApiEventMetrics, dimensions: &[ApiEventDimensions], - auth: &AuthInfo, + merchant_id: &common_utils::id_type::MerchantId, filters: &ApiEventFilters, granularity: &Option, time_range: &TimeRange, @@ -840,7 +840,14 @@ impl AnalyticsProvider { | Self::CombinedSqlx(_, ckh_pool) => { // Since API events are ckh only use ckh here metric - .load_metrics(dimensions, auth, filters, granularity, time_range, ckh_pool) + .load_metrics( + dimensions, + merchant_id, + filters, + granularity, + time_range, + ckh_pool, + ) .await } } diff --git a/crates/analytics/src/opensearch.rs b/crates/analytics/src/opensearch.rs index f3dd5eea4413..7486ab50a519 100644 --- a/crates/analytics/src/opensearch.rs +++ b/crates/analytics/src/opensearch.rs @@ -24,7 +24,7 @@ use storage_impl::errors::ApplicationError; use time::PrimitiveDateTime; use super::{health_check::HealthCheck, query::QueryResult, types::QueryExecutionError}; -use crate::query::QueryBuildingError; +use crate::{enums::AuthInfo, query::QueryBuildingError}; #[derive(Clone, Debug, serde::Deserialize)] #[serde(tag = "auth")] @@ -397,13 +397,15 @@ pub struct OpenSearchQueryBuilder { pub count: Option, pub filters: Vec<(String, Vec)>, pub time_range: Option, + pub search_params: Vec, } impl OpenSearchQueryBuilder { - pub fn new(query_type: OpenSearchQuery, query: String) -> Self { + pub fn new(query_type: OpenSearchQuery, query: String, search_params: Vec) -> Self { Self { query_type, query, + search_params, offset: Default::default(), count: Default::default(), filters: Default::default(), @@ -498,8 +500,80 @@ impl OpenSearchQueryBuilder { })); } - bool_obj.insert("filter".to_string(), Value::Array(filter_array)); - query_obj.insert("bool".to_string(), Value::Object(bool_obj)); + let should_array = self + .search_params + .iter() + .map(|user_level| match user_level { + AuthInfo::OrgLevel { org_id } => { + let must_clauses = vec![json!({ + "term": { + "organization_id.keyword": { + "value": org_id + } + } + })]; + + json!({ + "bool": { + "must": must_clauses + } + }) + } + AuthInfo::MerchantLevel { + org_id: _, + merchant_ids, + } => { + let must_clauses = vec![ + // TODO: Add org_id field to the filters + json!({ + "terms": { + "merchant_id.keyword": merchant_ids + } + }), + ]; + + json!({ + "bool": { + "must": must_clauses + } + }) + } + AuthInfo::ProfileLevel { + org_id: _, + merchant_id, + profile_ids, + } => { + let must_clauses = vec![ + // TODO: Add org_id field to the filters + json!({ + "term": { + "merchant_id.keyword": { + "value": merchant_id + } + } + }), + json!({ + "terms": { + "profile_id.keyword": profile_ids + } + }), + ]; + + json!({ + "bool": { + "must": must_clauses + } + }) + } + }) + .collect::>(); + + if !filter_array.is_empty() { + bool_obj.insert("filter".to_string(), Value::Array(filter_array)); + } + if !bool_obj.is_empty() { + query_obj.insert("bool".to_string(), Value::Object(bool_obj)); + } let mut query = Map::new(); query.insert("query".to_string(), Value::Object(query_obj)); @@ -514,9 +588,19 @@ impl OpenSearchQueryBuilder { .and_then(|f| f.as_array()) .map(|filters| self.replace_status_field(filters, index)) .unwrap_or_default(); - + let mut final_bool_obj = Map::new(); + if !updated_query.is_empty() { + final_bool_obj.insert("filter".to_string(), Value::Array(updated_query)); + } + if !should_array.is_empty() { + final_bool_obj.insert("should".to_string(), Value::Array(should_array.clone())); + final_bool_obj + .insert("minimum_should_match".to_string(), Value::Number(1.into())); + } let mut final_query = Map::new(); - final_query.insert("bool".to_string(), json!({ "filter": updated_query })); + if !final_bool_obj.is_empty() { + final_query.insert("bool".to_string(), Value::Object(final_bool_obj)); + } let mut sort_obj = Map::new(); sort_obj.insert( diff --git a/crates/analytics/src/search.rs b/crates/analytics/src/search.rs index 95b7f204b977..c81ff2f416bf 100644 --- a/crates/analytics/src/search.rs +++ b/crates/analytics/src/search.rs @@ -6,14 +6,15 @@ use common_utils::errors::{CustomResult, ReportSwitchExt}; use error_stack::ResultExt; use router_env::tracing; -use crate::opensearch::{ - OpenSearchClient, OpenSearchError, OpenSearchQuery, OpenSearchQueryBuilder, +use crate::{ + enums::AuthInfo, + opensearch::{OpenSearchClient, OpenSearchError, OpenSearchQuery, OpenSearchQueryBuilder}, }; pub async fn msearch_results( client: &OpenSearchClient, req: GetGlobalSearchRequest, - merchant_id: &common_utils::id_type::MerchantId, + search_params: Vec, indexes: Vec, ) -> CustomResult, OpenSearchError> { if req.query.trim().is_empty() @@ -27,15 +28,11 @@ pub async fn msearch_results( ) .into()); } - let mut query_builder = - OpenSearchQueryBuilder::new(OpenSearchQuery::Msearch(indexes.clone()), req.query); - - query_builder - .add_filter_clause( - "merchant_id.keyword".to_string(), - vec![merchant_id.get_string_repr().to_owned()], - ) - .switch()?; + let mut query_builder = OpenSearchQueryBuilder::new( + OpenSearchQuery::Msearch(indexes.clone()), + req.query, + search_params, + ); if let Some(filters) = req.filters { if let Some(currency) = filters.currency { @@ -152,19 +149,15 @@ pub async fn msearch_results( pub async fn search_results( client: &OpenSearchClient, req: GetSearchRequestWithIndex, - merchant_id: &common_utils::id_type::MerchantId, + search_params: Vec, ) -> CustomResult { let search_req = req.search_req; - let mut query_builder = - OpenSearchQueryBuilder::new(OpenSearchQuery::Search(req.index), search_req.query); - - query_builder - .add_filter_clause( - "merchant_id.keyword".to_string(), - vec![merchant_id.get_string_repr().to_owned()], - ) - .switch()?; + let mut query_builder = OpenSearchQueryBuilder::new( + OpenSearchQuery::Search(req.index), + search_req.query, + search_params, + ); if let Some(filters) = search_req.filters { if let Some(currency) = filters.currency { diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index aef5f106be16..b27a279b9624 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -9,7 +9,6 @@ use common_utils::{ }; #[cfg(feature = "v1")] use common_utils::{crypto::OptionalEncryptableName, ext_traits::ValueExt}; -use indexmap::IndexMap; #[cfg(feature = "v2")] use masking::ExposeInterface; use masking::Secret; @@ -1911,7 +1910,8 @@ pub struct BusinessProfileCreate { pub outgoing_webhook_custom_http_headers: Option>, /// Merchant Connector id to be stored for tax_calculator connector - pub tax_connector_id: Option, + #[schema(value_type = Option)] + pub tax_connector_id: Option, /// Indicates if tax_calculator connector is enabled or not. /// If set to `true` tax_connector_id will be checked. @@ -2014,7 +2014,8 @@ pub struct BusinessProfileCreate { pub outgoing_webhook_custom_http_headers: Option>, /// Merchant Connector id to be stored for tax_calculator connector - pub tax_connector_id: Option, + #[schema(value_type = Option)] + pub tax_connector_id: Option, /// Indicates if tax_calculator connector is enabled or not. /// If set to `true` tax_connector_id will be checked. @@ -2131,7 +2132,8 @@ pub struct BusinessProfileResponse { pub outgoing_webhook_custom_http_headers: Option>>, /// Merchant Connector id to be stored for tax_calculator connector - pub tax_connector_id: Option, + #[schema(value_type = Option)] + pub tax_connector_id: Option, /// Indicates if tax_calculator connector is enabled or not. /// If set to `true` tax_connector_id will be checked. @@ -2238,7 +2240,8 @@ pub struct BusinessProfileResponse { pub order_fulfillment_time_origin: Option, /// Merchant Connector id to be stored for tax_calculator connector - pub tax_connector_id: Option, + #[schema(value_type = Option)] + pub tax_connector_id: Option, /// Indicates if tax_calculator connector is enabled or not. /// If set to `true` tax_connector_id will be checked. @@ -2346,7 +2349,8 @@ pub struct BusinessProfileUpdate { pub outgoing_webhook_custom_http_headers: Option>, /// Merchant Connector id to be stored for tax_calculator connector - pub tax_connector_id: Option, + #[schema(value_type = Option)] + pub tax_connector_id: Option, /// Indicates if tax_calculator connector is enabled or not. /// If set to `true` tax_connector_id will be checked. @@ -2445,7 +2449,8 @@ pub struct BusinessProfileUpdate { pub outgoing_webhook_custom_http_headers: Option>, /// Merchant Connector id to be stored for tax_calculator connector - pub tax_connector_id: Option, + #[schema(value_type = Option)] + pub tax_connector_id: Option, /// Indicates if tax_calculator connector is enabled or not. /// If set to `true` tax_connector_id will be checked. @@ -2467,6 +2472,10 @@ pub struct BusinessPayoutLinkConfig { #[serde(flatten)] pub config: BusinessGenericLinkConfig, + /// Form layout of the payout link + #[schema(value_type = Option, max_length = 255, example = "tabs")] + pub form_layout: Option, + /// Allows for removing any validations / pre-requisites which are necessary in a production environment #[schema(value_type = Option, default = false)] pub payout_test_mode: Option, @@ -2574,8 +2583,32 @@ pub struct PaymentLinkConfigRequest { #[schema(default = false, example = true)] pub enabled_saved_payment_method: Option, /// Dynamic details related to merchant to be rendered in payment link - #[schema(value_type = Option, example = r#"{ "value1": "some-value", "value2": "some-value" }"#)] - pub transaction_details: Option>, + pub transaction_details: Option>, +} + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)] +pub struct PaymentLinkTransactionDetails { + /// Key for the transaction details + #[schema(value_type = String, max_length = 255, example = "Policy-Number")] + pub key: String, + /// Value for the transaction details + #[schema(value_type = String, max_length = 255, example = "297472368473924")] + pub value: String, + /// UI configuration for the transaction details + pub ui_configuration: Option, +} + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)] +pub struct TransactionDetailsUiConfiguration { + /// Position of the key-value pair in the UI + #[schema(value_type = Option, example = 5)] + pub position: Option, + /// Whether the key should be bold + #[schema(default = false, example = true)] + pub is_key_bold: Option, + /// Whether the value should be bold + #[schema(default = false, example = true)] + pub is_value_bold: Option, } #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, ToSchema)] @@ -2595,7 +2628,7 @@ pub struct PaymentLinkConfig { /// A list of allowed domains (glob patterns) where this link can be embedded / opened from pub allowed_domains: Option>, /// Dynamic details related to merchant to be rendered in payment link - pub transaction_details: Option, + pub transaction_details: Option>, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)] diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index 34a0445887b1..64af2349f7c2 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -46,7 +46,6 @@ pub enum RoutingAlgorithm { #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] pub enum Connector { - // Novalnet, // Nexixpay, Adyenplatform, #[cfg(feature = "dummy_connector")] @@ -95,7 +94,7 @@ pub enum Connector { Cryptopay, Cybersource, Datatrans, - // Deutschebank, + Deutschebank, Dlocal, Ebanx, Fiserv, @@ -117,6 +116,7 @@ pub enum Connector { Nexinets, Nmi, Noon, + Novalnet, Nuvei, // Opayo, added as template code for future usage Opennode, @@ -135,8 +135,9 @@ pub enum Connector { Square, Stax, Stripe, - // Taxjar, + Taxjar, Threedsecureio, + //Thunes, Trustpay, Tsys, Volt, @@ -186,6 +187,7 @@ impl Connector { matches!( (self, payment_method), (Self::Airwallex, _) + | (Self::Deutschebank, _) | (Self::Globalpay, _) | (Self::Paypal, _) | (Self::Payu, _) @@ -213,9 +215,8 @@ impl Connector { | Self::DummyConnector7 => false, Self::Aci // Add Separate authentication support for connectors - // | Self::Novalnet // | Self::Nexixpay - // | Self::Taxjar + // | Self::Fiuu | Self::Adyen | Self::Adyenplatform | Self::Airwallex @@ -231,7 +232,7 @@ impl Connector { | Self::Cashtocode | Self::Coinbase | Self::Cryptopay - // | Self::Deutschebank + | Self::Deutschebank | Self::Dlocal | Self::Ebanx | Self::Fiserv @@ -250,6 +251,7 @@ impl Connector { | Self::Mollie | Self::Multisafepay | Self::Nexinets + | Self::Novalnet | Self::Nuvei | Self::Opennode | Self::Paybox @@ -264,6 +266,8 @@ impl Connector { | Self::Shift4 | Self::Square | Self::Stax + | Self::Taxjar + //| Self::Thunes | Self::Trustpay | Self::Tsys | Self::Volt @@ -415,6 +419,26 @@ pub enum FrmConnectors { Riskified, } +#[derive( + Clone, + Copy, + Debug, + Eq, + Hash, + PartialEq, + serde::Serialize, + serde::Deserialize, + strum::Display, + strum::EnumString, + ToSchema, +)] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] + +pub enum TaxConnectors { + Taxjar, +} + #[derive( Clone, Debug, serde::Deserialize, serde::Serialize, strum::Display, strum::EnumString, ToSchema, )] @@ -491,6 +515,9 @@ pub enum FieldType { UserPixKey, UserCpf, UserCnpj, + UserIban, + BrowserLanguage, + BrowserIp, } impl FieldType { @@ -656,6 +683,10 @@ pub fn convert_authentication_connector(connector_name: &str) -> Option Option { + TaxConnectors::from_str(connector_name).ok() +} + #[derive( Clone, Debug, diff --git a/crates/api_models/src/events/payment.rs b/crates/api_models/src/events/payment.rs index ba137cd3bafe..aa8cd43ca97f 100644 --- a/crates/api_models/src/events/payment.rs +++ b/crates/api_models/src/events/payment.rs @@ -20,7 +20,8 @@ use crate::{ PaymentListFilterConstraints, PaymentListFilters, PaymentListFiltersV2, PaymentListResponse, PaymentListResponseV2, PaymentsAggregateResponse, PaymentsApproveRequest, PaymentsCancelRequest, PaymentsCaptureRequest, - PaymentsCompleteAuthorizeRequest, PaymentsExternalAuthenticationRequest, + PaymentsCompleteAuthorizeRequest, PaymentsDynamicTaxCalculationRequest, + PaymentsDynamicTaxCalculationResponse, PaymentsExternalAuthenticationRequest, PaymentsExternalAuthenticationResponse, PaymentsIncrementalAuthorizationRequest, PaymentsManualUpdateRequest, PaymentsManualUpdateResponse, PaymentsRejectRequest, PaymentsRequest, PaymentsResponse, PaymentsRetrieveRequest, PaymentsSessionResponse, @@ -62,6 +63,16 @@ impl ApiEventMetric for PaymentsCompleteAuthorizeRequest { } } +impl ApiEventMetric for PaymentsDynamicTaxCalculationRequest { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Payment { + payment_id: self.payment_id.clone(), + }) + } +} + +impl ApiEventMetric for PaymentsDynamicTaxCalculationResponse {} + impl ApiEventMetric for PaymentsCancelRequest { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { diff --git a/crates/api_models/src/events/refund.rs b/crates/api_models/src/events/refund.rs index 0717b66c6bf5..d180753735f3 100644 --- a/crates/api_models/src/events/refund.rs +++ b/crates/api_models/src/events/refund.rs @@ -1,9 +1,9 @@ use common_utils::events::{ApiEventMetric, ApiEventsType}; use crate::refunds::{ - RefundListFilters, RefundListMetaData, RefundListRequest, RefundListResponse, - RefundManualUpdateRequest, RefundRequest, RefundResponse, RefundUpdateRequest, - RefundsRetrieveRequest, + RefundAggregateResponse, RefundListFilters, RefundListMetaData, RefundListRequest, + RefundListResponse, RefundManualUpdateRequest, RefundRequest, RefundResponse, + RefundUpdateRequest, RefundsRetrieveRequest, }; impl ApiEventMetric for RefundRequest { @@ -66,6 +66,12 @@ impl ApiEventMetric for RefundListResponse { } } +impl ApiEventMetric for RefundAggregateResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::ResourceListAPI) + } +} + impl ApiEventMetric for RefundListMetaData { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::ResourceListAPI) diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index 2204a7fc584e..c6cf6380150a 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -1482,6 +1482,9 @@ pub struct PaymentMethodListResponse { /// flag that indicates whether to collect billing details from wallets or from the customer pub collect_billing_details_from_wallets: Option, + + /// flag that indicates whether to calculate tax on the order amount + pub is_tax_calculation_enabled: bool, } #[derive(Eq, PartialEq, Hash, Debug, serde::Deserialize, ToSchema)] diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 22084f1d9f2b..abd795361bb8 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -3,7 +3,7 @@ use std::{ fmt, num::NonZeroI64, }; - +pub mod additional_info; use cards::CardNumber; use common_utils::{ consts::default_payments_list_limit, @@ -278,7 +278,7 @@ pub struct CustomerDetailsResponse { #[generate_schemas(PaymentsCreateRequest, PaymentsUpdateRequest, PaymentsConfirmRequest)] #[serde(deny_unknown_fields)] pub struct PaymentsRequest { - /// The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and ¥100 since ¥ is a zero-decimal currency + /// The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies) #[schema(value_type = Option, example = 6540)] #[serde(default, deserialize_with = "amount::deserialize_option")] #[mandatory_in(PaymentsCreateRequest = u64)] @@ -294,6 +294,10 @@ pub struct PaymentsRequest { #[schema(value_type = Option, example = 6540)] pub amount_to_capture: Option, + /// The shipping cost for the payment. This is required for tax calculation in some regions. + #[schema(value_type = Option, example = 6540)] + pub shipping_cost: Option, + /// Unique identifier for the payment. This ensures idempotency for multiple payments /// that have been done by a single merchant. The value for this field can be specified in the request, it will be auto generated otherwise and returned in the API response. #[schema( @@ -556,6 +560,9 @@ pub struct PaymentsRequest { example = "Custom_Order_id_123" )] pub merchant_order_reference_id: Option, + + /// Whether to calculate tax for this payment intent + pub skip_external_tax_calculation: Option, } /// Checks if the inner values of two options are equal @@ -2026,6 +2033,8 @@ pub enum AdditionalPaymentData { Card(Box), BankRedirect { bank_name: Option, + #[serde(flatten)] + details: Option, }, Wallet { apple_pay: Option, @@ -2033,18 +2042,48 @@ pub enum AdditionalPaymentData { PayLater { klarna_sdk: Option, }, - BankTransfer {}, - Crypto {}, - BankDebit {}, + BankTransfer { + #[serde(flatten)] + details: Option, + }, + Crypto { + #[serde(flatten)] + details: Option, + }, + BankDebit { + #[serde(flatten)] + details: Option, + }, MandatePayment {}, Reward {}, - RealTimePayment {}, - Upi {}, - GiftCard {}, - Voucher {}, - CardRedirect {}, - CardToken {}, - OpenBanking {}, + RealTimePayment { + #[serde(flatten)] + details: Option, + }, + Upi { + #[serde(flatten)] + details: Option, + }, + GiftCard { + #[serde(flatten)] + details: Option, + }, + Voucher { + #[serde(flatten)] + details: Option, + }, + CardRedirect { + #[serde(flatten)] + details: Option, + }, + CardToken { + #[serde(flatten)] + details: Option, + }, + OpenBanking { + #[serde(flatten)] + details: Option, + }, } #[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] @@ -3011,21 +3050,21 @@ where { match payment_method_data { PaymentMethodDataResponse::Reward {} => serializer.serialize_str("reward"), - PaymentMethodDataResponse::BankDebit {} - | PaymentMethodDataResponse::BankRedirect {} + PaymentMethodDataResponse::BankDebit(_) + | PaymentMethodDataResponse::BankRedirect(_) | PaymentMethodDataResponse::Card(_) - | PaymentMethodDataResponse::CardRedirect {} - | PaymentMethodDataResponse::CardToken {} - | PaymentMethodDataResponse::Crypto {} + | PaymentMethodDataResponse::CardRedirect(_) + | PaymentMethodDataResponse::CardToken(_) + | PaymentMethodDataResponse::Crypto(_) | PaymentMethodDataResponse::MandatePayment {} - | PaymentMethodDataResponse::GiftCard {} + | PaymentMethodDataResponse::GiftCard(_) | PaymentMethodDataResponse::PayLater(_) - | PaymentMethodDataResponse::RealTimePayment {} - | PaymentMethodDataResponse::Upi {} + | PaymentMethodDataResponse::RealTimePayment(_) + | PaymentMethodDataResponse::Upi(_) | PaymentMethodDataResponse::Wallet {} - | PaymentMethodDataResponse::BankTransfer {} - | PaymentMethodDataResponse::OpenBanking {} - | PaymentMethodDataResponse::Voucher {} => { + | PaymentMethodDataResponse::BankTransfer(_) + | PaymentMethodDataResponse::OpenBanking(_) + | PaymentMethodDataResponse::Voucher(_) => { payment_method_data_response.serialize(serializer) } } @@ -3041,23 +3080,98 @@ where #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] #[serde(rename_all = "snake_case")] pub enum PaymentMethodDataResponse { - #[serde(rename = "card")] Card(Box), - BankTransfer {}, + BankTransfer(Box), Wallet {}, PayLater(Box), - BankRedirect {}, - Crypto {}, - BankDebit {}, + BankRedirect(Box), + Crypto(Box), + BankDebit(Box), MandatePayment {}, Reward {}, - RealTimePayment {}, - Upi {}, - Voucher {}, - GiftCard {}, - CardRedirect {}, - CardToken {}, - OpenBanking {}, + RealTimePayment(Box), + Upi(Box), + Voucher(Box), + GiftCard(Box), + CardRedirect(Box), + CardToken(Box), + OpenBanking(Box), +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct BankDebitResponse { + #[serde(flatten)] + #[schema(value_type = Option)] + details: Option, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +#[serde(rename_all = "snake_case", tag = "type")] +pub struct BankRedirectResponse { + /// Name of the bank + #[schema(value_type = Option)] + pub bank_name: Option, + #[serde(flatten)] + #[schema(value_type = Option)] + pub details: Option, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct BankTransferResponse { + #[serde(flatten)] + #[schema(value_type = Option)] + details: Option, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct CardRedirectResponse { + #[serde(flatten)] + details: Option, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct CardTokenResponse { + #[serde(flatten)] + #[schema(value_type = Option)] + details: Option, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct CryptoResponse { + #[serde(flatten)] + details: Option, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct GiftCardResponse { + #[serde(flatten)] + #[schema(value_type = Option)] + details: Option, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct OpenBankingResponse { + #[serde(flatten)] + details: Option, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct RealTimePaymentDataResponse { + #[serde(flatten)] + details: Option, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct UpiResponse { + #[serde(flatten)] + #[schema(value_type = Option)] + details: Option, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct VoucherResponse { + #[serde(flatten)] + details: Option, } #[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)] @@ -3605,8 +3719,8 @@ pub struct PaymentsResponse { #[schema(value_type = i64, example = 6540)] pub amount: MinorUnit, - /// The payment net amount. net_amount = amount + surcharge_details.surcharge_amount + surcharge_details.tax_amount, - /// If no surcharge_details, net_amount = amount + /// The payment net amount. net_amount = amount + surcharge_details.surcharge_amount + surcharge_details.tax_amount + shipping_cost + order_tax_amount, + /// If no surcharge_details, shipping_cost, order_tax_amount, net_amount = amount #[schema(value_type = i64, example = 6540)] pub net_amount: MinorUnit, @@ -3906,6 +4020,8 @@ pub struct PaymentsResponse { example = "Custom_Order_id_123" )] pub merchant_order_reference_id: Option, + /// order tax amount calculated by tax connectors + pub order_tax_amount: Option, } /// Fee information to be charged on the payment being collected @@ -4299,19 +4415,39 @@ impl From for PaymentMethodDataResponse { None => Self::PayLater(Box::new(PaylaterResponse { klarna_sdk: None })), }, AdditionalPaymentData::Wallet { .. } => Self::Wallet {}, - AdditionalPaymentData::BankRedirect { .. } => Self::BankRedirect {}, - AdditionalPaymentData::Crypto {} => Self::Crypto {}, - AdditionalPaymentData::BankDebit {} => Self::BankDebit {}, + AdditionalPaymentData::BankRedirect { bank_name, details } => { + Self::BankRedirect(Box::new(BankRedirectResponse { bank_name, details })) + } + AdditionalPaymentData::Crypto { details } => { + Self::Crypto(Box::new(CryptoResponse { details })) + } + AdditionalPaymentData::BankDebit { details } => { + Self::BankDebit(Box::new(BankDebitResponse { details })) + } AdditionalPaymentData::MandatePayment {} => Self::MandatePayment {}, AdditionalPaymentData::Reward {} => Self::Reward {}, - AdditionalPaymentData::RealTimePayment {} => Self::RealTimePayment {}, - AdditionalPaymentData::Upi {} => Self::Upi {}, - AdditionalPaymentData::BankTransfer {} => Self::BankTransfer {}, - AdditionalPaymentData::Voucher {} => Self::Voucher {}, - AdditionalPaymentData::GiftCard {} => Self::GiftCard {}, - AdditionalPaymentData::CardRedirect {} => Self::CardRedirect {}, - AdditionalPaymentData::CardToken {} => Self::CardToken {}, - AdditionalPaymentData::OpenBanking {} => Self::OpenBanking {}, + AdditionalPaymentData::RealTimePayment { details } => { + Self::RealTimePayment(Box::new(RealTimePaymentDataResponse { details })) + } + AdditionalPaymentData::Upi { details } => Self::Upi(Box::new(UpiResponse { details })), + AdditionalPaymentData::BankTransfer { details } => { + Self::BankTransfer(Box::new(BankTransferResponse { details })) + } + AdditionalPaymentData::Voucher { details } => { + Self::Voucher(Box::new(VoucherResponse { details })) + } + AdditionalPaymentData::GiftCard { details } => { + Self::GiftCard(Box::new(GiftCardResponse { details })) + } + AdditionalPaymentData::CardRedirect { details } => { + Self::CardRedirect(Box::new(CardRedirectResponse { details })) + } + AdditionalPaymentData::CardToken { details } => { + Self::CardToken(Box::new(CardTokenResponse { details })) + } + AdditionalPaymentData::OpenBanking { details } => { + Self::OpenBanking(Box::new(OpenBankingResponse { details })) + } } } } @@ -4391,6 +4527,8 @@ pub struct OrderDetailsWithAmount { pub brand: Option, /// Type of the product that is being purchased pub product_type: Option, + /// The tax code for the product + pub product_tax_code: Option, } #[derive(Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, Clone, ToSchema)] @@ -4427,6 +4565,8 @@ pub struct OrderDetails { pub brand: Option, /// Type of the product that is being purchased pub product_type: Option, + /// The tax code for the product + pub product_tax_code: Option, } #[derive(Default, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, Clone, ToSchema)] @@ -4452,6 +4592,35 @@ pub struct PaymentsSessionRequest { pub merchant_connector_details: Option, } +#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)] +pub struct PaymentsDynamicTaxCalculationRequest { + /// The unique identifier for the payment + #[serde(skip_deserializing)] + #[schema(value_type = String)] + pub payment_id: id_type::PaymentId, + /// The shipping address for the payment + pub shipping: Address, + /// Client Secret + #[schema(value_type = String)] + pub client_secret: Secret, + /// Payment method type + #[schema(value_type = PaymentMethodType)] + pub payment_method_type: api_enums::PaymentMethodType, +} + +#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)] +pub struct PaymentsDynamicTaxCalculationResponse { + /// The identifier for the payment + #[schema(value_type = String)] + pub payment_id: id_type::PaymentId, + /// net amount = amount + order_tax_amount + shipping_cost + pub net_amount: MinorUnit, + /// order tax amount calculated by tax connectors + pub order_tax_amount: Option, + /// shipping cost for the order + pub shipping_cost: Option, +} + #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] pub struct GpayAllowedMethodsParameters { /// The list of allowed auth methods (ex: 3DS, No3DS, PAN_ONLY etc) @@ -5456,7 +5625,7 @@ pub struct PaymentLinkDetails { pub sdk_layout: String, pub display_sdk_only: bool, pub locale: Option, - pub transaction_details: Option, + pub transaction_details: Option>, } #[derive(Debug, serde::Serialize, Clone)] @@ -5482,7 +5651,7 @@ pub struct PaymentLinkStatusDetails { pub theme: String, pub return_url: String, pub locale: Option, - pub transaction_details: Option, + pub transaction_details: Option>, pub unified_code: Option, pub unified_message: Option, } diff --git a/crates/api_models/src/payments/additional_info.rs b/crates/api_models/src/payments/additional_info.rs new file mode 100644 index 000000000000..77500b0edc5c --- /dev/null +++ b/crates/api_models/src/payments/additional_info.rs @@ -0,0 +1,213 @@ +use common_utils::new_type::{ + MaskedBankAccount, MaskedIban, MaskedRoutingNumber, MaskedSortCode, MaskedUpiVpaId, +}; +use masking::Secret; +use utoipa::ToSchema; + +use crate::enums as api_enums; + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum BankDebitAdditionalData { + Ach(Box), + Bacs(Box), + Becs(Box), + Sepa(Box), +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct AchBankDebitAdditionalData { + /// Partially masked account number for ach bank debit payment + #[schema(value_type = String, example = "0001****3456")] + pub account_number: MaskedBankAccount, + + /// Partially masked routing number for ach bank debit payment + #[schema(value_type = String, example = "110***000")] + pub routing_number: MaskedRoutingNumber, + + /// Card holder's name + #[schema(value_type = Option, example = "John Doe")] + pub card_holder_name: Option>, + + /// Bank account's owner name + #[schema(value_type = Option, example = "John Doe")] + pub bank_account_holder_name: Option>, + + /// Name of the bank + #[schema(value_type = Option, example = "ach")] + pub bank_name: Option, + + /// Bank account type + #[schema(value_type = Option, example = "checking")] + pub bank_type: Option, + + /// Bank holder entity type + #[schema(value_type = Option, example = "personal")] + pub bank_holder_type: Option, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct BacsBankDebitAdditionalData { + /// Partially masked account number for Bacs payment method + #[schema(value_type = String, example = "0001****3456")] + pub account_number: MaskedBankAccount, + + /// Partially masked sort code for Bacs payment method + #[schema(value_type = String, example = "108800")] + pub sort_code: MaskedSortCode, + + /// Bank account's owner name + #[schema(value_type = Option, example = "John Doe")] + pub bank_account_holder_name: Option>, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct BecsBankDebitAdditionalData { + /// Partially masked account number for Becs payment method + #[schema(value_type = String, example = "0001****3456")] + pub account_number: MaskedBankAccount, + + /// Bank-State-Branch (bsb) number + #[schema(value_type = String, example = "000000")] + pub bsb_number: Secret, + + /// Bank account's owner name + #[schema(value_type = Option, example = "John Doe")] + pub bank_account_holder_name: Option>, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct SepaBankDebitAdditionalData { + /// Partially masked international bank account number (iban) for SEPA + #[schema(value_type = String, example = "DE8937******013000")] + pub iban: MaskedIban, + + /// Bank account's owner name + #[schema(value_type = Option, example = "John Doe")] + pub bank_account_holder_name: Option>, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +pub enum BankRedirectDetails { + BancontactCard(Box), + Blik(Box), + Giropay(Box), +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct BancontactBankRedirectAdditionalData { + /// Last 4 digits of the card number + #[schema(value_type = Option, example = "4242")] + pub last4: Option, + + /// The card's expiry month + #[schema(value_type = Option, example = "12")] + pub card_exp_month: Option>, + + /// The card's expiry year + #[schema(value_type = Option, example = "24")] + pub card_exp_year: Option>, + + /// The card holder's name + #[schema(value_type = Option, example = "John Test")] + pub card_holder_name: Option>, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct BlikBankRedirectAdditionalData { + #[schema(value_type = Option, example = "3GD9MO")] + pub blik_code: Option, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct GiropayBankRedirectAdditionalData { + #[schema(value_type = Option)] + /// Masked bank account bic code + pub bic: Option, + + /// Partially masked international bank account number (iban) for SEPA + #[schema(value_type = Option)] + pub iban: Option, + + /// Country for bank payment + #[schema(value_type = Option, example = "US")] + pub country: Option, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum BankTransferAdditionalData { + Ach {}, + Sepa {}, + Bacs {}, + Multibanco {}, + Permata {}, + Bca {}, + BniVa {}, + BriVa {}, + CimbVa {}, + DanamonVa {}, + MandiriVa {}, + Pix(Box), + Pse {}, + LocalBankTransfer(Box), +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct PixBankTransferAdditionalData { + /// Partially masked unique key for pix transfer + #[schema(value_type = Option, example = "a1f4102e ****** 6fa48899c1d1")] + pub pix_key: Option, + + /// Partially masked CPF - CPF is a Brazilian tax identification number + #[schema(value_type = Option, example = "**** 124689")] + pub cpf: Option, + + /// Partially masked CNPJ - CNPJ is a Brazilian company tax identification number + #[schema(value_type = Option, example = "**** 417312")] + pub cnpj: Option, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct LocalBankTransferAdditionalData { + /// Partially masked bank code + #[schema(value_type = Option, example = "**** OA2312")] + pub bank_code: Option, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum GiftCardAdditionalData { + Givex(Box), + PaySafeCard {}, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct GivexGiftCardAdditionalData { + /// Last 4 digits of the gift card number + #[schema(value_type = String, example = "4242")] + pub last4: Secret, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct CardTokenAdditionalData { + /// The card holder's name + #[schema(value_type = String, example = "John Test")] + pub card_holder_name: Option>, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum UpiAdditionalData { + UpiCollect(Box), + #[schema(value_type = UpiIntentData)] + UpiIntent(Box), +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub struct UpiCollectAdditionalData { + /// Masked VPA ID + #[schema(value_type = Option, example = "ab********@okhdfcbank")] + pub vpa_id: Option, +} diff --git a/crates/api_models/src/payouts.rs b/crates/api_models/src/payouts.rs index aad0cfcf3031..01bb63642bea 100644 --- a/crates/api_models/src/payouts.rs +++ b/crates/api_models/src/payouts.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use cards::CardNumber; use common_utils::{ consts::default_payouts_list_limit, @@ -5,11 +7,12 @@ use common_utils::{ pii::{self, Email}, }; use masking::Secret; +use router_derive::FlatStruct; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; use utoipa::ToSchema; -use crate::{enums as api_enums, payments}; +use crate::{enums as api_enums, payment_methods::RequiredFieldInfo, payments}; #[derive(Debug, Deserialize, Serialize, Clone, ToSchema)] pub enum PayoutRequest { @@ -204,6 +207,10 @@ pub struct PayoutCreatePayoutLinkConfig { #[schema(value_type = Option>, example = r#"[{"payment_method": "bank_transfer", "payment_method_types": ["ach", "bacs"]}]"#)] pub enabled_payment_methods: Option>, + /// Form layout of the payout link + #[schema(value_type = Option, max_length = 255, example = "tabs")] + pub form_layout: Option, + /// `test_mode` allows for opening payout links without any restrictions. This removes /// - domain name validations /// - check for making sure link is accessed within an iframe @@ -411,7 +418,7 @@ pub struct PayoutCreateResponse { pub payout_type: Option, /// The billing address for the payout - #[schema(value_type = Option, example = json!(r#"{ + #[schema(value_type = Option
, example = json!(r#"{ "address": { "line1": "1467", "line2": "Harrison Street", @@ -778,12 +785,31 @@ pub struct PayoutLinkDetails { #[serde(flatten)] pub ui_config: link_utils::GenericLinkUiConfigFormData, pub enabled_payment_methods: Vec, + pub enabled_payment_methods_with_required_fields: Vec, pub amount: common_utils::types::StringMajorUnit, pub currency: common_enums::Currency, pub locale: String, + pub form_layout: Option, pub test_mode: bool, } +#[derive(Clone, Debug, serde::Serialize)] +pub struct PayoutEnabledPaymentMethodsInfo { + pub payment_method: common_enums::PaymentMethod, + pub payment_method_types_info: Vec, +} + +#[derive(Clone, Debug, serde::Serialize)] +pub struct PaymentMethodTypeInfo { + pub payment_method_type: common_enums::PaymentMethodType, + pub required_fields: Option>, +} + +#[derive(Clone, Debug, serde::Serialize, FlatStruct)] +pub struct RequiredFieldsOverrideRequest { + pub billing: Option, +} + #[derive(Clone, Debug, serde::Serialize)] pub struct PayoutLinkStatusDetails { pub payout_link_id: String, diff --git a/crates/api_models/src/recon.rs b/crates/api_models/src/recon.rs index 3aafcc413d24..afee0fb5626a 100644 --- a/crates/api_models/src/recon.rs +++ b/crates/api_models/src/recon.rs @@ -5,7 +5,6 @@ use crate::enums; #[derive(serde::Deserialize, Debug, serde::Serialize)] pub struct ReconUpdateMerchantRequest { - pub merchant_id: common_utils::id_type::MerchantId, pub recon_status: enums::ReconStatus, pub user_email: pii::Email, } diff --git a/crates/api_models/src/refunds.rs b/crates/api_models/src/refunds.rs index 649c0dbf5cf3..edc0e75bdf27 100644 --- a/crates/api_models/src/refunds.rs +++ b/crates/api_models/src/refunds.rs @@ -234,6 +234,12 @@ pub struct RefundListFilters { pub refund_status: Vec, } +#[derive(Clone, Debug, serde::Serialize, ToSchema)] +pub struct RefundAggregateResponse { + /// The list of refund status with their count + pub status_with_count: HashMap, +} + /// The status for refunds #[derive( Debug, diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs index 4674abf39763..7bb8409993d4 100644 --- a/crates/api_models/src/user.rs +++ b/crates/api_models/src/user.rs @@ -1,6 +1,6 @@ use std::fmt::Debug; -use common_enums::{PermissionGroup, RoleScope, TokenPurpose}; +use common_enums::{EntityType, PermissionGroup, RoleScope, TokenPurpose}; use common_utils::{crypto::OptionalEncryptableName, id_type, pii}; use masking::Secret; @@ -158,6 +158,8 @@ pub struct GetUserDetailsResponse { pub org_id: id_type::OrganizationId, pub is_two_factor_auth_setup: bool, pub recovery_codes_left: Option, + pub profile_id: id_type::ProfileId, + pub entity_type: EntityType, } #[derive(Debug, serde::Deserialize, serde::Serialize)] @@ -185,7 +187,7 @@ pub struct GetUserRoleDetailsResponseV2 { pub merchant: Option>, pub profile: Option>, pub status: UserStatus, - pub entity_type: common_enums::EntityType, + pub entity_type: EntityType, } #[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] diff --git a/crates/api_models/src/user_role.rs b/crates/api_models/src/user_role.rs index 05087d09e808..8e1a0483c07c 100644 --- a/crates/api_models/src/user_role.rs +++ b/crates/api_models/src/user_role.rs @@ -37,6 +37,7 @@ pub enum Permission { PayoutRead, WebhookEventWrite, GenerateReport, + ReconAdmin, } #[derive(Clone, Debug, serde::Serialize, PartialEq, Eq, Hash)] @@ -50,6 +51,7 @@ pub enum ParentGroup { Merchant, #[serde(rename = "OrganizationAccess")] Organization, + Recon, } #[derive(Debug, serde::Serialize)] @@ -67,6 +69,7 @@ pub enum PermissionModule { SurchargeDecisionManager, AccountCreate, Payouts, + Recon, } #[derive(Debug, serde::Serialize)] diff --git a/crates/api_models/src/user_role/role.rs b/crates/api_models/src/user_role/role.rs index 828dfeb20f83..22aa80a459af 100644 --- a/crates/api_models/src/user_role/role.rs +++ b/crates/api_models/src/user_role/role.rs @@ -63,5 +63,5 @@ pub enum RoleCheckType { #[derive(Debug, serde::Serialize, Clone)] pub struct MinimalRoleInfo { pub role_id: String, - pub role_name: String, + pub role_name: Option, } diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 56d86d34bc50..10c9dae2cc10 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -208,7 +208,7 @@ pub enum RoutableConnectors { Cryptopay, Cybersource, Datatrans, - // Deutschebank, + Deutschebank, Dlocal, Ebanx, Fiserv, @@ -228,7 +228,7 @@ pub enum RoutableConnectors { Nexinets, Nmi, Noon, - // Novalnet, + Novalnet, Nuvei, // Opayo, added as template code for future usage Opennode, @@ -251,6 +251,7 @@ pub enum RoutableConnectors { Stripe, // Taxjar, Trustpay, + // Thunes // Tsys, Tsys, Volt, @@ -402,6 +403,26 @@ pub enum AuthorizationStatus { Unresolved, } +// #[derive( +// Clone, +// Debug, +// Eq, +// PartialEq, +// serde::Deserialize, +// serde::Serialize, +// strum::Display, +// strum::EnumString, +// ToSchema, +// Hash, +// )] +// #[router_derive::diesel_enum(storage_type = "text")] +// #[serde(rename_all = "snake_case")] +// #[strum(serialize_all = "snake_case")] +// pub enum SessionUpdateStatus { +// Success, +// Failure, +// } + #[derive( Clone, Debug, @@ -492,6 +513,8 @@ pub enum ConnectorType { PaymentMethodAuth, /// 3DS Authentication Service Providers AuthenticationProcessor, + /// Tax Calculation Processor + TaxProcessor, } #[derive(Debug, Eq, PartialEq)] @@ -2773,6 +2796,7 @@ pub enum PermissionGroup { MerchantDetailsView, MerchantDetailsManage, OrganizationManage, + ReconOps, } /// Name of banks supported by Hyperswitch @@ -2798,6 +2822,7 @@ pub enum BankNames { AllianceBank, AmBank, BankOfAmerica, + BankOfChina, BankIslam, BankMuamalat, BankRakyat, @@ -3136,6 +3161,27 @@ pub enum OrderFulfillmentTimeOrigin { Confirm, } +#[derive( + Clone, + Copy, + Debug, + Eq, + PartialEq, + serde::Deserialize, + serde::Serialize, + strum::Display, + strum::EnumString, + ToSchema, + Hash, +)] +#[router_derive::diesel_enum(storage_type = "db_enum")] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum UIWidgetFormLayout { + Tabs, + Journey, +} + #[derive( Clone, Copy, diff --git a/crates/common_utils/Cargo.toml b/crates/common_utils/Cargo.toml index eee1bd573ef2..c6e29801b2f7 100644 --- a/crates/common_utils/Cargo.toml +++ b/crates/common_utils/Cargo.toml @@ -12,6 +12,7 @@ default = ["v1"] keymanager = ["dep:router_env"] keymanager_mtls = ["reqwest/rustls-tls"] encryption_service = ["dep:router_env"] +km_forward_x_request_id = ["dep:router_env", "router_env/actix_web"] signals = ["dep:signal-hook-tokio", "dep:signal-hook", "dep:tokio", "dep:router_env", "dep:futures"] async_ext = ["dep:async-trait", "dep:futures"] logs = ["dep:router_env"] diff --git a/crates/common_utils/src/consts.rs b/crates/common_utils/src/consts.rs index f59fedf5c4e6..299e70dc4c98 100644 --- a/crates/common_utils/src/consts.rs +++ b/crates/common_utils/src/consts.rs @@ -89,9 +89,6 @@ pub const DEFAULT_ENABLE_SAVED_PAYMENT_METHOD: bool = false; /// Default allowed domains for payment links pub const DEFAULT_ALLOWED_DOMAINS: Option> = None; -/// Default merchant details for payment links -pub const DEFAULT_TRANSACTION_DETAILS: Option = None; - /// Default ttl for Extended card info in redis (in seconds) pub const DEFAULT_TTL_FOR_EXTENDED_CARD_INFO: u16 = 15 * 60; diff --git a/crates/common_utils/src/id_type/global_id.rs b/crates/common_utils/src/id_type/global_id.rs index dcfb9998d3e2..f04bfc3f92b1 100644 --- a/crates/common_utils/src/id_type/global_id.rs +++ b/crates/common_utils/src/id_type/global_id.rs @@ -11,7 +11,7 @@ use crate::{ id_type::{AlphaNumericId, AlphaNumericIdError, LengthId, LengthIdError}, }; -#[derive(Debug, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Hash, PartialEq, Eq, serde::Serialize)] /// A global id that can be used to identify any entity /// This id will have information about the entity and cell in a distributed system architecture pub(crate) struct GlobalId(LengthId); diff --git a/crates/common_utils/src/id_type/payment.rs b/crates/common_utils/src/id_type/payment.rs index 87e6eff29f10..e0269f2beffe 100644 --- a/crates/common_utils/src/id_type/payment.rs +++ b/crates/common_utils/src/id_type/payment.rs @@ -1,7 +1,7 @@ use crate::{ errors::{CustomResult, ValidationError}, generate_id_with_default_len, - id_type::{AlphaNumericId, LengthId}, + id_type::{global_id, AlphaNumericId, LengthId}, }; crate::id_type!( @@ -18,6 +18,20 @@ crate::impl_try_from_cow_str_id_type!(PaymentId, "payment_id"); crate::impl_queryable_id_type!(PaymentId); crate::impl_to_sql_from_sql_id_type!(PaymentId); +/// A global id that can be used to identify a payment +#[derive( + Debug, + Clone, + Hash, + PartialEq, + Eq, + serde::Serialize, + serde::Deserialize, + diesel::expression::AsExpression, +)] +#[diesel(sql_type = diesel::sql_types::Text)] +pub struct PaymentGlobalId(global_id::GlobalId); + impl PaymentId { /// Get the hash key to be stored in redis pub fn get_hash_key_for_kv_store(&self) -> String { @@ -75,12 +89,3 @@ impl From for router_env::opentelemetry::Value { Self::String(router_env::opentelemetry::StringValue::from(string_value)) } } - -// #[cfg(feature = "metrics")] -// /// This is implemented so that we can use payment id directly as attribute in metrics -// impl router_env::tracing::Value for PaymentId { -// fn record(&self, key: &router_env::types::Field, visitor: &mut dyn router_env::types::Visit) { -// let string_value = self.get_string_repr(); -// visitor.record_str(key, &string_value); -// } -// } diff --git a/crates/common_utils/src/keymanager.rs b/crates/common_utils/src/keymanager.rs index 9d6e93150741..53761951791c 100644 --- a/crates/common_utils/src/keymanager.rs +++ b/crates/common_utils/src/keymanager.rs @@ -23,6 +23,8 @@ use crate::{ const CONTENT_TYPE: &str = "Content-Type"; static ENCRYPTION_API_CLIENT: OnceCell = OnceCell::new(); static DEFAULT_ENCRYPTION_VERSION: &str = "v1"; +#[cfg(feature = "km_forward_x_request_id")] +const X_REQUEST_ID: &str = "X-Request-Id"; /// Get keymanager client constructed from the url and state #[instrument(skip_all)] @@ -104,18 +106,25 @@ where let url = format!("{}/{endpoint}", &state.url); logger::info!(key_manager_request=?request_body); - + let mut header = vec![]; + header.push(( + HeaderName::from_str(CONTENT_TYPE) + .change_context(errors::KeyManagerClientError::FailedtoConstructHeader)?, + HeaderValue::from_str("application/json") + .change_context(errors::KeyManagerClientError::FailedtoConstructHeader)?, + )); + #[cfg(feature = "km_forward_x_request_id")] + if let Some(request_id) = state.request_id { + header.push(( + HeaderName::from_str(X_REQUEST_ID) + .change_context(errors::KeyManagerClientError::FailedtoConstructHeader)?, + HeaderValue::from_str(request_id.as_hyphenated().to_string().as_str()) + .change_context(errors::KeyManagerClientError::FailedtoConstructHeader)?, + )) + } let response = send_encryption_request( state, - HeaderMap::from_iter( - vec![( - HeaderName::from_str(CONTENT_TYPE) - .change_context(errors::KeyManagerClientError::FailedtoConstructHeader)?, - HeaderValue::from_str("application/json") - .change_context(errors::KeyManagerClientError::FailedtoConstructHeader)?, - )] - .into_iter(), - ), + HeaderMap::from_iter(header.into_iter()), url, method, request_body, diff --git a/crates/common_utils/src/link_utils.rs b/crates/common_utils/src/link_utils.rs index dc7153f2c5b6..2201000d0622 100644 --- a/crates/common_utils/src/link_utils.rs +++ b/crates/common_utils/src/link_utils.rs @@ -2,7 +2,7 @@ use std::{collections::HashSet, primitive::i64}; -use common_enums::enums; +use common_enums::{enums, UIWidgetFormLayout}; use diesel::{ backend::Backend, deserialize, @@ -167,6 +167,8 @@ pub struct PayoutLinkData { pub currency: enums::Currency, /// A list of allowed domains (glob patterns) where this link can be embedded / opened from pub allowed_domains: HashSet, + /// Form layout of the payout link + pub form_layout: Option, /// `test_mode` can be used for testing payout links without any restrictions pub test_mode: Option, } diff --git a/crates/common_utils/src/new_type.rs b/crates/common_utils/src/new_type.rs index 01d23448d223..9d05e0be2763 100644 --- a/crates/common_utils/src/new_type.rs +++ b/crates/common_utils/src/new_type.rs @@ -1,5 +1,7 @@ //! Contains new types with restrictions -use crate::consts::MAX_ALLOWED_MERCHANT_NAME_LENGTH; +use masking::{ExposeInterface, Secret}; + +use crate::{consts::MAX_ALLOWED_MERCHANT_NAME_LENGTH, pii::UpiVpaMaskingStrategy}; #[nutype::nutype( derive(Clone, Serialize, Deserialize, Debug), @@ -8,3 +10,202 @@ use crate::consts::MAX_ALLOWED_MERCHANT_NAME_LENGTH; pub struct MerchantName(String); impl masking::SerializableSecret for MerchantName {} + +/// Function for masking alphanumeric characters in a string. +/// +/// # Arguments +/// `val` +/// - holds reference to the string to be masked. +/// `unmasked_char_count` +/// - minimum character count to remain unmasked for identification +/// - this number is for keeping the characters unmasked from +/// both beginning (if feasible) and ending of the string. +/// `min_masked_char_count` +/// - this ensures the minimum number of characters to be masked +/// +/// # Behaviour +/// - Returns the original string if its length is less than or equal to `unmasked_char_count`. +/// - If the string length allows, keeps `unmasked_char_count` characters unmasked at both start and end. +/// - Otherwise, keeps `unmasked_char_count` characters unmasked only at the end. +/// - Only alphanumeric characters are masked; other characters remain unchanged. +/// +/// # Examples +/// Sort Code +/// (12-34-56, 2, 2) -> 12-**-56 +/// Routing number +/// (026009593, 3, 3) -> 026***593 +/// CNPJ +/// (12345678901, 4, 4) -> *******8901 +/// CNPJ +/// (12345678901, 4, 3) -> 1234***8901 +/// Pix key +/// (123e-a452-1243-1244-000, 4, 4) -> 123e-****-****-****-000 +/// IBAN +/// (AL35202111090000000001234567, 5, 5) -> AL352******************34567 +fn apply_mask(val: &str, unmasked_char_count: usize, min_masked_char_count: usize) -> String { + let len = val.len(); + if len <= unmasked_char_count { + return val.to_string(); + } + + let mask_start_index = + // For showing only last `unmasked_char_count` characters + if len < (unmasked_char_count * 2 + min_masked_char_count) { + 0 + // For showing first and last `unmasked_char_count` characters + } else { + unmasked_char_count + }; + let mask_end_index = len - unmasked_char_count - 1; + let range = mask_start_index..=mask_end_index; + + val.chars() + .enumerate() + .fold(String::new(), |mut acc, (index, ch)| { + if ch.is_alphanumeric() && range.contains(&index) { + acc.push('*'); + } else { + acc.push(ch); + } + acc + }) +} + +/// Masked sort code +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct MaskedSortCode(Secret); +impl From for MaskedSortCode { + fn from(src: String) -> Self { + let masked_value = apply_mask(src.as_ref(), 2, 2); + Self(Secret::from(masked_value)) + } +} +impl From> for MaskedSortCode { + fn from(secret: Secret) -> Self { + Self::from(secret.expose()) + } +} + +/// Masked Routing number +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct MaskedRoutingNumber(Secret); +impl From for MaskedRoutingNumber { + fn from(src: String) -> Self { + let masked_value = apply_mask(src.as_ref(), 3, 3); + Self(Secret::from(masked_value)) + } +} +impl From> for MaskedRoutingNumber { + fn from(secret: Secret) -> Self { + Self::from(secret.expose()) + } +} + +/// Masked bank account +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct MaskedBankAccount(Secret); +impl From for MaskedBankAccount { + fn from(src: String) -> Self { + let masked_value = apply_mask(src.as_ref(), 4, 4); + Self(Secret::from(masked_value)) + } +} +impl From> for MaskedBankAccount { + fn from(secret: Secret) -> Self { + Self::from(secret.expose()) + } +} + +/// Masked IBAN +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct MaskedIban(Secret); +impl From for MaskedIban { + fn from(src: String) -> Self { + let masked_value = apply_mask(src.as_ref(), 5, 5); + Self(Secret::from(masked_value)) + } +} +impl From> for MaskedIban { + fn from(secret: Secret) -> Self { + Self::from(secret.expose()) + } +} + +/// Masked UPI ID +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct MaskedUpiVpaId(Secret); +impl From for MaskedUpiVpaId { + fn from(src: String) -> Self { + let unmasked_char_count = 2; + let masked_value = if let Some((user_identifier, bank_or_psp)) = src.split_once('@') { + let masked_user_identifier = user_identifier + .to_string() + .chars() + .take(unmasked_char_count) + .collect::() + + &"*".repeat(user_identifier.len() - unmasked_char_count); + format!("{}@{}", masked_user_identifier, bank_or_psp) + } else { + let masked_value = apply_mask(src.as_ref(), unmasked_char_count, 8); + masked_value + }; + + Self(Secret::from(masked_value)) + } +} +impl From> for MaskedUpiVpaId { + fn from(secret: Secret) -> Self { + Self::from(secret.expose()) + } +} + +#[cfg(test)] +mod apply_mask_fn_test { + use masking::PeekInterface; + + use crate::new_type::{ + apply_mask, MaskedBankAccount, MaskedIban, MaskedRoutingNumber, MaskedSortCode, + MaskedUpiVpaId, + }; + #[test] + fn test_masked_types() { + let sort_code = MaskedSortCode::from("110011".to_string()); + let routing_number = MaskedRoutingNumber::from("056008849".to_string()); + let bank_account = MaskedBankAccount::from("12345678901234".to_string()); + let iban = MaskedIban::from("NL02ABNA0123456789".to_string()); + let upi_vpa = MaskedUpiVpaId::from("someusername@okhdfcbank".to_string()); + + // Standard masked data tests + assert_eq!(sort_code.0.peek().to_owned(), "11**11".to_string()); + assert_eq!(routing_number.0.peek().to_owned(), "056***849".to_string()); + assert_eq!( + bank_account.0.peek().to_owned(), + "1234******1234".to_string() + ); + assert_eq!(iban.0.peek().to_owned(), "NL02A********56789".to_string()); + assert_eq!( + upi_vpa.0.peek().to_owned(), + "so**********@okhdfcbank".to_string() + ); + } + + #[test] + fn test_apply_mask_fn() { + let value = "12345678901".to_string(); + + // Generic masked tests + assert_eq!(apply_mask(&value, 2, 2), "12*******01".to_string()); + assert_eq!(apply_mask(&value, 3, 2), "123*****901".to_string()); + assert_eq!(apply_mask(&value, 3, 3), "123*****901".to_string()); + assert_eq!(apply_mask(&value, 4, 3), "1234***8901".to_string()); + assert_eq!(apply_mask(&value, 4, 4), "*******8901".to_string()); + assert_eq!(apply_mask(&value, 5, 4), "******78901".to_string()); + assert_eq!(apply_mask(&value, 5, 5), "******78901".to_string()); + assert_eq!(apply_mask(&value, 6, 5), "*****678901".to_string()); + assert_eq!(apply_mask(&value, 6, 6), "*****678901".to_string()); + assert_eq!(apply_mask(&value, 7, 6), "****5678901".to_string()); + assert_eq!(apply_mask(&value, 7, 7), "****5678901".to_string()); + assert_eq!(apply_mask(&value, 8, 7), "***45678901".to_string()); + assert_eq!(apply_mask(&value, 8, 8), "***45678901".to_string()); + } +} diff --git a/crates/common_utils/src/request.rs b/crates/common_utils/src/request.rs index 264179fc6037..38244e2a3ded 100644 --- a/crates/common_utils/src/request.rs +++ b/crates/common_utils/src/request.rs @@ -152,6 +152,11 @@ impl RequestBuilder { self } + pub fn set_optional_body>(mut self, body: Option) -> Self { + body.map(|body| self.body.replace(body.into())); + self + } + pub fn set_body>(mut self, body: T) -> Self { self.body.replace(body.into()); self diff --git a/crates/common_utils/src/types/keymanager.rs b/crates/common_utils/src/types/keymanager.rs index 05305b537071..abcb7aa87f7e 100644 --- a/crates/common_utils/src/types/keymanager.rs +++ b/crates/common_utils/src/types/keymanager.rs @@ -6,6 +6,8 @@ use base64::Engine; use masking::{ExposeInterface, PeekInterface, Secret, Strategy, StrongSecret}; #[cfg(feature = "encryption_service")] use router_env::logger; +#[cfg(feature = "km_forward_x_request_id")] +use router_env::tracing_actix_web::RequestId; use rustc_hash::FxHashMap; use serde::{ de::{self, Unexpected, Visitor}, @@ -26,6 +28,8 @@ pub struct KeyManagerState { pub enabled: Option, pub url: String, pub client_idle_timeout: Option, + #[cfg(feature = "km_forward_x_request_id")] + pub request_id: Option, #[cfg(feature = "keymanager_mtls")] pub ca: Secret, #[cfg(feature = "keymanager_mtls")] diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index cb13ec9d86d7..3ead246702c6 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -139,6 +139,7 @@ pub struct ConnectorTomlConfig { pub gift_card: Option>, pub card_redirect: Option>, pub is_verifiable: Option, + pub real_time_payment: Option>, } #[serde_with::skip_serializing_none] #[derive(Debug, Deserialize, serde::Serialize, Clone)] @@ -170,6 +171,7 @@ pub struct ConnectorConfig { pub opennode: Option, pub bambora: Option, pub datatrans: Option, + pub deutschebank: Option, pub dlocal: Option, pub ebanx_payout: Option, pub fiserv: Option, @@ -188,6 +190,7 @@ pub struct ConnectorConfig { pub nexinets: Option, pub nmi: Option, pub noon: Option, + pub novalnet: Option, pub nuvei: Option, pub paybox: Option, pub payme: Option, @@ -226,6 +229,7 @@ pub struct ConnectorConfig { pub paypal_test: Option, pub zen: Option, pub zsl: Option, + pub taxjar: Option, } impl ConnectorConfig { @@ -319,6 +323,7 @@ impl ConnectorConfig { Connector::Opennode => Ok(connector_data.opennode), Connector::Bambora => Ok(connector_data.bambora), Connector::Datatrans => Ok(connector_data.datatrans), + Connector::Deutschebank => Ok(connector_data.deutschebank), Connector::Dlocal => Ok(connector_data.dlocal), Connector::Ebanx => Ok(connector_data.ebanx_payout), Connector::Fiserv => Ok(connector_data.fiserv), @@ -337,6 +342,7 @@ impl ConnectorConfig { Connector::Nexinets => Ok(connector_data.nexinets), Connector::Prophetpay => Ok(connector_data.prophetpay), Connector::Nmi => Ok(connector_data.nmi), + Connector::Novalnet => Ok(connector_data.novalnet), Connector::Noon => Ok(connector_data.noon), Connector::Nuvei => Ok(connector_data.nuvei), Connector::Paybox => Ok(connector_data.paybox), @@ -357,6 +363,7 @@ impl ConnectorConfig { Connector::Stripe => Ok(connector_data.stripe), Connector::Trustpay => Ok(connector_data.trustpay), Connector::Threedsecureio => Ok(connector_data.threedsecureio), + Connector::Taxjar => Ok(connector_data.taxjar), Connector::Tsys => Ok(connector_data.tsys), Connector::Volt => Ok(connector_data.volt), Connector::Wellsfargo => Ok(connector_data.wellsfargo), diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index 06825a107a26..41bcb52704a0 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -1353,6 +1353,14 @@ api_key="Key" key1="Merchant ID" api_secret="Shared Secret" +[deutschebank] +[[deutschebank.bank_debit]] + payment_method_type = "sepa" +[deutschebank.connector_auth.SignatureKey] +api_key="Client ID" +key1="Merchant ID" +api_secret="Client Key" + [dlocal] [[dlocal.credit]] payment_method_type = "Mastercard" @@ -2191,6 +2199,48 @@ placeholder="Enter Google Pay Merchant Key" required=true type="Text" +[novalnet] +[[novalnet.credit]] + payment_method_type = "Mastercard" +[[novalnet.credit]] + payment_method_type = "Visa" +[[novalnet.credit]] + payment_method_type = "Interac" +[[novalnet.credit]] + payment_method_type = "AmericanExpress" +[[novalnet.credit]] + payment_method_type = "JCB" +[[novalnet.credit]] + payment_method_type = "DinersClub" +[[novalnet.credit]] + payment_method_type = "Discover" +[[novalnet.credit]] + payment_method_type = "CartesBancaires" +[[novalnet.credit]] + payment_method_type = "UnionPay" +[[novalnet.debit]] + payment_method_type = "Mastercard" +[[novalnet.debit]] + payment_method_type = "Visa" +[[novalnet.debit]] + payment_method_type = "Interac" +[[novalnet.debit]] + payment_method_type = "AmericanExpress" +[[novalnet.debit]] + payment_method_type = "JCB" +[[novalnet.debit]] + payment_method_type = "DinersClub" +[[novalnet.debit]] + payment_method_type = "Discover" +[[novalnet.debit]] + payment_method_type = "CartesBancaires" +[[novalnet.debit]] + payment_method_type = "UnionPay" +[novalnet.connector_auth.SignatureKey] +api_key="Product Activation Key" +key1="Payment Access Key" +api_secret="Tariff ID" + [nuvei] [[nuvei.credit]] payment_method_type = "Mastercard" @@ -4027,6 +4077,10 @@ api_secret="Shared Secret" payment_method_type = "CartesBancaires" [[fiuu.debit]] payment_method_type = "UnionPay" +[[fiuu.real_time_payment]] + payment_method_type = "duit_now" +[[fiuu.bank_redirect]] + payment_method_type = "online_banking_fpx" [fiuu.connector_auth.SignatureKey] api_key="Verify Key" key1="Merchant ID" diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index 3fc4f8c9f0d5..8d6d2d3a5c63 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -1125,6 +1125,14 @@ placeholder="Enter Acquirer Country Code" required=false type="Text" +[deutschebank] +[[deutschebank.bank_debit]] + payment_method_type = "sepa" +[deutschebank.connector_auth.SignatureKey] +api_key="Client ID" +key1="Merchant ID" +api_secret="Client Key" + [dlocal] [[dlocal.credit]] payment_method_type = "Mastercard" @@ -1702,6 +1710,48 @@ key1="Public Key" [nmi.connector_webhook_details] merchant_secret="Source verification key" +[novalnet] +[[novalnet.credit]] + payment_method_type = "Mastercard" +[[novalnet.credit]] + payment_method_type = "Visa" +[[novalnet.credit]] + payment_method_type = "Interac" +[[novalnet.credit]] + payment_method_type = "AmericanExpress" +[[novalnet.credit]] + payment_method_type = "JCB" +[[novalnet.credit]] + payment_method_type = "DinersClub" +[[novalnet.credit]] + payment_method_type = "Discover" +[[novalnet.credit]] + payment_method_type = "CartesBancaires" +[[novalnet.credit]] + payment_method_type = "UnionPay" +[[novalnet.debit]] + payment_method_type = "Mastercard" +[[novalnet.debit]] + payment_method_type = "Visa" +[[novalnet.debit]] + payment_method_type = "Interac" +[[novalnet.debit]] + payment_method_type = "AmericanExpress" +[[novalnet.debit]] + payment_method_type = "JCB" +[[novalnet.debit]] + payment_method_type = "DinersClub" +[[novalnet.debit]] + payment_method_type = "Discover" +[[novalnet.debit]] + payment_method_type = "CartesBancaires" +[[novalnet.debit]] + payment_method_type = "UnionPay" +[novalnet.connector_auth.SignatureKey] +api_key="Product Activation Key" +key1="Payment Access Key" +api_secret="Tariff ID" + [nuvei] [[nuvei.credit]] payment_method_type = "Mastercard" @@ -3011,6 +3061,10 @@ api_secret="Shared Secret" payment_method_type = "CartesBancaires" [[fiuu.debit]] payment_method_type = "UnionPay" +[[fiuu.real_time_payment]] + payment_method_type = "duit_now" +[[fiuu.bank_redirect]] + payment_method_type = "online_banking_fpx" [fiuu.connector_auth.SignatureKey] api_key="Verify Key" key1="Merchant ID" diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index 9de845f0b503..165682b75019 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -1352,6 +1352,14 @@ api_key="Key" key1="Merchant ID" api_secret="Shared Secret" +[deutschebank] +[[deutschebank.bank_debit]] + payment_method_type = "sepa" +[deutschebank.connector_auth.SignatureKey] +api_key="Client ID" +key1="Merchant ID" +api_secret="Client Key" + [dlocal] [[dlocal.credit]] payment_method_type = "Mastercard" @@ -2187,6 +2195,48 @@ placeholder="Enter Google Pay Merchant Key" required=true type="Text" +[novalnet] +[[novalnet.credit]] + payment_method_type = "Mastercard" +[[novalnet.credit]] + payment_method_type = "Visa" +[[novalnet.credit]] + payment_method_type = "Interac" +[[novalnet.credit]] + payment_method_type = "AmericanExpress" +[[novalnet.credit]] + payment_method_type = "JCB" +[[novalnet.credit]] + payment_method_type = "DinersClub" +[[novalnet.credit]] + payment_method_type = "Discover" +[[novalnet.credit]] + payment_method_type = "CartesBancaires" +[[novalnet.credit]] + payment_method_type = "UnionPay" +[[novalnet.debit]] + payment_method_type = "Mastercard" +[[novalnet.debit]] + payment_method_type = "Visa" +[[novalnet.debit]] + payment_method_type = "Interac" +[[novalnet.debit]] + payment_method_type = "AmericanExpress" +[[novalnet.debit]] + payment_method_type = "JCB" +[[novalnet.debit]] + payment_method_type = "DinersClub" +[[novalnet.debit]] + payment_method_type = "Discover" +[[novalnet.debit]] + payment_method_type = "CartesBancaires" +[[novalnet.debit]] + payment_method_type = "UnionPay" +[novalnet.connector_auth.SignatureKey] +api_key="Product Activation Key" +key1="Payment Access Key" +api_secret="Tariff ID" + [nuvei] [[nuvei.credit]] payment_method_type = "Mastercard" @@ -4020,6 +4070,10 @@ api_secret="Shared Secret" payment_method_type = "CartesBancaires" [[fiuu.debit]] payment_method_type = "UnionPay" +[[fiuu.real_time_payment]] + payment_method_type = "duit_now" +[[fiuu.bank_redirect]] + payment_method_type = "online_banking_fpx" [fiuu.connector_auth.SignatureKey] api_key="Verify Key" key1="Merchant ID" diff --git a/crates/diesel_models/src/business_profile.rs b/crates/diesel_models/src/business_profile.rs index 7883e3b29bf0..029add42ced0 100644 --- a/crates/diesel_models/src/business_profile.rs +++ b/crates/diesel_models/src/business_profile.rs @@ -1,6 +1,6 @@ use std::collections::{HashMap, HashSet}; -use common_enums::AuthenticationConnectors; +use common_enums::{AuthenticationConnectors, UIWidgetFormLayout}; use common_utils::{encryption::Encryption, pii}; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; use masking::Secret; @@ -50,7 +50,7 @@ pub struct BusinessProfile { pub outgoing_webhook_custom_http_headers: Option, pub always_collect_billing_details_from_wallet_connector: Option, pub always_collect_shipping_details_from_wallet_connector: Option, - pub tax_connector_id: Option, + pub tax_connector_id: Option, pub is_tax_connector_enabled: Option, pub version: common_enums::ApiVersion, } @@ -90,7 +90,7 @@ pub struct BusinessProfileNew { pub outgoing_webhook_custom_http_headers: Option, pub always_collect_billing_details_from_wallet_connector: Option, pub always_collect_shipping_details_from_wallet_connector: Option, - pub tax_connector_id: Option, + pub tax_connector_id: Option, pub is_tax_connector_enabled: Option, pub version: common_enums::ApiVersion, } @@ -127,7 +127,7 @@ pub struct BusinessProfileUpdateInternal { pub outgoing_webhook_custom_http_headers: Option, pub always_collect_billing_details_from_wallet_connector: Option, pub always_collect_shipping_details_from_wallet_connector: Option, - pub tax_connector_id: Option, + pub tax_connector_id: Option, pub is_tax_connector_enabled: Option, } @@ -256,7 +256,7 @@ pub struct BusinessProfile { pub outgoing_webhook_custom_http_headers: Option, pub always_collect_billing_details_from_wallet_connector: Option, pub always_collect_shipping_details_from_wallet_connector: Option, - pub tax_connector_id: Option, + pub tax_connector_id: Option, pub is_tax_connector_enabled: Option, pub routing_algorithm_id: Option, pub order_fulfillment_time: Option, @@ -310,7 +310,7 @@ pub struct BusinessProfileNew { pub outgoing_webhook_custom_http_headers: Option, pub always_collect_billing_details_from_wallet_connector: Option, pub always_collect_shipping_details_from_wallet_connector: Option, - pub tax_connector_id: Option, + pub tax_connector_id: Option, pub is_tax_connector_enabled: Option, pub routing_algorithm_id: Option, pub order_fulfillment_time: Option, @@ -350,7 +350,7 @@ pub struct BusinessProfileUpdateInternal { pub outgoing_webhook_custom_http_headers: Option, pub always_collect_billing_details_from_wallet_connector: Option, pub always_collect_shipping_details_from_wallet_connector: Option, - pub tax_connector_id: Option, + pub tax_connector_id: Option, pub is_tax_connector_enabled: Option, pub routing_algorithm_id: Option, pub order_fulfillment_time: Option, @@ -556,6 +556,7 @@ common_utils::impl_to_sql_from_sql_json!(BusinessPaymentLinkConfig); pub struct BusinessPayoutLinkConfig { #[serde(flatten)] pub config: BusinessGenericLinkConfig, + pub form_layout: Option, pub payout_test_mode: Option, } diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index 4e57ec34208f..152037d55fea 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -1,4 +1,4 @@ -use common_utils::{id_type, pii}; +use common_utils::{id_type, pii, types::MinorUnit}; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -81,6 +81,8 @@ pub struct PaymentAttempt { pub profile_id: id_type::ProfileId, pub organization_id: id_type::OrganizationId, pub card_network: Option, + pub shipping_cost: Option, + pub order_tax_amount: Option, } #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] @@ -155,12 +157,27 @@ pub struct PaymentAttempt { pub profile_id: id_type::ProfileId, pub organization_id: id_type::OrganizationId, pub card_network: Option, + pub shipping_cost: Option, + pub order_tax_amount: Option, } impl PaymentAttempt { pub fn get_or_calculate_net_amount(&self) -> i64 { + let shipping_cost = self + .shipping_cost + .unwrap_or(MinorUnit::new(0)) + .get_amount_as_i64(); + let order_tax_amount = self + .order_tax_amount + .unwrap_or(MinorUnit::new(0)) + .get_amount_as_i64(); + self.net_amount.unwrap_or( - self.amount + self.surcharge_amount.unwrap_or(0) + self.tax_amount.unwrap_or(0), + self.amount + + self.surcharge_amount.unwrap_or(0) + + self.tax_amount.unwrap_or(0) + + shipping_cost + + order_tax_amount, ) } } @@ -240,12 +257,22 @@ pub struct PaymentAttemptNew { pub profile_id: id_type::ProfileId, pub organization_id: id_type::OrganizationId, pub card_network: Option, + pub shipping_cost: Option, + pub order_tax_amount: Option, } impl PaymentAttemptNew { - /// returns amount + surcharge_amount + tax_amount + /// returns amount + surcharge_amount + tax_amount (surcharge) + shipping_cost + order_tax_amount pub fn calculate_net_amount(&self) -> i64 { - self.amount + self.surcharge_amount.unwrap_or(0) + self.tax_amount.unwrap_or(0) + let shipping_cost = self + .shipping_cost + .unwrap_or(MinorUnit::new(0)) + .get_amount_as_i64(); + + self.amount + + self.surcharge_amount.unwrap_or(0) + + self.tax_amount.unwrap_or(0) + + shipping_cost } pub fn get_or_calculate_net_amount(&self) -> i64 { @@ -326,6 +353,8 @@ pub enum PaymentAttemptUpdate { client_source: Option, client_version: Option, customer_acceptance: Option, + shipping_cost: Option, + order_tax_amount: Option, }, VoidUpdate { status: storage_enums::AttemptStatus, @@ -499,11 +528,23 @@ pub struct PaymentAttemptUpdateInternal { client_version: Option, customer_acceptance: Option, card_network: Option, + shipping_cost: Option, + order_tax_amount: Option, } impl PaymentAttemptUpdateInternal { pub fn populate_derived_fields(self, source: &PaymentAttempt) -> Self { let mut update_internal = self; + let shipping_cost = update_internal + .shipping_cost + .or(source.shipping_cost) + .unwrap_or(MinorUnit::new(0)) + .get_amount_as_i64(); + let order_tax_amount = update_internal + .order_tax_amount + .or(source.order_tax_amount) + .unwrap_or(MinorUnit::new(0)) + .get_amount_as_i64(); update_internal.net_amount = Some( update_internal.amount.unwrap_or(source.amount) + update_internal @@ -513,7 +554,9 @@ impl PaymentAttemptUpdateInternal { + update_internal .tax_amount .or(source.tax_amount) - .unwrap_or(0), + .unwrap_or(0) + + shipping_cost + + order_tax_amount, ); update_internal.card_network = update_internal .payment_method_data @@ -578,6 +621,8 @@ impl PaymentAttemptUpdate { client_version, customer_acceptance, card_network, + shipping_cost, + order_tax_amount, } = PaymentAttemptUpdateInternal::from(self).populate_derived_fields(&source); PaymentAttempt { amount: amount.unwrap_or(source.amount), @@ -631,6 +676,8 @@ impl PaymentAttemptUpdate { client_version: client_version.or(source.client_version), customer_acceptance: customer_acceptance.or(source.customer_acceptance), card_network: card_network.or(source.card_network), + shipping_cost: shipping_cost.or(source.shipping_cost), + order_tax_amount: order_tax_amount.or(source.order_tax_amount), ..source } } @@ -707,6 +754,8 @@ impl From for PaymentAttemptUpdateInternal { client_version: None, customer_acceptance: None, card_network: None, + shipping_cost: None, + order_tax_amount: None, }, PaymentAttemptUpdate::AuthenticationTypeUpdate { authentication_type, @@ -759,6 +808,8 @@ impl From for PaymentAttemptUpdateInternal { client_version: None, customer_acceptance: None, card_network: None, + shipping_cost: None, + order_tax_amount: None, }, PaymentAttemptUpdate::ConfirmUpdate { amount, @@ -791,6 +842,8 @@ impl From for PaymentAttemptUpdateInternal { client_source, client_version, customer_acceptance, + shipping_cost, + order_tax_amount, } => Self { amount: Some(amount), currency: Some(currency), @@ -839,6 +892,8 @@ impl From for PaymentAttemptUpdateInternal { unified_message: None, charge_id: None, card_network: None, + shipping_cost, + order_tax_amount, }, PaymentAttemptUpdate::VoidUpdate { status, @@ -892,6 +947,8 @@ impl From for PaymentAttemptUpdateInternal { client_version: None, customer_acceptance: None, card_network: None, + shipping_cost: None, + order_tax_amount: None, }, PaymentAttemptUpdate::RejectUpdate { status, @@ -946,6 +1003,8 @@ impl From for PaymentAttemptUpdateInternal { client_version: None, customer_acceptance: None, card_network: None, + shipping_cost: None, + order_tax_amount: None, }, PaymentAttemptUpdate::BlocklistUpdate { status, @@ -1000,6 +1059,8 @@ impl From for PaymentAttemptUpdateInternal { client_version: None, customer_acceptance: None, card_network: None, + shipping_cost: None, + order_tax_amount: None, }, PaymentAttemptUpdate::PaymentMethodDetailsUpdate { payment_method_id, @@ -1052,6 +1113,8 @@ impl From for PaymentAttemptUpdateInternal { client_version: None, customer_acceptance: None, card_network: None, + shipping_cost: None, + order_tax_amount: None, }, PaymentAttemptUpdate::ResponseUpdate { status, @@ -1122,6 +1185,8 @@ impl From for PaymentAttemptUpdateInternal { client_version: None, customer_acceptance: None, card_network: None, + shipping_cost: None, + order_tax_amount: None, }, PaymentAttemptUpdate::ErrorUpdate { connector, @@ -1184,6 +1249,8 @@ impl From for PaymentAttemptUpdateInternal { client_version: None, customer_acceptance: None, card_network: None, + shipping_cost: None, + order_tax_amount: None, }, PaymentAttemptUpdate::StatusUpdate { status, updated_by } => Self { status: Some(status), @@ -1233,6 +1300,8 @@ impl From for PaymentAttemptUpdateInternal { client_version: None, customer_acceptance: None, card_network: None, + shipping_cost: None, + order_tax_amount: None, }, PaymentAttemptUpdate::UpdateTrackers { payment_token, @@ -1291,6 +1360,8 @@ impl From for PaymentAttemptUpdateInternal { client_version: None, customer_acceptance: None, card_network: None, + shipping_cost: None, + order_tax_amount: None, }, PaymentAttemptUpdate::UnresolvedResponseUpdate { status, @@ -1350,6 +1421,8 @@ impl From for PaymentAttemptUpdateInternal { client_version: None, customer_acceptance: None, card_network: None, + shipping_cost: None, + order_tax_amount: None, }, PaymentAttemptUpdate::PreprocessingUpdate { status, @@ -1407,6 +1480,8 @@ impl From for PaymentAttemptUpdateInternal { client_version: None, customer_acceptance: None, card_network: None, + shipping_cost: None, + order_tax_amount: None, }, PaymentAttemptUpdate::CaptureUpdate { multiple_capture_count, @@ -1460,6 +1535,8 @@ impl From for PaymentAttemptUpdateInternal { client_version: None, customer_acceptance: None, card_network: None, + shipping_cost: None, + order_tax_amount: None, }, PaymentAttemptUpdate::AmountToCaptureUpdate { status, @@ -1513,6 +1590,8 @@ impl From for PaymentAttemptUpdateInternal { client_version: None, customer_acceptance: None, card_network: None, + shipping_cost: None, + order_tax_amount: None, }, PaymentAttemptUpdate::ConnectorResponse { authentication_data, @@ -1569,6 +1648,8 @@ impl From for PaymentAttemptUpdateInternal { client_version: None, customer_acceptance: None, card_network: None, + shipping_cost: None, + order_tax_amount: None, }, PaymentAttemptUpdate::IncrementalAuthorizationAmountUpdate { amount, @@ -1621,6 +1702,8 @@ impl From for PaymentAttemptUpdateInternal { client_version: None, customer_acceptance: None, card_network: None, + shipping_cost: None, + order_tax_amount: None, }, PaymentAttemptUpdate::AuthenticationUpdate { status, @@ -1676,6 +1759,8 @@ impl From for PaymentAttemptUpdateInternal { client_version: None, customer_acceptance: None, card_network: None, + shipping_cost: None, + order_tax_amount: None, }, PaymentAttemptUpdate::ManualUpdate { status, @@ -1734,6 +1819,8 @@ impl From for PaymentAttemptUpdateInternal { client_version: None, customer_acceptance: None, card_network: None, + shipping_cost: None, + order_tax_amount: None, }, } } diff --git a/crates/diesel_models/src/payment_intent.rs b/crates/diesel_models/src/payment_intent.rs index 179769f9c34b..42424c9bfffc 100644 --- a/crates/diesel_models/src/payment_intent.rs +++ b/crates/diesel_models/src/payment_intent.rs @@ -1,4 +1,4 @@ -use common_enums::RequestIncrementalAuthorization; +use common_enums::{PaymentMethodType, RequestIncrementalAuthorization}; use common_utils::{encryption::Encryption, pii, types::MinorUnit}; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; use serde::{Deserialize, Serialize}; @@ -12,9 +12,8 @@ use crate::schema_v2::payment_intent; #[cfg(all(feature = "v2", feature = "payment_v2"))] #[derive(Clone, Debug, PartialEq, Identifiable, Queryable, Serialize, Deserialize, Selectable)] -#[diesel(table_name = payment_intent, primary_key(payment_id, merchant_id), check_for_backend(diesel::pg::Pg))] +#[diesel(table_name = payment_intent, primary_key(id), check_for_backend(diesel::pg::Pg))] pub struct PaymentIntent { - pub payment_id: common_utils::id_type::PaymentId, pub merchant_id: common_utils::id_type::MerchantId, pub status: storage_enums::IntentStatus, pub amount: MinorUnit, @@ -24,11 +23,7 @@ pub struct PaymentIntent { pub description: Option, pub return_url: Option, pub metadata: Option, - pub connector_id: Option, - pub shipping_address_id: Option, - pub billing_address_id: Option, pub statement_descriptor_name: Option, - pub statement_descriptor_suffix: Option, #[serde(with = "common_utils::custom_serde::iso8601")] pub created_at: PrimitiveDateTime, #[serde(with = "common_utils::custom_serde::iso8601")] @@ -39,8 +34,6 @@ pub struct PaymentIntent { pub off_session: Option, pub client_secret: Option, pub active_attempt_id: String, - pub business_country: Option, - pub business_label: Option, #[diesel(deserialize_as = super::OptionalDieselArray)] pub order_details: Option>, pub allowed_payment_method_types: Option, @@ -48,28 +41,38 @@ pub struct PaymentIntent { pub feature_metadata: Option, pub attempt_count: i16, pub profile_id: Option, - // Denotes the action(approve or reject) taken by merchant in case of manual review. - // Manual review can occur when the transaction is marked as risky by the frm_processor, payment processor or when there is underpayment/over payment incase of crypto payment - pub merchant_decision: Option, pub payment_link_id: Option, pub payment_confirm_source: Option, pub updated_by: String, pub surcharge_applicable: Option, pub request_incremental_authorization: Option, - pub incremental_authorization_allowed: Option, pub authorization_count: Option, pub session_expiry: Option, - pub fingerprint_id: Option, pub request_external_three_ds_authentication: Option, pub charges: Option, pub frm_metadata: Option, pub customer_details: Option, - pub billing_details: Option, pub merchant_order_reference_id: Option, - pub shipping_details: Option, pub is_payment_processor_token_flow: Option, + pub shipping_cost: Option, pub organization_id: common_utils::id_type::OrganizationId, + pub tax_details: Option, + pub skip_external_tax_calculation: Option, + pub merchant_reference_id: String, + pub billing_address: Option, + pub shipping_address: Option, + pub capture_method: Option, + pub authentication_type: Option, + pub amount_to_capture: Option, + pub prerouting_algorithm: Option, + pub surcharge_amount: Option, + pub tax_on_surcharge: Option, + // Denotes the action(approve or reject) taken by merchant in case of manual review. + // Manual review can occur when the transaction is marked as risky by the frm_processor, payment processor or when there is underpayment/over payment incase of crypto payment + pub frm_merchant_decision: Option, + // TODO: change this to global id + pub id: common_utils::id_type::PaymentId, } #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] @@ -115,7 +118,6 @@ pub struct PaymentIntent { pub merchant_decision: Option, pub payment_link_id: Option, pub payment_confirm_source: Option, - pub updated_by: String, pub surcharge_applicable: Option, pub request_incremental_authorization: Option, @@ -131,9 +133,100 @@ pub struct PaymentIntent { pub merchant_order_reference_id: Option, pub shipping_details: Option, pub is_payment_processor_token_flow: Option, + pub shipping_cost: Option, + pub organization_id: common_utils::id_type::OrganizationId, + pub tax_details: Option, + pub skip_external_tax_calculation: Option, +} + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize, diesel::AsExpression)] +#[diesel(sql_type = diesel::sql_types::Jsonb)] +pub struct TaxDetails { + pub default: Option, + pub payment_method_type: Option, +} + +common_utils::impl_to_sql_from_sql_json!(TaxDetails); + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct PaymentMethodTypeTax { + pub order_tax_amount: MinorUnit, + pub pmt: PaymentMethodType, +} + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct DefaultTax { + pub order_tax_amount: MinorUnit, +} + +#[cfg(all(feature = "v2", feature = "payment_v2"))] +#[derive( + Clone, Debug, PartialEq, Insertable, router_derive::DebugAsDisplay, Serialize, Deserialize, +)] +#[diesel(table_name = payment_intent)] +pub struct PaymentIntentNew { + pub merchant_id: common_utils::id_type::MerchantId, + pub status: storage_enums::IntentStatus, + pub amount: MinorUnit, + pub currency: Option, + pub amount_captured: Option, + pub customer_id: Option, + pub description: Option, + pub return_url: Option, + pub metadata: Option, + pub statement_descriptor_name: Option, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub created_at: PrimitiveDateTime, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub modified_at: PrimitiveDateTime, + #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + pub last_synced: Option, + pub setup_future_usage: Option, + pub off_session: Option, + pub client_secret: Option, + pub active_attempt_id: String, + #[diesel(deserialize_as = super::OptionalDieselArray)] + pub order_details: Option>, + pub allowed_payment_method_types: Option, + pub connector_metadata: Option, + pub feature_metadata: Option, + pub attempt_count: i16, + pub profile_id: Option, + pub payment_link_id: Option, + pub payment_confirm_source: Option, + pub updated_by: String, + pub surcharge_applicable: Option, + pub request_incremental_authorization: Option, + pub authorization_count: Option, + #[serde(with = "common_utils::custom_serde::iso8601::option")] + pub session_expiry: Option, + pub request_external_three_ds_authentication: Option, + pub charges: Option, + pub frm_metadata: Option, + pub customer_details: Option, + pub merchant_order_reference_id: Option, + pub is_payment_processor_token_flow: Option, + pub shipping_cost: Option, pub organization_id: common_utils::id_type::OrganizationId, + pub tax_details: Option, + pub skip_external_tax_calculation: Option, + pub merchant_reference_id: String, + pub billing_address: Option, + pub shipping_address: Option, + pub capture_method: Option, + pub authentication_type: Option, + pub amount_to_capture: Option, + pub prerouting_algorithm: Option, + pub surcharge_amount: Option, + pub tax_on_surcharge: Option, + // Denotes the action(approve or reject) taken by merchant in case of manual review. + // Manual review can occur when the transaction is marked as risky by the frm_processor, payment processor or when there is underpayment/over payment incase of crypto payment + pub frm_merchant_decision: Option, + // TODO: change this to global id + pub id: common_utils::id_type::PaymentId, } +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] #[derive( Clone, Debug, PartialEq, Insertable, router_derive::DebugAsDisplay, Serialize, Deserialize, )] @@ -192,9 +285,92 @@ pub struct PaymentIntentNew { pub merchant_order_reference_id: Option, pub shipping_details: Option, pub is_payment_processor_token_flow: Option, + pub shipping_cost: Option, pub organization_id: common_utils::id_type::OrganizationId, + pub tax_details: Option, + pub skip_external_tax_calculation: Option, } +#[cfg(all(feature = "v2", feature = "payment_v2"))] +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum PaymentIntentUpdate { + ResponseUpdate { + status: storage_enums::IntentStatus, + amount_captured: Option, + // Moved to attempt + // fingerprint_id: Option, + return_url: Option, + updated_by: String, + // Moved to attempt + // incremental_authorization_allowed: Option, + }, + MetadataUpdate { + metadata: serde_json::Value, + updated_by: String, + }, + Update(Box), + PaymentCreateUpdate { + return_url: Option, + status: Option, + customer_id: Option, + shipping_address: Option, + billing_address: Option, + customer_details: Option, + updated_by: String, + }, + MerchantStatusUpdate { + status: storage_enums::IntentStatus, + shipping_address: Option, + billing_address: Option, + updated_by: String, + }, + PGStatusUpdate { + status: storage_enums::IntentStatus, + updated_by: String, + // Moved to attempt + // incremental_authorization_allowed: Option, + }, + PaymentAttemptAndAttemptCountUpdate { + active_attempt_id: String, + attempt_count: i16, + updated_by: String, + }, + StatusAndAttemptUpdate { + status: storage_enums::IntentStatus, + active_attempt_id: String, + attempt_count: i16, + updated_by: String, + }, + ApproveUpdate { + status: storage_enums::IntentStatus, + frm_merchant_decision: Option, + updated_by: String, + }, + RejectUpdate { + status: storage_enums::IntentStatus, + frm_merchant_decision: Option, + updated_by: String, + }, + SurchargeApplicableUpdate { + surcharge_applicable: Option, + updated_by: String, + }, + IncrementalAuthorizationAmountUpdate { + amount: MinorUnit, + }, + AuthorizationCountUpdate { + authorization_count: i32, + }, + CompleteAuthorizeUpdate { + shipping_address: Option, + }, + ManualUpdate { + status: Option, + updated_by: String, + }, +} + +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] #[derive(Debug, Clone, Serialize, Deserialize)] pub enum PaymentIntentUpdate { ResponseUpdate { @@ -268,8 +444,40 @@ pub enum PaymentIntentUpdate { status: Option, updated_by: String, }, + SessionResponseUpdate { + tax_details: TaxDetails, + shipping_address_id: Option, + updated_by: String, + shipping_details: Option, + }, +} + +#[cfg(all(feature = "v2", feature = "payment_v2"))] +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PaymentIntentUpdateFields { + pub amount: MinorUnit, + pub currency: storage_enums::Currency, + pub setup_future_usage: Option, + pub status: storage_enums::IntentStatus, + pub customer_id: Option, + pub shipping_address: Option, + pub billing_address: Option, + pub return_url: Option, + pub description: Option, + pub statement_descriptor_name: Option, + pub order_details: Option>, + pub metadata: Option, + pub payment_confirm_source: Option, + pub updated_by: String, + pub session_expiry: Option, + pub request_external_three_ds_authentication: Option, + pub frm_metadata: Option, + pub customer_details: Option, + pub merchant_order_reference_id: Option, + pub is_payment_processor_token_flow: Option, } +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PaymentIntentUpdateFields { pub amount: MinorUnit, @@ -298,8 +506,45 @@ pub struct PaymentIntentUpdateFields { pub merchant_order_reference_id: Option, pub shipping_details: Option, pub is_payment_processor_token_flow: Option, + pub tax_details: Option, } +#[cfg(all(feature = "v2", feature = "payment_v2"))] +#[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)] +#[diesel(table_name = payment_intent)] +pub struct PaymentIntentUpdateInternal { + pub amount: Option, + pub currency: Option, + pub status: Option, + pub amount_captured: Option, + pub customer_id: Option, + pub return_url: Option, + pub setup_future_usage: Option, + pub off_session: Option, + pub metadata: Option, + pub modified_at: PrimitiveDateTime, + pub active_attempt_id: Option, + pub description: Option, + pub statement_descriptor_name: Option, + #[diesel(deserialize_as = super::OptionalDieselArray)] + pub order_details: Option>, + pub attempt_count: Option, + pub payment_confirm_source: Option, + pub updated_by: String, + pub surcharge_applicable: Option, + pub authorization_count: Option, + pub session_expiry: Option, + pub request_external_three_ds_authentication: Option, + pub frm_metadata: Option, + pub customer_details: Option, + pub billing_address: Option, + pub shipping_address: Option, + pub merchant_order_reference_id: Option, + pub is_payment_processor_token_flow: Option, + pub frm_merchant_decision: Option, +} + +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] #[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)] #[diesel(table_name = payment_intent)] pub struct PaymentIntentUpdateInternal { @@ -339,8 +584,81 @@ pub struct PaymentIntentUpdateInternal { pub merchant_order_reference_id: Option, pub shipping_details: Option, pub is_payment_processor_token_flow: Option, + pub tax_details: Option, } +#[cfg(all(feature = "v2", feature = "payment_v2"))] +impl PaymentIntentUpdate { + pub fn apply_changeset(self, source: PaymentIntent) -> PaymentIntent { + let PaymentIntentUpdateInternal { + amount, + currency, + status, + amount_captured, + customer_id, + return_url, + setup_future_usage, + off_session, + metadata, + modified_at: _, + active_attempt_id, + description, + statement_descriptor_name, + order_details, + attempt_count, + frm_merchant_decision, + payment_confirm_source, + updated_by, + surcharge_applicable, + authorization_count, + session_expiry, + request_external_three_ds_authentication, + frm_metadata, + customer_details, + billing_address, + merchant_order_reference_id, + shipping_address, + is_payment_processor_token_flow, + } = self.into(); + PaymentIntent { + amount: amount.unwrap_or(source.amount), + currency: currency.or(source.currency), + status: status.unwrap_or(source.status), + amount_captured: amount_captured.or(source.amount_captured), + customer_id: customer_id.or(source.customer_id), + return_url: return_url.or(source.return_url), + setup_future_usage: setup_future_usage.or(source.setup_future_usage), + off_session: off_session.or(source.off_session), + metadata: metadata.or(source.metadata), + modified_at: common_utils::date_time::now(), + active_attempt_id: active_attempt_id.unwrap_or(source.active_attempt_id), + description: description.or(source.description), + statement_descriptor_name: statement_descriptor_name + .or(source.statement_descriptor_name), + order_details: order_details.or(source.order_details), + attempt_count: attempt_count.unwrap_or(source.attempt_count), + frm_merchant_decision: frm_merchant_decision.or(source.frm_merchant_decision), + payment_confirm_source: payment_confirm_source.or(source.payment_confirm_source), + updated_by, + surcharge_applicable: surcharge_applicable.or(source.surcharge_applicable), + authorization_count: authorization_count.or(source.authorization_count), + session_expiry: session_expiry.or(source.session_expiry), + request_external_three_ds_authentication: request_external_three_ds_authentication + .or(source.request_external_three_ds_authentication), + frm_metadata: frm_metadata.or(source.frm_metadata), + customer_details: customer_details.or(source.customer_details), + billing_address: billing_address.or(source.billing_address), + shipping_address: shipping_address.or(source.shipping_address), + merchant_order_reference_id: merchant_order_reference_id + .or(source.merchant_order_reference_id), + is_payment_processor_token_flow: is_payment_processor_token_flow + .or(source.is_payment_processor_token_flow), + ..source + } + } +} + +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] impl PaymentIntentUpdate { pub fn apply_changeset(self, source: PaymentIntent) -> PaymentIntent { let PaymentIntentUpdateInternal { @@ -379,6 +697,7 @@ impl PaymentIntentUpdate { merchant_order_reference_id, shipping_details, is_payment_processor_token_flow, + tax_details, } = self.into(); PaymentIntent { amount: amount.unwrap_or(source.amount), @@ -423,11 +742,521 @@ impl PaymentIntentUpdate { shipping_details: shipping_details.or(source.shipping_details), is_payment_processor_token_flow: is_payment_processor_token_flow .or(source.is_payment_processor_token_flow), + tax_details: tax_details.or(source.tax_details), ..source } } } +#[cfg(all(feature = "v2", feature = "payment_v2"))] +impl From for PaymentIntentUpdateInternal { + fn from(payment_intent_update: PaymentIntentUpdate) -> Self { + match payment_intent_update { + PaymentIntentUpdate::MetadataUpdate { + metadata, + updated_by, + } => Self { + metadata: Some(metadata), + modified_at: common_utils::date_time::now(), + updated_by, + amount: None, + currency: None, + status: None, + amount_captured: None, + customer_id: None, + return_url: None, + setup_future_usage: None, + off_session: None, + active_attempt_id: None, + description: None, + statement_descriptor_name: None, + order_details: None, + attempt_count: None, + frm_merchant_decision: None, + payment_confirm_source: None, + surcharge_applicable: None, + authorization_count: None, + session_expiry: None, + request_external_three_ds_authentication: None, + frm_metadata: None, + customer_details: None, + billing_address: None, + merchant_order_reference_id: None, + shipping_address: None, + is_payment_processor_token_flow: None, + }, + PaymentIntentUpdate::Update(value) => Self { + amount: Some(value.amount), + currency: Some(value.currency), + setup_future_usage: value.setup_future_usage, + status: Some(value.status), + customer_id: value.customer_id, + shipping_address: value.shipping_address, + billing_address: value.billing_address, + return_url: value.return_url, + description: value.description, + statement_descriptor_name: value.statement_descriptor_name, + order_details: value.order_details, + metadata: value.metadata, + payment_confirm_source: value.payment_confirm_source, + updated_by: value.updated_by, + session_expiry: value.session_expiry, + request_external_three_ds_authentication: value + .request_external_three_ds_authentication, + frm_metadata: value.frm_metadata, + customer_details: value.customer_details, + merchant_order_reference_id: value.merchant_order_reference_id, + amount_captured: None, + off_session: None, + modified_at: common_utils::date_time::now(), + active_attempt_id: None, + attempt_count: None, + frm_merchant_decision: None, + surcharge_applicable: None, + authorization_count: None, + is_payment_processor_token_flow: value.is_payment_processor_token_flow, + }, + PaymentIntentUpdate::PaymentCreateUpdate { + return_url, + status, + customer_id, + shipping_address, + billing_address, + customer_details, + updated_by, + } => Self { + return_url, + status, + customer_id, + customer_details, + modified_at: common_utils::date_time::now(), + updated_by, + amount: None, + currency: None, + amount_captured: None, + setup_future_usage: None, + off_session: None, + metadata: None, + active_attempt_id: None, + description: None, + statement_descriptor_name: None, + order_details: None, + attempt_count: None, + frm_merchant_decision: None, + payment_confirm_source: None, + surcharge_applicable: None, + authorization_count: None, + session_expiry: None, + request_external_three_ds_authentication: None, + frm_metadata: None, + billing_address, + merchant_order_reference_id: None, + shipping_address, + is_payment_processor_token_flow: None, + }, + PaymentIntentUpdate::PGStatusUpdate { status, updated_by } => Self { + status: Some(status), + modified_at: common_utils::date_time::now(), + updated_by, + amount: None, + currency: None, + amount_captured: None, + customer_id: None, + return_url: None, + setup_future_usage: None, + off_session: None, + metadata: None, + active_attempt_id: None, + description: None, + statement_descriptor_name: None, + order_details: None, + attempt_count: None, + frm_merchant_decision: None, + payment_confirm_source: None, + surcharge_applicable: None, + authorization_count: None, + session_expiry: None, + request_external_three_ds_authentication: None, + frm_metadata: None, + customer_details: None, + billing_address: None, + merchant_order_reference_id: None, + shipping_address: None, + is_payment_processor_token_flow: None, + }, + PaymentIntentUpdate::MerchantStatusUpdate { + status, + billing_address, + shipping_address, + updated_by, + } => Self { + status: Some(status), + modified_at: common_utils::date_time::now(), + updated_by, + amount: None, + currency: None, + amount_captured: None, + customer_id: None, + return_url: None, + setup_future_usage: None, + off_session: None, + metadata: None, + active_attempt_id: None, + description: None, + statement_descriptor_name: None, + order_details: None, + attempt_count: None, + frm_merchant_decision: None, + payment_confirm_source: None, + surcharge_applicable: None, + authorization_count: None, + session_expiry: None, + request_external_three_ds_authentication: None, + frm_metadata: None, + customer_details: None, + billing_address, + merchant_order_reference_id: None, + shipping_address, + is_payment_processor_token_flow: None, + }, + PaymentIntentUpdate::ResponseUpdate { + // amount, + // currency, + status, + amount_captured, + // customer_id, + return_url, + updated_by, + } => Self { + // amount, + // currency: Some(currency), + status: Some(status), + amount_captured, + // customer_id, + return_url, + modified_at: common_utils::date_time::now(), + updated_by, + amount: None, + currency: None, + customer_id: None, + setup_future_usage: None, + off_session: None, + metadata: None, + active_attempt_id: None, + description: None, + statement_descriptor_name: None, + order_details: None, + attempt_count: None, + frm_merchant_decision: None, + payment_confirm_source: None, + surcharge_applicable: None, + authorization_count: None, + session_expiry: None, + request_external_three_ds_authentication: None, + frm_metadata: None, + customer_details: None, + billing_address: None, + merchant_order_reference_id: None, + shipping_address: None, + is_payment_processor_token_flow: None, + }, + PaymentIntentUpdate::PaymentAttemptAndAttemptCountUpdate { + active_attempt_id, + attempt_count, + updated_by, + } => Self { + active_attempt_id: Some(active_attempt_id), + attempt_count: Some(attempt_count), + updated_by, + amount: None, + currency: None, + status: None, + amount_captured: None, + customer_id: None, + return_url: None, + setup_future_usage: None, + off_session: None, + metadata: None, + modified_at: common_utils::date_time::now(), + description: None, + statement_descriptor_name: None, + order_details: None, + frm_merchant_decision: None, + payment_confirm_source: None, + surcharge_applicable: None, + authorization_count: None, + session_expiry: None, + request_external_three_ds_authentication: None, + frm_metadata: None, + customer_details: None, + billing_address: None, + merchant_order_reference_id: None, + shipping_address: None, + is_payment_processor_token_flow: None, + }, + PaymentIntentUpdate::StatusAndAttemptUpdate { + status, + active_attempt_id, + attempt_count, + updated_by, + } => Self { + status: Some(status), + active_attempt_id: Some(active_attempt_id), + attempt_count: Some(attempt_count), + updated_by, + amount: None, + currency: None, + amount_captured: None, + customer_id: None, + return_url: None, + setup_future_usage: None, + off_session: None, + metadata: None, + modified_at: common_utils::date_time::now(), + description: None, + statement_descriptor_name: None, + order_details: None, + frm_merchant_decision: None, + payment_confirm_source: None, + surcharge_applicable: None, + authorization_count: None, + session_expiry: None, + request_external_three_ds_authentication: None, + frm_metadata: None, + customer_details: None, + billing_address: None, + merchant_order_reference_id: None, + shipping_address: None, + is_payment_processor_token_flow: None, + }, + PaymentIntentUpdate::ApproveUpdate { + status, + frm_merchant_decision, + updated_by, + } => Self { + status: Some(status), + frm_merchant_decision, + updated_by, + amount: None, + currency: None, + amount_captured: None, + customer_id: None, + return_url: None, + setup_future_usage: None, + off_session: None, + metadata: None, + modified_at: common_utils::date_time::now(), + active_attempt_id: None, + description: None, + statement_descriptor_name: None, + order_details: None, + attempt_count: None, + payment_confirm_source: None, + surcharge_applicable: None, + authorization_count: None, + session_expiry: None, + request_external_three_ds_authentication: None, + frm_metadata: None, + customer_details: None, + billing_address: None, + merchant_order_reference_id: None, + shipping_address: None, + is_payment_processor_token_flow: None, + }, + PaymentIntentUpdate::RejectUpdate { + status, + frm_merchant_decision, + updated_by, + } => Self { + status: Some(status), + frm_merchant_decision, + updated_by, + amount: None, + currency: None, + amount_captured: None, + customer_id: None, + return_url: None, + setup_future_usage: None, + off_session: None, + metadata: None, + modified_at: common_utils::date_time::now(), + active_attempt_id: None, + description: None, + statement_descriptor_name: None, + order_details: None, + attempt_count: None, + payment_confirm_source: None, + surcharge_applicable: None, + authorization_count: None, + session_expiry: None, + request_external_three_ds_authentication: None, + frm_metadata: None, + customer_details: None, + billing_address: None, + merchant_order_reference_id: None, + shipping_address: None, + is_payment_processor_token_flow: None, + }, + PaymentIntentUpdate::SurchargeApplicableUpdate { + surcharge_applicable, + updated_by, + } => Self { + surcharge_applicable, + updated_by, + amount: None, + currency: None, + status: None, + amount_captured: None, + customer_id: None, + return_url: None, + setup_future_usage: None, + off_session: None, + metadata: None, + modified_at: common_utils::date_time::now(), + active_attempt_id: None, + description: None, + statement_descriptor_name: None, + order_details: None, + attempt_count: None, + frm_merchant_decision: None, + payment_confirm_source: None, + authorization_count: None, + session_expiry: None, + request_external_three_ds_authentication: None, + frm_metadata: None, + customer_details: None, + billing_address: None, + merchant_order_reference_id: None, + shipping_address: None, + is_payment_processor_token_flow: None, + }, + PaymentIntentUpdate::IncrementalAuthorizationAmountUpdate { amount } => Self { + amount: Some(amount), + currency: None, + status: None, + amount_captured: None, + customer_id: None, + return_url: None, + setup_future_usage: None, + off_session: None, + metadata: None, + modified_at: common_utils::date_time::now(), + active_attempt_id: None, + description: None, + statement_descriptor_name: None, + order_details: None, + attempt_count: None, + frm_merchant_decision: None, + payment_confirm_source: None, + updated_by: String::default(), + surcharge_applicable: None, + authorization_count: None, + session_expiry: None, + request_external_three_ds_authentication: None, + frm_metadata: None, + customer_details: None, + billing_address: None, + merchant_order_reference_id: None, + shipping_address: None, + is_payment_processor_token_flow: None, + }, + PaymentIntentUpdate::AuthorizationCountUpdate { + authorization_count, + } => Self { + authorization_count: Some(authorization_count), + amount: None, + currency: None, + status: None, + amount_captured: None, + customer_id: None, + return_url: None, + setup_future_usage: None, + off_session: None, + metadata: None, + modified_at: common_utils::date_time::now(), + active_attempt_id: None, + description: None, + statement_descriptor_name: None, + order_details: None, + attempt_count: None, + frm_merchant_decision: None, + payment_confirm_source: None, + updated_by: String::default(), + surcharge_applicable: None, + session_expiry: None, + request_external_three_ds_authentication: None, + frm_metadata: None, + customer_details: None, + billing_address: None, + merchant_order_reference_id: None, + shipping_address: None, + is_payment_processor_token_flow: None, + }, + PaymentIntentUpdate::CompleteAuthorizeUpdate { shipping_address } => Self { + amount: None, + currency: None, + status: None, + amount_captured: None, + customer_id: None, + return_url: None, + setup_future_usage: None, + off_session: None, + metadata: None, + modified_at: common_utils::date_time::now(), + active_attempt_id: None, + description: None, + statement_descriptor_name: None, + order_details: None, + attempt_count: None, + frm_merchant_decision: None, + payment_confirm_source: None, + updated_by: String::default(), + surcharge_applicable: None, + authorization_count: None, + session_expiry: None, + request_external_three_ds_authentication: None, + frm_metadata: None, + customer_details: None, + billing_address: None, + merchant_order_reference_id: None, + shipping_address, + is_payment_processor_token_flow: None, + }, + PaymentIntentUpdate::ManualUpdate { status, updated_by } => Self { + status, + updated_by, + amount: None, + currency: None, + amount_captured: None, + customer_id: None, + return_url: None, + setup_future_usage: None, + off_session: None, + metadata: None, + modified_at: common_utils::date_time::now(), + active_attempt_id: None, + description: None, + statement_descriptor_name: None, + order_details: None, + attempt_count: None, + frm_merchant_decision: None, + payment_confirm_source: None, + surcharge_applicable: None, + authorization_count: None, + session_expiry: None, + request_external_three_ds_authentication: None, + frm_metadata: None, + customer_details: None, + billing_address: None, + merchant_order_reference_id: None, + shipping_address: None, + is_payment_processor_token_flow: None, + }, + } + } +} + +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] impl From for PaymentIntentUpdateInternal { fn from(payment_intent_update: PaymentIntentUpdate) -> Self { match payment_intent_update { @@ -470,6 +1299,7 @@ impl From for PaymentIntentUpdateInternal { merchant_order_reference_id: None, shipping_details: None, is_payment_processor_token_flow: None, + tax_details: None, }, PaymentIntentUpdate::Update(value) => Self { amount: Some(value.amount), @@ -508,6 +1338,7 @@ impl From for PaymentIntentUpdateInternal { incremental_authorization_allowed: None, authorization_count: None, is_payment_processor_token_flow: value.is_payment_processor_token_flow, + tax_details: None, }, PaymentIntentUpdate::PaymentCreateUpdate { return_url, @@ -553,6 +1384,7 @@ impl From for PaymentIntentUpdateInternal { merchant_order_reference_id: None, shipping_details: None, is_payment_processor_token_flow: None, + tax_details: None, }, PaymentIntentUpdate::PGStatusUpdate { status, @@ -594,6 +1426,7 @@ impl From for PaymentIntentUpdateInternal { merchant_order_reference_id: None, shipping_details: None, is_payment_processor_token_flow: None, + tax_details: None, }, PaymentIntentUpdate::MerchantStatusUpdate { status, @@ -636,6 +1469,7 @@ impl From for PaymentIntentUpdateInternal { merchant_order_reference_id: None, shipping_details: None, is_payment_processor_token_flow: None, + tax_details: None, }, PaymentIntentUpdate::ResponseUpdate { // amount, @@ -686,6 +1520,7 @@ impl From for PaymentIntentUpdateInternal { merchant_order_reference_id: None, shipping_details: None, is_payment_processor_token_flow: None, + tax_details: None, }, PaymentIntentUpdate::PaymentAttemptAndAttemptCountUpdate { active_attempt_id, @@ -727,6 +1562,7 @@ impl From for PaymentIntentUpdateInternal { merchant_order_reference_id: None, shipping_details: None, is_payment_processor_token_flow: None, + tax_details: None, }, PaymentIntentUpdate::StatusAndAttemptUpdate { status, @@ -769,6 +1605,7 @@ impl From for PaymentIntentUpdateInternal { merchant_order_reference_id: None, shipping_details: None, is_payment_processor_token_flow: None, + tax_details: None, }, PaymentIntentUpdate::ApproveUpdate { status, @@ -810,6 +1647,7 @@ impl From for PaymentIntentUpdateInternal { merchant_order_reference_id: None, shipping_details: None, is_payment_processor_token_flow: None, + tax_details: None, }, PaymentIntentUpdate::RejectUpdate { status, @@ -851,6 +1689,7 @@ impl From for PaymentIntentUpdateInternal { merchant_order_reference_id: None, shipping_details: None, is_payment_processor_token_flow: None, + tax_details: None, }, PaymentIntentUpdate::SurchargeApplicableUpdate { surcharge_applicable, @@ -891,6 +1730,7 @@ impl From for PaymentIntentUpdateInternal { merchant_order_reference_id: None, shipping_details: None, is_payment_processor_token_flow: None, + tax_details: None, }, PaymentIntentUpdate::IncrementalAuthorizationAmountUpdate { amount } => Self { amount: Some(amount), @@ -928,6 +1768,7 @@ impl From for PaymentIntentUpdateInternal { merchant_order_reference_id: None, shipping_details: None, is_payment_processor_token_flow: None, + tax_details: None, }, PaymentIntentUpdate::AuthorizationCountUpdate { authorization_count, @@ -967,6 +1808,7 @@ impl From for PaymentIntentUpdateInternal { merchant_order_reference_id: None, shipping_details: None, is_payment_processor_token_flow: None, + tax_details: None, }, PaymentIntentUpdate::CompleteAuthorizeUpdate { shipping_address_id, @@ -1006,6 +1848,7 @@ impl From for PaymentIntentUpdateInternal { merchant_order_reference_id: None, shipping_details: None, is_payment_processor_token_flow: None, + tax_details: None, }, PaymentIntentUpdate::ManualUpdate { status, updated_by } => Self { status, @@ -1043,6 +1886,50 @@ impl From for PaymentIntentUpdateInternal { merchant_order_reference_id: None, shipping_details: None, is_payment_processor_token_flow: None, + tax_details: None, + }, + PaymentIntentUpdate::SessionResponseUpdate { + tax_details, + shipping_address_id, + updated_by, + shipping_details, + } => Self { + shipping_address_id, + amount: None, + tax_details: Some(tax_details), + currency: None, + status: None, + amount_captured: None, + customer_id: None, + return_url: None, + setup_future_usage: None, + off_session: None, + metadata: None, + billing_address_id: None, + modified_at: common_utils::date_time::now(), + active_attempt_id: None, + business_country: None, + business_label: None, + description: None, + statement_descriptor_name: None, + statement_descriptor_suffix: None, + order_details: None, + attempt_count: None, + merchant_decision: None, + payment_confirm_source: None, + updated_by, + surcharge_applicable: None, + incremental_authorization_allowed: None, + authorization_count: None, + session_expiry: None, + fingerprint_id: None, + request_external_three_ds_authentication: None, + frm_metadata: None, + customer_details: None, + billing_details: None, + merchant_order_reference_id: None, + shipping_details, + is_payment_processor_token_flow: None, }, } } diff --git a/crates/diesel_models/src/query/payment_attempt.rs b/crates/diesel_models/src/query/payment_attempt.rs index d8a9d5f40f49..a9d44bca88fb 100644 --- a/crates/diesel_models/src/query/payment_attempt.rs +++ b/crates/diesel_models/src/query/payment_attempt.rs @@ -322,6 +322,7 @@ impl PaymentAttempt { payment_method: Option>, payment_method_type: Option>, authentication_type: Option>, + profile_id_list: Option>, merchant_connector_id: Option>, ) -> StorageResult { let mut filter = ::table() @@ -346,6 +347,9 @@ impl PaymentAttempt { if let Some(merchant_connector_id) = merchant_connector_id { filter = filter.filter(dsl::merchant_connector_id.eq_any(merchant_connector_id)) } + if let Some(profile_id_list) = profile_id_list { + filter = filter.filter(dsl::profile_id.eq_any(profile_id_list)) + } router_env::logger::debug!(query = %debug_query::(&filter).to_string()); db_metrics::track_database_call::<::Table, _, _>( diff --git a/crates/diesel_models/src/query/payment_intent.rs b/crates/diesel_models/src/query/payment_intent.rs index 86069e1c662b..ccf82d280481 100644 --- a/crates/diesel_models/src/query/payment_intent.rs +++ b/crates/diesel_models/src/query/payment_intent.rs @@ -20,6 +20,36 @@ impl PaymentIntentNew { } impl PaymentIntent { + #[cfg(all(feature = "v2", feature = "payment_v2"))] + pub async fn update( + self, + conn: &PgPooledConn, + payment_intent: PaymentIntentUpdate, + ) -> StorageResult { + match generics::generic_update_by_id::<::Table, _, _, _>( + conn, + self.id.to_owned(), + PaymentIntentUpdateInternal::from(payment_intent), + ) + .await + { + Err(error) => match error.current_context() { + errors::DatabaseError::NoFieldsToUpdate => Ok(self), + _ => Err(error), + }, + Ok(payment_intent) => Ok(payment_intent), + } + } + + #[cfg(all(feature = "v2", feature = "payment_v2"))] + pub async fn find_by_global_id( + conn: &PgPooledConn, + id: &common_utils::id_type::PaymentId, + ) -> StorageResult { + generics::generic_find_by_id::<::Table, _, _>(conn, id.to_owned()).await + } + + #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] pub async fn update( self, conn: &PgPooledConn, @@ -44,6 +74,22 @@ impl PaymentIntent { } } + #[cfg(all(feature = "v2", feature = "payment_v2"))] + pub async fn find_by_merchant_reference_id_merchant_id( + conn: &PgPooledConn, + merchant_reference_id: &str, + merchant_id: &common_utils::id_type::MerchantId, + ) -> StorageResult { + generics::generic_find_one::<::Table, _, _>( + conn, + dsl::merchant_id + .eq(merchant_id.to_owned()) + .and(dsl::merchant_reference_id.eq(merchant_reference_id.to_owned())), + ) + .await + } + + #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] pub async fn find_by_payment_id_merchant_id( conn: &PgPooledConn, payment_id: &common_utils::id_type::PaymentId, @@ -58,6 +104,22 @@ impl PaymentIntent { .await } + #[cfg(all(feature = "v2", feature = "payment_v2"))] + pub async fn find_optional_by_merchant_id_merchant_reference_id( + conn: &PgPooledConn, + merchant_reference_id: &str, + merchant_id: &common_utils::id_type::MerchantId, + ) -> StorageResult> { + generics::generic_find_one_optional::<::Table, _, _>( + conn, + dsl::merchant_id + .eq(merchant_id.to_owned()) + .and(dsl::merchant_reference_id.eq(merchant_reference_id.to_owned())), + ) + .await + } + + #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] pub async fn find_optional_by_payment_id_merchant_id( conn: &PgPooledConn, payment_id: &common_utils::id_type::PaymentId, diff --git a/crates/diesel_models/src/query/user/sample_data.rs b/crates/diesel_models/src/query/user/sample_data.rs index ed88ccf46d73..3890ee2cd051 100644 --- a/crates/diesel_models/src/query/user/sample_data.rs +++ b/crates/diesel_models/src/query/user/sample_data.rs @@ -61,6 +61,7 @@ pub async fn insert_refunds( .attach_printable("Error while inserting refunds") } +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] pub async fn delete_payment_intents( conn: &PgPooledConn, merchant_id: &common_utils::id_type::MerchantId, @@ -86,6 +87,33 @@ pub async fn delete_payment_intents( _ => Ok(result), }) } + +#[cfg(all(feature = "v2", feature = "payment_v2"))] +pub async fn delete_payment_intents( + conn: &PgPooledConn, + merchant_id: &common_utils::id_type::MerchantId, +) -> StorageResult> { + let query = diesel::delete(::table()) + .filter(payment_intent_dsl::merchant_id.eq(merchant_id.to_owned())) + .filter(payment_intent_dsl::merchant_reference_id.like("test_%")); + + logger::debug!(query = %debug_query::(&query).to_string()); + + query + .get_results_async(conn) + .await + .change_context(errors::DatabaseError::Others) + .attach_printable("Error while deleting payment intents") + .and_then(|result| match result.len() { + n if n > 0 => { + logger::debug!("{n} records deleted"); + Ok(result) + } + 0 => Err(error_stack::report!(errors::DatabaseError::NotFound) + .attach_printable("No records deleted")), + _ => Ok(result), + }) +} pub async fn delete_payment_attempts( conn: &PgPooledConn, merchant_id: &common_utils::id_type::MerchantId, diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 58cf20636291..6e8918f02a46 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -840,6 +840,8 @@ diesel::table! { organization_id -> Varchar, #[max_length = 32] card_network -> Nullable, + shipping_cost -> Nullable, + order_tax_amount -> Nullable, } } @@ -915,8 +917,11 @@ diesel::table! { merchant_order_reference_id -> Nullable, shipping_details -> Nullable, is_payment_processor_token_flow -> Nullable, + shipping_cost -> Nullable, #[max_length = 32] organization_id -> Varchar, + tax_details -> Nullable, + skip_external_tax_calculation -> Nullable, } } diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index cda3157767e6..bd64c4bb0027 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -820,6 +820,8 @@ diesel::table! { organization_id -> Varchar, #[max_length = 32] card_network -> Nullable, + shipping_cost -> Nullable, + order_tax_amount -> Nullable, } } @@ -827,9 +829,7 @@ diesel::table! { use diesel::sql_types::*; use crate::enums::diesel_exports::*; - payment_intent (payment_id, merchant_id) { - #[max_length = 64] - payment_id -> Varchar, + payment_intent (id) { #[max_length = 64] merchant_id -> Varchar, status -> IntentStatus, @@ -843,16 +843,8 @@ diesel::table! { #[max_length = 255] return_url -> Nullable, metadata -> Nullable, - #[max_length = 64] - connector_id -> Nullable, - #[max_length = 64] - shipping_address_id -> Nullable, - #[max_length = 64] - billing_address_id -> Nullable, #[max_length = 255] statement_descriptor_name -> Nullable, - #[max_length = 255] - statement_descriptor_suffix -> Nullable, created_at -> Timestamp, modified_at -> Timestamp, last_synced -> Nullable, @@ -862,9 +854,6 @@ diesel::table! { client_secret -> Nullable, #[max_length = 64] active_attempt_id -> Varchar, - business_country -> Nullable, - #[max_length = 64] - business_label -> Nullable, order_details -> Nullable>>, allowed_payment_method_types -> Nullable, connector_metadata -> Nullable, @@ -872,8 +861,6 @@ diesel::table! { attempt_count -> Int2, #[max_length = 64] profile_id -> Nullable, - #[max_length = 64] - merchant_decision -> Nullable, #[max_length = 255] payment_link_id -> Nullable, payment_confirm_source -> Nullable, @@ -881,22 +868,34 @@ diesel::table! { updated_by -> Varchar, surcharge_applicable -> Nullable, request_incremental_authorization -> Nullable, - incremental_authorization_allowed -> Nullable, authorization_count -> Nullable, session_expiry -> Nullable, - #[max_length = 64] - fingerprint_id -> Nullable, request_external_three_ds_authentication -> Nullable, charges -> Nullable, frm_metadata -> Nullable, customer_details -> Nullable, - billing_details -> Nullable, #[max_length = 255] merchant_order_reference_id -> Nullable, - shipping_details -> Nullable, is_payment_processor_token_flow -> Nullable, + shipping_cost -> Nullable, #[max_length = 32] organization_id -> Varchar, + tax_details -> Nullable, + skip_external_tax_calculation -> Nullable, + #[max_length = 64] + merchant_reference_id -> Varchar, + billing_address -> Nullable, + shipping_address -> Nullable, + capture_method -> Nullable, + authentication_type -> Nullable, + amount_to_capture -> Nullable, + prerouting_algorithm -> Nullable, + surcharge_amount -> Nullable, + tax_on_surcharge -> Nullable, + #[max_length = 64] + frm_merchant_decision -> Nullable, + #[max_length = 64] + id -> Varchar, } } diff --git a/crates/diesel_models/src/user/sample_data.rs b/crates/diesel_models/src/user/sample_data.rs index 2b1359968fd6..f4c8cf30fab1 100644 --- a/crates/diesel_models/src/user/sample_data.rs +++ b/crates/diesel_models/src/user/sample_data.rs @@ -2,6 +2,7 @@ use common_enums::{ AttemptStatus, AuthenticationType, CaptureMethod, Currency, PaymentExperience, PaymentMethod, PaymentMethodType, }; +use common_utils::types::MinorUnit; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -82,6 +83,8 @@ pub struct PaymentAttemptBatchNew { pub customer_acceptance: Option, pub profile_id: common_utils::id_type::ProfileId, pub organization_id: common_utils::id_type::OrganizationId, + pub shipping_cost: Option, + pub order_tax_amount: Option, } #[allow(dead_code)] @@ -157,6 +160,8 @@ impl PaymentAttemptBatchNew { customer_acceptance: self.customer_acceptance, profile_id: self.profile_id, organization_id: self.organization_id, + shipping_cost: self.shipping_cost, + order_tax_amount: self.order_tax_amount, } } } diff --git a/crates/drainer/src/health_check.rs b/crates/drainer/src/health_check.rs index 10514108991b..946489513b4d 100644 --- a/crates/drainer/src/health_check.rs +++ b/crates/drainer/src/health_check.rs @@ -170,7 +170,7 @@ impl HealthCheckInterface for Store { logger::debug!("Redis set_key was successful"); redis_conn - .get_key("test_key") + .get_key::<()>("test_key") .await .change_context(HealthCheckRedisError::GetFailed)?; diff --git a/crates/hyperswitch_connectors/Cargo.toml b/crates/hyperswitch_connectors/Cargo.toml index c0fdc2213aac..2676f6372230 100644 --- a/crates/hyperswitch_connectors/Cargo.toml +++ b/crates/hyperswitch_connectors/Cargo.toml @@ -19,13 +19,16 @@ bytes = "1.6.0" error-stack = "0.4.1" hex = "0.4.3" http = "0.2.12" +image = { version = "0.25.1", default-features = false, features = ["png"] } once_cell = "1.19.0" +qrcode = "0.14.0" rand = "0.8.5" regex = "1.10.4" reqwest = { version = "0.11.27" } ring = "0.17.8" serde = { version = "1.0.197", features = ["derive"] } serde_json = "1.0.115" +serde_urlencoded = "0.7.1" strum = { version = "0.26", features = ["derive"] } time = "0.3.35" url = "2.5.0" diff --git a/crates/hyperswitch_connectors/src/connectors.rs b/crates/hyperswitch_connectors/src/connectors.rs index 79024411320d..037f91e90ca3 100644 --- a/crates/hyperswitch_connectors/src/connectors.rs +++ b/crates/hyperswitch_connectors/src/connectors.rs @@ -11,12 +11,13 @@ pub mod novalnet; pub mod powertranz; pub mod stax; pub mod taxjar; +pub mod thunes; pub mod tsys; pub mod worldline; pub use self::{ bambora::Bambora, bitpay::Bitpay, deutschebank::Deutschebank, fiserv::Fiserv, fiservemea::Fiservemea, fiuu::Fiuu, globepay::Globepay, helcim::Helcim, nexixpay::Nexixpay, - novalnet::Novalnet, powertranz::Powertranz, stax::Stax, taxjar::Taxjar, tsys::Tsys, - worldline::Worldline, + novalnet::Novalnet, powertranz::Powertranz, stax::Stax, taxjar::Taxjar, thunes::Thunes, + tsys::Tsys, worldline::Worldline, }; diff --git a/crates/hyperswitch_connectors/src/connectors/bambora/transformers.rs b/crates/hyperswitch_connectors/src/connectors/bambora/transformers.rs index 8499aecaed77..b8c75e3c4dbb 100644 --- a/crates/hyperswitch_connectors/src/connectors/bambora/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/bambora/transformers.rs @@ -1,6 +1,9 @@ use base64::Engine; use common_enums::enums; -use common_utils::{ext_traits::ValueExt, pii::IpAddress}; +use common_utils::{ + ext_traits::ValueExt, + pii::{Email, IpAddress}, +}; use error_stack::ResultExt; use hyperswitch_domain_models::{ payment_method_data::PaymentMethodData, @@ -86,6 +89,7 @@ pub struct BamboraPaymentsRequest { customer_ip: Option>, term_url: Option, card: BamboraCard, + billing: AddressData, } #[derive(Default, Debug, Serialize)] @@ -173,6 +177,28 @@ impl TryFrom> for Bambora complete: item.router_data.request.is_auto_capture()?, }; + let (country, province) = match ( + item.router_data.get_optional_billing_country(), + item.router_data.get_optional_billing_state_2_digit(), + ) { + (Some(billing_country), Some(billing_state)) => { + (Some(billing_country), Some(billing_state)) + } + _ => (None, None), + }; + + let billing = AddressData { + name: item.router_data.get_optional_billing_full_name(), + address_line1: item.router_data.get_optional_billing_line1(), + address_line2: item.router_data.get_optional_billing_line2(), + city: item.router_data.get_optional_billing_city(), + province, + country, + postal_code: item.router_data.get_optional_billing_zip(), + phone_number: item.router_data.get_optional_billing_phone_number(), + email_address: item.router_data.get_optional_billing_email(), + }; + Ok(Self { order_number: item.router_data.connector_request_reference_id.clone(), amount: item.amount, @@ -180,6 +206,7 @@ impl TryFrom> for Bambora card, customer_ip, term_url: item.router_data.request.complete_authorize_url.clone(), + billing, }) } PaymentMethodData::CardRedirect(_) @@ -342,15 +369,15 @@ pub struct AvsObject { #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct AddressData { - name: Secret, - address_line1: Secret, - address_line2: Secret, - city: String, - province: String, - country: String, - postal_code: Secret, - phone_number: Secret, - email_address: Secret, + name: Option>, + address_line1: Option>, + address_line2: Option>, + city: Option, + province: Option>, + country: Option, + postal_code: Option>, + phone_number: Option>, + email_address: Option, } #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] diff --git a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs index 3d7e55c3a90f..30e67babe898 100644 --- a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs +++ b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs @@ -1,27 +1,36 @@ pub mod transformers; +use std::time::SystemTime; + +use actix_web::http::header::Date; +use base64::Engine; +use common_enums::enums; use common_utils::{ errors::CustomResult, ext_traits::BytesExt, request::{Method, Request, RequestBuilder, RequestContent}, - types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, + types::{AmountConvertor, MinorUnit, MinorUnitForConnector}, }; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::{ access_token_auth::AccessTokenAuth, - payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + payments::{ + Authorize, Capture, CompleteAuthorize, PSync, PaymentMethodToken, Session, + SetupMandate, Void, + }, refunds::{Execute, RSync}, }, router_request_types::{ - AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, - PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, - RefundsData, SetupMandateRequestData, + AccessTokenRequestData, CompleteAuthorizeData, PaymentMethodTokenizationData, + PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, + PaymentsSyncData, RefundsData, SetupMandateRequestData, }, router_response_types::{PaymentsResponseData, RefundsResponseData}, types::{ - PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsCompleteAuthorizeRouterData, PaymentsSyncRouterData, RefreshTokenRouterData, RefundSyncRouterData, RefundsRouterData, }, }; @@ -33,20 +42,26 @@ use hyperswitch_interfaces::{ types::{self, Response}, webhooks, }; -use masking::{ExposeInterface, Mask}; +use masking::{ExposeInterface, Mask, Secret}; +use rand::distributions::{Alphanumeric, DistString}; +use ring::hmac; use transformers as deutschebank; -use crate::{constants::headers, types::ResponseRouterData, utils}; +use crate::{ + constants::headers, + types::ResponseRouterData, + utils::{self, PaymentsCompleteAuthorizeRequestData, RefundsRequestData}, +}; #[derive(Clone)] pub struct Deutschebank { - amount_converter: &'static (dyn AmountConvertor + Sync), + amount_converter: &'static (dyn AmountConvertor + Sync), } impl Deutschebank { pub fn new() -> &'static Self { &Self { - amount_converter: &StringMinorUnitForConnector, + amount_converter: &MinorUnitForConnector, } } } @@ -56,6 +71,7 @@ impl api::PaymentSession for Deutschebank {} impl api::ConnectorAccessToken for Deutschebank {} impl api::MandateSetup for Deutschebank {} impl api::PaymentAuthorize for Deutschebank {} +impl api::PaymentsCompleteAuthorize for Deutschebank {} impl api::PaymentSync for Deutschebank {} impl api::PaymentCapture for Deutschebank {} impl api::PaymentVoid for Deutschebank {} @@ -79,10 +95,20 @@ where req: &RouterData, _connectors: &Connectors, ) -> CustomResult)>, errors::ConnectorError> { - let mut header = vec![( - headers::CONTENT_TYPE.to_string(), - self.get_content_type().to_string().into(), - )]; + let access_token = req + .access_token + .clone() + .ok_or(errors::ConnectorError::FailedToObtainAuthType)?; + let mut header = vec![ + ( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + ), + ( + headers::AUTHORIZATION.to_string(), + format!("Bearer {}", access_token.token.expose()).into_masked(), + ), + ]; let mut api_key = self.get_auth_header(&req.connector_auth_type)?; header.append(&mut api_key); Ok(header) @@ -96,9 +122,6 @@ impl ConnectorCommon for Deutschebank { fn get_currency_unit(&self) -> api::CurrencyUnit { api::CurrencyUnit::Base - // TODO! Check connector documentation, on which unit they are processing the currency. - // If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor, - // if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base } fn common_get_content_type(&self) -> &'static str { @@ -116,8 +139,8 @@ impl ConnectorCommon for Deutschebank { let auth = deutschebank::DeutschebankAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; Ok(vec![( - headers::AUTHORIZATION.to_string(), - auth.api_key.expose().into_masked(), + headers::MERCHANT_ID.to_string(), + auth.merchant_id.expose().into_masked(), )]) } @@ -136,9 +159,9 @@ impl ConnectorCommon for Deutschebank { Ok(ErrorResponse { status_code: res.status_code, - code: response.code, - message: response.message, - reason: response.reason, + code: response.rc, + message: response.message.clone(), + reason: Some(response.message), attempt_status: None, connector_transaction_id: None, }) @@ -146,20 +169,124 @@ impl ConnectorCommon for Deutschebank { } impl ConnectorValidation for Deutschebank { - //TODO: implement functions when support enabled + fn validate_capture_method( + &self, + capture_method: Option, + _pmt: Option, + ) -> CustomResult<(), errors::ConnectorError> { + let capture_method = capture_method.unwrap_or_default(); + match capture_method { + enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( + utils::construct_not_implemented_error_report(capture_method, self.id()), + ), + } + } } impl ConnectorIntegration for Deutschebank { //TODO: implement sessions flow } -impl ConnectorIntegration for Deutschebank {} - impl ConnectorIntegration for Deutschebank { } +impl ConnectorIntegration for Deutschebank { + fn get_url( + &self, + _req: &RefreshTokenRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!("{}/security/v1/token", self.base_url(connectors))) + } + + fn get_content_type(&self) -> &'static str { + "application/x-www-form-urlencoded" + } + + fn build_request( + &self, + req: &RefreshTokenRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let auth = deutschebank::DeutschebankAuthType::try_from(&req.connector_auth_type)?; + let client_id = auth.client_id.expose(); + let date = Date(SystemTime::now().into()).to_string(); + let random_string = Alphanumeric.sample_string(&mut rand::thread_rng(), 50); + + let string_to_sign = client_id.clone() + &date + &random_string; + let key = hmac::Key::new(hmac::HMAC_SHA256, auth.client_key.expose().as_bytes()); + let client_secret = format!( + "V1:{}", + common_utils::consts::BASE64_ENGINE + .encode(hmac::sign(&key, string_to_sign.as_bytes()).as_ref()) + ); + + let headers = vec![ + ( + headers::X_RANDOM_VALUE.to_string(), + random_string.into_masked(), + ), + (headers::X_REQUEST_DATE.to_string(), date.into_masked()), + ( + headers::CONTENT_TYPE.to_string(), + types::RefreshTokenType::get_content_type(self) + .to_string() + .into(), + ), + ]; + + let connector_req = deutschebank::DeutschebankAccessTokenRequest { + client_id: Secret::from(client_id), + client_secret: Secret::from(client_secret), + grant_type: "client_credentials".to_string(), + scope: "ftx".to_string(), + }; + let body = RequestContent::FormUrlEncoded(Box::new(connector_req)); + + let req = Some( + RequestBuilder::new() + .method(Method::Post) + .headers(headers) + .url(&types::RefreshTokenType::get_url(self, req, connectors)?) + .set_body(body) + .build(), + ); + Ok(req) + } + + fn handle_response( + &self, + data: &RefreshTokenRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: deutschebank::DeutschebankAccessTokenResponse = res + .response + .parse_struct("Paypal PaypalAuthUpdateResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + impl ConnectorIntegration for Deutschebank { fn get_headers( &self, @@ -176,9 +303,12 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!( + "{}/services/v2.1/managedmandate", + self.base_url(connectors) + )) } fn get_request_body( @@ -226,7 +356,7 @@ impl ConnectorIntegration, res: Response, ) -> CustomResult { - let response: deutschebank::DeutschebankPaymentsResponse = res + let response: deutschebank::DeutschebankMandatePostResponse = res .response .parse_struct("Deutschebank PaymentsAuthorizeResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; @@ -248,6 +378,105 @@ impl ConnectorIntegration + for Deutschebank +{ + fn get_headers( + &self, + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult { + let event_id = req.connector_request_reference_id.clone(); + let tx_action = if req.request.is_auto_capture()? { + "authorization" + } else { + "preauthorization" + }; + Ok(format!( + "{}/services/v2.1/payment/event/{event_id}/directdebit/{tx_action}", + self.base_url(connectors) + )) + } + + fn get_request_body( + &self, + req: &PaymentsCompleteAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + + let connector_router_data = deutschebank::DeutschebankRouterData::from((amount, req)); + let connector_req = + deutschebank::DeutschebankDirectDebitRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsCompleteAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsCompleteAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCompleteAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: deutschebank::DeutschebankPaymentsResponse = res + .response + .parse_struct("Deutschebank PaymentsCompleteAuthorizeResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + impl ConnectorIntegration for Deutschebank { fn get_headers( &self, @@ -263,10 +492,18 @@ impl ConnectorIntegration for Deu fn get_url( &self, - _req: &PaymentsSyncRouterData, - _connectors: &Connectors, + req: &PaymentsSyncRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let tx_id = req + .request + .connector_transaction_id + .get_connector_transaction_id() + .change_context(errors::ConnectorError::MissingConnectorTransactionID)?; + Ok(format!( + "{}/services/v2.1/payment/tx/{tx_id}", + self.base_url(connectors) + )) } fn build_request( @@ -292,7 +529,7 @@ impl ConnectorIntegration for Deu ) -> CustomResult { let response: deutschebank::DeutschebankPaymentsResponse = res .response - .parse_struct("deutschebank PaymentsSyncResponse") + .parse_struct("DeutschebankPaymentsSyncResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); @@ -327,18 +564,32 @@ impl ConnectorIntegration fo fn get_url( &self, - _req: &PaymentsCaptureRouterData, - _connectors: &Connectors, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let event_id = req.connector_request_reference_id.clone(); + let tx_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}/services/v2.1/payment/event/{event_id}/tx/{tx_id}/capture", + self.base_url(connectors) + )) } fn get_request_body( &self, - _req: &PaymentsCaptureRouterData, + req: &PaymentsCaptureRouterData, _connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount_to_capture, + req.request.currency, + )?; + + let connector_router_data = deutschebank::DeutschebankRouterData::from((amount, req)); + let connector_req = + deutschebank::DeutschebankCaptureRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -389,7 +640,86 @@ impl ConnectorIntegration fo } } -impl ConnectorIntegration for Deutschebank {} +impl ConnectorIntegration for Deutschebank { + fn get_headers( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult { + let event_id = req.connector_request_reference_id.clone(); + let tx_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}/services/v2.1/payment/event/{event_id}/tx/{tx_id}/reversal", + self.base_url(connectors) + )) + } + + fn get_request_body( + &self, + req: &PaymentsCancelRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = deutschebank::DeutschebankReversalRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) + .set_body(types::PaymentsVoidType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCancelRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: deutschebank::DeutschebankPaymentsResponse = res + .response + .parse_struct("Deutschebank PaymentsCancelResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} impl ConnectorIntegration for Deutschebank { fn get_headers( @@ -406,10 +736,15 @@ impl ConnectorIntegration for Deutsch fn get_url( &self, - _req: &RefundsRouterData, - _connectors: &Connectors, + req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let event_id = req.attempt_id.clone(); + let tx_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}/services/v2.1/payment/event/{event_id}/tx/{tx_id}/refund", + self.base_url(connectors) + )) } fn get_request_body( @@ -455,9 +790,9 @@ impl ConnectorIntegration for Deutsch event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult, errors::ConnectorError> { - let response: deutschebank::RefundResponse = res + let response: deutschebank::DeutschebankPaymentsResponse = res .response - .parse_struct("deutschebank RefundResponse") + .parse_struct("DeutschebankPaymentsResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); @@ -492,10 +827,14 @@ impl ConnectorIntegration for Deutscheb fn get_url( &self, - _req: &RefundSyncRouterData, - _connectors: &Connectors, + req: &RefundSyncRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let tx_id = req.request.get_connector_refund_id()?; + Ok(format!( + "{}/services/v2.1/payment/tx/{tx_id}", + self.base_url(connectors) + )) } fn build_request( @@ -522,9 +861,9 @@ impl ConnectorIntegration for Deutscheb event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: deutschebank::RefundResponse = res + let response: deutschebank::DeutschebankPaymentsResponse = res .response - .parse_struct("deutschebank RefundSyncResponse") + .parse_struct("DeutschebankPaymentsResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); diff --git a/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs b/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs index aca2bd497b65..dc146dc38d4e 100644 --- a/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs @@ -1,31 +1,43 @@ +use std::collections::HashMap; + use common_enums::enums; -use common_utils::types::StringMinorUnit; +use common_utils::{pii::Email, types::MinorUnit}; use hyperswitch_domain_models::{ - payment_method_data::PaymentMethodData, - router_data::{ConnectorAuthType, RouterData}, - router_flow_types::refunds::{Execute, RSync}, - router_request_types::ResponseId, - router_response_types::{PaymentsResponseData, RefundsResponseData}, - types::{PaymentsAuthorizeRouterData, RefundsRouterData}, + payment_method_data::{BankDebitData, PaymentMethodData}, + router_data::{AccessToken, ConnectorAuthType, RouterData}, + router_flow_types::{ + payments::{Authorize, Capture, CompleteAuthorize, PSync}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + CompleteAuthorizeData, PaymentsAuthorizeData, PaymentsCaptureData, PaymentsSyncData, + ResponseId, + }, + router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsCompleteAuthorizeRouterData, RefundsRouterData, + }, }; use hyperswitch_interfaces::errors; -use masking::Secret; +use masking::{PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use crate::{ - types::{RefundsResponseRouterData, ResponseRouterData}, - utils::PaymentsAuthorizeRequestData, + types::{PaymentsCancelResponseRouterData, RefundsResponseRouterData, ResponseRouterData}, + utils::{ + AddressDetailsData, PaymentsAuthorizeRequestData, PaymentsCompleteAuthorizeRequestData, + RefundsRequestData, RouterData as OtherRouterData, + }, }; -//TODO: Fill the struct with respective fields pub struct DeutschebankRouterData { - pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub amount: MinorUnit, pub router_data: T, } -impl From<(StringMinorUnit, T)> for DeutschebankRouterData { - fn from((amount, item): (StringMinorUnit, T)) -> Self { - //Todo : use utils to convert the amount to the type of amount that a connector accepts +impl From<(MinorUnit, T)> for DeutschebankRouterData { + fn from((amount, item): (MinorUnit, T)) -> Self { Self { amount, router_data: item, @@ -33,20 +45,80 @@ impl From<(StringMinorUnit, T)> for DeutschebankRouterData { } } -//TODO: Fill the struct with respective fields +pub struct DeutschebankAuthType { + pub(super) client_id: Secret, + pub(super) merchant_id: Secret, + pub(super) client_key: Secret, +} + +impl TryFrom<&ConnectorAuthType> for DeutschebankAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &ConnectorAuthType) -> Result { + match auth_type { + ConnectorAuthType::SignatureKey { + api_key, + key1, + api_secret, + } => Ok(Self { + client_id: api_key.to_owned(), + merchant_id: key1.to_owned(), + client_key: api_secret.to_owned(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} + #[derive(Default, Debug, Serialize, PartialEq)] -pub struct DeutschebankPaymentsRequest { - amount: StringMinorUnit, - card: DeutschebankCard, +pub struct DeutschebankAccessTokenRequest { + pub grant_type: String, + pub client_id: Secret, + pub client_secret: Secret, + pub scope: String, } -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -pub struct DeutschebankCard { - number: cards::CardNumber, - expiry_month: Secret, - expiry_year: Secret, - cvc: Secret, - complete: bool, +#[derive(Default, Debug, Clone, Deserialize, PartialEq, Serialize)] +pub struct DeutschebankAccessTokenResponse { + pub access_token: Secret, + pub expires_in: i64, + pub expires_on: i64, + pub scope: String, + pub token_type: String, +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(AccessToken { + token: item.response.access_token, + expires: item.response.expires_in, + }), + ..item.data + }) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "UPPERCASE")] +pub enum DeutschebankSEPAApproval { + Click, + Email, + Sms, + Dynamic, +} + +#[derive(Debug, Serialize, PartialEq)] +pub struct DeutschebankPaymentsRequest { + approval_by: DeutschebankSEPAApproval, + email_address: Email, + iban: Secret, + first_name: Secret, + last_name: Secret, } impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>> @@ -56,81 +128,315 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>> fn try_from( item: &DeutschebankRouterData<&PaymentsAuthorizeRouterData>, ) -> Result { + let billing_address = item.router_data.get_billing_address()?; match item.router_data.request.payment_method_data.clone() { - PaymentMethodData::Card(req_card) => { - let card = DeutschebankCard { - number: req_card.card_number, - expiry_month: req_card.card_exp_month, - expiry_year: req_card.card_exp_year, - cvc: req_card.card_cvc, - complete: item.router_data.request.is_auto_capture()?, - }; - Ok(Self { - amount: item.amount.clone(), - card, - }) - } + PaymentMethodData::BankDebit(BankDebitData::SepaBankDebit { iban, .. }) => Ok(Self { + approval_by: DeutschebankSEPAApproval::Click, + email_address: item.router_data.request.get_email()?, + iban, + first_name: billing_address.get_first_name()?.clone(), + last_name: billing_address.get_last_name()?.clone(), + }), _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), } } } -//TODO: Fill the struct with respective fields -// Auth Struct -pub struct DeutschebankAuthType { - pub(super) api_key: Secret, +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum DeutschebankSEPAMandateStatus { + Created, + PendingApproval, + PendingSecondaryApproval, + PendingReview, + PendingSubmission, + Submitted, + Active, + Failed, + Discarded, + Expired, + Replaced, } -impl TryFrom<&ConnectorAuthType> for DeutschebankAuthType { +impl From for common_enums::AttemptStatus { + fn from(item: DeutschebankSEPAMandateStatus) -> Self { + match item { + DeutschebankSEPAMandateStatus::Active + | DeutschebankSEPAMandateStatus::Created + | DeutschebankSEPAMandateStatus::PendingApproval + | DeutschebankSEPAMandateStatus::PendingSecondaryApproval + | DeutschebankSEPAMandateStatus::PendingReview + | DeutschebankSEPAMandateStatus::PendingSubmission + | DeutschebankSEPAMandateStatus::Submitted => Self::AuthenticationPending, + DeutschebankSEPAMandateStatus::Failed + | DeutschebankSEPAMandateStatus::Discarded + | DeutschebankSEPAMandateStatus::Expired + | DeutschebankSEPAMandateStatus::Replaced => Self::Failure, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct DeutschebankMandatePostResponse { + rc: String, + message: String, + mandate_id: Option, + reference: Option, + approval_date: Option, + language: Option, + approval_by: Option, + state: Option, +} + +impl + TryFrom< + ResponseRouterData< + Authorize, + DeutschebankMandatePostResponse, + PaymentsAuthorizeData, + PaymentsResponseData, + >, + > for RouterData +{ type Error = error_stack::Report; - fn try_from(auth_type: &ConnectorAuthType) -> Result { - match auth_type { - ConnectorAuthType::HeaderKey { api_key } => Ok(Self { - api_key: api_key.to_owned(), + fn try_from( + item: ResponseRouterData< + Authorize, + DeutschebankMandatePostResponse, + PaymentsAuthorizeData, + PaymentsResponseData, + >, + ) -> Result { + let signed_on = match item.response.approval_date { + Some(date) => date.chars().take(10).collect(), + None => time::OffsetDateTime::now_utc().date().to_string(), + }; + match item.response.reference { + Some(reference) => Ok(Self { + status: if item.response.rc == "0" { + match item.response.state { + Some(state) => common_enums::AttemptStatus::from(state), + None => common_enums::AttemptStatus::Failure, + } + } else { + common_enums::AttemptStatus::Failure + }, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::NoResponseId, + redirection_data: Some(RedirectForm::Form { + endpoint: item.data.request.get_complete_authorize_url()?, + method: common_utils::request::Method::Get, + form_fields: HashMap::from([ + ("reference".to_string(), reference), + ("signed_on".to_string(), signed_on), + ]), + }), + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }), + None => Ok(Self { + status: common_enums::AttemptStatus::Failure, + ..item.data }), - _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), } } } -// PaymentsResponse -//TODO: Append the remaining status flags -#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum DeutschebankPaymentStatus { - Succeeded, - Failed, - #[default] - Processing, + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +pub struct DeutschebankAmount { + amount: MinorUnit, + currency: api_models::enums::Currency, } -impl From for common_enums::AttemptStatus { - fn from(item: DeutschebankPaymentStatus) -> Self { - match item { - DeutschebankPaymentStatus::Succeeded => Self::Charged, - DeutschebankPaymentStatus::Failed => Self::Failure, - DeutschebankPaymentStatus::Processing => Self::Authorizing, +#[derive(Debug, Serialize, PartialEq)] +pub struct DeutschebankMeansOfPayment { + bank_account: DeutschebankBankAccount, +} + +#[derive(Debug, Serialize, PartialEq)] +pub struct DeutschebankBankAccount { + account_holder: Secret, + iban: Secret, +} + +#[derive(Debug, Serialize, PartialEq)] +pub struct DeutschebankMandate { + reference: Secret, + signed_on: Secret, +} + +#[derive(Debug, Serialize, PartialEq)] +pub struct DeutschebankDirectDebitRequest { + amount_total: DeutschebankAmount, + means_of_payment: DeutschebankMeansOfPayment, + mandate: DeutschebankMandate, +} + +impl TryFrom<&DeutschebankRouterData<&PaymentsCompleteAuthorizeRouterData>> + for DeutschebankDirectDebitRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &DeutschebankRouterData<&PaymentsCompleteAuthorizeRouterData>, + ) -> Result { + let account_holder = item.router_data.get_billing_address()?.get_full_name()?; + let redirect_response = item.router_data.request.redirect_response.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "redirect_response", + }, + )?; + let queries_params = redirect_response + .params + .map(|param| { + let mut queries = HashMap::::new(); + let values = param.peek().split('&').collect::>(); + for value in values { + let pair = value.split('=').collect::>(); + queries.insert( + pair.first() + .ok_or(errors::ConnectorError::ResponseDeserializationFailed)? + .to_string(), + pair.get(1) + .ok_or(errors::ConnectorError::ResponseDeserializationFailed)? + .to_string(), + ); + } + Ok::<_, errors::ConnectorError>(queries) + }) + .transpose()? + .ok_or(errors::ConnectorError::ResponseDeserializationFailed)?; + let reference = Secret::from( + queries_params + .get("reference") + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "reference", + })? + .to_owned(), + ); + let signed_on = Secret::from( + queries_params + .get("signed_on") + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "signed_on", + })? + .to_owned(), + ); + + match item.router_data.request.payment_method_data.clone() { + Some(PaymentMethodData::BankDebit(BankDebitData::SepaBankDebit { iban, .. })) => { + Ok(Self { + amount_total: DeutschebankAmount { + amount: item.amount, + currency: item.router_data.request.currency, + }, + means_of_payment: DeutschebankMeansOfPayment { + bank_account: DeutschebankBankAccount { + account_holder, + iban, + }, + }, + mandate: { + DeutschebankMandate { + reference, + signed_on, + } + }, + }) + } + _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), } } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum DeutschebankTXAction { + Authorization, + Capture, + Credit, + Preauthorization, + Refund, + Reversal, + RiskCheck, + #[serde(rename = "verify-mop")] + VerifyMop, + Payment, + AccountInformation, +} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +pub struct BankAccount { + account_holder: Option>, + bank_name: Option>, + bic: Option>, + iban: Option>, +} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +pub struct TransactionBankAccountInfo { + bank_account: Option, +} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] +pub struct DeutschebankTransactionInfo { + back_state: Option, + ip_address: Option>, + #[serde(rename = "type")] + pm_type: Option, + transaction_bankaccount_info: Option, +} + +#[derive(Debug, Deserialize, Serialize, PartialEq)] pub struct DeutschebankPaymentsResponse { - status: DeutschebankPaymentStatus, - id: String, + rc: String, + message: String, + timestamp: String, + back_ext_id: Option, + back_rc: Option, + event_id: Option, + kind: Option, + tx_action: Option, + tx_id: String, + amount_total: Option, + transaction_info: Option, } -impl TryFrom> - for RouterData +impl + TryFrom< + ResponseRouterData< + CompleteAuthorize, + DeutschebankPaymentsResponse, + CompleteAuthorizeData, + PaymentsResponseData, + >, + > for RouterData { type Error = error_stack::Report; fn try_from( - item: ResponseRouterData, + item: ResponseRouterData< + CompleteAuthorize, + DeutschebankPaymentsResponse, + CompleteAuthorizeData, + PaymentsResponseData, + >, ) -> Result { Ok(Self { - status: common_enums::AttemptStatus::from(item.response.status), + status: if item.response.rc == "0" { + match item.data.request.is_auto_capture()? { + true => common_enums::AttemptStatus::Charged, + false => common_enums::AttemptStatus::Authorized, + } + } else { + common_enums::AttemptStatus::Failure + }, response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(item.response.id), + resource_id: ResponseId::ConnectorTransactionId(item.response.tx_id), redirection_data: None, mandate_reference: None, connector_metadata: None, @@ -144,87 +450,230 @@ impl TryFrom TryFrom<&DeutschebankRouterData<&RefundsRouterData>> for DeutschebankRefundRequest { +#[derive(Debug, Serialize, PartialEq)] +pub struct DeutschebankCaptureRequest { + changed_amount: MinorUnit, + kind: DeutschebankTransactionKind, +} + +impl TryFrom<&DeutschebankRouterData<&PaymentsCaptureRouterData>> for DeutschebankCaptureRequest { type Error = error_stack::Report; - fn try_from(item: &DeutschebankRouterData<&RefundsRouterData>) -> Result { + fn try_from( + item: &DeutschebankRouterData<&PaymentsCaptureRouterData>, + ) -> Result { Ok(Self { - amount: item.amount.to_owned(), + changed_amount: item.amount, + kind: DeutschebankTransactionKind::Directdebit, }) } } -// Type definition for Refund Response - -#[allow(dead_code)] -#[derive(Debug, Serialize, Default, Deserialize, Clone)] -pub enum RefundStatus { - Succeeded, - Failed, - #[default] - Processing, +impl + TryFrom< + ResponseRouterData< + Capture, + DeutschebankPaymentsResponse, + PaymentsCaptureData, + PaymentsResponseData, + >, + > for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + Capture, + DeutschebankPaymentsResponse, + PaymentsCaptureData, + PaymentsResponseData, + >, + ) -> Result { + Ok(Self { + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.tx_id), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + status: if item.response.rc == "0" { + common_enums::AttemptStatus::Charged + } else { + common_enums::AttemptStatus::Failure + }, + ..item.data + }) + } } -impl From for enums::RefundStatus { - fn from(item: RefundStatus) -> Self { - match item { - RefundStatus::Succeeded => Self::Success, - RefundStatus::Failed => Self::Failure, - RefundStatus::Processing => Self::Pending, - //TODO: Review mapping +impl + TryFrom< + ResponseRouterData< + PSync, + DeutschebankPaymentsResponse, + PaymentsSyncData, + PaymentsResponseData, + >, + > for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + PSync, + DeutschebankPaymentsResponse, + PaymentsSyncData, + PaymentsResponseData, + >, + ) -> Result { + let status = if item.response.rc == "0" { + item.response + .tx_action + .and_then(|tx_action| match tx_action { + DeutschebankTXAction::Preauthorization => { + Some(common_enums::AttemptStatus::Authorized) + } + DeutschebankTXAction::Authorization | DeutschebankTXAction::Capture => { + Some(common_enums::AttemptStatus::Charged) + } + DeutschebankTXAction::Credit + | DeutschebankTXAction::Refund + | DeutschebankTXAction::Reversal + | DeutschebankTXAction::RiskCheck + | DeutschebankTXAction::VerifyMop + | DeutschebankTXAction::Payment + | DeutschebankTXAction::AccountInformation => None, + }) + } else { + Some(common_enums::AttemptStatus::Failure) + }; + match status { + Some(status) => Ok(Self { + status, + ..item.data + }), + None => Ok(Self { ..item.data }), } } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize)] -pub struct RefundResponse { - id: String, - status: RefundStatus, +#[derive(Debug, Serialize)] +pub struct DeutschebankReversalRequest { + kind: DeutschebankTransactionKind, +} + +impl TryFrom<&PaymentsCancelRouterData> for DeutschebankReversalRequest { + type Error = error_stack::Report; + fn try_from(_item: &PaymentsCancelRouterData) -> Result { + Ok(Self { + kind: DeutschebankTransactionKind::Directdebit, + }) + } } -impl TryFrom> for RefundsRouterData { +impl TryFrom> + for PaymentsCancelRouterData +{ type Error = error_stack::Report; fn try_from( - item: RefundsResponseRouterData, + item: PaymentsCancelResponseRouterData, ) -> Result { Ok(Self { - response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), - }), + status: if item.response.rc == "0" { + common_enums::AttemptStatus::Voided + } else { + common_enums::AttemptStatus::VoidFailed + }, ..item.data }) } } -impl TryFrom> for RefundsRouterData { +#[derive(Debug, Serialize)] +pub struct DeutschebankRefundRequest { + changed_amount: MinorUnit, + kind: DeutschebankTransactionKind, +} + +impl TryFrom<&DeutschebankRouterData<&RefundsRouterData>> for DeutschebankRefundRequest { + type Error = error_stack::Report; + fn try_from(item: &DeutschebankRouterData<&RefundsRouterData>) -> Result { + Ok(Self { + changed_amount: item.amount.to_owned(), + kind: DeutschebankTransactionKind::Directdebit, + }) + } +} + +impl TryFrom> + for RefundsRouterData +{ type Error = error_stack::Report; fn try_from( - item: RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), + connector_refund_id: item.response.tx_id, + refund_status: if item.response.rc == "0" { + enums::RefundStatus::Success + } else { + enums::RefundStatus::Failure + }, }), ..item.data }) } } -//TODO: Fill the struct with respective fields +impl TryFrom> + for RefundsRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + let status = if item.response.rc == "0" { + item.response + .tx_action + .and_then(|tx_action| match tx_action { + DeutschebankTXAction::Credit | DeutschebankTXAction::Refund => { + Some(enums::RefundStatus::Success) + } + DeutschebankTXAction::Preauthorization + | DeutschebankTXAction::Authorization + | DeutschebankTXAction::Capture + | DeutschebankTXAction::Reversal + | DeutschebankTXAction::RiskCheck + | DeutschebankTXAction::VerifyMop + | DeutschebankTXAction::Payment + | DeutschebankTXAction::AccountInformation => None, + }) + } else { + Some(enums::RefundStatus::Failure) + }; + match status { + Some(refund_status) => Ok(Self { + response: Ok(RefundsResponseData { + refund_status, + connector_refund_id: item.data.request.get_connector_refund_id()?, + }), + ..item.data + }), + None => Ok(Self { ..item.data }), + } + } +} + #[derive(Default, Debug, Serialize, Deserialize, PartialEq)] pub struct DeutschebankErrorResponse { - pub status_code: u16, - pub code: String, + pub rc: String, pub message: String, - pub reason: Option, } diff --git a/crates/hyperswitch_connectors/src/connectors/fiuu.rs b/crates/hyperswitch_connectors/src/connectors/fiuu.rs index 97b5092787f9..df79159c653b 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiuu.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiuu.rs @@ -2,7 +2,7 @@ pub mod transformers; use std::collections::HashMap; -use common_enums::{CaptureMethod, PaymentMethodType}; +use common_enums::{CaptureMethod, PaymentMethod, PaymentMethodType}; use common_utils::{ errors::{self as common_errors, CustomResult}, ext_traits::BytesExt, @@ -230,13 +230,19 @@ impl ConnectorIntegration CustomResult { - Ok(format!( - "{}RMS/API/Direct/1.4.0/index.php", - self.base_url(connectors) - )) + match req.payment_method { + PaymentMethod::RealTimePayment => { + let base_url = connectors.fiuu.third_base_url.clone(); + Ok(format!("{}RMS/API/staticqr/index.php", base_url)) + } + _ => Ok(format!( + "{}RMS/API/Direct/1.4.0/index.php", + self.base_url(connectors) + )), + } } fn get_request_body( @@ -284,7 +290,7 @@ impl ConnectorIntegration CustomResult { let response: fiuu::FiuuPaymentsResponse = res .response - .parse_struct("Fiuu PaymentsAuthorizeResponse") + .parse_struct("Fiuu FiuuPaymentsResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); @@ -317,11 +323,7 @@ impl ConnectorIntegration for Fiu _req: &PaymentsSyncRouterData, connectors: &Connectors, ) -> CustomResult { - let base_url = connectors - .fiuu - .secondary_base_url - .as_ref() - .ok_or(errors::ConnectorError::FailedToObtainIntegrationUrl)?; + let base_url = connectors.fiuu.secondary_base_url.clone(); Ok(format!("{}RMS/API/gate-query/index.php", base_url)) } fn get_request_body( @@ -384,11 +386,7 @@ impl ConnectorIntegration fo _req: &PaymentsCaptureRouterData, connectors: &Connectors, ) -> CustomResult { - let base_url = connectors - .fiuu - .secondary_base_url - .as_ref() - .ok_or(errors::ConnectorError::FailedToObtainIntegrationUrl)?; + let base_url = connectors.fiuu.secondary_base_url.clone(); Ok(format!("{}RMS/API/capstxn/index.php", base_url)) } @@ -462,11 +460,7 @@ impl ConnectorIntegration for Fi _req: &PaymentsCancelRouterData, connectors: &Connectors, ) -> CustomResult { - let base_url = connectors - .fiuu - .secondary_base_url - .as_ref() - .ok_or(errors::ConnectorError::FailedToObtainIntegrationUrl)?; + let base_url = connectors.fiuu.secondary_base_url.clone(); Ok(format!("{}RMS/API/refundAPI/refund.php", base_url)) } @@ -531,11 +525,7 @@ impl ConnectorIntegration for Fiuu { _req: &RefundsRouterData, connectors: &Connectors, ) -> CustomResult { - let base_url = connectors - .fiuu - .secondary_base_url - .as_ref() - .ok_or(errors::ConnectorError::FailedToObtainIntegrationUrl)?; + let base_url = connectors.fiuu.secondary_base_url.clone(); Ok(format!("{}RMS/API/refundAPI/index.php", base_url)) } @@ -607,11 +597,7 @@ impl ConnectorIntegration for Fiuu { _req: &RefundSyncRouterData, connectors: &Connectors, ) -> CustomResult { - let base_url = connectors - .fiuu - .secondary_base_url - .as_ref() - .ok_or(errors::ConnectorError::FailedToObtainIntegrationUrl)?; + let base_url = connectors.fiuu.secondary_base_url.clone(); Ok(format!("{}RMS/API/refundAPI/q_by_txn.php", base_url)) } diff --git a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs index a45b31b8967f..561447fc075c 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs @@ -1,15 +1,18 @@ use std::collections::HashMap; +use api_models::payments; use cards::CardNumber; -use common_enums::{enums, CaptureMethod, Currency}; +use common_enums::{enums, BankNames, CaptureMethod, Currency}; use common_utils::{ crypto::GenerateDigest, + errors::CustomResult, + ext_traits::Encode, request::Method, types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector}, }; use error_stack::{Report, ResultExt}; use hyperswitch_domain_models::{ - payment_method_data::PaymentMethodData, + payment_method_data::{BankRedirectData, PaymentMethodData, RealTimePaymentData}, router_data::{ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::refunds::{Execute, RSync}, router_request_types::{PaymentsAuthorizeData, ResponseId}, @@ -23,13 +26,14 @@ use hyperswitch_interfaces::errors; use masking::{PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use strum::Display; +use url::Url; use crate::{ types::{ PaymentsCancelResponseRouterData, PaymentsCaptureResponseRouterData, PaymentsSyncResponseRouterData, RefundsResponseRouterData, ResponseRouterData, }, - utils::{PaymentsAuthorizeRequestData, RouterData as _}, + utils::{self, PaymentsAuthorizeRequestData, QrImage, RouterData as _}, }; pub struct FiuuRouterData { @@ -88,15 +92,108 @@ impl TryFrom> for TxnType { } } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Display, Debug)] #[serde(rename_all = "UPPERCASE")] enum TxnChannel { + #[serde(rename = "CREDITAN")] + #[strum(serialize = "CREDITAN")] Creditan, + #[serde(rename = "DuitNowSQR")] + #[strum(serialize = "DuitNowSQR")] + DuitNowSqr, +} + +#[derive(Serialize, Deserialize, Display, Debug)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +#[strum(serialize_all = "SCREAMING_SNAKE_CASE")] +pub enum FPXTxnChannel { + FpxAbb, + FpxUob, + FpxAbmb, + FpxScb, + FpxBsn, + FpxKfh, + FpxBmmb, + FpxBkrm, + FpxHsbc, + FpxAgrobank, + FpxBocm, + FpxMb2u, + FpxCimbclicks, + FpxAmb, + FpxHlb, + FpxPbb, + FpxRhb, + FpxBimb, + FpxOcbc, +} +impl TryFrom for FPXTxnChannel { + type Error = Report; + fn try_from(bank_names: BankNames) -> Result { + match bank_names { + BankNames::AffinBank => Ok(Self::FpxAbb), + BankNames::AgroBank => Ok(Self::FpxAgrobank), + BankNames::AllianceBank => Ok(Self::FpxAbmb), + BankNames::AmBank => Ok(Self::FpxAmb), + BankNames::BankOfChina => Ok(Self::FpxBocm), + BankNames::BankIslam => Ok(Self::FpxBimb), + BankNames::BankMuamalat => Ok(Self::FpxBmmb), + BankNames::BankRakyat => Ok(Self::FpxBkrm), + BankNames::BankSimpananNasional => Ok(Self::FpxBsn), + BankNames::CimbBank => Ok(Self::FpxCimbclicks), + BankNames::HongLeongBank => Ok(Self::FpxHlb), + BankNames::HsbcBank => Ok(Self::FpxHsbc), + BankNames::KuwaitFinanceHouse => Ok(Self::FpxKfh), + BankNames::Maybank => Ok(Self::FpxMb2u), + BankNames::PublicBank => Ok(Self::FpxPbb), + BankNames::RhbBank => Ok(Self::FpxRhb), + BankNames::StandardCharteredBank => Ok(Self::FpxScb), + BankNames::UobBank => Ok(Self::FpxUob), + BankNames::OcbcBank => Ok(Self::FpxOcbc), + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Fiuu"), + ))?, + } + } +} + +#[derive(Serialize, Debug, Deserialize)] +#[serde(untagged)] +#[serde(rename_all = "PascalCase")] +pub enum FiuuPaymentsRequest { + QRPaymentRequest(FiuuQRPaymentRequest), + CardPaymentRequest(FiuuCardPaymentRequest), + FpxPaymentRequest(FiuuFPXPyamentRequest), } #[derive(Serialize, Debug, Deserialize)] #[serde(rename_all = "PascalCase")] -pub struct FiuuPaymentsRequest { +pub struct FiuuFPXPyamentRequest { + #[serde(rename = "MerchantID")] + merchant_id: Secret, + reference_no: String, + txn_type: TxnType, + txn_channel: FPXTxnChannel, + txn_currency: Currency, + txn_amount: StringMajorUnit, + signature: Secret, + #[serde(rename = "ReturnURL")] + return_url: Option, +} +#[derive(Serialize, Debug, Deserialize)] +pub struct FiuuQRPaymentRequest { + #[serde(rename = "merchantID")] + merchant_id: Secret, + channel: TxnChannel, + orderid: String, + currency: Currency, + amount: StringMajorUnit, + checksum: Secret, +} + +#[derive(Serialize, Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct FiuuCardPaymentRequest { #[serde(rename = "MerchantID")] merchant_id: Secret, reference_no: String, @@ -140,32 +237,77 @@ impl TryFrom<&FiuuRouterData<&PaymentsAuthorizeRouterData>> for FiuuPaymentsRequ let txn_amount = item.amount.clone(); let reference_no = item.router_data.connector_request_reference_id.clone(); let verify_key = auth.verify_key.peek().to_string(); - let signature = calculate_signature(format!( - "{}{merchant_id}{reference_no}{verify_key}", - txn_amount.get_amount_as_string() - ))?; match item.router_data.request.payment_method_data.clone() { - PaymentMethodData::Card(req_card) => Ok(Self { - merchant_id: auth.merchant_id, - reference_no, - txn_type: match item.router_data.request.is_auto_capture()? { - true => TxnType::Sals, - false => TxnType::Auts, - }, - txn_channel: TxnChannel::Creditan, - txn_currency, - txn_amount, - signature, - cc_pan: req_card.card_number, - cc_cvv2: req_card.card_cvc, - cc_month: req_card.card_exp_month, - cc_year: req_card.card_exp_year, - non_3ds: match item.router_data.is_three_ds() { - false => 1, - true => 0, - }, - return_url: item.router_data.request.router_return_url.clone(), - }), + PaymentMethodData::Card(req_card) => { + let signature = calculate_signature(format!( + "{}{merchant_id}{reference_no}{verify_key}", + txn_amount.get_amount_as_string() + ))?; + + Ok(Self::CardPaymentRequest(FiuuCardPaymentRequest { + merchant_id: auth.merchant_id, + reference_no, + txn_type: match item.router_data.request.is_auto_capture()? { + true => TxnType::Sals, + false => TxnType::Auts, + }, + txn_channel: TxnChannel::Creditan, + txn_currency, + txn_amount, + signature, + cc_pan: req_card.card_number, + cc_cvv2: req_card.card_cvc, + cc_month: req_card.card_exp_month, + cc_year: req_card.card_exp_year, + non_3ds: match item.router_data.is_three_ds() { + false => 1, + true => 0, + }, + return_url: item.router_data.request.router_return_url.clone(), + })) + } + PaymentMethodData::RealTimePayment(real_time_payment_data) => { + match *real_time_payment_data { + RealTimePaymentData::DuitNow {} => { + Ok(Self::QRPaymentRequest(FiuuQRPaymentRequest { + merchant_id: auth.merchant_id, + channel: TxnChannel::DuitNowSqr, + orderid: reference_no.clone(), + currency: txn_currency, + amount: txn_amount.clone(), + checksum: calculate_signature(format!( + "{merchant_id}{}{reference_no}{txn_currency}{}{verify_key}", + TxnChannel::DuitNowSqr, + txn_amount.get_amount_as_string() + ))?, + })) + } + RealTimePaymentData::Fps {} + | RealTimePaymentData::PromptPay {} + | RealTimePaymentData::VietQr {} => Err( + errors::ConnectorError::NotImplemented("Payment methods".to_string()) + .into(), + ), + } + } + PaymentMethodData::BankRedirect(BankRedirectData::OnlineBankingFpx { issuer }) => { + Ok(Self::FpxPaymentRequest(FiuuFPXPyamentRequest { + merchant_id: auth.merchant_id.clone(), + reference_no: reference_no.clone(), + txn_type: match item.router_data.request.is_auto_capture()? { + true => TxnType::Sals, + false => TxnType::Auts, + }, + txn_channel: FPXTxnChannel::try_from(issuer)?, + txn_currency, + txn_amount: txn_amount.clone(), + signature: calculate_signature(format!( + "{}{merchant_id}{reference_no}{verify_key}", + txn_amount.get_amount_as_string() + ))?, + return_url: item.router_data.request.router_return_url.clone(), + })) + } _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), } } @@ -173,7 +315,7 @@ impl TryFrom<&FiuuRouterData<&PaymentsAuthorizeRouterData>> for FiuuPaymentsRequ #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] -pub struct FiuuPaymentsSuccessResponse { +pub struct PaymentsResponse { pub reference_no: String, #[serde(rename = "TxnID")] pub txn_id: String, @@ -184,10 +326,17 @@ pub struct FiuuPaymentsSuccessResponse { pub txn_data: TxnData, } +#[derive(Debug, Serialize, Deserialize)] +pub struct DuitNowQrCodeResponse { + status: bool, + qrcode_data: Secret, +} + #[derive(Debug, Serialize, Deserialize)] #[serde(untagged)] pub enum FiuuPaymentsResponse { - Success(Box), + PaymentResponse(Box), + QRPaymentResponse(Box), Error(FiuuErrorResponse), } @@ -208,17 +357,11 @@ pub enum RequestType { Response, } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct ThreeDSResponseData { - #[serde(rename = "paRes")] - pa_res: String, -} - #[derive(Debug, Serialize, Deserialize)] #[serde(untagged)] pub enum RequestData { NonThreeDS(NonThreeDSResponseData), - ThreeDS(ThreeDSResponseData), + RedirectData(Option>), } #[derive(Debug, Serialize, Deserialize)] pub struct NonThreeDSResponseData { @@ -244,6 +387,23 @@ impl >, ) -> Result { match item.response { + FiuuPaymentsResponse::QRPaymentResponse(response) => Ok(Self { + status: match response.status { + false => enums::AttemptStatus::Failure, + true => enums::AttemptStatus::AuthenticationPending, + }, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::NoResponseId, + redirection_data: None, + mandate_reference: None, + connector_metadata: get_qr_metadata(&response)?, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }), FiuuPaymentsResponse::Error(error) => Ok(Self { response: Err(ErrorResponse { code: error.error_code.clone(), @@ -255,13 +415,8 @@ impl }), ..item.data }), - FiuuPaymentsResponse::Success(data) => match data.txn_data.request_data { - RequestData::ThreeDS(three_ds_data) => { - let form_fields = { - let mut map = HashMap::new(); - map.insert("paRes".to_string(), three_ds_data.pa_res.clone()); - map - }; + FiuuPaymentsResponse::PaymentResponse(data) => match data.txn_data.request_data { + RequestData::RedirectData(redirection_data) => { let redirection_data = Some(RedirectForm::Form { endpoint: data.txn_data.request_url.to_string(), method: if data.txn_data.request_method.as_str() == "POST" { @@ -269,7 +424,7 @@ impl } else { Method::Get }, - form_fields, + form_fields: redirection_data.unwrap_or_default(), }); Ok(Self { status: enums::AttemptStatus::AuthenticationPending, @@ -286,9 +441,8 @@ impl ..item.data }) } - - RequestData::NonThreeDS(non_threeds_data) => Ok(Self { - status: match non_threeds_data.status.as_str() { + RequestData::NonThreeDS(non_threeds_data) => { + let status = match non_threeds_data.status.as_str() { "00" => { if item.data.request.is_auto_capture()? { Ok(enums::AttemptStatus::Charged) @@ -301,19 +455,40 @@ impl other => Err(errors::ConnectorError::UnexpectedResponseError( bytes::Bytes::from(other.to_owned()), )), - }?, - response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(data.txn_id), - redirection_data: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: None, - incremental_authorization_allowed: None, - charge_id: None, - }), - ..item.data - }), + }?; + let response = if status == enums::AttemptStatus::Failure { + Err(ErrorResponse { + code: non_threeds_data + .error_code + .clone() + .unwrap_or_else(|| "NO_ERROR_CODE".to_string()), + message: non_threeds_data + .error_desc + .clone() + .unwrap_or_else(|| "NO_ERROR_MESSAGE".to_string()), + reason: non_threeds_data.error_desc.clone(), + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: None, + }) + } else { + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(data.txn_id), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }) + }; + Ok(Self { + status, + response, + ..item.data + }) + } }, } } @@ -335,15 +510,9 @@ pub struct FiuuRefundRequest { #[derive(Debug, Serialize, Display)] pub enum RefundType { #[serde(rename = "P")] + #[strum(serialize = "P")] Partial, } -impl RefundType { - pub fn as_str(&self) -> &'static str { - match self { - Self::Partial => "P", - } - } -} impl TryFrom<&FiuuRouterData<&RefundsRouterData>> for FiuuRefundRequest { type Error = Report; @@ -362,7 +531,7 @@ impl TryFrom<&FiuuRouterData<&RefundsRouterData>> for FiuuRefundRequest amount: txn_amount.clone(), signature: calculate_signature(format!( "{}{merchant_id}{reference_no}{txn_id}{}{secret_key}", - RefundType::Partial.as_str(), + RefundType::Partial, txn_amount.get_amount_as_string() ))?, }) @@ -877,3 +1046,26 @@ impl From for enums::RefundStatus { } } } + +pub fn get_qr_metadata( + response: &DuitNowQrCodeResponse, +) -> CustomResult, errors::ConnectorError> { + let image_data = QrImage::new_from_data(response.qrcode_data.peek().clone()) + .change_context(errors::ConnectorError::ResponseHandlingFailed)?; + + let image_data_url = Url::parse(image_data.data.clone().as_str()).ok(); + let display_to_timestamp = None; + + if let Some(image_data_url) = image_data_url { + let qr_code_info = payments::QrCodeInformation::QrDataUrl { + image_data_url, + display_to_timestamp, + }; + + Some(qr_code_info.encode_to_value()) + .transpose() + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } else { + Ok(None) + } +} diff --git a/crates/hyperswitch_connectors/src/connectors/novalnet.rs b/crates/hyperswitch_connectors/src/connectors/novalnet.rs index 83a8015e73c6..025e2bed2b8e 100644 --- a/crates/hyperswitch_connectors/src/connectors/novalnet.rs +++ b/crates/hyperswitch_connectors/src/connectors/novalnet.rs @@ -1,5 +1,6 @@ pub mod transformers; - +use base64::Engine; +use common_enums::enums; use common_utils::{ errors::CustomResult, ext_traits::BytesExt, @@ -21,12 +22,15 @@ use hyperswitch_domain_models::{ }, router_response_types::{PaymentsResponseData, RefundsResponseData}, types::{ - PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, - RefundSyncRouterData, RefundsRouterData, + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, }, }; use hyperswitch_interfaces::{ - api::{self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorValidation}, + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorRedirectResponse, + ConnectorValidation, + }, configs::Connectors, errors, events::connector_api_logs::ConnectorEvent, @@ -36,7 +40,9 @@ use hyperswitch_interfaces::{ use masking::{ExposeInterface, Mask}; use transformers as novalnet; -use crate::{constants::headers, types::ResponseRouterData, utils}; +use crate::{ + constants::headers, types::ResponseRouterData, utils, utils::PaymentsAuthorizeRequestData, +}; #[derive(Clone)] pub struct Novalnet { @@ -95,10 +101,7 @@ impl ConnectorCommon for Novalnet { } fn get_currency_unit(&self) -> api::CurrencyUnit { - api::CurrencyUnit::Base - // TODO! Check connector documentation, on which unit they are processing the currency. - // If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor, - // if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base + api::CurrencyUnit::Minor } fn common_get_content_type(&self) -> &'static str { @@ -115,9 +118,11 @@ impl ConnectorCommon for Novalnet { ) -> CustomResult)>, errors::ConnectorError> { let auth = novalnet::NovalnetAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + let api_key: String = auth.payment_access_key.expose(); + let encoded_api_key = common_utils::consts::BASE64_ENGINE.encode(api_key); Ok(vec![( - headers::AUTHORIZATION.to_string(), - auth.api_key.expose().into_masked(), + headers::X_NN_ACCESS_KEY.to_string(), + encoded_api_key.into_masked(), )]) } @@ -146,7 +151,52 @@ impl ConnectorCommon for Novalnet { } impl ConnectorValidation for Novalnet { - //TODO: implement functions when support enabled + fn validate_capture_method( + &self, + capture_method: Option, + _pmt: Option, + ) -> CustomResult<(), errors::ConnectorError> { + let capture_method = capture_method.unwrap_or_default(); + match capture_method { + enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( + utils::construct_not_implemented_error_report(capture_method, self.id()), + ), + } + } + + fn validate_psync_reference_id( + &self, + data: &PaymentsSyncData, + _is_three_ds: bool, + _status: enums::AttemptStatus, + _connector_meta_data: Option, + ) -> CustomResult<(), errors::ConnectorError> { + if data.encoded_data.is_some() + || data + .connector_transaction_id + .get_connector_transaction_id() + .is_ok() + { + return Ok(()); + } + + Err(errors::ConnectorError::MissingConnectorTransactionID.into()) + } +} + +impl ConnectorRedirectResponse for Novalnet { + fn get_flow_type( + &self, + _query_params: &str, + _json_payload: Option, + action: enums::PaymentAction, + ) -> CustomResult { + match action { + enums::PaymentAction::PSync => Ok(enums::CallConnectorAction::Trigger), + _ => Ok(enums::CallConnectorAction::Avoid), + } + } } impl ConnectorIntegration for Novalnet { @@ -175,10 +225,14 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let endpoint = self.base_url(connectors); + match req.request.is_auto_capture()? { + true => Ok(format!("{}/payment", endpoint)), + false => Ok(format!("{}/authorize", endpoint)), + } } fn get_request_body( @@ -263,9 +317,19 @@ impl ConnectorIntegration for Nov fn get_url( &self, _req: &PaymentsSyncRouterData, - _connectors: &Connectors, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let endpoint = self.base_url(connectors); + Ok(format!("{}/transaction/details", endpoint)) + } + + fn get_request_body( + &self, + req: &PaymentsSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = novalnet::NovalnetSyncRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -275,10 +339,13 @@ impl ConnectorIntegration for Nov ) -> CustomResult, errors::ConnectorError> { Ok(Some( RequestBuilder::new() - .method(Method::Get) + .method(Method::Post) .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .set_body(types::PaymentsSyncType::get_request_body( + self, req, connectors, + )?) .build(), )) } @@ -289,7 +356,7 @@ impl ConnectorIntegration for Nov event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: novalnet::NovalnetPaymentsResponse = res + let response: novalnet::NovalnetPSyncResponse = res .response .parse_struct("novalnet PaymentsSyncResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; @@ -327,17 +394,26 @@ impl ConnectorIntegration fo fn get_url( &self, _req: &PaymentsCaptureRouterData, - _connectors: &Connectors, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let endpoint = self.base_url(connectors); + Ok(format!("{}/transaction/capture", endpoint)) } fn get_request_body( &self, - _req: &PaymentsCaptureRouterData, + req: &PaymentsCaptureRouterData, _connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount_to_capture, + req.request.currency, + )?; + + let connector_router_data = novalnet::NovalnetRouterData::from((amount, req)); + let connector_req = novalnet::NovalnetCaptureRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -366,7 +442,7 @@ impl ConnectorIntegration fo event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: novalnet::NovalnetPaymentsResponse = res + let response: novalnet::NovalnetCaptureResponse = res .response .parse_struct("Novalnet PaymentsCaptureResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; @@ -388,8 +464,6 @@ impl ConnectorIntegration fo } } -impl ConnectorIntegration for Novalnet {} - impl ConnectorIntegration for Novalnet { fn get_headers( &self, @@ -406,9 +480,10 @@ impl ConnectorIntegration for Novalne fn get_url( &self, _req: &RefundsRouterData, - _connectors: &Connectors, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let endpoint = self.base_url(connectors); + Ok(format!("{}/transaction/refund", endpoint)) } fn get_request_body( @@ -452,7 +527,7 @@ impl ConnectorIntegration for Novalne event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult, errors::ConnectorError> { - let response: novalnet::RefundResponse = res + let response: novalnet::NovalnetRefundResponse = res .response .parse_struct("novalnet RefundResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; @@ -490,9 +565,19 @@ impl ConnectorIntegration for Novalnet fn get_url( &self, _req: &RefundSyncRouterData, - _connectors: &Connectors, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let endpoint = self.base_url(connectors); + Ok(format!("{}/transaction/details", endpoint)) + } + + fn get_request_body( + &self, + req: &RefundSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = novalnet::NovalnetSyncRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -502,7 +587,7 @@ impl ConnectorIntegration for Novalnet ) -> CustomResult, errors::ConnectorError> { Ok(Some( RequestBuilder::new() - .method(Method::Get) + .method(Method::Post) .url(&types::RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundSyncType::get_headers(self, req, connectors)?) @@ -519,7 +604,7 @@ impl ConnectorIntegration for Novalnet event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: novalnet::RefundResponse = res + let response: novalnet::NovalnetRefundSyncResponse = res .response .parse_struct("novalnet RefundSyncResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; @@ -541,6 +626,85 @@ impl ConnectorIntegration for Novalnet } } +impl ConnectorIntegration for Novalnet { + fn get_headers( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult { + let endpoint = self.base_url(connectors); + Ok(format!("{}/transaction/cancel", endpoint)) + } + + fn get_request_body( + &self, + req: &PaymentsCancelRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = novalnet::NovalnetCancelRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) + .set_body(types::PaymentsVoidType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCancelRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: novalnet::NovalnetCancelResponse = res + .response + .parse_struct("NovalnetPaymentsVoidResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + #[async_trait::async_trait] impl webhooks::IncomingWebhook for Novalnet { fn get_webhook_object_reference_id( diff --git a/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs b/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs index de63e9b48c00..dbdbbdb64370 100644 --- a/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs @@ -1,31 +1,46 @@ -use common_enums::enums; -use common_utils::types::StringMinorUnit; +use std::collections::HashMap; + +use cards::CardNumber; +use common_enums::{enums, enums as api_enums}; +use common_utils::{ + ext_traits::OptionExt, + pii::{Email, IpAddress}, + request::Method, + types::StringMinorUnit, +}; +use error_stack::ResultExt; use hyperswitch_domain_models::{ payment_method_data::PaymentMethodData, - router_data::{ConnectorAuthType, RouterData}, + router_data::{ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::refunds::{Execute, RSync}, - router_request_types::ResponseId, - router_response_types::{PaymentsResponseData, RefundsResponseData}, - types::{PaymentsAuthorizeRouterData, RefundsRouterData}, + router_request_types::{PaymentsCancelData, PaymentsCaptureData, PaymentsSyncData, ResponseId}, + router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, + }, }; use hyperswitch_interfaces::errors; -use masking::Secret; +use masking::{ExposeInterface, Secret}; use serde::{Deserialize, Serialize}; +use strum::Display; use crate::{ types::{RefundsResponseRouterData, ResponseRouterData}, - utils::PaymentsAuthorizeRequestData, + utils::{ + BrowserInformationData, PaymentsAuthorizeRequestData, PaymentsCancelRequestData, + PaymentsCaptureRequestData, PaymentsSyncRequestData, RefundsRequestData, + RouterData as OtherRouterData, + }, }; -//TODO: Fill the struct with respective fields pub struct NovalnetRouterData { - pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub amount: StringMinorUnit, pub router_data: T, } impl From<(StringMinorUnit, T)> for NovalnetRouterData { fn from((amount, item): (StringMinorUnit, T)) -> Self { - //Todo : use utils to convert the amount to the type of amount that a connector accepts Self { amount, router_data: item, @@ -33,20 +48,76 @@ impl From<(StringMinorUnit, T)> for NovalnetRouterData { } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Serialize, PartialEq)] -pub struct NovalnetPaymentsRequest { +#[derive(Debug, Copy, Serialize, Deserialize, Clone)] +pub enum NovalNetPaymentTypes { + CREDITCARD, +} + +#[derive(Default, Debug, Serialize, PartialEq, Clone)] +pub struct NovalnetPaymentsRequestMerchant { + signature: Secret, + tariff: Secret, +} + +#[derive(Default, Debug, Serialize, PartialEq, Clone)] +pub struct NovalnetPaymentsRequestBilling { + house_no: Secret, + street: Secret, + city: String, + zip: Secret, + country_code: api_enums::CountryAlpha2, +} + +#[derive(Default, Debug, Serialize, PartialEq, Clone)] +pub struct NovalnetPaymentsRequestCustomer { + first_name: Secret, + last_name: Secret, + email: Email, + mobile: Option>, + billing: NovalnetPaymentsRequestBilling, + customer_ip: Secret, +} +#[derive(Default, Debug, Clone, Serialize, Deserialize)] + +pub struct NovalNetCard { + card_number: CardNumber, + card_expiry_month: Secret, + card_expiry_year: Secret, + card_cvc: Secret, + card_holder: Secret, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(untagged)] +pub enum NovalNetPaymentData { + PaymentCard(NovalNetCard), +} + +#[derive(Default, Debug, Serialize, Clone)] +pub struct NovalnetCustom { + lang: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct NovalnetPaymentsRequestTransaction { + test_mode: i8, + payment_type: NovalNetPaymentTypes, amount: StringMinorUnit, - card: NovalnetCard, + currency: String, + order_no: String, + payment_data: NovalNetPaymentData, + hook_url: Option, + return_url: Option, + error_return_url: Option, + enforce_3d: Option, //NOTE: Needed for CREDITCARD, GOOGLEPAY } -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -pub struct NovalnetCard { - number: cards::CardNumber, - expiry_month: Secret, - expiry_year: Secret, - cvc: Secret, - complete: bool, +#[derive(Debug, Serialize, Clone)] +pub struct NovalnetPaymentsRequest { + merchant: NovalnetPaymentsRequestMerchant, + customer: NovalnetPaymentsRequestCustomer, + transaction: NovalnetPaymentsRequestTransaction, + custom: NovalnetCustom, } impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaymentsRequest { @@ -56,16 +127,81 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym ) -> Result { match item.router_data.request.payment_method_data.clone() { PaymentMethodData::Card(req_card) => { - let card = NovalnetCard { - number: req_card.card_number, - expiry_month: req_card.card_exp_month, - expiry_year: req_card.card_exp_year, - cvc: req_card.card_cvc, - complete: item.router_data.request.is_auto_capture()?, + let auth = NovalnetAuthType::try_from(&item.router_data.connector_auth_type)?; + + let merchant = NovalnetPaymentsRequestMerchant { + signature: auth.product_activation_key, + tariff: auth.tariff_id, }; - Ok(Self { + + let novalnet_card = NovalNetPaymentData::PaymentCard(NovalNetCard { + card_number: req_card.card_number, + card_expiry_month: req_card.card_exp_month, + card_expiry_year: req_card.card_exp_year, + card_cvc: req_card.card_cvc, + card_holder: item.router_data.get_billing_full_name()?, + }); + + let enforce_3d = match item.router_data.auth_type { + enums::AuthenticationType::ThreeDs => Some(1), + enums::AuthenticationType::NoThreeDs => None, + }; + let test_mode = match item.router_data.test_mode { + Some(true) => 1, + Some(false) | None => 0, + }; + + let return_url = item.router_data.request.get_return_url().ok(); + let hook_url = item.router_data.request.get_webhook_url().ok(); + let transaction = NovalnetPaymentsRequestTransaction { + test_mode, + payment_type: NovalNetPaymentTypes::CREDITCARD, amount: item.amount.clone(), - card, + currency: item.router_data.request.currency.to_string(), + order_no: item.router_data.connector_request_reference_id.clone(), + hook_url, + return_url: return_url.clone(), + error_return_url: return_url.clone(), + payment_data: novalnet_card, + enforce_3d, + }; + + let billing = NovalnetPaymentsRequestBilling { + house_no: item.router_data.get_billing_line1()?, + street: item.router_data.get_billing_line2()?, + city: item.router_data.get_billing_city()?, + zip: item.router_data.get_billing_zip()?, + country_code: item.router_data.get_billing_country()?, + }; + + let customer_ip = item + .router_data + .request + .get_browser_info()? + .get_ip_address()?; + + let customer = NovalnetPaymentsRequestCustomer { + first_name: item.router_data.get_billing_first_name()?, + last_name: item.router_data.get_billing_last_name()?, + email: item.router_data.get_billing_email()?, + mobile: item.router_data.get_billing_phone_number().ok(), + billing, + customer_ip, + }; + + let lang = item + .router_data + .request + .get_optional_language_from_browser_info() + .unwrap_or("EN".to_string()); + + let custom = NovalnetCustom { lang }; + + Ok(Self { + merchant, + transaction, + customer, + custom, }) } _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), @@ -73,49 +209,105 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym } } -//TODO: Fill the struct with respective fields // Auth Struct pub struct NovalnetAuthType { - pub(super) api_key: Secret, + pub(super) product_activation_key: Secret, + pub(super) payment_access_key: Secret, + pub(super) tariff_id: Secret, } impl TryFrom<&ConnectorAuthType> for NovalnetAuthType { type Error = error_stack::Report; fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - ConnectorAuthType::HeaderKey { api_key } => Ok(Self { - api_key: api_key.to_owned(), + ConnectorAuthType::SignatureKey { + api_key, + key1, + api_secret, + } => Ok(Self { + product_activation_key: api_key.to_owned(), + payment_access_key: key1.to_owned(), + tariff_id: api_secret.to_owned(), }), _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), } } } + // PaymentsResponse -//TODO: Append the remaining status flags -#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum NovalnetPaymentStatus { - Succeeded, - Failed, +#[derive(Debug, Copy, Clone, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum NovalnetTransactionStatus { + Success, + Failure, + Confirmed, + OnHold, + Pending, + #[default] + Deactivated, + Progress, +} + +#[derive(Debug, Copy, Display, Clone, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum NovalnetAPIStatus { + Success, #[default] - Processing, + Failure, } -impl From for common_enums::AttemptStatus { - fn from(item: NovalnetPaymentStatus) -> Self { +impl From for common_enums::AttemptStatus { + fn from(item: NovalnetTransactionStatus) -> Self { match item { - NovalnetPaymentStatus::Succeeded => Self::Charged, - NovalnetPaymentStatus::Failed => Self::Failure, - NovalnetPaymentStatus::Processing => Self::Authorizing, + NovalnetTransactionStatus::Success | NovalnetTransactionStatus::Confirmed => { + Self::Charged + } + NovalnetTransactionStatus::OnHold => Self::Authorized, + NovalnetTransactionStatus::Pending => Self::Pending, + NovalnetTransactionStatus::Progress => Self::AuthenticationPending, + NovalnetTransactionStatus::Deactivated => Self::Voided, + NovalnetTransactionStatus::Failure => Self::Failure, } } } -//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct ResultData { + redirect_url: Option>, + status: NovalnetAPIStatus, + status_code: i32, + status_text: String, + additional_message: Option, +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct TransactionData { + payment_type: Option, + status_code: i32, + txn_secret: Option, + tid: Option>, + test_mode: Option, + status: Option, +} + #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct NovalnetPaymentsResponse { - status: NovalnetPaymentStatus, - id: String, + result: ResultData, + transaction: Option, +} + +pub fn get_error_response(result: ResultData, status_code: u16) -> ErrorResponse { + let error_code = result.status; + let error_reason = result.status_text.clone(); + + ErrorResponse { + code: error_code.to_string(), + message: error_reason.clone(), + reason: Some(error_reason), + status_code, + attempt_status: None, + connector_transaction_id: None, + } } impl TryFrom> @@ -125,99 +317,669 @@ impl TryFrom, ) -> Result { + match item.response.result.status { + NovalnetAPIStatus::Success => { + let redirection_data: Option = + item.response + .result + .redirect_url + .map(|url| RedirectForm::Form { + endpoint: url.expose().to_string(), + method: Method::Get, + form_fields: HashMap::new(), + }); + + let transaction_id = item + .response + .transaction + .clone() + .and_then(|data| data.tid.map(|tid| tid.expose().to_string())); + let transaction_status = item + .response + .transaction + .and_then(|transaction_data| transaction_data.status) + .unwrap_or(if redirection_data.is_some() { + NovalnetTransactionStatus::Progress + } else { + NovalnetTransactionStatus::Pending + }); + // NOTE: if result.status is success, we should always get a redirection url for 3DS flow + // since Novalnet does not always send the transaction.status + // so default value is kept as Progress if flow is 3ds, otherwise default value is kept as Pending + + Ok(Self { + status: common_enums::AttemptStatus::from(transaction_status), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: transaction_id + .clone() + .map(ResponseId::ConnectorTransactionId) + .unwrap_or(ResponseId::NoResponseId), + redirection_data, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: transaction_id.clone(), + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } + NovalnetAPIStatus::Failure => { + let response = Err(get_error_response(item.response.result, item.http_code)); + Ok(Self { + response, + ..item.data + }) + } + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct NovalnetResponseCustomer { + pub billing: NovalnetResponseBilling, + pub customer_ip: Secret, + pub email: Secret, + pub first_name: Secret, + pub gender: Secret, + pub last_name: Secret, + pub mobile: Secret, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct NovalnetResponseBilling { + pub city: Secret, + pub country_code: Secret, + pub house_no: Secret, + pub street: Secret, + pub zip: Secret, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct NovalnetResponseMerchant { + pub project: u32, + pub vendor: u32, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct NovalnetResponseTransactionData { + pub amount: u32, + pub currency: String, + pub date: Option, + pub order_no: String, + pub payment_data: NovalnetResponsePaymentData, + pub payment_type: String, + pub status: NovalnetTransactionStatus, + pub status_code: u16, + pub test_mode: u8, + pub tid: Secret, + pub txn_secret: Secret, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(untagged)] +pub enum NovalnetResponsePaymentData { + PaymentCard(NovalnetResponseCard), +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct NovalnetResponseCard { + pub card_brand: Secret, + pub card_expiry_month: Secret, + pub card_expiry_year: Secret, + pub card_holder: Secret, + pub card_number: Secret, + pub cc_3d: Secret, + pub last_four: Secret, + pub token: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NovalnetPSyncResponse { + pub customer: Option, + pub merchant: Option, + pub result: ResultData, + pub transaction: Option, +} + +#[derive(Debug, Copy, Serialize, Default, Deserialize, Clone)] +pub enum CaptureType { + #[default] + Partial, + Final, +} + +#[derive(Default, Debug, Serialize)] +pub struct Capture { + #[serde(rename = "type")] + cap_type: CaptureType, + reference: String, +} +#[derive(Default, Debug, Serialize)] +pub struct NovalnetTransaction { + tid: String, + amount: Option, + capture: Capture, +} + +#[derive(Default, Debug, Serialize)] +pub struct NovalnetCaptureRequest { + pub transaction: NovalnetTransaction, + pub custom: NovalnetCustom, +} + +impl TryFrom<&NovalnetRouterData<&PaymentsCaptureRouterData>> for NovalnetCaptureRequest { + type Error = error_stack::Report; + fn try_from( + item: &NovalnetRouterData<&PaymentsCaptureRouterData>, + ) -> Result { + let capture_type = CaptureType::Final; + let reference = item.router_data.connector_request_reference_id.clone(); + let capture = Capture { + cap_type: capture_type, + reference, + }; + + let transaction = NovalnetTransaction { + tid: item.router_data.request.connector_transaction_id.clone(), + capture, + amount: Some(item.amount.to_owned()), + }; + + let custom = NovalnetCustom { + lang: item + .router_data + .request + .get_optional_language_from_browser_info() + .unwrap_or("EN".to_string()), + }; Ok(Self { - status: common_enums::AttemptStatus::from(item.response.status), - response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(item.response.id), - redirection_data: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: None, - incremental_authorization_allowed: None, - charge_id: None, - }), - ..item.data + transaction, + custom, }) } } -//TODO: Fill the struct with respective fields -// REFUND : // Type definition for RefundRequest +#[derive(Default, Debug, Serialize)] +pub struct NovalnetRefundTransaction { + tid: String, + amount: Option, +} + #[derive(Default, Debug, Serialize)] pub struct NovalnetRefundRequest { - pub amount: StringMinorUnit, + pub transaction: NovalnetRefundTransaction, + pub custom: NovalnetCustom, } impl TryFrom<&NovalnetRouterData<&RefundsRouterData>> for NovalnetRefundRequest { type Error = error_stack::Report; fn try_from(item: &NovalnetRouterData<&RefundsRouterData>) -> Result { + let transaction = NovalnetRefundTransaction { + tid: item.router_data.request.connector_transaction_id.clone(), + amount: Some(item.amount.to_owned()), + }; + + let custom = NovalnetCustom { + lang: item + .router_data + .request + .get_optional_language_from_browser_info() + .unwrap_or("EN".to_string()), + }; Ok(Self { - amount: item.amount.to_owned(), + transaction, + custom, }) } } -// Type definition for Refund Response +impl From for enums::RefundStatus { + fn from(item: NovalnetTransactionStatus) -> Self { + match item { + NovalnetTransactionStatus::Success | NovalnetTransactionStatus::Confirmed => { + Self::Success + } + NovalnetTransactionStatus::Pending => Self::Pending, + NovalnetTransactionStatus::Failure + | NovalnetTransactionStatus::OnHold + | NovalnetTransactionStatus::Deactivated + | NovalnetTransactionStatus::Progress => Self::Failure, + } + } +} -#[allow(dead_code)] -#[derive(Debug, Serialize, Default, Deserialize, Clone)] -pub enum RefundStatus { - Succeeded, - Failed, - #[default] - Processing, +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NovalnetRefundSyncResponse { + result: ResultData, + transaction: Option, } -impl From for enums::RefundStatus { - fn from(item: RefundStatus) -> Self { - match item { - RefundStatus::Succeeded => Self::Success, - RefundStatus::Failed => Self::Failure, - RefundStatus::Processing => Self::Pending, - //TODO: Review mapping +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NovalnetRefundsTransactionData { + amount: u32, + date: Option, + currency: String, + order_no: String, + payment_type: String, + refund: RefundData, + refunded_amount: u32, + status: NovalnetTransactionStatus, + status_code: u16, + test_mode: u8, + tid: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RefundData { + amount: u32, + currency: String, + payment_type: String, + tid: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NovalnetRefundResponse { + pub customer: Option, + pub merchant: Option, + pub result: ResultData, + pub transaction: Option, +} + +impl TryFrom> + for RefundsRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + match item.response.result.status { + NovalnetAPIStatus::Success => { + let refund_id = item + .response + .transaction + .clone() + .and_then(|data| data.tid.map(|tid| tid.expose().to_string())) + .ok_or(errors::ConnectorError::ResponseHandlingFailed)?; + + let transaction_status = item + .response + .transaction + .map(|transaction| transaction.status) + .unwrap_or(NovalnetTransactionStatus::Pending); + + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: refund_id, + refund_status: enums::RefundStatus::from(transaction_status), + }), + ..item.data + }) + } + NovalnetAPIStatus::Failure => { + let response = Err(get_error_response(item.response.result, item.http_code)); + Ok(Self { + response, + ..item.data + }) + } } } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize)] -pub struct RefundResponse { - id: String, - status: RefundStatus, +#[derive(Default, Debug, Serialize, Deserialize)] +pub struct NovolnetRedirectionResponse { + status: NovalnetTransactionStatus, + tid: Secret, +} + +impl TryFrom<&PaymentsSyncRouterData> for NovalnetSyncRequest { + type Error = error_stack::Report; + fn try_from(item: &PaymentsSyncRouterData) -> Result { + let transaction = if item + .request + .encoded_data + .clone() + .get_required_value("encoded_data") + .is_ok() + { + let encoded_data = item + .request + .encoded_data + .clone() + .get_required_value("encoded_data") + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + let novalnet_redirection_response = + serde_urlencoded::from_str::(encoded_data.as_str()) + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + NovalnetSyncTransaction { + tid: novalnet_redirection_response.tid.expose(), + } + } else { + NovalnetSyncTransaction { + tid: item + .request + .get_connector_transaction_id() + .change_context(errors::ConnectorError::MissingConnectorTransactionID)?, + } + }; + + let custom = NovalnetCustom { + lang: "EN".to_string(), + }; + Ok(Self { + transaction, + custom, + }) + } +} + +impl + TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + match item.response.result.status { + NovalnetAPIStatus::Success => { + let transaction_id = item + .response + .transaction + .clone() + .map(|data| data.tid.expose().to_string()); + let transaction_status = item + .response + .transaction + .map(|transaction_data| transaction_data.status) + .unwrap_or(NovalnetTransactionStatus::Pending); + + Ok(Self { + status: common_enums::AttemptStatus::from(transaction_status), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: transaction_id + .clone() + .map(ResponseId::ConnectorTransactionId) + .unwrap_or(ResponseId::NoResponseId), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: transaction_id.clone(), + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } + NovalnetAPIStatus::Failure => { + let response = Err(get_error_response(item.response.result, item.http_code)); + Ok(Self { + response, + ..item.data + }) + } + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CaptureTransactionData { + amount: Option, + capture: CaptureData, + currency: Option, + order_no: Option, + payment_type: Option, + status: Option, + status_code: Option, + test_mode: Option, + tid: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CaptureData { + amount: Option, + payment_type: Option, + status: Option, + status_code: u16, + tid: Option>, } -impl TryFrom> for RefundsRouterData { +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NovalnetCaptureResponse { + pub result: ResultData, + pub transaction: Option, +} + +impl + TryFrom< + ResponseRouterData, + > for RouterData +{ type Error = error_stack::Report; fn try_from( - item: RefundsResponseRouterData, + item: ResponseRouterData< + F, + NovalnetCaptureResponse, + PaymentsCaptureData, + PaymentsResponseData, + >, ) -> Result { + match item.response.result.status { + NovalnetAPIStatus::Success => { + let transaction_id = item + .response + .transaction + .clone() + .and_then(|data| data.tid.map(|tid| tid.expose().to_string())); + let transaction_status = item + .response + .transaction + .and_then(|transaction_data| transaction_data.status) + .unwrap_or(NovalnetTransactionStatus::Pending); + + Ok(Self { + status: common_enums::AttemptStatus::from(transaction_status), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: transaction_id + .clone() + .map(ResponseId::ConnectorTransactionId) + .unwrap_or(ResponseId::NoResponseId), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: transaction_id.clone(), + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } + NovalnetAPIStatus::Failure => { + let response = Err(get_error_response(item.response.result, item.http_code)); + Ok(Self { + response, + ..item.data + }) + } + } + } +} + +#[derive(Default, Debug, Serialize)] +pub struct NovalnetSyncTransaction { + tid: String, +} + +#[derive(Default, Debug, Serialize)] +pub struct NovalnetSyncRequest { + pub transaction: NovalnetSyncTransaction, + pub custom: NovalnetCustom, +} + +impl TryFrom<&RefundSyncRouterData> for NovalnetSyncRequest { + type Error = error_stack::Report; + fn try_from(item: &RefundSyncRouterData) -> Result { + let transaction = NovalnetSyncTransaction { + tid: item.request.connector_transaction_id.clone(), + }; + + let custom = NovalnetCustom { + lang: item + .request + .get_optional_language_from_browser_info() + .unwrap_or("EN".to_string()), + }; Ok(Self { - response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), - }), - ..item.data + transaction, + custom, }) } } -impl TryFrom> for RefundsRouterData { +impl TryFrom> + for RefundsRouterData +{ type Error = error_stack::Report; fn try_from( - item: RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { + match item.response.result.status { + NovalnetAPIStatus::Success => { + let refund_id = item + .response + .transaction + .clone() + .map(|transaction_data: NovalnetResponseTransactionData| { + transaction_data.tid.expose().to_string() + }) + .unwrap_or("".to_string()); + //NOTE: Mapping refund_id with "" incase we dont get any tid + + let transaction_status = item + .response + .transaction + .map(|transaction_data| transaction_data.status) + .unwrap_or(NovalnetTransactionStatus::Pending); + + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: refund_id, + refund_status: enums::RefundStatus::from(transaction_status), + }), + ..item.data + }) + } + NovalnetAPIStatus::Failure => { + let response = Err(get_error_response(item.response.result, item.http_code)); + Ok(Self { + response, + ..item.data + }) + } + } + } +} + +#[derive(Default, Debug, Serialize)] +pub struct NovalnetCancelTransaction { + tid: String, +} + +#[derive(Default, Debug, Serialize)] +pub struct NovalnetCancelRequest { + pub transaction: NovalnetCancelTransaction, + pub custom: NovalnetCustom, +} + +impl TryFrom<&PaymentsCancelRouterData> for NovalnetCancelRequest { + type Error = error_stack::Report; + fn try_from(item: &PaymentsCancelRouterData) -> Result { + let transaction = NovalnetCancelTransaction { + tid: item.request.connector_transaction_id.clone(), + }; + + let custom = NovalnetCustom { + lang: item + .request + .get_optional_language_from_browser_info() + .unwrap_or("EN".to_string()), + }; Ok(Self { - response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), - }), - ..item.data + transaction, + custom, }) } } +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct NovalnetCancelResponse { + result: ResultData, + transaction: Option, +} + +impl + TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + F, + NovalnetCancelResponse, + PaymentsCancelData, + PaymentsResponseData, + >, + ) -> Result { + match item.response.result.status { + NovalnetAPIStatus::Success => { + let transaction_id = item + .response + .transaction + .clone() + .and_then(|data| data.tid.map(|tid| tid.expose().to_string())); + let transaction_status = item + .response + .transaction + .and_then(|transaction_data| transaction_data.status) + .unwrap_or(NovalnetTransactionStatus::Pending); + Ok(Self { + status: if transaction_status == NovalnetTransactionStatus::Deactivated { + enums::AttemptStatus::Voided + } else { + enums::AttemptStatus::VoidFailed + }, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: transaction_id + .clone() + .map(ResponseId::ConnectorTransactionId) + .unwrap_or(ResponseId::NoResponseId), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: transaction_id.clone(), + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } + NovalnetAPIStatus::Failure => { + let response = Err(get_error_response(item.response.result, item.http_code)); + Ok(Self { + response, + ..item.data + }) + } + } + } +} + //TODO: Fill the struct with respective fields #[derive(Default, Debug, Serialize, Deserialize, PartialEq)] pub struct NovalnetErrorResponse { diff --git a/crates/hyperswitch_connectors/src/connectors/taxjar.rs b/crates/hyperswitch_connectors/src/connectors/taxjar.rs index 3ea453e52859..009a9cb0c97c 100644 --- a/crates/hyperswitch_connectors/src/connectors/taxjar.rs +++ b/crates/hyperswitch_connectors/src/connectors/taxjar.rs @@ -4,26 +4,28 @@ use common_utils::{ errors::CustomResult, ext_traits::BytesExt, request::{Method, Request, RequestBuilder, RequestContent}, - types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, + types::{AmountConvertor, FloatMajorUnit, FloatMajorUnitForConnector, MinorUnit}, }; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::{ access_token_auth::AccessTokenAuth, - payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + payments::{ + Authorize, CalculateTax, Capture, PSync, PaymentMethodToken, Session, SetupMandate, + Void, + }, refunds::{Execute, RSync}, }, router_request_types::{ AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, - RefundsData, SetupMandateRequestData, + PaymentsTaxCalculationData, RefundsData, SetupMandateRequestData, }, - router_response_types::{PaymentsResponseData, RefundsResponseData}, - types::{ - PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, - RefundSyncRouterData, RefundsRouterData, + router_response_types::{ + PaymentsResponseData, RefundsResponseData, TaxCalculationResponseData, }, + types::PaymentsTaxCalculationRouterData, }; use hyperswitch_interfaces::{ api::{self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorValidation}, @@ -33,20 +35,20 @@ use hyperswitch_interfaces::{ types::{self, Response}, webhooks, }; -use masking::{ExposeInterface, Mask}; +use masking::{Mask, PeekInterface}; use transformers as taxjar; use crate::{constants::headers, types::ResponseRouterData, utils}; #[derive(Clone)] pub struct Taxjar { - amount_converter: &'static (dyn AmountConvertor + Sync), + amount_converter: &'static (dyn AmountConvertor + Sync), } impl Taxjar { pub fn new() -> &'static Self { &Self { - amount_converter: &StringMinorUnitForConnector, + amount_converter: &FloatMajorUnitForConnector, } } } @@ -63,6 +65,7 @@ impl api::Refund for Taxjar {} impl api::RefundExecute for Taxjar {} impl api::RefundSync for Taxjar {} impl api::PaymentToken for Taxjar {} +impl api::TaxCalculation for Taxjar {} impl ConnectorIntegration for Taxjar @@ -96,9 +99,6 @@ impl ConnectorCommon for Taxjar { fn get_currency_unit(&self) -> api::CurrencyUnit { api::CurrencyUnit::Base - // TODO! Check connector documentation, on which unit they are processing the currency. - // If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor, - // if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base } fn common_get_content_type(&self) -> &'static str { @@ -117,7 +117,7 @@ impl ConnectorCommon for Taxjar { .change_context(errors::ConnectorError::FailedToObtainAuthType)?; Ok(vec![( headers::AUTHORIZATION.to_string(), - auth.api_key.expose().into_masked(), + format!("Bearer {}", auth.api_key.peek()).into_masked(), )]) } @@ -136,31 +136,31 @@ impl ConnectorCommon for Taxjar { Ok(ErrorResponse { status_code: res.status_code, - code: response.code, - message: response.message, - reason: response.reason, + code: response.status.clone(), + message: response.detail.clone(), + reason: Some(response.detail), attempt_status: None, connector_transaction_id: None, }) } } -impl ConnectorValidation for Taxjar { - //TODO: implement functions when support enabled -} +impl ConnectorValidation for Taxjar {} -impl ConnectorIntegration for Taxjar { - //TODO: implement sessions flow -} +impl ConnectorIntegration for Taxjar {} impl ConnectorIntegration for Taxjar {} impl ConnectorIntegration for Taxjar {} -impl ConnectorIntegration for Taxjar { +impl ConnectorIntegration for Taxjar {} + +impl ConnectorIntegration + for Taxjar +{ fn get_headers( &self, - req: &PaymentsAuthorizeRouterData, + req: &PaymentsTaxCalculationRouterData, connectors: &Connectors, ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) @@ -172,44 +172,48 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}taxes", self.base_url(connectors))) } - fn get_request_body( &self, - req: &PaymentsAuthorizeRouterData, + req: &PaymentsTaxCalculationRouterData, _connectors: &Connectors, ) -> CustomResult { let amount = utils::convert_amount( self.amount_converter, - req.request.minor_amount, + req.request.amount, req.request.currency, )?; - let connector_router_data = taxjar::TaxjarRouterData::from((amount, req)); + let shipping = utils::convert_amount( + self.amount_converter, + req.request.shipping_cost.unwrap_or(MinorUnit::new(0)), + req.request.currency, + )?; + + let connector_router_data = taxjar::TaxjarRouterData::from((amount, shipping, req)); let connector_req = taxjar::TaxjarPaymentsRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } - fn build_request( &self, - req: &PaymentsAuthorizeRouterData, + req: &PaymentsTaxCalculationRouterData, connectors: &Connectors, ) -> CustomResult, errors::ConnectorError> { Ok(Some( RequestBuilder::new() .method(Method::Post) - .url(&types::PaymentsAuthorizeType::get_url( + .url(&types::PaymentsTaxCalculationType::get_url( self, req, connectors, )?) .attach_default_headers() - .headers(types::PaymentsAuthorizeType::get_headers( + .headers(types::PaymentsTaxCalculationType::get_headers( self, req, connectors, )?) - .set_body(types::PaymentsAuthorizeType::get_request_body( + .set_body(types::PaymentsTaxCalculationType::get_request_body( self, req, connectors, )?) .build(), @@ -218,77 +222,13 @@ impl ConnectorIntegration, - res: Response, - ) -> CustomResult { - let response: taxjar::TaxjarPaymentsResponse = res - .response - .parse_struct("Taxjar PaymentsAuthorizeResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - event_builder.map(|i| i.set_response_body(&response)); - router_env::logger::info!(connector_response=?response); - RouterData::try_from(ResponseRouterData { - response, - data: data.clone(), - http_code: res.status_code, - }) - } - - fn get_error_response( - &self, - res: Response, - event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { - self.build_error_response(res, event_builder) - } -} - -impl ConnectorIntegration for Taxjar { - fn get_headers( - &self, - req: &PaymentsSyncRouterData, - connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) - } - - fn get_content_type(&self) -> &'static str { - self.common_get_content_type() - } - - fn get_url( - &self, - _req: &PaymentsSyncRouterData, - _connectors: &Connectors, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) - } - - fn build_request( - &self, - req: &PaymentsSyncRouterData, - connectors: &Connectors, - ) -> CustomResult, errors::ConnectorError> { - Ok(Some( - RequestBuilder::new() - .method(Method::Get) - .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) - .build(), - )) - } - - fn handle_response( - &self, - data: &PaymentsSyncRouterData, + data: &PaymentsTaxCalculationRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: taxjar::TaxjarPaymentsResponse = res .response - .parse_struct("taxjar PaymentsSyncResponse") + .parse_struct("Taxjar PaymentsResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); @@ -308,235 +248,15 @@ impl ConnectorIntegration for Tax } } -impl ConnectorIntegration for Taxjar { - fn get_headers( - &self, - req: &PaymentsCaptureRouterData, - connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) - } - - fn get_content_type(&self) -> &'static str { - self.common_get_content_type() - } - - fn get_url( - &self, - _req: &PaymentsCaptureRouterData, - _connectors: &Connectors, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) - } - - fn get_request_body( - &self, - _req: &PaymentsCaptureRouterData, - _connectors: &Connectors, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) - } - - fn build_request( - &self, - req: &PaymentsCaptureRouterData, - connectors: &Connectors, - ) -> CustomResult, errors::ConnectorError> { - Ok(Some( - RequestBuilder::new() - .method(Method::Post) - .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::PaymentsCaptureType::get_headers( - self, req, connectors, - )?) - .set_body(types::PaymentsCaptureType::get_request_body( - self, req, connectors, - )?) - .build(), - )) - } - - fn handle_response( - &self, - data: &PaymentsCaptureRouterData, - event_builder: Option<&mut ConnectorEvent>, - res: Response, - ) -> CustomResult { - let response: taxjar::TaxjarPaymentsResponse = res - .response - .parse_struct("Taxjar PaymentsCaptureResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - event_builder.map(|i| i.set_response_body(&response)); - router_env::logger::info!(connector_response=?response); - RouterData::try_from(ResponseRouterData { - response, - data: data.clone(), - http_code: res.status_code, - }) - } +impl ConnectorIntegration for Taxjar {} - fn get_error_response( - &self, - res: Response, - event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { - self.build_error_response(res, event_builder) - } -} +impl ConnectorIntegration for Taxjar {} impl ConnectorIntegration for Taxjar {} -impl ConnectorIntegration for Taxjar { - fn get_headers( - &self, - req: &RefundsRouterData, - connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) - } - - fn get_content_type(&self) -> &'static str { - self.common_get_content_type() - } - - fn get_url( - &self, - _req: &RefundsRouterData, - _connectors: &Connectors, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) - } - - fn get_request_body( - &self, - req: &RefundsRouterData, - _connectors: &Connectors, - ) -> CustomResult { - let refund_amount = utils::convert_amount( - self.amount_converter, - req.request.minor_refund_amount, - req.request.currency, - )?; - - let connector_router_data = taxjar::TaxjarRouterData::from((refund_amount, req)); - let connector_req = taxjar::TaxjarRefundRequest::try_from(&connector_router_data)?; - Ok(RequestContent::Json(Box::new(connector_req))) - } - - fn build_request( - &self, - req: &RefundsRouterData, - connectors: &Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = RequestBuilder::new() - .method(Method::Post) - .url(&types::RefundExecuteType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::RefundExecuteType::get_headers( - self, req, connectors, - )?) - .set_body(types::RefundExecuteType::get_request_body( - self, req, connectors, - )?) - .build(); - Ok(Some(request)) - } - - fn handle_response( - &self, - data: &RefundsRouterData, - event_builder: Option<&mut ConnectorEvent>, - res: Response, - ) -> CustomResult, errors::ConnectorError> { - let response: taxjar::RefundResponse = - res.response - .parse_struct("taxjar RefundResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - event_builder.map(|i| i.set_response_body(&response)); - router_env::logger::info!(connector_response=?response); - RouterData::try_from(ResponseRouterData { - response, - data: data.clone(), - http_code: res.status_code, - }) - } - - fn get_error_response( - &self, - res: Response, - event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { - self.build_error_response(res, event_builder) - } -} - -impl ConnectorIntegration for Taxjar { - fn get_headers( - &self, - req: &RefundSyncRouterData, - connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) - } - - fn get_content_type(&self) -> &'static str { - self.common_get_content_type() - } - - fn get_url( - &self, - _req: &RefundSyncRouterData, - _connectors: &Connectors, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) - } +impl ConnectorIntegration for Taxjar {} - fn build_request( - &self, - req: &RefundSyncRouterData, - connectors: &Connectors, - ) -> CustomResult, errors::ConnectorError> { - Ok(Some( - RequestBuilder::new() - .method(Method::Get) - .url(&types::RefundSyncType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::RefundSyncType::get_headers(self, req, connectors)?) - .set_body(types::RefundSyncType::get_request_body( - self, req, connectors, - )?) - .build(), - )) - } - - fn handle_response( - &self, - data: &RefundSyncRouterData, - event_builder: Option<&mut ConnectorEvent>, - res: Response, - ) -> CustomResult { - let response: taxjar::RefundResponse = res - .response - .parse_struct("taxjar RefundSyncResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - event_builder.map(|i| i.set_response_body(&response)); - router_env::logger::info!(connector_response=?response); - RouterData::try_from(ResponseRouterData { - response, - data: data.clone(), - http_code: res.status_code, - }) - } - - fn get_error_response( - &self, - res: Response, - event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { - self.build_error_response(res, event_builder) - } -} +impl ConnectorIntegration for Taxjar {} #[async_trait::async_trait] impl webhooks::IncomingWebhook for Taxjar { diff --git a/crates/hyperswitch_connectors/src/connectors/taxjar/transformers.rs b/crates/hyperswitch_connectors/src/connectors/taxjar/transformers.rs index 81493c8f5d9f..53c120a2788b 100644 --- a/crates/hyperswitch_connectors/src/connectors/taxjar/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/taxjar/transformers.rs @@ -1,43 +1,55 @@ use common_enums::enums; -use common_utils::types::StringMinorUnit; +use common_utils::types::{FloatMajorUnit, FloatMajorUnitForConnector}; +use error_stack::report; use hyperswitch_domain_models::{ - payment_method_data::PaymentMethodData, router_data::{ConnectorAuthType, RouterData}, - router_flow_types::refunds::{Execute, RSync}, - router_request_types::ResponseId, - router_response_types::{PaymentsResponseData, RefundsResponseData}, - types::{PaymentsAuthorizeRouterData, RefundsRouterData}, + router_request_types::PaymentsTaxCalculationData, + router_response_types::TaxCalculationResponseData, + types, }; -use hyperswitch_interfaces::errors; +use hyperswitch_interfaces::{api, errors}; use masking::Secret; use serde::{Deserialize, Serialize}; use crate::{ - types::{RefundsResponseRouterData, ResponseRouterData}, - utils::PaymentsAuthorizeRequestData, + types::ResponseRouterData, + utils::{self, AddressDetailsData}, }; -//TODO: Fill the struct with respective fields pub struct TaxjarRouterData { - pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub amount: FloatMajorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub shipping: FloatMajorUnit, pub router_data: T, } -impl From<(StringMinorUnit, T)> for TaxjarRouterData { - fn from((amount, item): (StringMinorUnit, T)) -> Self { - //Todo : use utils to convert the amount to the type of amount that a connector accepts +impl From<(FloatMajorUnit, FloatMajorUnit, T)> for TaxjarRouterData { + fn from((amount, shipping, item): (FloatMajorUnit, FloatMajorUnit, T)) -> Self { Self { amount, + shipping, router_data: item, } } } -//TODO: Fill the struct with respective fields #[derive(Default, Debug, Serialize, PartialEq)] pub struct TaxjarPaymentsRequest { - amount: StringMinorUnit, - card: TaxjarCard, + to_country: enums::CountryAlpha2, + to_zip: Secret, + to_state: Secret, + to_city: Option, + to_street: Option>, + amount: FloatMajorUnit, + shipping: FloatMajorUnit, + line_items: Vec, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct LineItem { + id: Option, + quantity: Option, + product_tax_code: Option, + unit_price: Option, } #[derive(Default, Debug, Serialize, Eq, PartialEq)] @@ -49,32 +61,64 @@ pub struct TaxjarCard { complete: bool, } -impl TryFrom<&TaxjarRouterData<&PaymentsAuthorizeRouterData>> for TaxjarPaymentsRequest { +impl TryFrom<&TaxjarRouterData<&types::PaymentsTaxCalculationRouterData>> + for TaxjarPaymentsRequest +{ type Error = error_stack::Report; fn try_from( - item: &TaxjarRouterData<&PaymentsAuthorizeRouterData>, + item: &TaxjarRouterData<&types::PaymentsTaxCalculationRouterData>, ) -> Result { - match item.router_data.request.payment_method_data.clone() { - PaymentMethodData::Card(req_card) => { - let card = TaxjarCard { - number: req_card.card_number, - expiry_month: req_card.card_exp_month, - expiry_year: req_card.card_exp_year, - cvc: req_card.card_cvc, - complete: item.router_data.request.is_auto_capture()?, - }; + let request = &item.router_data.request; + let currency = item.router_data.request.currency; + let currency_unit = &api::CurrencyUnit::Base; + let shipping = &item + .router_data + .request + .shipping_address + .address + .clone() + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "address", + })?; + + match request.order_details.clone() { + Some(order_details) => { + let line_items: Result, error_stack::Report> = + order_details + .iter() + .map(|line_item| { + let unit_price = utils::get_amount_as_f64( + currency_unit, + line_item.amount, + currency, + )?; + Ok(LineItem { + id: line_item.product_id.clone(), + quantity: Some(line_item.quantity), + product_tax_code: line_item.product_tax_code.clone(), + unit_price: Some(unit_price), + }) + }) + .collect(); + Ok(Self { - amount: item.amount.clone(), - card, + to_country: shipping.get_country()?.to_owned(), + to_zip: shipping.get_zip()?.to_owned(), + to_state: shipping.to_state_code()?.to_owned(), + to_city: shipping.get_optional_city(), + to_street: shipping.get_optional_line1(), + amount: item.amount, + shipping: item.shipping, + line_items: line_items?, }) } - _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), + None => Err(report!(errors::ConnectorError::MissingRequiredField { + field_name: "order_details" + })), } } } -//TODO: Fill the struct with respective fields -// Auth Struct pub struct TaxjarAuthType { pub(super) api_key: Secret, } @@ -90,139 +134,56 @@ impl TryFrom<&ConnectorAuthType> for TaxjarAuthType { } } } -// PaymentsResponse -//TODO: Append the remaining status flags -#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum TaxjarPaymentStatus { - Succeeded, - Failed, - #[default] - Processing, -} - -impl From for common_enums::AttemptStatus { - fn from(item: TaxjarPaymentStatus) -> Self { - match item { - TaxjarPaymentStatus::Succeeded => Self::Charged, - TaxjarPaymentStatus::Failed => Self::Failure, - TaxjarPaymentStatus::Processing => Self::Authorizing, - } - } -} -//TODO: Fill the struct with respective fields #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct TaxjarPaymentsResponse { - status: TaxjarPaymentStatus, - id: String, + tax: Tax, } -impl TryFrom> - for RouterData +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Tax { + amount_to_collect: FloatMajorUnit, //calculated_tax_amount +} + +impl + TryFrom< + ResponseRouterData< + F, + TaxjarPaymentsResponse, + PaymentsTaxCalculationData, + TaxCalculationResponseData, + >, + > for RouterData { type Error = error_stack::Report; fn try_from( - item: ResponseRouterData, - ) -> Result { - Ok(Self { - status: common_enums::AttemptStatus::from(item.response.status), - response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(item.response.id), - redirection_data: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: None, - incremental_authorization_allowed: None, - charge_id: None, - }), - ..item.data - }) - } -} - -//TODO: Fill the struct with respective fields -// REFUND : -// Type definition for RefundRequest -#[derive(Default, Debug, Serialize)] -pub struct TaxjarRefundRequest { - pub amount: StringMinorUnit, -} - -impl TryFrom<&TaxjarRouterData<&RefundsRouterData>> for TaxjarRefundRequest { - type Error = error_stack::Report; - fn try_from(item: &TaxjarRouterData<&RefundsRouterData>) -> Result { - Ok(Self { - amount: item.amount.to_owned(), - }) - } -} - -// Type definition for Refund Response - -#[allow(dead_code)] -#[derive(Debug, Serialize, Default, Deserialize, Clone)] -pub enum RefundStatus { - Succeeded, - Failed, - #[default] - Processing, -} - -impl From for enums::RefundStatus { - fn from(item: RefundStatus) -> Self { - match item { - RefundStatus::Succeeded => Self::Success, - RefundStatus::Failed => Self::Failure, - RefundStatus::Processing => Self::Pending, - //TODO: Review mapping - } - } -} - -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize)] -pub struct RefundResponse { - id: String, - status: RefundStatus, -} - -impl TryFrom> for RefundsRouterData { - type Error = error_stack::Report; - fn try_from( - item: RefundsResponseRouterData, + item: ResponseRouterData< + F, + TaxjarPaymentsResponse, + PaymentsTaxCalculationData, + TaxCalculationResponseData, + >, ) -> Result { - Ok(Self { - response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), - }), - ..item.data - }) - } -} + let currency = item.data.request.currency; + let amount_to_collect = item.response.tax.amount_to_collect; + let calculated_tax = utils::convert_back_amount_to_minor_units( + &FloatMajorUnitForConnector, + amount_to_collect, + currency, + )?; -impl TryFrom> for RefundsRouterData { - type Error = error_stack::Report; - fn try_from( - item: RefundsResponseRouterData, - ) -> Result { Ok(Self { - response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), + response: Ok(TaxCalculationResponseData { + order_tax_amount: calculated_tax, }), ..item.data }) } } -//TODO: Fill the struct with respective fields #[derive(Default, Debug, Serialize, Deserialize, PartialEq)] pub struct TaxjarErrorResponse { - pub status_code: u16, - pub code: String, - pub message: String, - pub reason: Option, + pub status: String, + pub error: String, + pub detail: String, } diff --git a/crates/hyperswitch_connectors/src/connectors/thunes.rs b/crates/hyperswitch_connectors/src/connectors/thunes.rs new file mode 100644 index 000000000000..972ba0d7dd30 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/thunes.rs @@ -0,0 +1,563 @@ +pub mod transformers; + +use common_utils::{ + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, +}; +use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::{ + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, + RefundSyncRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorValidation}, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks, +}; +use masking::{ExposeInterface, Mask}; +use transformers as thunes; + +use crate::{constants::headers, types::ResponseRouterData, utils}; + +#[derive(Clone)] +pub struct Thunes { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl Thunes { + pub fn new() -> &'static Self { + &Self { + amount_converter: &StringMinorUnitForConnector, + } + } +} + +impl api::Payment for Thunes {} +impl api::PaymentSession for Thunes {} +impl api::ConnectorAccessToken for Thunes {} +impl api::MandateSetup for Thunes {} +impl api::PaymentAuthorize for Thunes {} +impl api::PaymentSync for Thunes {} +impl api::PaymentCapture for Thunes {} +impl api::PaymentVoid for Thunes {} +impl api::Refund for Thunes {} +impl api::RefundExecute for Thunes {} +impl api::RefundSync for Thunes {} +impl api::PaymentToken for Thunes {} + +impl ConnectorIntegration + for Thunes +{ + // Not Implemented (R) +} + +impl ConnectorCommonExt for Thunes +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } +} + +impl ConnectorCommon for Thunes { + fn id(&self) -> &'static str { + "thunes" + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Base + // TODO! Check connector documentation, on which unit they are processing the currency. + // If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor, + // if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { + connectors.thunes.base_url.as_ref() + } + + fn get_auth_header( + &self, + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + let auth = thunes::ThunesAuthType::try_from(auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + Ok(vec![( + headers::AUTHORIZATION.to_string(), + auth.api_key.expose().into_masked(), + )]) + } + + fn build_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: thunes::ThunesErrorResponse = res + .response + .parse_struct("ThunesErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.code, + message: response.message, + reason: response.reason, + attempt_status: None, + connector_transaction_id: None, + }) + } +} + +impl ConnectorValidation for Thunes { + //TODO: implement functions when support enabled +} + +impl ConnectorIntegration for Thunes { + //TODO: implement sessions flow +} + +impl ConnectorIntegration for Thunes {} + +impl ConnectorIntegration for Thunes {} + +impl ConnectorIntegration for Thunes { + fn get_headers( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + + let connector_router_data = thunes::ThunesRouterData::from((amount, req)); + let connector_req = thunes::ThunesPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: thunes::ThunesPaymentsResponse = res + .response + .parse_struct("Thunes PaymentsAuthorizeResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Thunes { + fn get_headers( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: thunes::ThunesPaymentsResponse = res + .response + .parse_struct("thunes PaymentsSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Thunes { + fn get_headers( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsCaptureType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCaptureType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCaptureRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: thunes::ThunesPaymentsResponse = res + .response + .parse_struct("Thunes PaymentsCaptureResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Thunes {} + +impl ConnectorIntegration for Thunes { + fn get_headers( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let refund_amount = utils::convert_amount( + self.amount_converter, + req.request.minor_refund_amount, + req.request.currency, + )?; + + let connector_router_data = thunes::ThunesRouterData::from((refund_amount, req)); + let connector_req = thunes::ThunesRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundExecuteType::get_headers( + self, req, connectors, + )?) + .set_body(types::RefundExecuteType::get_request_body( + self, req, connectors, + )?) + .build(); + Ok(Some(request)) + } + + fn handle_response( + &self, + data: &RefundsRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult, errors::ConnectorError> { + let response: thunes::RefundResponse = + res.response + .parse_struct("thunes RefundResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Thunes { + fn get_headers( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &RefundSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::RefundSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundSyncType::get_headers(self, req, connectors)?) + .set_body(types::RefundSyncType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &RefundSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: thunes::RefundResponse = res + .response + .parse_struct("thunes RefundSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +#[async_trait::async_trait] +impl webhooks::IncomingWebhook for Thunes { + fn get_webhook_object_reference_id( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_event_type( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_resource_object( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } +} diff --git a/crates/hyperswitch_connectors/src/connectors/thunes/transformers.rs b/crates/hyperswitch_connectors/src/connectors/thunes/transformers.rs new file mode 100644 index 000000000000..c0ee0fdae944 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/thunes/transformers.rs @@ -0,0 +1,228 @@ +use common_enums::enums; +use common_utils::types::StringMinorUnit; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{ConnectorAuthType, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{PaymentsAuthorizeRouterData, RefundsRouterData}, +}; +use hyperswitch_interfaces::errors; +use masking::Secret; +use serde::{Deserialize, Serialize}; + +use crate::{ + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::PaymentsAuthorizeRequestData, +}; + +//TODO: Fill the struct with respective fields +pub struct ThunesRouterData { + pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub router_data: T, +} + +impl From<(StringMinorUnit, T)> for ThunesRouterData { + fn from((amount, item): (StringMinorUnit, T)) -> Self { + //Todo : use utils to convert the amount to the type of amount that a connector accepts + Self { + amount, + router_data: item, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, PartialEq)] +pub struct ThunesPaymentsRequest { + amount: StringMinorUnit, + card: ThunesCard, +} + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct ThunesCard { + number: cards::CardNumber, + expiry_month: Secret, + expiry_year: Secret, + cvc: Secret, + complete: bool, +} + +impl TryFrom<&ThunesRouterData<&PaymentsAuthorizeRouterData>> for ThunesPaymentsRequest { + type Error = error_stack::Report; + fn try_from( + item: &ThunesRouterData<&PaymentsAuthorizeRouterData>, + ) -> Result { + match item.router_data.request.payment_method_data.clone() { + PaymentMethodData::Card(req_card) => { + let card = ThunesCard { + number: req_card.card_number, + expiry_month: req_card.card_exp_month, + expiry_year: req_card.card_exp_year, + cvc: req_card.card_cvc, + complete: item.router_data.request.is_auto_capture()?, + }; + Ok(Self { + amount: item.amount.clone(), + card, + }) + } + _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), + } + } +} + +//TODO: Fill the struct with respective fields +// Auth Struct +pub struct ThunesAuthType { + pub(super) api_key: Secret, +} + +impl TryFrom<&ConnectorAuthType> for ThunesAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &ConnectorAuthType) -> Result { + match auth_type { + ConnectorAuthType::HeaderKey { api_key } => Ok(Self { + api_key: api_key.to_owned(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} +// PaymentsResponse +//TODO: Append the remaining status flags +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum ThunesPaymentStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for common_enums::AttemptStatus { + fn from(item: ThunesPaymentStatus) -> Self { + match item { + ThunesPaymentStatus::Succeeded => Self::Charged, + ThunesPaymentStatus::Failed => Self::Failure, + ThunesPaymentStatus::Processing => Self::Authorizing, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct ThunesPaymentsResponse { + status: ThunesPaymentStatus, + id: String, +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + Ok(Self { + status: common_enums::AttemptStatus::from(item.response.status), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.id), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +// REFUND : +// Type definition for RefundRequest +#[derive(Default, Debug, Serialize)] +pub struct ThunesRefundRequest { + pub amount: StringMinorUnit, +} + +impl TryFrom<&ThunesRouterData<&RefundsRouterData>> for ThunesRefundRequest { + type Error = error_stack::Report; + fn try_from(item: &ThunesRouterData<&RefundsRouterData>) -> Result { + Ok(Self { + amount: item.amount.to_owned(), + }) + } +} + +// Type definition for Refund Response + +#[allow(dead_code)] +#[derive(Debug, Serialize, Default, Deserialize, Clone)] +pub enum RefundStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for enums::RefundStatus { + fn from(item: RefundStatus) -> Self { + match item { + RefundStatus::Succeeded => Self::Success, + RefundStatus::Failed => Self::Failure, + RefundStatus::Processing => Self::Pending, + //TODO: Review mapping + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct RefundResponse { + id: String, + status: RefundStatus, +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct ThunesErrorResponse { + pub status_code: u16, + pub code: String, + pub message: String, + pub reason: Option, +} diff --git a/crates/hyperswitch_connectors/src/constants.rs b/crates/hyperswitch_connectors/src/constants.rs index 1427b567c44f..c5ca6512a345 100644 --- a/crates/hyperswitch_connectors/src/constants.rs +++ b/crates/hyperswitch_connectors/src/constants.rs @@ -7,6 +7,10 @@ pub(crate) mod headers { pub(crate) const DATE: &str = "Date"; pub(crate) const IDEMPOTENCY_KEY: &str = "Idempotency-Key"; pub(crate) const MESSAGE_SIGNATURE: &str = "Message-Signature"; + pub(crate) const MERCHANT_ID: &str = "Merchant-ID"; pub(crate) const TIMESTAMP: &str = "Timestamp"; pub(crate) const X_ACCEPT_VERSION: &str = "X-Accept-Version"; + pub(crate) const X_NN_ACCESS_KEY: &str = "X-NN-Access-Key"; + pub(crate) const X_RANDOM_VALUE: &str = "X-RandomValue"; + pub(crate) const X_REQUEST_DATE: &str = "X-RequestDate"; } diff --git a/crates/hyperswitch_connectors/src/default_implementations.rs b/crates/hyperswitch_connectors/src/default_implementations.rs index 02d83636b59f..b9ae736e4353 100644 --- a/crates/hyperswitch_connectors/src/default_implementations.rs +++ b/crates/hyperswitch_connectors/src/default_implementations.rs @@ -28,8 +28,9 @@ use hyperswitch_domain_models::{ files::{Retrieve, Upload}, mandate_revoke::MandateRevoke, payments::{ - Approve, AuthorizeSessionToken, CompleteAuthorize, CreateConnectorCustomer, - IncrementalAuthorization, PostProcessing, PreProcessing, Reject, + Approve, AuthorizeSessionToken, CalculateTax, CompleteAuthorize, + CreateConnectorCustomer, IncrementalAuthorization, PostProcessing, PreProcessing, + Reject, SdkSessionUpdate, }, webhooks::VerifyWebhookSource, }, @@ -37,13 +38,14 @@ use hyperswitch_domain_models::{ AcceptDisputeRequestData, AuthorizeSessionTokenData, CompleteAuthorizeData, ConnectorCustomerData, DefendDisputeRequestData, MandateRevokeRequestData, PaymentsApproveData, PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, - PaymentsPreProcessingData, PaymentsRejectData, RetrieveFileRequestData, - SubmitEvidenceRequestData, UploadFileRequestData, VerifyWebhookSourceRequestData, + PaymentsPreProcessingData, PaymentsRejectData, PaymentsTaxCalculationData, + RetrieveFileRequestData, SdkPaymentsSessionUpdateData, SubmitEvidenceRequestData, + UploadFileRequestData, VerifyWebhookSourceRequestData, }, router_response_types::{ AcceptDisputeResponse, DefendDisputeResponse, MandateRevokeResponseData, - PaymentsResponseData, RetrieveFileResponse, SubmitEvidenceResponse, UploadFileResponse, - VerifyWebhookSourceResponseData, + PaymentsResponseData, RetrieveFileResponse, SubmitEvidenceResponse, + TaxCalculationResponseData, UploadFileResponse, VerifyWebhookSourceResponseData, }, }; #[cfg(feature = "frm")] @@ -63,8 +65,9 @@ use hyperswitch_interfaces::{ files::{FileUpload, RetrieveFile, UploadFile}, payments::{ ConnectorCustomer, PaymentApprove, PaymentAuthorizeSessionToken, - PaymentIncrementalAuthorization, PaymentReject, PaymentsCompleteAuthorize, - PaymentsPostProcessing, PaymentsPreProcessing, + PaymentIncrementalAuthorization, PaymentReject, PaymentSessionUpdate, + PaymentsCompleteAuthorize, PaymentsPostProcessing, PaymentsPreProcessing, + TaxCalculation, }, ConnectorIntegration, ConnectorMandateRevoke, ConnectorRedirectResponse, }, @@ -99,10 +102,76 @@ default_imp_for_authorize_session_token!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); +macro_rules! default_imp_for_calculate_tax { + ($($path:ident::$connector:ident),*) => { + $( impl TaxCalculation for $path::$connector {} + impl + ConnectorIntegration< + CalculateTax, + PaymentsTaxCalculationData, + TaxCalculationResponseData, + > for $path::$connector + {} + )* + }; +} + +default_imp_for_calculate_tax!( + connectors::Bambora, + connectors::Bitpay, + connectors::Fiserv, + connectors::Fiservemea, + connectors::Helcim, + connectors::Stax, + connectors::Novalnet, + connectors::Nexixpay, + connectors::Fiuu, + connectors::Globepay, + connectors::Worldline, + connectors::Powertranz, + connectors::Thunes, + connectors::Tsys, + connectors::Deutschebank +); + +macro_rules! default_imp_for_session_update { + ($($path:ident::$connector:ident),*) => { + $( impl PaymentSessionUpdate for $path::$connector {} + impl + ConnectorIntegration< + SdkSessionUpdate, + SdkPaymentsSessionUpdateData, + PaymentsResponseData, + > for $path::$connector + {} + )* + }; +} + +default_imp_for_session_update!( + connectors::Bambora, + connectors::Bitpay, + connectors::Fiserv, + connectors::Fiservemea, + connectors::Helcim, + connectors::Stax, + connectors::Taxjar, + connectors::Novalnet, + connectors::Nexixpay, + connectors::Fiuu, + connectors::Globepay, + connectors::Worldline, + connectors::Powertranz, + connectors::Thunes, + connectors::Tsys, + connectors::Deutschebank +); + use crate::connectors; macro_rules! default_imp_for_complete_authorize { ($($path:ident::$connector:ident),*) => { @@ -121,7 +190,6 @@ macro_rules! default_imp_for_complete_authorize { default_imp_for_complete_authorize!( connectors::Bitpay, - connectors::Deutschebank, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, @@ -131,6 +199,7 @@ default_imp_for_complete_authorize!( connectors::Nexixpay, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -164,6 +233,7 @@ default_imp_for_incremental_authorization!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -196,6 +266,7 @@ default_imp_for_create_customer!( connectors::Nexixpay, connectors::Powertranz, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -225,11 +296,11 @@ default_imp_for_connector_redirect_response!( connectors::Fiuu, connectors::Globepay, connectors::Helcim, - connectors::Novalnet, connectors::Nexixpay, connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -263,6 +334,7 @@ default_imp_for_pre_processing_steps!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -296,6 +368,7 @@ default_imp_for_post_processing_steps!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -329,6 +402,7 @@ default_imp_for_approve!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -362,6 +436,7 @@ default_imp_for_reject!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -395,6 +470,7 @@ default_imp_for_webhook_source_verification!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -429,6 +505,7 @@ default_imp_for_accept_dispute!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -462,6 +539,7 @@ default_imp_for_submit_evidence!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -495,6 +573,7 @@ default_imp_for_defend_dispute!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -537,6 +616,7 @@ default_imp_for_file_upload!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -572,6 +652,7 @@ default_imp_for_payouts_create!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -607,6 +688,7 @@ default_imp_for_payouts_retrieve!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -642,6 +724,7 @@ default_imp_for_payouts_eligibility!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -677,6 +760,7 @@ default_imp_for_payouts_fulfill!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -712,6 +796,7 @@ default_imp_for_payouts_cancel!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -747,6 +832,7 @@ default_imp_for_payouts_quote!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -782,6 +868,7 @@ default_imp_for_payouts_recipient!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -817,6 +904,7 @@ default_imp_for_payouts_recipient_account!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -852,6 +940,7 @@ default_imp_for_frm_sale!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -887,6 +976,7 @@ default_imp_for_frm_checkout!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -922,6 +1012,7 @@ default_imp_for_frm_transaction!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -957,6 +1048,7 @@ default_imp_for_frm_fulfillment!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -992,6 +1084,7 @@ default_imp_for_frm_record_return!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -1024,6 +1117,7 @@ default_imp_for_revoking_mandates!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); diff --git a/crates/hyperswitch_connectors/src/default_implementations_v2.rs b/crates/hyperswitch_connectors/src/default_implementations_v2.rs index fcab2b721d2e..82ac725de3c2 100644 --- a/crates/hyperswitch_connectors/src/default_implementations_v2.rs +++ b/crates/hyperswitch_connectors/src/default_implementations_v2.rs @@ -12,9 +12,9 @@ use hyperswitch_domain_models::{ files::{Retrieve, Upload}, mandate_revoke::MandateRevoke, payments::{ - Approve, Authorize, AuthorizeSessionToken, Capture, CompleteAuthorize, + Approve, Authorize, AuthorizeSessionToken, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, IncrementalAuthorization, PSync, PaymentMethodToken, - PostProcessing, PreProcessing, Reject, Session, SetupMandate, Void, + PostProcessing, PreProcessing, Reject, SdkSessionUpdate, Session, SetupMandate, Void, }, refunds::{Execute, RSync}, webhooks::VerifyWebhookSource, @@ -27,13 +27,14 @@ use hyperswitch_domain_models::{ PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, PaymentsPreProcessingData, PaymentsRejectData, PaymentsSessionData, PaymentsSyncData, - RefundsData, RetrieveFileRequestData, SetupMandateRequestData, SubmitEvidenceRequestData, + PaymentsTaxCalculationData, RefundsData, RetrieveFileRequestData, + SdkPaymentsSessionUpdateData, SetupMandateRequestData, SubmitEvidenceRequestData, UploadFileRequestData, VerifyWebhookSourceRequestData, }, router_response_types::{ AcceptDisputeResponse, DefendDisputeResponse, MandateRevokeResponseData, PaymentsResponseData, RefundsResponseData, RetrieveFileResponse, SubmitEvidenceResponse, - UploadFileResponse, VerifyWebhookSourceResponseData, + TaxCalculationResponseData, UploadFileResponse, VerifyWebhookSourceResponseData, }, }; #[cfg(feature = "frm")] @@ -73,9 +74,9 @@ use hyperswitch_interfaces::{ payments_v2::{ ConnectorCustomerV2, MandateSetupV2, PaymentApproveV2, PaymentAuthorizeSessionTokenV2, PaymentAuthorizeV2, PaymentCaptureV2, PaymentIncrementalAuthorizationV2, - PaymentRejectV2, PaymentSessionV2, PaymentSyncV2, PaymentTokenV2, PaymentV2, - PaymentVoidV2, PaymentsCompleteAuthorizeV2, PaymentsPostProcessingV2, - PaymentsPreProcessingV2, + PaymentRejectV2, PaymentSessionUpdateV2, PaymentSessionV2, PaymentSyncV2, + PaymentTokenV2, PaymentV2, PaymentVoidV2, PaymentsCompleteAuthorizeV2, + PaymentsPostProcessingV2, PaymentsPreProcessingV2, TaxCalculationV2, }, refunds_v2::{RefundExecuteV2, RefundSyncV2, RefundV2}, ConnectorAccessTokenV2, ConnectorMandateRevokeV2, ConnectorVerifyWebhookSourceV2, @@ -104,6 +105,8 @@ macro_rules! default_imp_for_new_connector_integration_payment { impl ConnectorCustomerV2 for $path::$connector{} impl PaymentsPreProcessingV2 for $path::$connector{} impl PaymentsPostProcessingV2 for $path::$connector{} + impl TaxCalculationV2 for $path::$connector{} + impl PaymentSessionUpdateV2 for $path::$connector{} impl ConnectorIntegrationV2 for $path::$connector{} @@ -176,6 +179,18 @@ macro_rules! default_imp_for_new_connector_integration_payment { AuthorizeSessionTokenData, PaymentsResponseData > for $path::$connector{} + impl ConnectorIntegrationV2< + CalculateTax, + PaymentFlowData, + PaymentsTaxCalculationData, + TaxCalculationResponseData, + > for $path::$connector{} + impl ConnectorIntegrationV2< + SdkSessionUpdate, + PaymentFlowData, + SdkPaymentsSessionUpdateData, + PaymentsResponseData, + > for $path::$connector{} )* }; } @@ -194,6 +209,7 @@ default_imp_for_new_connector_integration_payment!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -228,6 +244,7 @@ default_imp_for_new_connector_integration_refund!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -257,6 +274,7 @@ default_imp_for_new_connector_integration_connector_access_token!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -292,6 +310,7 @@ default_imp_for_new_connector_integration_accept_dispute!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -326,6 +345,7 @@ default_imp_for_new_connector_integration_submit_evidence!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -360,6 +380,7 @@ default_imp_for_new_connector_integration_defend_dispute!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -404,6 +425,7 @@ default_imp_for_new_connector_integration_file_upload!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -440,6 +462,7 @@ default_imp_for_new_connector_integration_payouts_create!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -476,6 +499,7 @@ default_imp_for_new_connector_integration_payouts_eligibility!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -512,6 +536,7 @@ default_imp_for_new_connector_integration_payouts_fulfill!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -548,6 +573,7 @@ default_imp_for_new_connector_integration_payouts_cancel!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -584,6 +610,7 @@ default_imp_for_new_connector_integration_payouts_quote!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -620,6 +647,7 @@ default_imp_for_new_connector_integration_payouts_recipient!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -656,6 +684,7 @@ default_imp_for_new_connector_integration_payouts_sync!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -692,6 +721,7 @@ default_imp_for_new_connector_integration_payouts_recipient_account!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -726,6 +756,7 @@ default_imp_for_new_connector_integration_webhook_source_verification!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -762,6 +793,7 @@ default_imp_for_new_connector_integration_frm_sale!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -798,6 +830,7 @@ default_imp_for_new_connector_integration_frm_checkout!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -834,6 +867,7 @@ default_imp_for_new_connector_integration_frm_transaction!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -870,6 +904,7 @@ default_imp_for_new_connector_integration_frm_fulfillment!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -906,6 +941,7 @@ default_imp_for_new_connector_integration_frm_record_return!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); @@ -939,6 +975,7 @@ default_imp_for_new_connector_integration_revoking_mandates!( connectors::Powertranz, connectors::Stax, connectors::Taxjar, + connectors::Thunes, connectors::Tsys, connectors::Worldline ); diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index e5ed9d3e7b46..bda133e65b70 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -1,10 +1,15 @@ use std::collections::HashMap; use api_models::payments::{self, Address, AddressDetails, OrderDetailsWithAmount, PhoneDetails}; -use common_enums::{enums, enums::FutureUsage}; +use base64::Engine; +use common_enums::{ + enums, + enums::{CanadaStatesAbbreviation, FutureUsage, UsStatesAbbreviation}, +}; use common_utils::{ + consts::BASE64_ENGINE, errors::{CustomResult, ReportSwitchExt}, - ext_traits::{OptionExt, ValueExt}, + ext_traits::{OptionExt, StringExt, ValueExt}, id_type, pii::{self, Email, IpAddress}, types::{AmountConvertor, MinorUnit}, @@ -20,6 +25,7 @@ use hyperswitch_domain_models::{ }, }; use hyperswitch_interfaces::{api, errors}; +use image::Luma; use masking::{ExposeInterface, PeekInterface, Secret}; use once_cell::sync::Lazy; use regex::Regex; @@ -147,6 +153,16 @@ pub(crate) fn convert_amount( .change_context(errors::ConnectorError::AmountConversionFailed) } +pub(crate) fn convert_back_amount_to_minor_units( + amount_convertor: &dyn AmountConvertor, + amount: T, + currency: enums::Currency, +) -> Result> { + amount_convertor + .convert_back(amount, currency) + .change_context(errors::ConnectorError::AmountConversionFailed) +} + // TODO: Make all traits as `pub(crate) trait` once all connectors are moved. pub trait RouterData { fn get_billing(&self) -> Result<&Address, Error>; @@ -163,6 +179,10 @@ pub trait RouterData { fn get_billing_full_name(&self) -> Result, Error>; fn get_billing_last_name(&self) -> Result, Error>; fn get_billing_line1(&self) -> Result, Error>; + fn get_billing_line2(&self) -> Result, Error>; + fn get_billing_zip(&self) -> Result, Error>; + fn get_billing_state(&self) -> Result, Error>; + fn get_billing_state_code(&self) -> Result, Error>; fn get_billing_city(&self) -> Result; fn get_billing_email(&self) -> Result; fn get_billing_phone_number(&self) -> Result, Error>; @@ -200,6 +220,7 @@ pub trait RouterData { fn get_optional_billing_country(&self) -> Option; fn get_optional_billing_zip(&self) -> Option>; fn get_optional_billing_state(&self) -> Option>; + fn get_optional_billing_state_2_digit(&self) -> Option>; fn get_optional_billing_first_name(&self) -> Option>; fn get_optional_billing_last_name(&self) -> Option>; fn get_optional_billing_phone_number(&self) -> Option>; @@ -405,6 +426,56 @@ impl RouterData "payment_method_data.billing.address.line1", )) } + fn get_billing_line2(&self) -> Result, Error> { + self.address + .get_payment_method_billing() + .and_then(|billing_address| { + billing_address + .clone() + .address + .and_then(|billing_details| billing_details.line2.clone()) + }) + .ok_or_else(missing_field_err( + "payment_method_data.billing.address.line2", + )) + } + fn get_billing_zip(&self) -> Result, Error> { + self.address + .get_payment_method_billing() + .and_then(|billing_address| { + billing_address + .clone() + .address + .and_then(|billing_details| billing_details.zip.clone()) + }) + .ok_or_else(missing_field_err("payment_method_data.billing.address.zip")) + } + fn get_billing_state(&self) -> Result, Error> { + self.address + .get_payment_method_billing() + .and_then(|billing_address| { + billing_address + .clone() + .address + .and_then(|billing_details| billing_details.state.clone()) + }) + .ok_or_else(missing_field_err( + "payment_method_data.billing.address.state", + )) + } + fn get_billing_state_code(&self) -> Result, Error> { + let country = self.get_billing_country()?; + let state = self.get_billing_state()?; + match country { + api_models::enums::CountryAlpha2::US => Ok(Secret::new( + UsStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), + )), + api_models::enums::CountryAlpha2::CA => Ok(Secret::new( + CanadaStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), + )), + _ => Ok(state.clone()), + } + } fn get_billing_city(&self) -> Result { self.address .get_payment_method_billing() @@ -501,6 +572,16 @@ impl RouterData }) } + fn get_optional_billing_state_2_digit(&self) -> Option> { + self.get_optional_billing_state().and_then(|state| { + if state.clone().expose().len() != 2 { + None + } else { + Some(state) + } + }) + } + fn get_optional_billing_first_name(&self) -> Option> { self.address .get_payment_method_billing() @@ -761,6 +842,10 @@ pub trait AddressDetailsData { fn get_zip(&self) -> Result<&Secret, Error>; fn get_country(&self) -> Result<&api_models::enums::CountryAlpha2, Error>; fn get_combined_address_line(&self) -> Result, Error>; + fn to_state_code(&self) -> Result, Error>; + fn to_state_code_as_optional(&self) -> Result>, Error>; + fn get_optional_city(&self) -> Option; + fn get_optional_line1(&self) -> Option>; fn get_optional_line2(&self) -> Option>; } @@ -833,6 +918,40 @@ impl AddressDetailsData for AddressDetails { ))) } + fn to_state_code(&self) -> Result, Error> { + let country = self.get_country()?; + let state = self.get_state()?; + match country { + api_models::enums::CountryAlpha2::US => Ok(Secret::new( + UsStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), + )), + api_models::enums::CountryAlpha2::CA => Ok(Secret::new( + CanadaStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), + )), + _ => Ok(state.clone()), + } + } + fn to_state_code_as_optional(&self) -> Result>, Error> { + self.state + .as_ref() + .map(|state| { + if state.peek().len() == 2 { + Ok(state.to_owned()) + } else { + self.to_state_code() + } + }) + .transpose() + } + + fn get_optional_city(&self) -> Option { + self.city.clone() + } + + fn get_optional_line1(&self) -> Option> { + self.line1.clone() + } + fn get_optional_line2(&self) -> Option> { self.line2.clone() } @@ -879,6 +998,7 @@ impl PhoneDetailsData for PhoneDetails { } pub trait PaymentsAuthorizeRequestData { + fn get_optional_language_from_browser_info(&self) -> Option; fn is_auto_capture(&self) -> Result; fn get_email(&self) -> Result; fn get_browser_info(&self) -> Result; @@ -920,6 +1040,12 @@ impl PaymentsAuthorizeRequestData for PaymentsAuthorizeData { .clone() .ok_or_else(missing_field_err("browser_info")) } + fn get_optional_language_from_browser_info(&self) -> Option { + self.browser_info + .clone() + .and_then(|browser_info| browser_info.language) + } + fn get_order_details(&self) -> Result, Error> { self.order_details .clone() @@ -1051,6 +1177,7 @@ impl PaymentsAuthorizeRequestData for PaymentsAuthorizeData { } pub trait PaymentsCaptureRequestData { + fn get_optional_language_from_browser_info(&self) -> Option; fn is_multiple_capture(&self) -> bool; fn get_browser_info(&self) -> Result; } @@ -1064,6 +1191,11 @@ impl PaymentsCaptureRequestData for PaymentsCaptureData { .clone() .ok_or_else(missing_field_err("browser_info")) } + fn get_optional_language_from_browser_info(&self) -> Option { + self.browser_info + .clone() + .and_then(|browser_info| browser_info.language) + } } pub trait PaymentsSyncRequestData { @@ -1094,6 +1226,7 @@ impl PaymentsSyncRequestData for PaymentsSyncData { } pub trait PaymentsCancelRequestData { + fn get_optional_language_from_browser_info(&self) -> Option; fn get_amount(&self) -> Result; fn get_currency(&self) -> Result; fn get_cancellation_reason(&self) -> Result; @@ -1117,9 +1250,15 @@ impl PaymentsCancelRequestData for PaymentsCancelData { .clone() .ok_or_else(missing_field_err("browser_info")) } + fn get_optional_language_from_browser_info(&self) -> Option { + self.browser_info + .clone() + .and_then(|browser_info| browser_info.language) + } } pub trait RefundsRequestData { + fn get_optional_language_from_browser_info(&self) -> Option; fn get_connector_refund_id(&self) -> Result; fn get_webhook_url(&self) -> Result; fn get_browser_info(&self) -> Result; @@ -1143,6 +1282,11 @@ impl RefundsRequestData for RefundsData { .clone() .ok_or_else(missing_field_err("browser_info")) } + fn get_optional_language_from_browser_info(&self) -> Option { + self.browser_info + .clone() + .and_then(|browser_info| browser_info.language) + } } pub trait PaymentsSetupMandateRequestData { @@ -1317,3 +1461,169 @@ macro_rules! unimplemented_payment_method { )) }; } + +impl ForeignTryFrom for UsStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = + StringExt::::parse_enum(value.to_uppercase().clone(), "UsStatesAbbreviation"); + + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "alabama" => Ok(Self::AL), + "alaska" => Ok(Self::AK), + "american samoa" => Ok(Self::AS), + "arizona" => Ok(Self::AZ), + "arkansas" => Ok(Self::AR), + "california" => Ok(Self::CA), + "colorado" => Ok(Self::CO), + "connecticut" => Ok(Self::CT), + "delaware" => Ok(Self::DE), + "district of columbia" | "columbia" => Ok(Self::DC), + "federated states of micronesia" | "micronesia" => Ok(Self::FM), + "florida" => Ok(Self::FL), + "georgia" => Ok(Self::GA), + "guam" => Ok(Self::GU), + "hawaii" => Ok(Self::HI), + "idaho" => Ok(Self::ID), + "illinois" => Ok(Self::IL), + "indiana" => Ok(Self::IN), + "iowa" => Ok(Self::IA), + "kansas" => Ok(Self::KS), + "kentucky" => Ok(Self::KY), + "louisiana" => Ok(Self::LA), + "maine" => Ok(Self::ME), + "marshall islands" => Ok(Self::MH), + "maryland" => Ok(Self::MD), + "massachusetts" => Ok(Self::MA), + "michigan" => Ok(Self::MI), + "minnesota" => Ok(Self::MN), + "mississippi" => Ok(Self::MS), + "missouri" => Ok(Self::MO), + "montana" => Ok(Self::MT), + "nebraska" => Ok(Self::NE), + "nevada" => Ok(Self::NV), + "new hampshire" => Ok(Self::NH), + "new jersey" => Ok(Self::NJ), + "new mexico" => Ok(Self::NM), + "new york" => Ok(Self::NY), + "north carolina" => Ok(Self::NC), + "north dakota" => Ok(Self::ND), + "northern mariana islands" => Ok(Self::MP), + "ohio" => Ok(Self::OH), + "oklahoma" => Ok(Self::OK), + "oregon" => Ok(Self::OR), + "palau" => Ok(Self::PW), + "pennsylvania" => Ok(Self::PA), + "puerto rico" => Ok(Self::PR), + "rhode island" => Ok(Self::RI), + "south carolina" => Ok(Self::SC), + "south dakota" => Ok(Self::SD), + "tennessee" => Ok(Self::TN), + "texas" => Ok(Self::TX), + "utah" => Ok(Self::UT), + "vermont" => Ok(Self::VT), + "virgin islands" => Ok(Self::VI), + "virginia" => Ok(Self::VA), + "washington" => Ok(Self::WA), + "west virginia" => Ok(Self::WV), + "wisconsin" => Ok(Self::WI), + "wyoming" => Ok(Self::WY), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for CanadaStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = + StringExt::::parse_enum(value.to_uppercase().clone(), "CanadaStatesAbbreviation"); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "alberta" => Ok(Self::AB), + "british columbia" => Ok(Self::BC), + "manitoba" => Ok(Self::MB), + "new brunswick" => Ok(Self::NB), + "newfoundland and labrador" | "newfoundland & labrador" => Ok(Self::NL), + "northwest territories" => Ok(Self::NT), + "nova scotia" => Ok(Self::NS), + "nunavut" => Ok(Self::NU), + "ontario" => Ok(Self::ON), + "prince edward island" => Ok(Self::PE), + "quebec" => Ok(Self::QC), + "saskatchewan" => Ok(Self::SK), + "yukon" => Ok(Self::YT), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +pub trait ForeignTryFrom: Sized { + type Error; + + fn foreign_try_from(from: F) -> Result; +} + +#[derive(Debug)] +pub struct QrImage { + pub data: String, +} + +// Qr Image data source starts with this string +// The base64 image data will be appended to it to image data source +pub(crate) const QR_IMAGE_DATA_SOURCE_STRING: &str = "data:image/png;base64"; + +impl QrImage { + pub fn new_from_data( + data: String, + ) -> Result> { + let qr_code = qrcode::QrCode::new(data.as_bytes()) + .change_context(common_utils::errors::QrCodeError::FailedToCreateQrCode)?; + + let qrcode_image_buffer = qr_code.render::>().build(); + let qrcode_dynamic_image = image::DynamicImage::ImageLuma8(qrcode_image_buffer); + + let mut image_bytes = std::io::BufWriter::new(std::io::Cursor::new(Vec::new())); + + // Encodes qrcode_dynamic_image and write it to image_bytes + let _ = qrcode_dynamic_image.write_to(&mut image_bytes, image::ImageFormat::Png); + + let image_data_source = format!( + "{},{}", + QR_IMAGE_DATA_SOURCE_STRING, + BASE64_ENGINE.encode(image_bytes.buffer()) + ); + Ok(Self { + data: image_data_source, + }) + } +} + +#[cfg(test)] +mod tests { + use crate::utils; + #[test] + fn test_image_data_source_url() { + let qr_image_data_source_url = utils::QrImage::new_from_data("Hyperswitch".to_string()); + assert!(qr_image_data_source_url.is_ok()); + } +} diff --git a/crates/hyperswitch_domain_models/src/business_profile.rs b/crates/hyperswitch_domain_models/src/business_profile.rs index ff98475e6251..c6b76680d488 100644 --- a/crates/hyperswitch_domain_models/src/business_profile.rs +++ b/crates/hyperswitch_domain_models/src/business_profile.rs @@ -51,7 +51,7 @@ pub struct BusinessProfile { pub outgoing_webhook_custom_http_headers: OptionalEncryptableValue, pub always_collect_billing_details_from_wallet_connector: Option, pub always_collect_shipping_details_from_wallet_connector: Option, - pub tax_connector_id: Option, + pub tax_connector_id: Option, pub is_tax_connector_enabled: bool, pub version: common_enums::ApiVersion, } @@ -88,7 +88,7 @@ pub struct BusinessProfileSetter { pub outgoing_webhook_custom_http_headers: OptionalEncryptableValue, pub always_collect_billing_details_from_wallet_connector: Option, pub always_collect_shipping_details_from_wallet_connector: Option, - pub tax_connector_id: Option, + pub tax_connector_id: Option, pub is_tax_connector_enabled: bool, } @@ -176,7 +176,7 @@ pub struct BusinessProfileGeneralUpdate { pub outgoing_webhook_custom_http_headers: OptionalEncryptableValue, pub always_collect_billing_details_from_wallet_connector: Option, pub always_collect_shipping_details_from_wallet_connector: Option, - pub tax_connector_id: Option, + pub tax_connector_id: Option, pub is_tax_connector_enabled: Option, } @@ -568,7 +568,7 @@ pub struct BusinessProfile { pub frm_routing_algorithm_id: Option, pub payout_routing_algorithm_id: Option, pub default_fallback_routing: Option, - pub tax_connector_id: Option, + pub tax_connector_id: Option, pub is_tax_connector_enabled: bool, pub version: common_enums::ApiVersion, } @@ -607,7 +607,7 @@ pub struct BusinessProfileSetter { pub frm_routing_algorithm_id: Option, pub payout_routing_algorithm_id: Option, pub default_fallback_routing: Option, - pub tax_connector_id: Option, + pub tax_connector_id: Option, pub is_tax_connector_enabled: bool, } @@ -659,6 +659,14 @@ impl From for BusinessProfile { } impl BusinessProfile { + pub fn get_is_tax_connector_enabled(&self) -> bool { + let is_tax_connector_enabled = self.is_tax_connector_enabled; + match &self.tax_connector_id { + Some(_id) => is_tax_connector_enabled, + _ => false, + } + } + #[cfg(feature = "v1")] pub fn get_order_fulfillment_time(&self) -> Option { self.intent_fulfillment_time diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index 54920dee6ba8..b0a660ad5d31 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -14,6 +14,7 @@ pub mod payment_methods; pub mod payments; #[cfg(feature = "payouts")] pub mod payouts; +pub mod refunds; pub mod router_data; pub mod router_data_v2; pub mod router_flow_types; diff --git a/crates/hyperswitch_domain_models/src/payment_method_data.rs b/crates/hyperswitch_domain_models/src/payment_method_data.rs index 5bff7b489ca6..b64c745eec0f 100644 --- a/crates/hyperswitch_domain_models/src/payment_method_data.rs +++ b/crates/hyperswitch_domain_models/src/payment_method_data.rs @@ -1,10 +1,13 @@ -use api_models::payments::ExtendedCardInfo; +use api_models::payments::{additional_info as payment_additional_types, ExtendedCardInfo}; use common_enums::enums as api_enums; use common_utils::{ id_type, + new_type::{ + MaskedBankAccount, MaskedIban, MaskedRoutingNumber, MaskedSortCode, MaskedUpiVpaId, + }, pii::{self, Email}, }; -use masking::Secret; +use masking::{PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use time::Date; @@ -281,6 +284,7 @@ pub enum BankRedirectData { card_number: Option, card_exp_month: Option>, card_exp_year: Option>, + card_holder_name: Option>, }, Bizum {}, Blik { @@ -288,19 +292,26 @@ pub enum BankRedirectData { }, Eps { bank_name: Option, + country: Option, }, Giropay { bank_account_bic: Option>, bank_account_iban: Option>, + country: Option, }, Ideal { bank_name: Option, }, - Interac {}, + Interac { + country: Option, + email: Option, + }, OnlineBankingCzechRepublic { issuer: common_enums::BankNames, }, - OnlineBankingFinland {}, + OnlineBankingFinland { + email: Option, + }, OnlineBankingPoland { issuer: common_enums::BankNames, }, @@ -309,14 +320,18 @@ pub enum BankRedirectData { }, OpenBankingUk { issuer: Option, + country: Option, }, Przelewy24 { bank_name: Option, }, Sofort { + country: Option, preferred_language: Option, }, - Trustly {}, + Trustly { + country: Option, + }, OnlineBankingFpx { issuer: common_enums::BankNames, }, @@ -421,20 +436,25 @@ pub enum BankDebitData { AchBankDebit { account_number: Secret, routing_number: Secret, + card_holder_name: Option>, + bank_account_holder_name: Option>, bank_name: Option, bank_type: Option, bank_holder_type: Option, }, SepaBankDebit { iban: Secret, + bank_account_holder_name: Option>, }, BecsBankDebit { account_number: Secret, bsb_number: Secret, + bank_account_holder_name: Option>, }, BacsBankDebit { account_number: Secret, sort_code: Secret, + bank_account_holder_name: Option>, }, } @@ -581,6 +601,17 @@ impl From for CardRedirectData { } } +impl From for api_models::payments::CardRedirectData { + fn from(value: CardRedirectData) -> Self { + match value { + CardRedirectData::Knet {} => Self::Knet {}, + CardRedirectData::Benefit {} => Self::Benefit {}, + CardRedirectData::MomoAtm {} => Self::MomoAtm {}, + CardRedirectData::CardRedirect {} => Self::CardRedirect {}, + } + } +} + impl From for WalletData { fn from(value: api_models::payments::WalletData) -> Self { match value { @@ -729,34 +760,40 @@ impl From for BankRedirectData { card_number, card_exp_month, card_exp_year, + card_holder_name, .. } => Self::BancontactCard { card_number, card_exp_month, card_exp_year, + card_holder_name, }, api_models::payments::BankRedirectData::Bizum {} => Self::Bizum {}, api_models::payments::BankRedirectData::Blik { blik_code } => Self::Blik { blik_code }, - api_models::payments::BankRedirectData::Eps { bank_name, .. } => { - Self::Eps { bank_name } - } + api_models::payments::BankRedirectData::Eps { + bank_name, country, .. + } => Self::Eps { bank_name, country }, api_models::payments::BankRedirectData::Giropay { bank_account_bic, bank_account_iban, + country, .. } => Self::Giropay { bank_account_bic, bank_account_iban, + country, }, api_models::payments::BankRedirectData::Ideal { bank_name, .. } => { Self::Ideal { bank_name } } - api_models::payments::BankRedirectData::Interac { .. } => Self::Interac {}, + api_models::payments::BankRedirectData::Interac { country, email } => { + Self::Interac { country, email } + } api_models::payments::BankRedirectData::OnlineBankingCzechRepublic { issuer } => { Self::OnlineBankingCzechRepublic { issuer } } - api_models::payments::BankRedirectData::OnlineBankingFinland { .. } => { - Self::OnlineBankingFinland {} + api_models::payments::BankRedirectData::OnlineBankingFinland { email } => { + Self::OnlineBankingFinland { email } } api_models::payments::BankRedirectData::OnlineBankingPoland { issuer } => { Self::OnlineBankingPoland { issuer } @@ -764,16 +801,23 @@ impl From for BankRedirectData { api_models::payments::BankRedirectData::OnlineBankingSlovakia { issuer } => { Self::OnlineBankingSlovakia { issuer } } - api_models::payments::BankRedirectData::OpenBankingUk { issuer, .. } => { - Self::OpenBankingUk { issuer } - } + api_models::payments::BankRedirectData::OpenBankingUk { + country, issuer, .. + } => Self::OpenBankingUk { country, issuer }, api_models::payments::BankRedirectData::Przelewy24 { bank_name, .. } => { Self::Przelewy24 { bank_name } } api_models::payments::BankRedirectData::Sofort { - preferred_language, .. - } => Self::Sofort { preferred_language }, - api_models::payments::BankRedirectData::Trustly { .. } => Self::Trustly {}, + preferred_language, + country, + .. + } => Self::Sofort { + country, + preferred_language, + }, + api_models::payments::BankRedirectData::Trustly { country } => Self::Trustly { + country: Some(country), + }, api_models::payments::BankRedirectData::OnlineBankingFpx { issuer } => { Self::OnlineBankingFpx { issuer } } @@ -800,6 +844,19 @@ impl From for CryptoData { } } +impl From for api_models::payments::CryptoData { + fn from(value: CryptoData) -> Self { + let CryptoData { + pay_currency, + network, + } = value; + Self { + pay_currency, + network, + } + } +} + impl From for UpiData { fn from(value: api_models::payments::UpiData) -> Self { match value { @@ -811,6 +868,21 @@ impl From for UpiData { } } +impl From for api_models::payments::additional_info::UpiAdditionalData { + fn from(value: UpiData) -> Self { + match value { + UpiData::UpiCollect(upi) => Self::UpiCollect(Box::new( + payment_additional_types::UpiCollectAdditionalData { + vpa_id: upi.vpa_id.map(MaskedUpiVpaId::from), + }, + )), + UpiData::UpiIntent(_) => { + Self::UpiIntent(Box::new(api_models::payments::UpiIntentData {})) + } + } + } +} + impl From for VoucherData { fn from(value: api_models::payments::VoucherData) -> Self { match value { @@ -842,6 +914,66 @@ impl From for VoucherData { } } +impl From> for Box { + fn from(value: Box) -> Self { + Self::new(api_models::payments::BoletoVoucherData { + social_security_number: value.social_security_number, + }) + } +} + +impl From> for Box { + fn from(_value: Box) -> Self { + Self::new(api_models::payments::AlfamartVoucherData { + first_name: None, + last_name: None, + email: None, + }) + } +} + +impl From> for Box { + fn from(_value: Box) -> Self { + Self::new(api_models::payments::IndomaretVoucherData { + first_name: None, + last_name: None, + email: None, + }) + } +} + +impl From> for Box { + fn from(_value: Box) -> Self { + Self::new(api_models::payments::JCSVoucherData { + first_name: None, + last_name: None, + email: None, + phone_number: None, + }) + } +} + +impl From for api_models::payments::VoucherData { + fn from(value: VoucherData) -> Self { + match value { + VoucherData::Boleto(boleto_data) => Self::Boleto(boleto_data.into()), + VoucherData::Alfamart(alfa_mart) => Self::Alfamart(alfa_mart.into()), + VoucherData::Indomaret(info_maret) => Self::Indomaret(info_maret.into()), + VoucherData::SevenEleven(jcs_data) + | VoucherData::Lawson(jcs_data) + | VoucherData::MiniStop(jcs_data) + | VoucherData::FamilyMart(jcs_data) + | VoucherData::Seicomart(jcs_data) + | VoucherData::PayEasy(jcs_data) => Self::SevenEleven(jcs_data.into()), + VoucherData::Efecty => Self::Efecty, + VoucherData::PagoEfectivo => Self::PagoEfectivo, + VoucherData::RedCompra => Self::RedCompra, + VoucherData::RedPagos => Self::RedPagos, + VoucherData::Oxxo => Self::Oxxo, + } + } +} + impl From for GiftCardData { fn from(value: api_models::payments::GiftCardData) -> Self { match value { @@ -854,6 +986,29 @@ impl From for GiftCardData { } } +impl From for payment_additional_types::GiftCardAdditionalData { + fn from(value: GiftCardData) -> Self { + match value { + GiftCardData::Givex(details) => Self::Givex(Box::new( + payment_additional_types::GivexGiftCardAdditionalData { + last4: details + .number + .peek() + .chars() + .rev() + .take(4) + .collect::() + .chars() + .rev() + .collect::() + .into(), + }, + )), + GiftCardData::PaySafeCard {} => Self::PaySafeCard {}, + } + } +} + impl From for CardToken { fn from(value: api_models::payments::CardToken) -> Self { let api_models::payments::CardToken { @@ -867,12 +1022,23 @@ impl From for CardToken { } } +impl From for payment_additional_types::CardTokenAdditionalData { + fn from(value: CardToken) -> Self { + let CardToken { + card_holder_name, .. + } = value; + Self { card_holder_name } + } +} + impl From for BankDebitData { fn from(value: api_models::payments::BankDebitData) -> Self { match value { api_models::payments::BankDebitData::AchBankDebit { account_number, routing_number, + card_holder_name, + bank_account_holder_name, bank_name, bank_type, bank_holder_type, @@ -880,33 +1046,101 @@ impl From for BankDebitData { } => Self::AchBankDebit { account_number, routing_number, + card_holder_name, + bank_account_holder_name, bank_name, bank_type, bank_holder_type, }, - api_models::payments::BankDebitData::SepaBankDebit { iban, .. } => { - Self::SepaBankDebit { iban } - } + api_models::payments::BankDebitData::SepaBankDebit { + iban, + bank_account_holder_name, + .. + } => Self::SepaBankDebit { + iban, + bank_account_holder_name, + }, api_models::payments::BankDebitData::BecsBankDebit { account_number, bsb_number, + bank_account_holder_name, .. } => Self::BecsBankDebit { account_number, bsb_number, + bank_account_holder_name, }, api_models::payments::BankDebitData::BacsBankDebit { account_number, sort_code, + bank_account_holder_name, .. } => Self::BacsBankDebit { account_number, sort_code, + bank_account_holder_name, }, } } } +impl From for api_models::payments::additional_info::BankDebitAdditionalData { + fn from(value: BankDebitData) -> Self { + match value { + BankDebitData::AchBankDebit { + account_number, + routing_number, + bank_name, + bank_type, + bank_holder_type, + card_holder_name, + bank_account_holder_name, + } => Self::Ach(Box::new( + payment_additional_types::AchBankDebitAdditionalData { + account_number: MaskedBankAccount::from(account_number), + routing_number: MaskedRoutingNumber::from(routing_number), + bank_name, + bank_type, + bank_holder_type, + card_holder_name, + bank_account_holder_name, + }, + )), + BankDebitData::SepaBankDebit { + iban, + bank_account_holder_name, + } => Self::Sepa(Box::new( + payment_additional_types::SepaBankDebitAdditionalData { + iban: MaskedIban::from(iban), + bank_account_holder_name, + }, + )), + BankDebitData::BecsBankDebit { + account_number, + bsb_number, + bank_account_holder_name, + } => Self::Becs(Box::new( + payment_additional_types::BecsBankDebitAdditionalData { + account_number: MaskedBankAccount::from(account_number), + bsb_number, + bank_account_holder_name, + }, + )), + BankDebitData::BacsBankDebit { + account_number, + sort_code, + bank_account_holder_name, + } => Self::Bacs(Box::new( + payment_additional_types::BacsBankDebitAdditionalData { + account_number: MaskedBankAccount::from(account_number), + sort_code: MaskedSortCode::from(sort_code), + bank_account_holder_name, + }, + )), + } + } +} + impl From for BankTransferData { fn from(value: api_models::payments::BankTransferData) -> Self { match value { @@ -954,6 +1188,37 @@ impl From for BankTransferData { } } +impl From for api_models::payments::additional_info::BankTransferAdditionalData { + fn from(value: BankTransferData) -> Self { + match value { + BankTransferData::AchBankTransfer {} => Self::Ach {}, + BankTransferData::SepaBankTransfer {} => Self::Sepa {}, + BankTransferData::BacsBankTransfer {} => Self::Bacs {}, + BankTransferData::MultibancoBankTransfer {} => Self::Multibanco {}, + BankTransferData::PermataBankTransfer {} => Self::Permata {}, + BankTransferData::BcaBankTransfer {} => Self::Bca {}, + BankTransferData::BniVaBankTransfer {} => Self::BniVa {}, + BankTransferData::BriVaBankTransfer {} => Self::BriVa {}, + BankTransferData::CimbVaBankTransfer {} => Self::CimbVa {}, + BankTransferData::DanamonVaBankTransfer {} => Self::DanamonVa {}, + BankTransferData::MandiriVaBankTransfer {} => Self::MandiriVa {}, + BankTransferData::Pix { pix_key, cpf, cnpj } => Self::Pix(Box::new( + api_models::payments::additional_info::PixBankTransferAdditionalData { + pix_key: pix_key.map(MaskedBankAccount::from), + cpf: cpf.map(MaskedBankAccount::from), + cnpj: cnpj.map(MaskedBankAccount::from), + }, + )), + BankTransferData::Pse {} => Self::Pse {}, + BankTransferData::LocalBankTransfer { bank_code } => Self::LocalBankTransfer(Box::new( + api_models::payments::additional_info::LocalBankTransferAdditionalData { + bank_code: bank_code.map(MaskedBankAccount::from), + }, + )), + } + } +} + impl From for RealTimePaymentData { fn from(value: api_models::payments::RealTimePaymentData) -> Self { match value { @@ -965,6 +1230,17 @@ impl From for RealTimePaymentData { } } +impl From for api_models::payments::RealTimePaymentData { + fn from(value: RealTimePaymentData) -> Self { + match value { + RealTimePaymentData::Fps {} => Self::Fps {}, + RealTimePaymentData::DuitNow {} => Self::DuitNow {}, + RealTimePaymentData::PromptPay {} => Self::PromptPay {}, + RealTimePaymentData::VietQr {} => Self::VietQr {}, + } + } +} + impl From for OpenBankingData { fn from(value: api_models::payments::OpenBankingData) -> Self { match value { @@ -973,6 +1249,14 @@ impl From for OpenBankingData { } } +impl From for api_models::payments::OpenBankingData { + fn from(value: OpenBankingData) -> Self { + match value { + OpenBankingData::OpenBankingPIS {} => Self::OpenBankingPIS {}, + } + } +} + #[derive(Debug, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct TokenizedCardValue1 { @@ -1024,6 +1308,16 @@ pub struct TokenizedBankRedirectValue2 { pub customer_id: Option, } +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct TokenizedBankDebitValue2 { + pub customer_id: Option, +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct TokenizedBankDebitValue1 { + pub data: BankDebitData, +} + pub trait GetPaymentMethodType { fn get_payment_method_type(&self) -> api_enums::PaymentMethodType; } diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index 70ab814d98b3..fee2155e8dca 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -1,4 +1,5 @@ use common_utils::{self, crypto::Encryptable, id_type, pii, types::MinorUnit}; +use diesel_models::payment_intent::TaxDetails; use masking::Secret; use time::PrimitiveDateTime; @@ -10,12 +11,14 @@ use common_enums as storage_enums; use self::payment_attempt::PaymentAttempt; use crate::RemoteStorageObject; +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] #[derive(Clone, Debug, PartialEq, serde::Serialize)] pub struct PaymentIntent { pub payment_id: id_type::PaymentId, pub merchant_id: id_type::MerchantId, pub status: storage_enums::IntentStatus, pub amount: MinorUnit, + pub shipping_cost: Option, pub currency: Option, pub amount_captured: Option, pub customer_id: Option, @@ -68,4 +71,81 @@ pub struct PaymentIntent { pub shipping_details: Option>>, pub is_payment_processor_token_flow: Option, pub organization_id: id_type::OrganizationId, + pub tax_details: Option, + pub skip_external_tax_calculation: Option, +} + +impl PaymentIntent { + #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2"),))] + pub fn get_id(&self) -> &id_type::PaymentId { + &self.payment_id + } + + #[cfg(all(feature = "v2", feature = "payment_v2",))] + pub fn get_id(&self) -> &id_type::PaymentId { + &self.id + } +} + +#[cfg(all(feature = "v2", feature = "payment_v2"))] +#[derive(Clone, Debug, PartialEq, serde::Serialize)] +pub struct PaymentIntent { + pub merchant_id: id_type::MerchantId, + pub status: storage_enums::IntentStatus, + pub amount: MinorUnit, + pub currency: Option, + pub amount_captured: Option, + pub customer_id: Option, + pub description: Option, + pub return_url: Option, + pub metadata: Option, + pub statement_descriptor_name: Option, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub created_at: PrimitiveDateTime, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub modified_at: PrimitiveDateTime, + #[serde(with = "common_utils::custom_serde::iso8601::option")] + pub last_synced: Option, + pub setup_future_usage: Option, + pub off_session: Option, + pub client_secret: Option, + pub active_attempt: RemoteStorageObject, + pub order_details: Option>, + pub allowed_payment_method_types: Option, + pub connector_metadata: Option, + pub feature_metadata: Option, + pub attempt_count: i16, + pub profile_id: Option, + pub payment_link_id: Option, + // Denotes the action(approve or reject) taken by merchant in case of manual review. + // Manual review can occur when the transaction is marked as risky by the frm_processor, payment processor or when there is underpayment/over payment incase of crypto payment + pub frm_merchant_decision: Option, + pub payment_confirm_source: Option, + + pub updated_by: String, + pub surcharge_applicable: Option, + pub request_incremental_authorization: Option, + pub authorization_count: Option, + #[serde(with = "common_utils::custom_serde::iso8601::option")] + pub session_expiry: Option, + pub request_external_three_ds_authentication: Option, + pub charges: Option, + pub frm_metadata: Option, + pub customer_details: Option>>, + pub merchant_order_reference_id: Option, + pub is_payment_processor_token_flow: Option, + pub shipping_cost: Option, + pub tax_details: Option, + pub merchant_reference_id: String, + pub billing_address: Option>>, + pub shipping_address: Option>>, + pub capture_method: Option, + pub id: id_type::PaymentId, + pub authentication_type: Option, + pub amount_to_capture: Option, + pub prerouting_algorithm: Option, + pub surcharge_amount: Option, + pub tax_on_surcharge: Option, + pub organization_id: id_type::OrganizationId, + pub skip_external_tax_calculation: Option, } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index 1fc327ef4e4a..b9cfba86d413 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -112,6 +112,7 @@ pub trait PaymentAttemptInterface { payment_method_type: Option>, authentication_type: Option>, merchant_connector_id: Option>, + profile_id_list: Option>, storage_scheme: storage_enums::MerchantStorageScheme, ) -> error_stack::Result; } @@ -183,6 +184,8 @@ pub struct PaymentAttempt { pub customer_acceptance: Option, pub profile_id: id_type::ProfileId, pub organization_id: id_type::OrganizationId, + pub shipping_cost: Option, + pub order_tax_amount: Option, } impl PaymentAttempt { @@ -275,6 +278,8 @@ pub struct PaymentAttemptNew { pub customer_acceptance: Option, pub profile_id: id_type::ProfileId, pub organization_id: id_type::OrganizationId, + pub shipping_cost: Option, + pub order_tax_amount: Option, } impl PaymentAttemptNew { @@ -283,6 +288,8 @@ impl PaymentAttemptNew { self.amount + self.surcharge_amount.unwrap_or_default() + self.tax_amount.unwrap_or_default() + + self.shipping_cost.unwrap_or_default() + + self.order_tax_amount.unwrap_or_default() } pub fn populate_derived_fields(self) -> Self { @@ -358,6 +365,8 @@ pub enum PaymentAttemptUpdate { client_source: Option, client_version: Option, customer_acceptance: Option, + shipping_cost: Option, + order_tax_amount: Option, }, RejectUpdate { status: storage_enums::AttemptStatus, @@ -499,7 +508,6 @@ impl behaviour::Conversion for PaymentIntent { async fn convert(self) -> CustomResult { Ok(DieselPaymentIntent { - payment_id: self.payment_id, merchant_id: self.merchant_id, status: self.status, amount: self.amount, @@ -509,11 +517,7 @@ impl behaviour::Conversion for PaymentIntent { description: self.description, return_url: self.return_url, metadata: self.metadata, - connector_id: self.connector_id, - shipping_address_id: self.shipping_address_id, - billing_address_id: self.billing_address_id, statement_descriptor_name: self.statement_descriptor_name, - statement_descriptor_suffix: self.statement_descriptor_suffix, created_at: self.created_at, modified_at: self.modified_at, last_synced: self.last_synced, @@ -521,33 +525,40 @@ impl behaviour::Conversion for PaymentIntent { off_session: self.off_session, client_secret: self.client_secret, active_attempt_id: self.active_attempt.get_id(), - business_country: self.business_country, - business_label: self.business_label, order_details: self.order_details, allowed_payment_method_types: self.allowed_payment_method_types, connector_metadata: self.connector_metadata, feature_metadata: self.feature_metadata, attempt_count: self.attempt_count, profile_id: self.profile_id, - merchant_decision: self.merchant_decision, + frm_merchant_decision: self.frm_merchant_decision, payment_link_id: self.payment_link_id, payment_confirm_source: self.payment_confirm_source, updated_by: self.updated_by, surcharge_applicable: self.surcharge_applicable, request_incremental_authorization: self.request_incremental_authorization, - incremental_authorization_allowed: self.incremental_authorization_allowed, authorization_count: self.authorization_count, - fingerprint_id: self.fingerprint_id, session_expiry: self.session_expiry, request_external_three_ds_authentication: self.request_external_three_ds_authentication, charges: self.charges, frm_metadata: self.frm_metadata, customer_details: self.customer_details.map(Encryption::from), - billing_details: self.billing_details.map(Encryption::from), + billing_address: self.billing_address.map(Encryption::from), merchant_order_reference_id: self.merchant_order_reference_id, - shipping_details: self.shipping_details.map(Encryption::from), + shipping_address: self.shipping_address.map(Encryption::from), is_payment_processor_token_flow: self.is_payment_processor_token_flow, + capture_method: self.capture_method, + id: self.id, + authentication_type: self.authentication_type, + amount_to_capture: self.amount_to_capture, + prerouting_algorithm: self.prerouting_algorithm, + merchant_reference_id: self.merchant_reference_id, + surcharge_amount: self.surcharge_amount, + tax_on_surcharge: self.tax_on_surcharge, organization_id: self.organization_id, + shipping_cost: self.shipping_cost, + tax_details: self.tax_details, + skip_external_tax_calculation: self.skip_external_tax_calculation, }) } async fn convert_back( @@ -572,7 +583,6 @@ impl behaviour::Conversion for PaymentIntent { .and_then(|val| val.try_into_optionaloperation()) }; Ok::>(Self { - payment_id: storage_model.payment_id, merchant_id: storage_model.merchant_id, status: storage_model.status, amount: storage_model.amount, @@ -582,11 +592,7 @@ impl behaviour::Conversion for PaymentIntent { description: storage_model.description, return_url: storage_model.return_url, metadata: storage_model.metadata, - connector_id: storage_model.connector_id, - shipping_address_id: storage_model.shipping_address_id, - billing_address_id: storage_model.billing_address_id, statement_descriptor_name: storage_model.statement_descriptor_name, - statement_descriptor_suffix: storage_model.statement_descriptor_suffix, created_at: storage_model.created_at, modified_at: storage_model.modified_at, last_synced: storage_model.last_synced, @@ -594,23 +600,19 @@ impl behaviour::Conversion for PaymentIntent { off_session: storage_model.off_session, client_secret: storage_model.client_secret, active_attempt: RemoteStorageObject::ForeignID(storage_model.active_attempt_id), - business_country: storage_model.business_country, - business_label: storage_model.business_label, order_details: storage_model.order_details, allowed_payment_method_types: storage_model.allowed_payment_method_types, connector_metadata: storage_model.connector_metadata, feature_metadata: storage_model.feature_metadata, attempt_count: storage_model.attempt_count, profile_id: storage_model.profile_id, - merchant_decision: storage_model.merchant_decision, + frm_merchant_decision: storage_model.frm_merchant_decision, payment_link_id: storage_model.payment_link_id, payment_confirm_source: storage_model.payment_confirm_source, updated_by: storage_model.updated_by, surcharge_applicable: storage_model.surcharge_applicable, request_incremental_authorization: storage_model.request_incremental_authorization, - incremental_authorization_allowed: storage_model.incremental_authorization_allowed, authorization_count: storage_model.authorization_count, - fingerprint_id: storage_model.fingerprint_id, session_expiry: storage_model.session_expiry, request_external_three_ds_authentication: storage_model .request_external_three_ds_authentication, @@ -620,17 +622,28 @@ impl behaviour::Conversion for PaymentIntent { .customer_details .async_lift(inner_decrypt) .await?, - billing_details: storage_model - .billing_details + billing_address: storage_model + .billing_address .async_lift(inner_decrypt) .await?, merchant_order_reference_id: storage_model.merchant_order_reference_id, - shipping_details: storage_model - .shipping_details + shipping_address: storage_model + .shipping_address .async_lift(inner_decrypt) .await?, is_payment_processor_token_flow: storage_model.is_payment_processor_token_flow, + capture_method: storage_model.capture_method, + id: storage_model.id, + merchant_reference_id: storage_model.merchant_reference_id, organization_id: storage_model.organization_id, + authentication_type: storage_model.authentication_type, + amount_to_capture: storage_model.amount_to_capture, + prerouting_algorithm: storage_model.prerouting_algorithm, + surcharge_amount: storage_model.surcharge_amount, + tax_on_surcharge: storage_model.tax_on_surcharge, + shipping_cost: storage_model.shipping_cost, + tax_details: storage_model.tax_details, + skip_external_tax_calculation: storage_model.skip_external_tax_calculation, }) } .await @@ -641,7 +654,6 @@ impl behaviour::Conversion for PaymentIntent { async fn construct_new(self) -> CustomResult { Ok(DieselPaymentIntentNew { - payment_id: self.payment_id, merchant_id: self.merchant_id, status: self.status, amount: self.amount, @@ -651,11 +663,7 @@ impl behaviour::Conversion for PaymentIntent { description: self.description, return_url: self.return_url, metadata: self.metadata, - connector_id: self.connector_id, - shipping_address_id: self.shipping_address_id, - billing_address_id: self.billing_address_id, statement_descriptor_name: self.statement_descriptor_name, - statement_descriptor_suffix: self.statement_descriptor_suffix, created_at: self.created_at, modified_at: self.modified_at, last_synced: self.last_synced, @@ -663,33 +671,40 @@ impl behaviour::Conversion for PaymentIntent { off_session: self.off_session, client_secret: self.client_secret, active_attempt_id: self.active_attempt.get_id(), - business_country: self.business_country, - business_label: self.business_label, order_details: self.order_details, allowed_payment_method_types: self.allowed_payment_method_types, connector_metadata: self.connector_metadata, feature_metadata: self.feature_metadata, attempt_count: self.attempt_count, profile_id: self.profile_id, - merchant_decision: self.merchant_decision, + frm_merchant_decision: self.frm_merchant_decision, payment_link_id: self.payment_link_id, payment_confirm_source: self.payment_confirm_source, updated_by: self.updated_by, surcharge_applicable: self.surcharge_applicable, request_incremental_authorization: self.request_incremental_authorization, - incremental_authorization_allowed: self.incremental_authorization_allowed, authorization_count: self.authorization_count, - fingerprint_id: self.fingerprint_id, session_expiry: self.session_expiry, request_external_three_ds_authentication: self.request_external_three_ds_authentication, charges: self.charges, frm_metadata: self.frm_metadata, customer_details: self.customer_details.map(Encryption::from), - billing_details: self.billing_details.map(Encryption::from), + billing_address: self.billing_address.map(Encryption::from), merchant_order_reference_id: self.merchant_order_reference_id, - shipping_details: self.shipping_details.map(Encryption::from), + shipping_address: self.shipping_address.map(Encryption::from), is_payment_processor_token_flow: self.is_payment_processor_token_flow, + capture_method: self.capture_method, + id: self.id, + merchant_reference_id: self.merchant_reference_id, + authentication_type: self.authentication_type, + amount_to_capture: self.amount_to_capture, + prerouting_algorithm: self.prerouting_algorithm, + surcharge_amount: self.surcharge_amount, + tax_on_surcharge: self.tax_on_surcharge, organization_id: self.organization_id, + shipping_cost: self.shipping_cost, + tax_details: self.tax_details, + skip_external_tax_calculation: self.skip_external_tax_calculation, }) } } @@ -751,6 +766,9 @@ impl behaviour::Conversion for PaymentIntent { shipping_details: self.shipping_details.map(Encryption::from), is_payment_processor_token_flow: self.is_payment_processor_token_flow, organization_id: self.organization_id, + shipping_cost: self.shipping_cost, + tax_details: self.tax_details, + skip_external_tax_calculation: self.skip_external_tax_calculation, }) } @@ -820,6 +838,8 @@ impl behaviour::Conversion for PaymentIntent { .request_external_three_ds_authentication, charges: storage_model.charges, frm_metadata: storage_model.frm_metadata, + shipping_cost: storage_model.shipping_cost, + tax_details: storage_model.tax_details, customer_details: storage_model .customer_details .async_lift(inner_decrypt) @@ -835,6 +855,7 @@ impl behaviour::Conversion for PaymentIntent { .await?, is_payment_processor_token_flow: storage_model.is_payment_processor_token_flow, organization_id: storage_model.organization_id, + skip_external_tax_calculation: storage_model.skip_external_tax_calculation, }) } .await @@ -894,6 +915,9 @@ impl behaviour::Conversion for PaymentIntent { shipping_details: self.shipping_details.map(Encryption::from), is_payment_processor_token_flow: self.is_payment_processor_token_flow, organization_id: self.organization_id, + shipping_cost: self.shipping_cost, + tax_details: self.tax_details, + skip_external_tax_calculation: self.skip_external_tax_calculation, }) } } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs index 6c9ba443de76..c4d19c83a398 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs @@ -32,6 +32,7 @@ pub trait PaymentIntentInterface { storage_scheme: storage_enums::MerchantStorageScheme, ) -> error_stack::Result; + #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] async fn find_payment_intent_by_payment_id_merchant_id( &self, state: &KeyManagerState, @@ -41,13 +42,26 @@ pub trait PaymentIntentInterface { storage_scheme: storage_enums::MerchantStorageScheme, ) -> error_stack::Result; + #[cfg(all(feature = "v2", feature = "payment_v2"))] + async fn find_payment_intent_by_id( + &self, + state: &KeyManagerState, + id: &id_type::PaymentId, + merchant_key_store: &MerchantKeyStore, + storage_scheme: storage_enums::MerchantStorageScheme, + ) -> error_stack::Result; + async fn get_active_payment_attempt( &self, payment: &mut PaymentIntent, storage_scheme: storage_enums::MerchantStorageScheme, ) -> error_stack::Result; - #[cfg(feature = "olap")] + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_v2"), + feature = "olap" + ))] async fn filter_payment_intent_by_constraints( &self, state: &KeyManagerState, @@ -57,7 +71,11 @@ pub trait PaymentIntentInterface { storage_scheme: storage_enums::MerchantStorageScheme, ) -> error_stack::Result, errors::StorageError>; - #[cfg(feature = "olap")] + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_v2"), + feature = "olap" + ))] async fn filter_payment_intents_by_time_range_constraints( &self, state: &KeyManagerState, @@ -67,14 +85,23 @@ pub trait PaymentIntentInterface { storage_scheme: storage_enums::MerchantStorageScheme, ) -> error_stack::Result, errors::StorageError>; - #[cfg(feature = "olap")] + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_v2"), + feature = "olap" + ))] async fn get_intent_status_with_count( &self, merchant_id: &id_type::MerchantId, + profile_id_list: Option>, constraints: &api_models::payments::TimeRange, ) -> error_stack::Result, errors::StorageError>; - #[cfg(feature = "olap")] + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_v2"), + feature = "olap" + ))] async fn get_filtered_payment_intents_attempt( &self, state: &KeyManagerState, @@ -84,7 +111,11 @@ pub trait PaymentIntentInterface { storage_scheme: storage_enums::MerchantStorageScheme, ) -> error_stack::Result, errors::StorageError>; - #[cfg(feature = "olap")] + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_v2"), + feature = "olap" + ))] async fn get_filtered_active_attempt_ids_for_total_count( &self, merchant_id: &id_type::MerchantId, @@ -101,6 +132,7 @@ pub struct CustomerData { pub phone_country_code: Option, } +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] #[derive(Clone, Debug, PartialEq)] pub struct PaymentIntentNew { pub payment_id: id_type::PaymentId, @@ -152,8 +184,89 @@ pub struct PaymentIntentNew { pub shipping_details: Option>>, pub is_payment_processor_token_flow: Option, pub organization_id: id_type::OrganizationId, + pub skip_external_tax_calculation: Option, } +#[cfg(all(feature = "v2", feature = "payment_v2"))] +#[derive(Clone, Debug, PartialEq)] +pub struct PaymentIntentNew { + pub payment_id: id_type::PaymentId, + pub merchant_id: id_type::MerchantId, + pub status: storage_enums::IntentStatus, + pub amount: MinorUnit, + pub currency: Option, + pub amount_captured: Option, + pub customer_id: Option, + pub description: Option, + pub return_url: Option, + pub metadata: Option, + pub frm_metadata: Option, + pub connector_id: Option, + pub shipping_address_id: Option, + pub billing_address_id: Option, + pub statement_descriptor_name: Option, + pub statement_descriptor_suffix: Option, + pub created_at: Option, + pub modified_at: Option, + pub last_synced: Option, + pub setup_future_usage: Option, + pub off_session: Option, + pub client_secret: Option, + pub active_attempt: RemoteStorageObject, + pub business_country: Option, + pub business_label: Option, + pub order_details: Option>, + pub allowed_payment_method_types: Option, + pub connector_metadata: Option, + pub feature_metadata: Option, + pub attempt_count: i16, + pub profile_id: Option, + pub merchant_decision: Option, + pub payment_link_id: Option, + pub payment_confirm_source: Option, + + pub updated_by: String, + pub surcharge_applicable: Option, + pub request_incremental_authorization: Option, + pub incremental_authorization_allowed: Option, + pub authorization_count: Option, + pub fingerprint_id: Option, + pub session_expiry: Option, + pub request_external_three_ds_authentication: Option, + pub charges: Option, + pub customer_details: Option>>, + pub billing_details: Option>>, + pub shipping_details: Option>>, + pub is_payment_processor_token_flow: Option, + pub organization_id: id_type::OrganizationId, +} + +#[cfg(all(feature = "v2", feature = "payment_v2"))] +#[derive(Debug, Clone, Serialize)] +pub struct PaymentIntentUpdateFields { + pub amount: MinorUnit, + pub currency: storage_enums::Currency, + pub setup_future_usage: Option, + pub status: storage_enums::IntentStatus, + pub customer_id: Option, + pub shipping_address: Option>>, + pub billing_address: Option>>, + pub return_url: Option, + pub description: Option, + pub statement_descriptor_name: Option, + pub order_details: Option>, + pub metadata: Option, + pub payment_confirm_source: Option, + pub updated_by: String, + pub session_expiry: Option, + pub request_external_three_ds_authentication: Option, + pub frm_metadata: Option, + pub customer_details: Option>>, + pub merchant_order_reference_id: Option, + pub is_payment_processor_token_flow: Option, +} + +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] #[derive(Debug, Clone, Serialize)] pub struct PaymentIntentUpdateFields { pub amount: MinorUnit, @@ -182,8 +295,10 @@ pub struct PaymentIntentUpdateFields { pub merchant_order_reference_id: Option, pub shipping_details: Option>>, pub is_payment_processor_token_flow: Option, + pub tax_details: Option, } +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] #[derive(Debug, Clone, Serialize)] pub enum PaymentIntentUpdate { ResponseUpdate { @@ -257,8 +372,121 @@ pub enum PaymentIntentUpdate { status: Option, updated_by: String, }, + SessionResponseUpdate { + tax_details: diesel_models::TaxDetails, + shipping_address_id: Option, + updated_by: String, + shipping_details: Option>>, + }, +} + +#[cfg(all(feature = "v2", feature = "payment_v2"))] +#[derive(Debug, Clone, Serialize)] +pub enum PaymentIntentUpdate { + ResponseUpdate { + status: storage_enums::IntentStatus, + amount_captured: Option, + return_url: Option, + updated_by: String, + }, + MetadataUpdate { + metadata: serde_json::Value, + updated_by: String, + }, + Update(Box), + PaymentCreateUpdate { + return_url: Option, + status: Option, + customer_id: Option, + shipping_address: Option>>, + billing_address: Option>>, + customer_details: Option>>, + updated_by: String, + }, + MerchantStatusUpdate { + status: storage_enums::IntentStatus, + shipping_address: Option>>, + billing_address: Option>>, + updated_by: String, + }, + PGStatusUpdate { + status: storage_enums::IntentStatus, + updated_by: String, + }, + PaymentAttemptAndAttemptCountUpdate { + active_attempt_id: String, + attempt_count: i16, + updated_by: String, + }, + StatusAndAttemptUpdate { + status: storage_enums::IntentStatus, + active_attempt_id: String, + attempt_count: i16, + updated_by: String, + }, + ApproveUpdate { + status: storage_enums::IntentStatus, + frm_merchant_decision: Option, + updated_by: String, + }, + RejectUpdate { + status: storage_enums::IntentStatus, + frm_merchant_decision: Option, + updated_by: String, + }, + SurchargeApplicableUpdate { + surcharge_applicable: bool, + updated_by: String, + }, + IncrementalAuthorizationAmountUpdate { + amount: MinorUnit, + }, + AuthorizationCountUpdate { + authorization_count: i32, + }, + CompleteAuthorizeUpdate { + shipping_address: Option>>, + }, + ManualUpdate { + status: Option, + updated_by: String, + }, } +#[cfg(all(feature = "v2", feature = "payment_v2"))] +#[derive(Clone, Debug, Default)] +pub struct PaymentIntentUpdateInternal { + pub amount: Option, + pub currency: Option, + pub status: Option, + pub amount_captured: Option, + pub customer_id: Option, + pub return_url: Option, + pub setup_future_usage: Option, + pub off_session: Option, + pub metadata: Option, + pub modified_at: Option, + pub active_attempt_id: Option, + pub description: Option, + pub statement_descriptor_name: Option, + pub order_details: Option>, + pub attempt_count: Option, + pub frm_merchant_decision: Option, + pub payment_confirm_source: Option, + pub updated_by: String, + pub surcharge_applicable: Option, + pub authorization_count: Option, + pub session_expiry: Option, + pub request_external_three_ds_authentication: Option, + pub frm_metadata: Option, + pub customer_details: Option>>, + pub billing_address: Option>>, + pub shipping_address: Option>>, + pub merchant_order_reference_id: Option, + pub is_payment_processor_token_flow: Option, +} + +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] #[derive(Clone, Debug, Default)] pub struct PaymentIntentUpdateInternal { pub amount: Option, @@ -299,8 +527,187 @@ pub struct PaymentIntentUpdateInternal { pub merchant_order_reference_id: Option, pub shipping_details: Option>>, pub is_payment_processor_token_flow: Option, + pub tax_details: Option, +} + +#[cfg(all(feature = "v2", feature = "payment_v2"))] +impl From for PaymentIntentUpdateInternal { + fn from(payment_intent_update: PaymentIntentUpdate) -> Self { + match payment_intent_update { + PaymentIntentUpdate::MetadataUpdate { + metadata, + updated_by, + } => Self { + metadata: Some(metadata), + modified_at: Some(common_utils::date_time::now()), + updated_by, + ..Default::default() + }, + PaymentIntentUpdate::Update(value) => Self { + amount: Some(value.amount), + currency: Some(value.currency), + setup_future_usage: value.setup_future_usage, + status: Some(value.status), + customer_id: value.customer_id, + return_url: value.return_url, + description: value.description, + statement_descriptor_name: value.statement_descriptor_name, + order_details: value.order_details, + metadata: value.metadata, + payment_confirm_source: value.payment_confirm_source, + updated_by: value.updated_by, + session_expiry: value.session_expiry, + request_external_three_ds_authentication: value + .request_external_three_ds_authentication, + frm_metadata: value.frm_metadata, + customer_details: value.customer_details, + billing_address: value.billing_address, + merchant_order_reference_id: value.merchant_order_reference_id, + shipping_address: value.shipping_address, + is_payment_processor_token_flow: value.is_payment_processor_token_flow, + modified_at: Some(common_utils::date_time::now()), + ..Default::default() + }, + PaymentIntentUpdate::PaymentCreateUpdate { + return_url, + status, + customer_id, + shipping_address, + billing_address, + customer_details, + updated_by, + } => Self { + return_url, + status, + customer_id, + shipping_address, + billing_address, + customer_details, + modified_at: Some(common_utils::date_time::now()), + updated_by, + ..Default::default() + }, + PaymentIntentUpdate::PGStatusUpdate { status, updated_by } => Self { + status: Some(status), + modified_at: Some(common_utils::date_time::now()), + updated_by, + ..Default::default() + }, + PaymentIntentUpdate::MerchantStatusUpdate { + status, + shipping_address, + billing_address, + updated_by, + } => Self { + status: Some(status), + shipping_address, + billing_address, + modified_at: Some(common_utils::date_time::now()), + updated_by, + ..Default::default() + }, + PaymentIntentUpdate::ResponseUpdate { + // amount, + // currency, + status, + amount_captured, + // customer_id, + return_url, + updated_by, + } => Self { + // amount, + // currency: Some(currency), + status: Some(status), + amount_captured, + // customer_id, + return_url, + modified_at: Some(common_utils::date_time::now()), + updated_by, + ..Default::default() + }, + PaymentIntentUpdate::PaymentAttemptAndAttemptCountUpdate { + active_attempt_id, + attempt_count, + updated_by, + } => Self { + active_attempt_id: Some(active_attempt_id), + attempt_count: Some(attempt_count), + updated_by, + modified_at: Some(common_utils::date_time::now()), + ..Default::default() + }, + PaymentIntentUpdate::StatusAndAttemptUpdate { + status, + active_attempt_id, + attempt_count, + updated_by, + } => Self { + status: Some(status), + active_attempt_id: Some(active_attempt_id), + attempt_count: Some(attempt_count), + updated_by, + modified_at: Some(common_utils::date_time::now()), + ..Default::default() + }, + PaymentIntentUpdate::ApproveUpdate { + status, + frm_merchant_decision, + updated_by, + } => Self { + status: Some(status), + frm_merchant_decision, + updated_by, + modified_at: Some(common_utils::date_time::now()), + ..Default::default() + }, + PaymentIntentUpdate::RejectUpdate { + status, + frm_merchant_decision, + updated_by, + } => Self { + status: Some(status), + frm_merchant_decision, + updated_by, + modified_at: Some(common_utils::date_time::now()), + ..Default::default() + }, + PaymentIntentUpdate::SurchargeApplicableUpdate { + surcharge_applicable, + updated_by, + } => Self { + surcharge_applicable: Some(surcharge_applicable), + modified_at: Some(common_utils::date_time::now()), + updated_by, + ..Default::default() + }, + PaymentIntentUpdate::IncrementalAuthorizationAmountUpdate { amount } => Self { + amount: Some(amount), + modified_at: Some(common_utils::date_time::now()), + ..Default::default() + }, + PaymentIntentUpdate::AuthorizationCountUpdate { + authorization_count, + } => Self { + authorization_count: Some(authorization_count), + modified_at: Some(common_utils::date_time::now()), + ..Default::default() + }, + PaymentIntentUpdate::CompleteAuthorizeUpdate { shipping_address } => Self { + shipping_address, + modified_at: Some(common_utils::date_time::now()), + ..Default::default() + }, + PaymentIntentUpdate::ManualUpdate { status, updated_by } => Self { + status, + modified_at: Some(common_utils::date_time::now()), + updated_by, + ..Default::default() + }, + } + } } +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] impl From for PaymentIntentUpdateInternal { fn from(payment_intent_update: PaymentIntentUpdate) -> Self { match payment_intent_update { @@ -481,6 +888,18 @@ impl From for PaymentIntentUpdateInternal { updated_by, ..Default::default() }, + PaymentIntentUpdate::SessionResponseUpdate { + tax_details, + shipping_address_id, + updated_by, + shipping_details, + } => Self { + tax_details: Some(tax_details), + shipping_address_id, + updated_by, + shipping_details, + ..Default::default() + }, } } } @@ -489,7 +908,150 @@ use diesel_models::{ PaymentIntentUpdate as DieselPaymentIntentUpdate, PaymentIntentUpdateFields as DieselPaymentIntentUpdateFields, }; +#[cfg(all(feature = "v2", feature = "payment_v2"))] +impl From for DieselPaymentIntentUpdate { + fn from(value: PaymentIntentUpdate) -> Self { + match value { + PaymentIntentUpdate::ResponseUpdate { + status, + amount_captured, + return_url, + updated_by, + } => Self::ResponseUpdate { + status, + amount_captured, + return_url, + updated_by, + }, + PaymentIntentUpdate::MetadataUpdate { + metadata, + updated_by, + } => Self::MetadataUpdate { + metadata, + updated_by, + }, + PaymentIntentUpdate::Update(value) => { + Self::Update(Box::new(DieselPaymentIntentUpdateFields { + amount: value.amount, + currency: value.currency, + setup_future_usage: value.setup_future_usage, + status: value.status, + customer_id: value.customer_id, + return_url: value.return_url, + description: value.description, + statement_descriptor_name: value.statement_descriptor_name, + order_details: value.order_details, + metadata: value.metadata, + payment_confirm_source: value.payment_confirm_source, + updated_by: value.updated_by, + session_expiry: value.session_expiry, + request_external_three_ds_authentication: value + .request_external_three_ds_authentication, + frm_metadata: value.frm_metadata, + customer_details: value.customer_details.map(Encryption::from), + billing_address: value.billing_address.map(Encryption::from), + shipping_address: value.shipping_address.map(Encryption::from), + merchant_order_reference_id: value.merchant_order_reference_id, + is_payment_processor_token_flow: value.is_payment_processor_token_flow, + })) + } + PaymentIntentUpdate::PaymentCreateUpdate { + return_url, + status, + customer_id, + shipping_address, + billing_address, + customer_details, + updated_by, + } => Self::PaymentCreateUpdate { + return_url, + status, + customer_id, + shipping_address: shipping_address.map(Encryption::from), + billing_address: billing_address.map(Encryption::from), + customer_details: customer_details.map(Encryption::from), + updated_by, + }, + PaymentIntentUpdate::MerchantStatusUpdate { + status, + shipping_address, + billing_address, + updated_by, + } => Self::MerchantStatusUpdate { + status, + shipping_address: shipping_address.map(Encryption::from), + billing_address: billing_address.map(Encryption::from), + updated_by, + }, + PaymentIntentUpdate::PGStatusUpdate { status, updated_by } => { + Self::PGStatusUpdate { status, updated_by } + } + PaymentIntentUpdate::PaymentAttemptAndAttemptCountUpdate { + active_attempt_id, + attempt_count, + updated_by, + } => Self::PaymentAttemptAndAttemptCountUpdate { + active_attempt_id, + attempt_count, + updated_by, + }, + PaymentIntentUpdate::StatusAndAttemptUpdate { + status, + active_attempt_id, + attempt_count, + updated_by, + } => Self::StatusAndAttemptUpdate { + status, + active_attempt_id, + attempt_count, + updated_by, + }, + PaymentIntentUpdate::ApproveUpdate { + status, + frm_merchant_decision, + updated_by, + } => Self::ApproveUpdate { + status, + frm_merchant_decision, + updated_by, + }, + PaymentIntentUpdate::RejectUpdate { + status, + frm_merchant_decision, + updated_by, + } => Self::RejectUpdate { + status, + frm_merchant_decision, + updated_by, + }, + PaymentIntentUpdate::SurchargeApplicableUpdate { + surcharge_applicable, + updated_by, + } => Self::SurchargeApplicableUpdate { + surcharge_applicable: Some(surcharge_applicable), + updated_by, + }, + PaymentIntentUpdate::IncrementalAuthorizationAmountUpdate { amount } => { + Self::IncrementalAuthorizationAmountUpdate { amount } + } + PaymentIntentUpdate::AuthorizationCountUpdate { + authorization_count, + } => Self::AuthorizationCountUpdate { + authorization_count, + }, + PaymentIntentUpdate::CompleteAuthorizeUpdate { shipping_address } => { + Self::CompleteAuthorizeUpdate { + shipping_address: shipping_address.map(Encryption::from), + } + } + PaymentIntentUpdate::ManualUpdate { status, updated_by } => { + Self::ManualUpdate { status, updated_by } + } + } + } +} +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] impl From for DieselPaymentIntentUpdate { fn from(value: PaymentIntentUpdate) -> Self { match value { @@ -544,6 +1106,7 @@ impl From for DieselPaymentIntentUpdate { merchant_order_reference_id: value.merchant_order_reference_id, shipping_details: value.shipping_details.map(Encryption::from), is_payment_processor_token_flow: value.is_payment_processor_token_flow, + tax_details: value.tax_details, })) } PaymentIntentUpdate::PaymentCreateUpdate { @@ -644,14 +1207,92 @@ impl From for DieselPaymentIntentUpdate { PaymentIntentUpdate::ManualUpdate { status, updated_by } => { Self::ManualUpdate { status, updated_by } } + PaymentIntentUpdate::SessionResponseUpdate { + tax_details, + shipping_address_id, + updated_by, + shipping_details, + } => Self::SessionResponseUpdate { + tax_details, + shipping_address_id, + updated_by, + shipping_details: shipping_details.map(Encryption::from), + }, } } } +#[cfg(all(feature = "v2", feature = "payment_v2"))] impl From for diesel_models::PaymentIntentUpdateInternal { fn from(value: PaymentIntentUpdateInternal) -> Self { let modified_at = common_utils::date_time::now(); + let PaymentIntentUpdateInternal { + amount, + currency, + status, + amount_captured, + customer_id, + return_url, + setup_future_usage, + off_session, + metadata, + modified_at: _, + active_attempt_id, + description, + statement_descriptor_name, + order_details, + attempt_count, + frm_merchant_decision, + payment_confirm_source, + updated_by, + surcharge_applicable, + authorization_count, + session_expiry, + request_external_three_ds_authentication, + frm_metadata, + customer_details, + billing_address, + merchant_order_reference_id, + shipping_address, + is_payment_processor_token_flow, + } = value; + Self { + amount, + currency, + status, + amount_captured, + customer_id, + return_url, + setup_future_usage, + off_session, + metadata, + modified_at, + active_attempt_id, + description, + statement_descriptor_name, + order_details, + attempt_count, + frm_merchant_decision, + payment_confirm_source, + updated_by, + surcharge_applicable, + authorization_count, + session_expiry, + request_external_three_ds_authentication, + frm_metadata, + customer_details: customer_details.map(Encryption::from), + billing_address: billing_address.map(Encryption::from), + merchant_order_reference_id, + shipping_address: shipping_address.map(Encryption::from), + is_payment_processor_token_flow, + } + } +} +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] +impl From for diesel_models::PaymentIntentUpdateInternal { + fn from(value: PaymentIntentUpdateInternal) -> Self { + let modified_at = common_utils::date_time::now(); let PaymentIntentUpdateInternal { amount, currency, @@ -688,8 +1329,8 @@ impl From for diesel_models::PaymentIntentUpdateInt merchant_order_reference_id, shipping_details, is_payment_processor_token_flow, + tax_details, } = value; - Self { amount, currency, @@ -726,6 +1367,7 @@ impl From for diesel_models::PaymentIntentUpdateInt merchant_order_reference_id, shipping_details: shipping_details.map(Encryption::from), is_payment_processor_token_flow, + tax_details, } } } @@ -737,6 +1379,16 @@ pub enum PaymentIntentFetchConstraints { List(Box), } +impl PaymentIntentFetchConstraints { + pub fn get_profile_id_list(&self) -> Option> { + if let Self::List(pi_list_params) = self { + pi_list_params.profile_id.clone() + } else { + None + } + } +} + pub struct PaymentIntentListParams { pub offset: u32, pub starting_at: Option, @@ -749,7 +1401,7 @@ pub struct PaymentIntentListParams { pub payment_method_type: Option>, pub authentication_type: Option>, pub merchant_connector_id: Option>, - pub profile_id: Option, + pub profile_id: Option>, pub customer_id: Option, pub starting_after_id: Option, pub ending_before_id: Option, @@ -759,10 +1411,21 @@ pub struct PaymentIntentListParams { impl From for PaymentIntentFetchConstraints { fn from(value: api_models::payments::PaymentListConstraints) -> Self { + let api_models::payments::PaymentListConstraints { + customer_id, + starting_after, + ending_before, + limit, + created, + created_lt, + created_gt, + created_lte, + created_gte, + } = value; Self::List(Box::new(PaymentIntentListParams { offset: 0, - starting_at: value.created_gte.or(value.created_gt).or(value.created), - ending_at: value.created_lte.or(value.created_lt).or(value.created), + starting_at: created_gte.or(created_gt).or(created), + ending_at: created_lte.or(created_lt).or(created), amount_filter: None, connector: None, currency: None, @@ -772,10 +1435,10 @@ impl From for PaymentIntentFetchCo authentication_type: None, merchant_connector_id: None, profile_id: None, - customer_id: value.customer_id, - starting_after_id: value.starting_after, - ending_before_id: value.ending_before, - limit: Some(std::cmp::min(value.limit, PAYMENTS_LIST_MAX_LIMIT_V1)), + customer_id, + starting_after_id: starting_after, + ending_before_id: ending_before, + limit: Some(std::cmp::min(limit, PAYMENTS_LIST_MAX_LIMIT_V1)), order: Default::default(), })) } @@ -807,28 +1470,96 @@ impl From for PaymentIntentFetchConstraints { impl From for PaymentIntentFetchConstraints { fn from(value: api_models::payments::PaymentListFilterConstraints) -> Self { - if let Some(payment_intent_id) = value.payment_id { + let api_models::payments::PaymentListFilterConstraints { + payment_id, + profile_id, + customer_id, + limit, + offset, + amount_filter, + time_range, + connector, + currency, + status, + payment_method, + payment_method_type, + authentication_type, + merchant_connector_id, + order, + } = value; + if let Some(payment_intent_id) = payment_id { Self::Single { payment_intent_id } } else { Self::List(Box::new(PaymentIntentListParams { - offset: value.offset.unwrap_or_default(), - starting_at: value.time_range.map(|t| t.start_time), - ending_at: value.time_range.and_then(|t| t.end_time), - amount_filter: value.amount_filter, - connector: value.connector, - currency: value.currency, - status: value.status, - payment_method: value.payment_method, - payment_method_type: value.payment_method_type, - authentication_type: value.authentication_type, - merchant_connector_id: value.merchant_connector_id, - profile_id: value.profile_id, - customer_id: value.customer_id, + offset: offset.unwrap_or_default(), + starting_at: time_range.map(|t| t.start_time), + ending_at: time_range.and_then(|t| t.end_time), + amount_filter, + connector, + currency, + status, + payment_method, + payment_method_type, + authentication_type, + merchant_connector_id, + profile_id: profile_id.map(|profile_id| vec![profile_id]), + customer_id, starting_after_id: None, ending_before_id: None, - limit: Some(std::cmp::min(value.limit, PAYMENTS_LIST_MAX_LIMIT_V2)), - order: value.order, + limit: Some(std::cmp::min(limit, PAYMENTS_LIST_MAX_LIMIT_V2)), + order, })) } } } + +impl TryFrom<(T, Option>)> for PaymentIntentFetchConstraints +where + Self: From, +{ + type Error = error_stack::Report; + fn try_from( + (constraints, auth_profile_id_list): (T, Option>), + ) -> Result { + let payment_intent_constraints = Self::from(constraints); + if let Self::List(mut pi_list_params) = payment_intent_constraints { + let profile_id_from_request_body = pi_list_params.profile_id; + match (profile_id_from_request_body, auth_profile_id_list) { + (None, None) => pi_list_params.profile_id = None, + (None, Some(auth_profile_id_list)) => { + pi_list_params.profile_id = Some(auth_profile_id_list) + } + (Some(profile_id_from_request_body), None) => { + pi_list_params.profile_id = Some(profile_id_from_request_body) + } + (Some(profile_id_from_request_body), Some(auth_profile_id_list)) => { + let profile_id_from_request_body_is_available_in_auth_profile_id_list = + profile_id_from_request_body + .iter() + .all(|profile_id| auth_profile_id_list.contains(profile_id)); + + if profile_id_from_request_body_is_available_in_auth_profile_id_list { + pi_list_params.profile_id = Some(profile_id_from_request_body) + } else { + // This scenario is very unlikely to happen + let inaccessible_profile_ids: Vec<_> = profile_id_from_request_body + .iter() + .filter(|profile_id| !auth_profile_id_list.contains(profile_id)) + .collect(); + return Err(error_stack::Report::new( + errors::api_error_response::ApiErrorResponse::PreconditionFailed { + message: format!( + "Access not available for the given profile_id {:?}", + inaccessible_profile_ids + ), + }, + )); + } + } + } + Ok(Self::List(pi_list_params)) + } else { + Ok(payment_intent_constraints) + } + } +} diff --git a/crates/hyperswitch_domain_models/src/payouts/payouts.rs b/crates/hyperswitch_domain_models/src/payouts/payouts.rs index e514dbfb4c36..42037c3c2d91 100644 --- a/crates/hyperswitch_domain_models/src/payouts/payouts.rs +++ b/crates/hyperswitch_domain_models/src/payouts/payouts.rs @@ -54,7 +54,12 @@ pub trait PayoutsInterface { _filters: &PayoutFetchConstraints, _storage_scheme: MerchantStorageScheme, ) -> error_stack::Result< - Vec<(Payouts, PayoutAttempt, Option)>, + Vec<( + Payouts, + PayoutAttempt, + Option, + Option, + )>, errors::StorageError, >; diff --git a/crates/hyperswitch_domain_models/src/refunds.rs b/crates/hyperswitch_domain_models/src/refunds.rs new file mode 100644 index 000000000000..3b3a79407d0f --- /dev/null +++ b/crates/hyperswitch_domain_models/src/refunds.rs @@ -0,0 +1,82 @@ +use crate::errors; + +pub struct RefundListConstraints { + pub payment_id: Option, + pub refund_id: Option, + pub profile_id: Option>, + pub limit: Option, + pub offset: Option, + pub time_range: Option, + pub amount_filter: Option, + pub connector: Option>, + pub merchant_connector_id: Option>, + pub currency: Option>, + pub refund_status: Option>, +} + +impl + TryFrom<( + api_models::refunds::RefundListRequest, + Option>, + )> for RefundListConstraints +{ + type Error = error_stack::Report; + + fn try_from( + (value, auth_profile_id_list): ( + api_models::refunds::RefundListRequest, + Option>, + ), + ) -> Result { + let api_models::refunds::RefundListRequest { + connector, + currency, + refund_status, + payment_id, + refund_id, + profile_id, + limit, + offset, + time_range, + amount_filter, + merchant_connector_id, + } = value; + let profile_id_from_request_body = profile_id; + let profile_id_list = match (profile_id_from_request_body, auth_profile_id_list) { + (None, None) => None, + (None, Some(auth_profile_id_list)) => Some(auth_profile_id_list), + (Some(profile_id_from_request_body), None) => Some(vec![profile_id_from_request_body]), + (Some(profile_id_from_request_body), Some(auth_profile_id_list)) => { + let profile_id_from_request_body_is_available_in_auth_profile_id_list = + auth_profile_id_list.contains(&profile_id_from_request_body); + + if profile_id_from_request_body_is_available_in_auth_profile_id_list { + Some(vec![profile_id_from_request_body]) + } else { + // This scenario is very unlikely to happen + return Err(error_stack::Report::new( + errors::api_error_response::ApiErrorResponse::PreconditionFailed { + message: format!( + "Access not available for the given profile_id {:?}", + profile_id_from_request_body + ), + }, + )); + } + } + }; + Ok(Self { + payment_id, + refund_id, + profile_id: profile_id_list, + limit, + offset, + time_range, + amount_filter, + connector, + merchant_connector_id, + currency, + refund_status, + }) + } +} diff --git a/crates/hyperswitch_domain_models/src/router_data.rs b/crates/hyperswitch_domain_models/src/router_data.rs index 122acbb0adc0..df0b63833d9b 100644 --- a/crates/hyperswitch_domain_models/src/router_data.rs +++ b/crates/hyperswitch_domain_models/src/router_data.rs @@ -7,7 +7,7 @@ use common_utils::{ types::MinorUnit, }; use error_stack::ResultExt; -use masking::Secret; +use masking::{ExposeInterface, Secret}; use crate::{payment_address::PaymentAddress, payment_method_data}; @@ -136,6 +136,74 @@ impl ConnectorAuthType { "ConnectorAuthType", )) } + + // show only first and last two digits of the key and mask others with * + // mask the entire key if it's length is less than or equal to 4 + fn mask_key(&self, key: String) -> Secret { + let key_len = key.len(); + let masked_key = if key_len <= 4 { + "*".repeat(key_len) + } else { + // Show the first two and last two characters, mask the rest with '*' + let mut masked_key = String::new(); + let key_len = key.len(); + // Iterate through characters by their index + for (index, character) in key.chars().enumerate() { + if index < 2 || index >= key_len - 2 { + masked_key.push(character); // Keep the first two and last two characters + } else { + masked_key.push('*'); // Mask the middle characters + } + } + masked_key + }; + Secret::new(masked_key) + } + + // Mask the keys in the auth_type + pub fn get_masked_keys(&self) -> Self { + match self { + Self::TemporaryAuth => Self::TemporaryAuth, + Self::NoKey => Self::NoKey, + Self::HeaderKey { api_key } => Self::HeaderKey { + api_key: self.mask_key(api_key.clone().expose()), + }, + Self::BodyKey { api_key, key1 } => Self::BodyKey { + api_key: self.mask_key(api_key.clone().expose()), + key1: self.mask_key(key1.clone().expose()), + }, + Self::SignatureKey { + api_key, + key1, + api_secret, + } => Self::SignatureKey { + api_key: self.mask_key(api_key.clone().expose()), + key1: self.mask_key(key1.clone().expose()), + api_secret: self.mask_key(api_secret.clone().expose()), + }, + Self::MultiAuthKey { + api_key, + key1, + api_secret, + key2, + } => Self::MultiAuthKey { + api_key: self.mask_key(api_key.clone().expose()), + key1: self.mask_key(key1.clone().expose()), + api_secret: self.mask_key(api_secret.clone().expose()), + key2: self.mask_key(key2.clone().expose()), + }, + Self::CurrencyAuthKey { auth_key_map } => Self::CurrencyAuthKey { + auth_key_map: auth_key_map.clone(), + }, + Self::CertificateAuth { + certificate, + private_key, + } => Self::CertificateAuth { + certificate: self.mask_key(certificate.clone().expose()), + private_key: self.mask_key(private_key.clone().expose()), + }, + } + } } #[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] diff --git a/crates/hyperswitch_domain_models/src/router_flow_types/payments.rs b/crates/hyperswitch_domain_models/src/router_flow_types/payments.rs index 1263f8f975a6..a0e6adc855a9 100644 --- a/crates/hyperswitch_domain_models/src/router_flow_types/payments.rs +++ b/crates/hyperswitch_domain_models/src/router_flow_types/payments.rs @@ -49,3 +49,9 @@ pub struct IncrementalAuthorization; #[derive(Debug, Clone)] pub struct PostProcessing; + +#[derive(Debug, Clone)] +pub struct CalculateTax; + +#[derive(Debug, Clone)] +pub struct SdkSessionUpdate; diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index ac7bf932fe70..ec182056f6cd 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -1,8 +1,11 @@ pub mod authentication; pub mod fraud_check; -use api_models::payments::RequestSurchargeDetails; +use api_models::payments::{Address, RequestSurchargeDetails}; use common_utils::{ - consts, errors, ext_traits::OptionExt, id_type, pii, types as common_types, types::MinorUnit, + consts, errors, + ext_traits::OptionExt, + id_type, pii, + types::{self as common_types, MinorUnit}, }; use diesel_models::enums as storage_enums; use error_stack::ResultExt; @@ -791,6 +794,21 @@ pub struct PaymentsSessionData { pub minor_amount: MinorUnit, } +#[derive(Debug, Clone, Default)] +pub struct PaymentsTaxCalculationData { + pub amount: MinorUnit, + pub currency: storage_enums::Currency, + pub shipping_cost: Option, + pub order_details: Option>, + pub shipping_address: Address, +} + +#[derive(Debug, Clone, Default)] +pub struct SdkPaymentsSessionUpdateData { + pub order_tax_amount: MinorUnit, + pub net_amount: MinorUnit, +} + #[derive(Debug, Clone)] pub struct SetupMandateRequestData { pub currency: storage_enums::Currency, diff --git a/crates/hyperswitch_domain_models/src/router_response_types.rs b/crates/hyperswitch_domain_models/src/router_response_types.rs index e41b0aa78b9d..cb682514c83b 100644 --- a/crates/hyperswitch_domain_models/src/router_response_types.rs +++ b/crates/hyperswitch_domain_models/src/router_response_types.rs @@ -68,6 +68,14 @@ pub enum PaymentsResponseData { PostProcessingResponse { session_token: Option, }, + // SessionUpdateResponse { + // status: common_enums::SessionUpdateStatus, + // }, +} + +#[derive(Debug, Clone)] +pub struct TaxCalculationResponseData { + pub order_tax_amount: MinorUnit, } #[derive(serde::Serialize, Debug, Clone)] diff --git a/crates/hyperswitch_domain_models/src/types.rs b/crates/hyperswitch_domain_models/src/types.rs index e425fc7fcc69..3d47afb9b828 100644 --- a/crates/hyperswitch_domain_models/src/types.rs +++ b/crates/hyperswitch_domain_models/src/types.rs @@ -1,15 +1,18 @@ use crate::{ - router_data::RouterData, + router_data::{AccessToken, RouterData}, router_flow_types::{ - Authorize, Capture, CompleteAuthorize, CreateConnectorCustomer, PSync, PaymentMethodToken, - RSync, SetupMandate, Void, + AccessTokenAuth, Authorize, CalculateTax, Capture, CompleteAuthorize, + CreateConnectorCustomer, PSync, PaymentMethodToken, RSync, SetupMandate, Void, }, router_request_types::{ - CompleteAuthorizeData, ConnectorCustomerData, PaymentMethodTokenizationData, - PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsSyncData, - RefundsData, SetupMandateRequestData, + AccessTokenRequestData, CompleteAuthorizeData, ConnectorCustomerData, + PaymentMethodTokenizationData, PaymentsAuthorizeData, PaymentsCancelData, + PaymentsCaptureData, PaymentsSyncData, PaymentsTaxCalculationData, RefundsData, + SetupMandateRequestData, + }, + router_response_types::{ + PaymentsResponseData, RefundsResponseData, TaxCalculationResponseData, }, - router_response_types::{PaymentsResponseData, RefundsResponseData}, }; pub type PaymentsAuthorizeRouterData = @@ -27,3 +30,6 @@ pub type ConnectorCustomerRouterData = RouterData; pub type PaymentsCompleteAuthorizeRouterData = RouterData; +pub type PaymentsTaxCalculationRouterData = + RouterData; +pub type RefreshTokenRouterData = RouterData; diff --git a/crates/hyperswitch_interfaces/src/api/payments.rs b/crates/hyperswitch_interfaces/src/api/payments.rs index 8a53f65805e7..43409680dc55 100644 --- a/crates/hyperswitch_interfaces/src/api/payments.rs +++ b/crates/hyperswitch_interfaces/src/api/payments.rs @@ -2,18 +2,19 @@ use hyperswitch_domain_models::{ router_flow_types::payments::{ - Approve, Authorize, AuthorizeSessionToken, Capture, CompleteAuthorize, + Approve, Authorize, AuthorizeSessionToken, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, IncrementalAuthorization, PSync, PaymentMethodToken, - PostProcessing, PreProcessing, Reject, Session, SetupMandate, Void, + PostProcessing, PreProcessing, Reject, SdkSessionUpdate, Session, SetupMandate, Void, }, router_request_types::{ AuthorizeSessionTokenData, CompleteAuthorizeData, ConnectorCustomerData, PaymentMethodTokenizationData, PaymentsApproveData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, PaymentsPreProcessingData, PaymentsRejectData, - PaymentsSessionData, PaymentsSyncData, SetupMandateRequestData, + PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, + SdkPaymentsSessionUpdateData, SetupMandateRequestData, }, - router_response_types::PaymentsResponseData, + router_response_types::{PaymentsResponseData, TaxCalculationResponseData}, }; use crate::api; @@ -37,6 +38,7 @@ pub trait Payment: + PaymentsPostProcessing + ConnectorCustomer + PaymentIncrementalAuthorization + + PaymentSessionUpdate { } @@ -110,6 +112,18 @@ pub trait PaymentIncrementalAuthorization: { } +/// trait TaxCalculation +pub trait TaxCalculation: + api::ConnectorIntegration +{ +} + +/// trait SessionUpdate +pub trait PaymentSessionUpdate: + api::ConnectorIntegration +{ +} + /// trait PaymentsCompleteAuthorize pub trait PaymentsCompleteAuthorize: api::ConnectorIntegration diff --git a/crates/hyperswitch_interfaces/src/api/payments_v2.rs b/crates/hyperswitch_interfaces/src/api/payments_v2.rs index 4ebe9e8517f0..cc5fd01f9f87 100644 --- a/crates/hyperswitch_interfaces/src/api/payments_v2.rs +++ b/crates/hyperswitch_interfaces/src/api/payments_v2.rs @@ -3,18 +3,19 @@ use hyperswitch_domain_models::{ router_data_v2::PaymentFlowData, router_flow_types::payments::{ - Approve, Authorize, AuthorizeSessionToken, Capture, CompleteAuthorize, + Approve, Authorize, AuthorizeSessionToken, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, IncrementalAuthorization, PSync, PaymentMethodToken, - PostProcessing, PreProcessing, Reject, Session, SetupMandate, Void, + PostProcessing, PreProcessing, Reject, SdkSessionUpdate, Session, SetupMandate, Void, }, router_request_types::{ AuthorizeSessionTokenData, CompleteAuthorizeData, ConnectorCustomerData, PaymentMethodTokenizationData, PaymentsApproveData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, PaymentsPreProcessingData, PaymentsRejectData, - PaymentsSessionData, PaymentsSyncData, SetupMandateRequestData, + PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, + SdkPaymentsSessionUpdateData, SetupMandateRequestData, }, - router_response_types::PaymentsResponseData, + router_response_types::{PaymentsResponseData, TaxCalculationResponseData}, }; use crate::api::{ConnectorCommon, ConnectorIntegrationV2, ConnectorValidation}; @@ -89,6 +90,28 @@ pub trait PaymentIncrementalAuthorizationV2: { } +///trait TaxCalculationV2 +pub trait TaxCalculationV2: + ConnectorIntegrationV2< + CalculateTax, + PaymentFlowData, + PaymentsTaxCalculationData, + TaxCalculationResponseData, +> +{ +} + +///trait PaymentSessionUpdateV2 +pub trait PaymentSessionUpdateV2: + ConnectorIntegrationV2< + SdkSessionUpdate, + PaymentFlowData, + SdkPaymentsSessionUpdateData, + PaymentsResponseData, +> +{ +} + /// trait PaymentsCompleteAuthorizeV2 pub trait PaymentsCompleteAuthorizeV2: ConnectorIntegrationV2< @@ -163,5 +186,7 @@ pub trait PaymentV2: + PaymentsPostProcessingV2 + ConnectorCustomerV2 + PaymentIncrementalAuthorizationV2 + + TaxCalculationV2 + + PaymentSessionUpdateV2 { } diff --git a/crates/hyperswitch_interfaces/src/configs.rs b/crates/hyperswitch_interfaces/src/configs.rs index 37e5eecea6ac..97223040ec6b 100644 --- a/crates/hyperswitch_interfaces/src/configs.rs +++ b/crates/hyperswitch_interfaces/src/configs.rs @@ -36,7 +36,7 @@ pub struct Connectors { pub ebanx: ConnectorParams, pub fiserv: ConnectorParams, pub fiservemea: ConnectorParams, - pub fiuu: ConnectorParams, + pub fiuu: ConnectorParamsWithThreeUrls, pub forte: ConnectorParams, pub globalpay: ConnectorParams, pub globepay: ConnectorParams, @@ -78,6 +78,7 @@ pub struct Connectors { pub stripe: ConnectorParamsWithFileUploadUrl, pub taxjar: ConnectorParams, pub threedsecureio: ConnectorParams, + pub thunes: ConnectorParams, pub trustpay: ConnectorParamsWithMoreUrls, pub tsys: ConnectorParams, pub volt: ConnectorParams, @@ -165,3 +166,14 @@ pub struct ConnectorParamsWithSecondaryBaseUrl { /// secondary base url pub secondary_base_url: String, } +/// struct ConnectorParamsWithThreeUrls +#[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)] +#[serde(default)] +pub struct ConnectorParamsWithThreeUrls { + /// base url + pub base_url: String, + /// secondary base url + pub secondary_base_url: String, + /// third base url + pub third_base_url: String, +} diff --git a/crates/hyperswitch_interfaces/src/connector_integration_v2.rs b/crates/hyperswitch_interfaces/src/connector_integration_v2.rs index d03dee5f7f33..9384ed0a0cda 100644 --- a/crates/hyperswitch_interfaces/src/connector_integration_v2.rs +++ b/crates/hyperswitch_interfaces/src/connector_integration_v2.rs @@ -1,7 +1,7 @@ //! definition of the new connector integration trait use common_utils::{ errors::CustomResult, - request::{Method, Request, RequestContent}, + request::{Method, Request, RequestBuilder, RequestContent}, }; use hyperswitch_domain_models::{router_data::ErrorResponse, router_data_v2::RouterDataV2}; use masking::Maskable; @@ -9,8 +9,7 @@ use router_env::metrics::add_attributes; use serde_json::json; use crate::{ - api::CaptureSyncMethod, configs::Connectors, errors, - events::connector_api_logs::ConnectorEvent, metrics, types, + api::CaptureSyncMethod, errors, events::connector_api_logs::ConnectorEvent, metrics, types, }; /// alias for Box of a type that implements trait ConnectorIntegrationV2 @@ -47,7 +46,6 @@ pub trait ConnectorIntegrationV2: fn get_headers( &self, _req: &RouterDataV2, - _connectors: &Connectors, ) -> CustomResult)>, errors::ConnectorError> { Ok(vec![]) } @@ -66,8 +64,12 @@ pub trait ConnectorIntegrationV2: fn get_url( &self, _req: &RouterDataV2, - _connectors: &Connectors, ) -> CustomResult { + metrics::UNIMPLEMENTED_FLOW.add( + &metrics::CONTEXT, + 1, + &add_attributes([("connector", self.id())]), + ); Ok(String::new()) } @@ -75,9 +77,8 @@ pub trait ConnectorIntegrationV2: fn get_request_body( &self, _req: &RouterDataV2, - _connectors: &Connectors, - ) -> CustomResult { - Ok(RequestContent::Json(Box::new(json!(r#"{}"#)))) + ) -> CustomResult, errors::ConnectorError> { + Ok(None) } /// returns form data @@ -91,15 +92,19 @@ pub trait ConnectorIntegrationV2: /// builds the request and returns it fn build_request_v2( &self, - _req: &RouterDataV2, - _connectors: &Connectors, + req: &RouterDataV2, ) -> CustomResult, errors::ConnectorError> { - metrics::UNIMPLEMENTED_FLOW.add( - &metrics::CONTEXT, - 1, - &add_attributes([("connector", self.id())]), - ); - Ok(None) + Ok(Some( + RequestBuilder::new() + .method(self.get_http_method()) + .url(self.get_url(req)?.as_str()) + .attach_default_headers() + .headers(self.get_headers(req)?) + .set_optional_body(self.get_request_body(req)?) + .add_certificate(self.get_certificate(req)?) + .add_certificate_key(self.get_certificate_key(req)?) + .build(), + )) } /// accepts the raw api response and decodes it @@ -172,7 +177,7 @@ pub trait ConnectorIntegrationV2: fn get_certificate( &self, _req: &RouterDataV2, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult>, errors::ConnectorError> { Ok(None) } @@ -180,7 +185,7 @@ pub trait ConnectorIntegrationV2: fn get_certificate_key( &self, _req: &RouterDataV2, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult>, errors::ConnectorError> { Ok(None) } } diff --git a/crates/hyperswitch_interfaces/src/types.rs b/crates/hyperswitch_interfaces/src/types.rs index 06a1d6028e66..e42942a94de9 100644 --- a/crates/hyperswitch_interfaces/src/types.rs +++ b/crates/hyperswitch_interfaces/src/types.rs @@ -7,7 +7,7 @@ use hyperswitch_domain_models::{ files::{Retrieve, Upload}, mandate_revoke::MandateRevoke, payments::{ - Authorize, AuthorizeSessionToken, Balance, Capture, CompleteAuthorize, + Authorize, AuthorizeSessionToken, Balance, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, IncrementalAuthorization, InitPayment, PSync, PaymentMethodToken, PostProcessing, PreProcessing, Session, SetupMandate, Void, }, @@ -20,13 +20,14 @@ use hyperswitch_domain_models::{ MandateRevokeRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, PaymentsPreProcessingData, PaymentsSessionData, - PaymentsSyncData, RefundsData, RetrieveFileRequestData, SetupMandateRequestData, - SubmitEvidenceRequestData, UploadFileRequestData, VerifyWebhookSourceRequestData, + PaymentsSyncData, PaymentsTaxCalculationData, RefundsData, RetrieveFileRequestData, + SetupMandateRequestData, SubmitEvidenceRequestData, UploadFileRequestData, + VerifyWebhookSourceRequestData, }, router_response_types::{ AcceptDisputeResponse, DefendDisputeResponse, MandateRevokeResponseData, PaymentsResponseData, RefundsResponseData, RetrieveFileResponse, SubmitEvidenceResponse, - UploadFileResponse, VerifyWebhookSourceResponseData, + TaxCalculationResponseData, UploadFileResponse, VerifyWebhookSourceResponseData, }, }; #[cfg(feature = "payouts")] @@ -54,6 +55,9 @@ pub struct Response { /// Type alias for `ConnectorIntegration` pub type PaymentsAuthorizeType = dyn ConnectorIntegration; +/// Type alias for `ConnectorIntegration` +pub type PaymentsTaxCalculationType = + dyn ConnectorIntegration; /// Type alias for `ConnectorIntegration` pub type SetupMandateType = dyn ConnectorIntegration; diff --git a/crates/masking/src/lib.rs b/crates/masking/src/lib.rs index 5393438a87d8..ca0da6b67672 100644 --- a/crates/masking/src/lib.rs +++ b/crates/masking/src/lib.rs @@ -35,6 +35,7 @@ pub use self::bytes::SecretBytesMut; #[cfg(feature = "alloc")] mod string; + #[cfg(feature = "alloc")] mod vec; diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 7a37f22afad1..3b63b7ac20f4 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -250,6 +250,8 @@ Never share your secret api keys. Keep them guarded and secure. api_models::enums::MandateStatus, api_models::enums::PaymentExperience, api_models::enums::BankNames, + api_models::enums::BankType, + api_models::enums::BankHolderType, api_models::enums::CardNetwork, api_models::enums::DisputeStage, api_models::enums::DisputeStatus, @@ -264,6 +266,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::enums::ConnectorStatus, api_models::enums::AuthorizationStatus, api_models::enums::PaymentMethodStatus, + api_models::enums::UIWidgetFormLayout, api_models::admin::MerchantConnectorCreate, api_models::admin::AdditionalMerchantData, api_models::admin::MerchantRecipientData, @@ -282,6 +285,8 @@ Never share your secret api keys. Keep them guarded and secure. api_models::admin::BusinessPaymentLinkConfig, api_models::admin::PaymentLinkConfigRequest, api_models::admin::PaymentLinkConfig, + api_models::admin::PaymentLinkTransactionDetails, + api_models::admin::TransactionDetailsUiConfiguration, api_models::disputes::DisputeResponse, api_models::disputes::DisputeResponsePaymentsRetrieve, api_models::gsm::GsmCreateRequest, @@ -458,6 +463,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payment_methods::PaymentMethodCollectLinkResponse, api_models::refunds::RefundListRequest, api_models::refunds::RefundListResponse, + api_models::refunds::RefundAggregateResponse, api_models::payments::TimeRange, api_models::payments::AmountFilter, api_models::mandates::MandateRevokedResponse, @@ -570,6 +576,36 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::CustomerDetailsResponse, api_models::payments::OpenBankingData, api_models::payments::OpenBankingSessionToken, + api_models::payments::BankDebitResponse, + api_models::payments::BankRedirectResponse, + api_models::payments::BankTransferResponse, + api_models::payments::CardRedirectResponse, + api_models::payments::CardTokenResponse, + api_models::payments::CryptoResponse, + api_models::payments::GiftCardResponse, + api_models::payments::OpenBankingResponse, + api_models::payments::RealTimePaymentDataResponse, + api_models::payments::UpiResponse, + api_models::payments::VoucherResponse, + api_models::payments::additional_info::CardTokenAdditionalData, + api_models::payments::additional_info::BankDebitAdditionalData, + api_models::payments::additional_info::AchBankDebitAdditionalData, + api_models::payments::additional_info::BacsBankDebitAdditionalData, + api_models::payments::additional_info::BecsBankDebitAdditionalData, + api_models::payments::additional_info::SepaBankDebitAdditionalData, + api_models::payments::additional_info::BankRedirectDetails, + api_models::payments::additional_info::BancontactBankRedirectAdditionalData, + api_models::payments::additional_info::BlikBankRedirectAdditionalData, + api_models::payments::additional_info::GiropayBankRedirectAdditionalData, + api_models::payments::additional_info::BankTransferAdditionalData, + api_models::payments::additional_info::PixBankTransferAdditionalData, + api_models::payments::additional_info::LocalBankTransferAdditionalData, + api_models::payments::additional_info::GiftCardAdditionalData, + api_models::payments::additional_info::GivexGiftCardAdditionalData, + api_models::payments::additional_info::UpiAdditionalData, + api_models::payments::additional_info::UpiCollectAdditionalData, + api_models::payments::PaymentsDynamicTaxCalculationRequest, + api_models::payments::PaymentsDynamicTaxCalculationResponse, )), modifiers(&SecurityAddon) )] diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index c58e4c5183ba..5393e8d6fae9 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -167,6 +167,8 @@ Never share your secret api keys. Keep them guarded and secure. api_models::enums::MandateStatus, api_models::enums::PaymentExperience, api_models::enums::BankNames, + api_models::enums::BankType, + api_models::enums::BankHolderType, api_models::enums::CardNetwork, api_models::enums::DisputeStage, api_models::enums::DisputeStatus, @@ -182,6 +184,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::enums::AuthorizationStatus, api_models::enums::PaymentMethodStatus, api_models::enums::OrderFulfillmentTimeOrigin, + api_models::enums::UIWidgetFormLayout, api_models::admin::MerchantConnectorCreate, api_models::admin::AdditionalMerchantData, api_models::admin::MerchantRecipientData, @@ -200,6 +203,8 @@ Never share your secret api keys. Keep them guarded and secure. api_models::admin::BusinessPaymentLinkConfig, api_models::admin::PaymentLinkConfigRequest, api_models::admin::PaymentLinkConfig, + api_models::admin::PaymentLinkTransactionDetails, + api_models::admin::TransactionDetailsUiConfiguration, api_models::disputes::DisputeResponse, api_models::disputes::DisputeResponsePaymentsRetrieve, api_models::gsm::GsmCreateRequest, @@ -376,6 +381,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payment_methods::PaymentMethodCollectLinkResponse, api_models::refunds::RefundListRequest, api_models::refunds::RefundListResponse, + api_models::refunds::RefundAggregateResponse, api_models::payments::TimeRange, api_models::payments::AmountFilter, api_models::mandates::MandateRevokedResponse, @@ -486,6 +492,36 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::CustomerDetailsResponse, api_models::payments::OpenBankingData, api_models::payments::OpenBankingSessionToken, + api_models::payments::BankDebitResponse, + api_models::payments::BankRedirectResponse, + api_models::payments::BankTransferResponse, + api_models::payments::CardRedirectResponse, + api_models::payments::CardTokenResponse, + api_models::payments::CryptoResponse, + api_models::payments::GiftCardResponse, + api_models::payments::OpenBankingResponse, + api_models::payments::RealTimePaymentDataResponse, + api_models::payments::UpiResponse, + api_models::payments::VoucherResponse, + api_models::payments::additional_info::CardTokenAdditionalData, + api_models::payments::additional_info::BankDebitAdditionalData, + api_models::payments::additional_info::AchBankDebitAdditionalData, + api_models::payments::additional_info::BacsBankDebitAdditionalData, + api_models::payments::additional_info::BecsBankDebitAdditionalData, + api_models::payments::additional_info::SepaBankDebitAdditionalData, + api_models::payments::additional_info::BankRedirectDetails, + api_models::payments::additional_info::BancontactBankRedirectAdditionalData, + api_models::payments::additional_info::BlikBankRedirectAdditionalData, + api_models::payments::additional_info::GiropayBankRedirectAdditionalData, + api_models::payments::additional_info::BankTransferAdditionalData, + api_models::payments::additional_info::PixBankTransferAdditionalData, + api_models::payments::additional_info::LocalBankTransferAdditionalData, + api_models::payments::additional_info::GiftCardAdditionalData, + api_models::payments::additional_info::GivexGiftCardAdditionalData, + api_models::payments::additional_info::UpiAdditionalData, + api_models::payments::additional_info::UpiCollectAdditionalData, + api_models::payments::PaymentsDynamicTaxCalculationRequest, + api_models::payments::PaymentsDynamicTaxCalculationResponse, )), modifiers(&SecurityAddon) )] diff --git a/crates/openapi/src/routes/payments.rs b/crates/openapi/src/routes/payments.rs index 0e3bb9fe072f..1ed8c541bfd8 100644 --- a/crates/openapi/src/routes/payments.rs +++ b/crates/openapi/src/routes/payments.rs @@ -545,3 +545,21 @@ pub fn payments_external_authentication() {} security(("publishable_key" = [])) )] pub fn payments_complete_authorize() {} + +/// Dynamic Tax Calculation +/// +/// +#[utoipa::path( + post, + path = "/payments/{payment_id}/calculate_tax", + request_body=PaymentsDynamicTaxCalculationRequest, + responses( + (status = 200, description = "Tax Calculation is done", body = PaymentsDynamicTaxCalculationResponse), + (status = 400, description = "Missing mandatory fields") + ), + tag = "Payments", + operation_id = "Create Tax Calculation for a Payment", + security(("publishable_key" = [])) +)] + +pub fn payments_dynamic_tax_calculation() {} diff --git a/crates/redis_interface/src/commands.rs b/crates/redis_interface/src/commands.rs index 9b76d8d9c816..9fd07a473d4a 100644 --- a/crates/redis_interface/src/commands.rs +++ b/crates/redis_interface/src/commands.rs @@ -19,7 +19,8 @@ use fred::{ prelude::RedisErrorKind, types::{ Expiration, FromRedis, MultipleIDs, MultipleKeys, MultipleOrderedPairs, MultipleStrings, - MultipleValues, RedisKey, RedisMap, RedisValue, Scanner, SetOptions, XCap, XReadResponse, + MultipleValues, RedisKey, RedisMap, RedisValue, ScanType, Scanner, SetOptions, XCap, + XReadResponse, }, }; use futures::StreamExt; @@ -209,6 +210,25 @@ impl super::RedisConnectionPool { .change_context(errors::RedisError::DeleteFailed) } + #[instrument(level = "DEBUG", skip(self))] + pub async fn delete_multiple_keys( + &self, + keys: Vec, + ) -> CustomResult, errors::RedisError> { + let mut del_result = Vec::with_capacity(keys.len()); + + for key in keys { + del_result.push( + self.pool + .del(self.add_prefix(&key)) + .await + .change_context(errors::RedisError::DeleteFailed)?, + ); + } + + Ok(del_result) + } + #[instrument(level = "DEBUG", skip(self))] pub async fn set_key_with_expiry( &self, @@ -372,16 +392,25 @@ impl super::RedisConnectionPool { } #[instrument(level = "DEBUG", skip(self))] - pub async fn increment_field_in_hash( + pub async fn increment_fields_in_hash( &self, key: &str, - field: &str, - increment: i64, - ) -> CustomResult { - self.pool - .hincrby(self.add_prefix(key), field, increment) - .await - .change_context(errors::RedisError::IncrementHashFieldFailed) + fields_to_increment: &[(T, i64)], + ) -> CustomResult, errors::RedisError> + where + T: Debug + ToString, + { + let mut values_after_increment = Vec::with_capacity(fields_to_increment.len()); + for (field, increment) in fields_to_increment.iter() { + values_after_increment.push( + self.pool + .hincrby(self.add_prefix(key), field.to_string(), *increment) + .await + .change_context(errors::RedisError::IncrementHashFieldFailed)?, + ) + } + + Ok(values_after_increment) } #[instrument(level = "DEBUG", skip(self))] @@ -405,7 +434,38 @@ impl super::RedisConnectionPool { Some(futures::stream::iter(v)) } Err(err) => { - tracing::error!(?err); + tracing::error!(redis_err=?err, "Redis error while executing hscan command"); + None + } + } + }) + .flatten() + .collect::>() + .await) + } + + #[instrument(level = "DEBUG", skip(self))] + pub async fn scan( + &self, + pattern: &str, + count: Option, + scan_type: Option, + ) -> CustomResult, errors::RedisError> { + Ok(self + .pool + .next() + .scan(&self.add_prefix(pattern), count, scan_type) + .filter_map(|value| async move { + match value { + Ok(mut v) => { + let v = v.take_results()?; + + let v: Vec = + v.into_iter().filter_map(|val| val.into_string()).collect(); + Some(futures::stream::iter(v)) + } + Err(err) => { + tracing::error!(redis_err=?err, "Redis error while executing scan command"); None } } @@ -450,6 +510,17 @@ impl super::RedisConnectionPool { .change_context(errors::RedisError::GetHashFieldFailed) } + #[instrument(level = "DEBUG", skip(self))] + pub async fn get_hash_fields(&self, key: &str) -> CustomResult + where + V: FromRedis + Unpin + Send + 'static, + { + self.pool + .hgetall(self.add_prefix(key)) + .await + .change_context(errors::RedisError::GetHashFieldFailed) + } + #[instrument(level = "DEBUG", skip(self))] pub async fn get_hash_field_and_deserialize( &self, diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index e9c9d504b47a..0c6e06cc42df 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -10,7 +10,7 @@ license.workspace = true [features] default = ["common_default", "v1"] -common_default = ["kv_store", "stripe", "oltp", "olap", "accounts_cache", "dummy_connector", "payouts", "payout_retry", "retry", "frm", "tls", "partial-auth"] +common_default = ["kv_store", "stripe", "oltp", "olap", "accounts_cache", "dummy_connector", "payouts", "payout_retry", "retry", "frm", "tls", "partial-auth", "km_forward_x_request_id"] olap = ["hyperswitch_domain_models/olap", "storage_impl/olap", "scheduler/olap", "api_models/olap", "dep:analytics"] tls = ["actix-web/rustls-0_22"] email = ["external_services/email", "scheduler/email", "olap"] @@ -18,6 +18,7 @@ email = ["external_services/email", "scheduler/email", "olap"] keymanager_create = [] keymanager_mtls = ["reqwest/rustls-tls", "common_utils/keymanager_mtls"] encryption_service = ["hyperswitch_domain_models/encryption_service", "common_utils/encryption_service"] +km_forward_x_request_id = ["common_utils/km_forward_x_request_id"] frm = ["api_models/frm", "hyperswitch_domain_models/frm", "hyperswitch_connectors/frm", "hyperswitch_interfaces/frm"] stripe = [] release = ["stripe", "email", "accounts_cache", "kv_store", "vergen", "recon", "external_services/aws_kms", "external_services/aws_s3", "keymanager_mtls", "keymanager_create", "encryption_service"] @@ -36,7 +37,7 @@ v2 = ["customer_v2", "payment_methods_v2", "payment_v2", "common_default", "api_ v1 = ["common_default", "api_models/v1", "diesel_models/v1", "hyperswitch_domain_models/v1", "storage_impl/v1", "hyperswitch_interfaces/v1", "kgraph_utils/v1"] customer_v2 = ["api_models/customer_v2", "diesel_models/customer_v2", "hyperswitch_domain_models/customer_v2", "storage_impl/customer_v2"] payment_v2 = ["api_models/payment_v2", "diesel_models/payment_v2", "hyperswitch_domain_models/payment_v2", "storage_impl/payment_v2"] -payment_methods_v2 = ["api_models/payment_methods_v2"] +payment_methods_v2 = ["api_models/payment_methods_v2", "diesel_models/payment_methods_v2", "hyperswitch_domain_models/payment_methods_v2", "storage_impl/payment_methods_v2"] # Partial Auth # The feature reduces the overhead of the router authenticating the merchant for every request, and trusts on `x-merchant-id` header to be present in the request. @@ -69,7 +70,6 @@ futures = "0.3.30" hex = "0.4.3" http = "0.2.12" hyper = "0.14.28" -image = { version = "0.25.1", default-features = false, features = ["png"] } infer = "0.15.0" iso_currency = "0.4.4" isocountry = "0.3.2" @@ -84,7 +84,6 @@ num-traits = "0.2.19" once_cell = "1.19.0" openidconnect = "3.5.0" # TODO: remove reqwest openssl = "0.10.64" -qrcode = "0.14.0" quick-xml = { version = "0.31.0", features = ["serialize"] } rand = "0.8.5" rand_chacha = "0.3.1" diff --git a/crates/router/src/analytics.rs b/crates/router/src/analytics.rs index a6e94ed294a0..c9b0a956dbcd 100644 --- a/crates/router/src/analytics.rs +++ b/crates/router/src/analytics.rs @@ -20,6 +20,8 @@ pub mod routes { GetRefundFilterRequest, GetRefundMetricRequest, GetSdkEventFiltersRequest, GetSdkEventMetricRequest, ReportRequest, }; + use common_enums::EntityType; + use common_utils::id_type::{MerchantId, OrganizationId}; use error_stack::{report, ResultExt}; use crate::{ @@ -214,14 +216,6 @@ pub mod routes { web::resource("filters/disputes") .route(web::post().to(get_org_dispute_filters)), ) - .service( - web::resource("filters/api_events") - .route(web::post().to(get_org_api_event_filters)), - ) - .service( - web::resource("metrics/api_events") - .route(web::post().to(get_org_api_events_metrics)), - ) .service( web::resource("report/dispute") .route(web::post().to(generate_org_dispute_report)), @@ -256,14 +250,6 @@ pub mod routes { web::resource("filters/refunds") .route(web::post().to(get_profile_refund_filters)), ) - .service( - web::resource("metrics/api_events") - .route(web::post().to(get_profile_api_events_metrics)), - ) - .service( - web::resource("filters/api_events") - .route(web::post().to(get_profile_api_event_filters)), - ) .service( web::resource("metrics/disputes") .route(web::post().to(get_profile_dispute_metrics)), @@ -393,7 +379,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -429,7 +418,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Organization, + }, api_locking::LockAction::NotApplicable, )) .await @@ -472,7 +464,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -510,7 +505,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -546,7 +544,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Organization, + }, api_locking::LockAction::NotApplicable, )) .await @@ -589,7 +590,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -627,7 +631,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -663,7 +670,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Organization, + }, api_locking::LockAction::NotApplicable, )) .await @@ -706,7 +716,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -738,7 +751,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -774,7 +790,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -811,7 +830,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -848,7 +870,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -876,7 +901,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -902,7 +930,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Organization, + }, api_locking::LockAction::NotApplicable, )) .await @@ -935,7 +966,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -961,7 +995,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -989,7 +1026,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -1015,7 +1055,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Organization, + }, api_locking::LockAction::NotApplicable, )) .await @@ -1048,7 +1091,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -1070,7 +1116,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -1096,7 +1145,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -1126,7 +1178,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -1157,7 +1212,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -1186,7 +1244,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -1234,7 +1295,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::GenerateReport), + &auth::JWTAuth { + permission: Permission::GenerateReport, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -1280,7 +1344,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::GenerateReport), + &auth::JWTAuth { + permission: Permission::GenerateReport, + minimum_entity_level: EntityType::Organization, + }, api_locking::LockAction::NotApplicable, )) .await @@ -1333,7 +1400,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::GenerateReport), + &auth::JWTAuth { + permission: Permission::GenerateReport, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -1381,7 +1451,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::GenerateReport), + &auth::JWTAuth { + permission: Permission::GenerateReport, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -1427,7 +1500,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::GenerateReport), + &auth::JWTAuth { + permission: Permission::GenerateReport, + minimum_entity_level: EntityType::Organization, + }, api_locking::LockAction::NotApplicable, )) .await @@ -1480,7 +1556,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::GenerateReport), + &auth::JWTAuth { + permission: Permission::GenerateReport, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -1528,7 +1607,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::GenerateReport), + &auth::JWTAuth { + permission: Permission::GenerateReport, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -1574,7 +1656,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::GenerateReport), + &auth::JWTAuth { + permission: Permission::GenerateReport, + minimum_entity_level: EntityType::Organization, + }, api_locking::LockAction::NotApplicable, )) .await @@ -1626,45 +1711,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::GenerateReport), - api_locking::LockAction::NotApplicable, - )) - .await - } - - /// # Panics - /// - /// Panics if `json_payload` array does not contain one `GetApiEventMetricRequest` element. - pub async fn get_merchant_api_events_metrics( - state: web::Data, - req: actix_web::HttpRequest, - json_payload: web::Json<[GetApiEventMetricRequest; 1]>, - ) -> impl Responder { - // safety: This shouldn't panic owing to the data type - #[allow(clippy::expect_used)] - let payload = json_payload - .into_inner() - .to_vec() - .pop() - .expect("Couldn't get GetApiEventMetricRequest"); - let flow = AnalyticsFlow::GetApiEventMetrics; - Box::pin(api::server_wrap( - flow, - state.clone(), - &req, - payload, - |state, auth: AuthenticationData, req, _| async move { - let org_id = auth.merchant_account.get_org_id(); - let merchant_id = auth.merchant_account.get_id(); - let auth: AuthInfo = AuthInfo::MerchantLevel { - org_id: org_id.clone(), - merchant_ids: vec![merchant_id.clone()], - }; - analytics::api_event::get_api_event_metrics(&state.pool, &auth, req) - .await - .map(ApplicationResponse::Json) + &auth::JWTAuth { + permission: Permission::GenerateReport, + minimum_entity_level: EntityType::Profile, }, - &auth::JWTAuth(Permission::Analytics), api_locking::LockAction::NotApplicable, )) .await @@ -1673,7 +1723,7 @@ pub mod routes { /// # Panics /// /// Panics if `json_payload` array does not contain one `GetApiEventMetricRequest` element. - pub async fn get_org_api_events_metrics( + pub async fn get_merchant_api_events_metrics( state: web::Data, req: actix_web::HttpRequest, json_payload: web::Json<[GetApiEventMetricRequest; 1]>, @@ -1692,58 +1742,18 @@ pub mod routes { &req, payload, |state, auth: AuthenticationData, req, _| async move { - let org_id = auth.merchant_account.get_org_id(); - let auth: AuthInfo = AuthInfo::OrgLevel { - org_id: org_id.clone(), - }; - analytics::api_event::get_api_event_metrics(&state.pool, &auth, req) - .await - .map(ApplicationResponse::Json) + analytics::api_event::get_api_event_metrics( + &state.pool, + auth.merchant_account.get_id(), + req, + ) + .await + .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), - api_locking::LockAction::NotApplicable, - )) - .await - } - - /// # Panics - /// - /// Panics if `json_payload` array does not contain one `GetApiEventMetricRequest` element. - pub async fn get_profile_api_events_metrics( - state: web::Data, - req: actix_web::HttpRequest, - json_payload: web::Json<[GetApiEventMetricRequest; 1]>, - ) -> impl Responder { - // safety: This shouldn't panic owing to the data type - #[allow(clippy::expect_used)] - let payload = json_payload - .into_inner() - .to_vec() - .pop() - .expect("Couldn't get GetApiEventMetricRequest"); - let flow = AnalyticsFlow::GetApiEventMetrics; - Box::pin(api::server_wrap( - flow, - state.clone(), - &req, - payload, - |state, auth: AuthenticationData, req, _| async move { - let org_id = auth.merchant_account.get_org_id(); - let merchant_id = auth.merchant_account.get_id(); - let profile_id = auth - .profile_id - .ok_or(report!(UserErrors::JwtProfileIdMissing)) - .change_context(AnalyticsError::AccessForbiddenError)?; - let auth: AuthInfo = AuthInfo::ProfileLevel { - org_id: org_id.clone(), - merchant_id: merchant_id.clone(), - profile_ids: vec![profile_id.clone()], - }; - analytics::api_event::get_api_event_metrics(&state.pool, &auth, req) - .await - .map(ApplicationResponse::Json) + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Merchant, }, - &auth::JWTAuth(Permission::Analytics), api_locking::LockAction::NotApplicable, )) .await @@ -1761,76 +1771,14 @@ pub mod routes { &req, json_payload.into_inner(), |state, auth: AuthenticationData, req, _| async move { - let org_id = auth.merchant_account.get_org_id(); - let merchant_id = auth.merchant_account.get_id(); - let auth: AuthInfo = AuthInfo::MerchantLevel { - org_id: org_id.clone(), - merchant_ids: vec![merchant_id.clone()], - }; - analytics::api_event::get_filters(&state.pool, req, &auth) + analytics::api_event::get_filters(&state.pool, req, auth.merchant_account.get_id()) .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), - api_locking::LockAction::NotApplicable, - )) - .await - } - - pub async fn get_org_api_event_filters( - state: web::Data, - req: actix_web::HttpRequest, - json_payload: web::Json, - ) -> impl Responder { - let flow = AnalyticsFlow::GetApiEventFilters; - Box::pin(api::server_wrap( - flow, - state.clone(), - &req, - json_payload.into_inner(), - |state, auth: AuthenticationData, req, _| async move { - let org_id = auth.merchant_account.get_org_id(); - let auth: AuthInfo = AuthInfo::OrgLevel { - org_id: org_id.clone(), - }; - analytics::api_event::get_filters(&state.pool, req, &auth) - .await - .map(ApplicationResponse::Json) + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Profile, }, - &auth::JWTAuth(Permission::Analytics), - api_locking::LockAction::NotApplicable, - )) - .await - } - - pub async fn get_profile_api_event_filters( - state: web::Data, - req: actix_web::HttpRequest, - json_payload: web::Json, - ) -> impl Responder { - let flow = AnalyticsFlow::GetApiEventFilters; - Box::pin(api::server_wrap( - flow, - state.clone(), - &req, - json_payload.into_inner(), - |state, auth: AuthenticationData, req, _| async move { - let org_id = auth.merchant_account.get_org_id(); - let merchant_id = auth.merchant_account.get_id(); - let profile_id = auth - .profile_id - .ok_or(report!(UserErrors::JwtProfileIdMissing)) - .change_context(AnalyticsError::AccessForbiddenError)?; - let auth: AuthInfo = AuthInfo::ProfileLevel { - org_id: org_id.clone(), - merchant_id: merchant_id.clone(), - profile_ids: vec![profile_id.clone()], - }; - analytics::api_event::get_filters(&state.pool, req, &auth) - .await - .map(ApplicationResponse::Json) - }, - &auth::JWTAuth(Permission::Analytics), api_locking::LockAction::NotApplicable, )) .await @@ -1859,7 +1807,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -1890,16 +1841,26 @@ pub mod routes { .map(|(i, _)| *i) .collect(); + let merchant_id: MerchantId = auth.merchant_id; + let org_id: OrganizationId = auth.org_id; + let search_params: Vec = vec![AuthInfo::MerchantLevel { + org_id: org_id.clone(), + merchant_ids: vec![merchant_id.clone()], + }]; + analytics::search::msearch_results( &state.opensearch_client, req, - &auth.merchant_id, + search_params, accessible_indexes, ) .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -1935,11 +1896,22 @@ pub mod routes { .filter(|(ind, _)| *ind == index) .find(|i| i.1.iter().any(|p| permissions.contains(p))) .ok_or(OpenSearchError::IndexAccessNotPermittedError(index))?; - analytics::search::search_results(&state.opensearch_client, req, &auth.merchant_id) + + let merchant_id: MerchantId = auth.merchant_id; + let org_id: OrganizationId = auth.org_id; + let search_params: Vec = vec![AuthInfo::MerchantLevel { + org_id: org_id.clone(), + merchant_ids: vec![merchant_id.clone()], + }]; + + analytics::search::search_results(&state.opensearch_client, req, search_params) .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -1967,7 +1939,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -2000,7 +1975,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -2026,7 +2004,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Organization, + }, api_locking::LockAction::NotApplicable, )) .await @@ -2064,7 +2045,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -2107,7 +2091,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -2143,7 +2130,10 @@ pub mod routes { .await .map(ApplicationResponse::Json) }, - &auth::JWTAuth(Permission::Analytics), + &auth::JWTAuth { + permission: Permission::Analytics, + minimum_entity_level: EntityType::Organization, + }, api_locking::LockAction::NotApplicable, )) .await diff --git a/crates/router/src/bin/scheduler.rs b/crates/router/src/bin/scheduler.rs index e2da76d51dc2..346d36156e84 100644 --- a/crates/router/src/bin/scheduler.rs +++ b/crates/router/src/bin/scheduler.rs @@ -251,6 +251,7 @@ pub async fn deep_health_check_func( #[derive(Debug, Copy, Clone)] pub struct WorkflowRunner; +#[cfg(feature = "v1")] #[async_trait::async_trait] impl ProcessTrackerWorkflows for WorkflowRunner { async fn trigger_workflow<'a>( @@ -358,6 +359,18 @@ impl ProcessTrackerWorkflows for WorkflowRunner { } } +#[cfg(feature = "v2")] +#[async_trait::async_trait] +impl ProcessTrackerWorkflows for WorkflowRunner { + async fn trigger_workflow<'a>( + &'a self, + _state: &'a routes::SessionState, + _process: storage::ProcessTracker, + ) -> CustomResult<(), ProcessTrackerError> { + todo!() + } +} + async fn start_scheduler( state: &routes::AppState, scheduler_flow: scheduler::SchedulerFlow, diff --git a/crates/router/src/compatibility/stripe/payment_intents.rs b/crates/router/src/compatibility/stripe/payment_intents.rs index 43737183e338..ec3dd9cc9536 100644 --- a/crates/router/src/compatibility/stripe/payment_intents.rs +++ b/crates/router/src/compatibility/stripe/payment_intents.rs @@ -73,7 +73,14 @@ pub async fn payment_intents_create( create_payment_req, |state, auth, req, req_state| { let eligible_connectors = req.connector.clone(); - payments::payments_core::( + payments::payments_core::< + api_types::Authorize, + api_types::PaymentsResponse, + _, + _, + _, + payments::PaymentData, + >( state, req_state, auth.merchant_account, @@ -136,7 +143,14 @@ pub async fn payment_intents_retrieve( &req, payload, |state, auth, payload, req_state| { - payments::payments_core::( + payments::payments_core::< + api_types::PSync, + api_types::PaymentsResponse, + _, + _, + _, + payments::PaymentData, + >( state, req_state, auth.merchant_account, @@ -207,7 +221,14 @@ pub async fn payment_intents_retrieve_with_gateway_creds( &req, payload, |state, auth, req, req_state| { - payments::payments_core::( + payments::payments_core::< + api_types::PSync, + payment_types::PaymentsResponse, + _, + _, + _, + payments::PaymentData, + >( state, req_state, auth.merchant_account, @@ -276,7 +297,14 @@ pub async fn payment_intents_update( payload, |state, auth, req, req_state| { let eligible_connectors = req.connector.clone(); - payments::payments_core::( + payments::payments_core::< + api_types::Authorize, + api_types::PaymentsResponse, + _, + _, + _, + payments::PaymentData, + >( state, req_state, auth.merchant_account, @@ -354,7 +382,14 @@ pub async fn payment_intents_confirm( payload, |state, auth, req, req_state| { let eligible_connectors = req.connector.clone(); - payments::payments_core::( + payments::payments_core::< + api_types::Authorize, + api_types::PaymentsResponse, + _, + _, + _, + payments::PaymentData, + >( state, req_state, auth.merchant_account, @@ -418,7 +453,14 @@ pub async fn payment_intents_capture( &req, payload, |state, auth, payload, req_state| { - payments::payments_core::( + payments::payments_core::< + api_types::Capture, + api_types::PaymentsResponse, + _, + _, + _, + payments::PaymentData, + >( state, req_state, auth.merchant_account, @@ -486,7 +528,14 @@ pub async fn payment_intents_cancel( &req, payload, |state, auth, req, req_state| { - payments::payments_core::( + payments::payments_core::< + api_types::Void, + api_types::PaymentsResponse, + _, + _, + _, + payments::PaymentData, + >( state, req_state, auth.merchant_account, diff --git a/crates/router/src/compatibility/stripe/setup_intents.rs b/crates/router/src/compatibility/stripe/setup_intents.rs index 72e9e4435dd2..fbd2fa812074 100644 --- a/crates/router/src/compatibility/stripe/setup_intents.rs +++ b/crates/router/src/compatibility/stripe/setup_intents.rs @@ -64,7 +64,8 @@ pub async fn setup_intents_create( api_types::PaymentsResponse, _, _, - _ + _, + payments::PaymentData, >( state, req_state, @@ -128,7 +129,14 @@ pub async fn setup_intents_retrieve( &req, payload, |state, auth, payload, req_state| { - payments::payments_core::( + payments::payments_core::< + api_types::PSync, + api_types::PaymentsResponse, + _, + _, + _, + payments::PaymentData, + >( state, req_state, auth.merchant_account, @@ -202,7 +210,8 @@ pub async fn setup_intents_update( api_types::PaymentsResponse, _, _, - _ + _, + payments::PaymentData, >( state, req_state, @@ -278,7 +287,8 @@ pub async fn setup_intents_confirm( api_types::PaymentsResponse, _, _, - _ + _, + payments::PaymentData, >( state, req_state, diff --git a/crates/router/src/configs/defaults.rs b/crates/router/src/configs/defaults.rs index 6898a9e002fc..659c94423d48 100644 --- a/crates/router/src/configs/defaults.rs +++ b/crates/router/src/configs/defaults.rs @@ -4,6 +4,9 @@ use api_models::{enums, payment_methods::RequiredFieldInfo}; use super::settings::{ConnectorFields, PaymentMethodType, RequiredFieldFinal}; +#[cfg(feature = "payouts")] +pub mod payout_required_fields; + impl Default for super::settings::Server { fn default() -> Self { Self { @@ -527,6 +530,15 @@ impl Default for super::settings::RequiredFields { value: None, } ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ) ] ), common: HashMap::new(), @@ -999,9 +1011,9 @@ impl Default for super::settings::RequiredFields { } ), ( - "email".to_string(), + "billing.email".to_string(), RequiredFieldInfo { - required_field: "email".to_string(), + required_field: "payment_method_data.billing.email".to_string(), display_name: "email".to_string(), field_type: enums::FieldType::UserEmailAddress, value: None, @@ -2134,6 +2146,107 @@ impl Default for super::settings::RequiredFields { ), } ), + ( + enums::Connector::Novalnet, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "browser_info.language".to_string(), + RequiredFieldInfo { + required_field: "browser_info.language".to_string(), + display_name: "browser_info_language".to_string(), + field_type: enums::FieldType::BrowserLanguage, + value: None, + } + ), + ( + "browser_info.ip_address".to_string(), + RequiredFieldInfo { + required_field: "browser_info.ip_address".to_string(), + display_name: "browser_info_ip_address".to_string(), + field_type: enums::FieldType::BrowserIp, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.line2".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line2".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine2, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "first_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "last_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email_address".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ] + ), + } + ), ( enums::Connector::Nuvei, RequiredFieldFinal { @@ -3390,6 +3503,15 @@ impl Default for super::settings::RequiredFields { value: None, } ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ) ] ), common: HashMap::new(), @@ -3862,9 +3984,9 @@ impl Default for super::settings::RequiredFields { } ), ( - "email".to_string(), + "billing.email".to_string(), RequiredFieldInfo { - required_field: "email".to_string(), + required_field: "payment_method_data.billing.email".to_string(), display_name: "email".to_string(), field_type: enums::FieldType::UserEmailAddress, value: None, @@ -4997,6 +5119,107 @@ impl Default for super::settings::RequiredFields { ), } ), + ( + enums::Connector::Novalnet, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "browser_info.language".to_string(), + RequiredFieldInfo { + required_field: "browser_info.language".to_string(), + display_name: "browser_info_language".to_string(), + field_type: enums::FieldType::BrowserLanguage, + value: None, + } + ), + ( + "browser_info.ip_address".to_string(), + RequiredFieldInfo { + required_field: "browser_info.ip_address".to_string(), + display_name: "browser_info_ip_address".to_string(), + field_type: enums::FieldType::BrowserIp, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.line2".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line2".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine2, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "first_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "last_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email_address".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ] + ), + } + ), ( enums::Connector::Nuvei, RequiredFieldFinal { @@ -6221,7 +6444,17 @@ impl Default for super::settings::RequiredFields { enums::Connector::Stripe, RequiredFieldFinal { mandate: HashMap::new(), - non_mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ) + ]), common: HashMap::new(), } )]), @@ -6242,9 +6475,7 @@ impl Default for super::settings::RequiredFields { ( enums::Connector::Stripe, RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from([ + mandate: HashMap::from([ ( "billing.email".to_string(), RequiredFieldInfo { @@ -6253,7 +6484,10 @@ impl Default for super::settings::RequiredFields { field_type: enums::FieldType::UserEmailAddress, value: None, } - ), + ) + ]), + non_mandate: HashMap::new(), + common: HashMap::from([ ( "billing.address.first_name".to_string(), RequiredFieldInfo { @@ -7073,27 +7307,10 @@ impl Default for super::settings::RequiredFields { field_type: enums::FieldType::UserEmailAddress, value: None, } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "billing_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "billing_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } ) ]), - non_mandate : HashMap::from([ + non_mandate : HashMap::new(), + common: HashMap::from([ ("billing.address.country".to_string(), RequiredFieldInfo { required_field: "payment_method_data.billing.address.country".to_string(), @@ -7109,11 +7326,26 @@ impl Default for super::settings::RequiredFields { }, value: None, } - )]), - common: HashMap::new( - ), - } + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "account_holder_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "account_holder_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + )]), + } ), ( enums::Connector::Trustpay, @@ -7684,9 +7916,9 @@ impl Default for super::settings::RequiredFields { non_mandate: HashMap::from( [ ( - "email".to_string(), + "billing.email".to_string(), RequiredFieldInfo { - required_field: "email".to_string(), + required_field: "payment_method_data.billing.email".to_string(), display_name: "email".to_string(), field_type: enums::FieldType::UserEmailAddress, value: None, @@ -8168,9 +8400,9 @@ impl Default for super::settings::RequiredFields { non_mandate: HashMap::from( [ ( - "email".to_string(), + "billing.email".to_string(), RequiredFieldInfo { - required_field: "email".to_string(), + required_field: "payment_method_data.billing.email".to_string(), display_name: "email".to_string(), field_type: enums::FieldType::UserEmailAddress, value: None, @@ -8498,6 +8730,21 @@ impl Default for super::settings::RequiredFields { ]), }, ), + ( + enums::PaymentMethodType::Cashapp, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ) + ]), + }, + ), ( enums::PaymentMethodType::MbWay, ConnectorFields { @@ -8526,6 +8773,15 @@ impl Default for super::settings::RequiredFields { value: None, } ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), ] ), } @@ -8689,8 +8945,7 @@ impl Default for super::settings::RequiredFields { enums::Connector::Paypal, RequiredFieldFinal { mandate: HashMap::new(), - non_mandate: HashMap::new( - ), + non_mandate: HashMap::new(), common: HashMap::new(), } ), @@ -9084,20 +9339,153 @@ impl Default for super::settings::RequiredFields { RequiredFieldFinal { mandate : HashMap::new(), non_mandate: HashMap::from([ - ( "name".to_string(), + ( + "billing.email".to_string(), RequiredFieldInfo { - required_field: "name".to_string(), - display_name: "cust_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - }), - ("payment_method_data.pay_later.afterpay_clearpay_redirect.billing_email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.pay_later.afterpay_clearpay_redirect.billing_email".to_string(), - display_name: "billing_email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - }) + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "GB".to_string(), + "AU".to_string(), + "CA".to_string(), + "US".to_string(), + "NZ".to_string(), + ] + }, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "shipping.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.first_name".to_string(), + display_name: "shipping_first_name".to_string(), + field_type: enums::FieldType::UserShippingName, + value: None, + } + ), + ( + "shipping.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.last_name".to_string(), + display_name: "shipping_last_name".to_string(), + field_type: enums::FieldType::UserShippingName, + value: None, + } + ), + ( + "shipping.address.city".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserShippingAddressCity, + value: None, + } + ), + ( + "shipping.address.state".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserShippingAddressState, + value: None, + } + ), + ( + "shipping.address.zip".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserShippingAddressPincode, + value: None, + } + ), + ( + "shipping.address.country".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserShippingAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ( + "shipping.address.line1".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserShippingAddressLine1, + value: None, + } + ), ]), common : HashMap::new(), } @@ -9270,8 +9658,30 @@ impl Default for super::settings::RequiredFields { required_field: "payment_method_data.pay_later.klarna.billing_country".to_string(), display_name: "billing_country".to_string(), field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "ALL".to_string(), + options: vec![ + "AU".to_string(), + "AT".to_string(), + "BE".to_string(), + "CA".to_string(), + "CZ".to_string(), + "DK".to_string(), + "FI".to_string(), + "FR".to_string(), + "GR".to_string(), + "DE".to_string(), + "IE".to_string(), + "IT".to_string(), + "NL".to_string(), + "NZ".to_string(), + "NO".to_string(), + "PL".to_string(), + "PT".to_string(), + "RO".to_string(), + "ES".to_string(), + "SE".to_string(), + "CH".to_string(), + "GB".to_string(), + "US".to_string(), ] }, value: None, @@ -9447,6 +9857,15 @@ impl Default for super::settings::RequiredFields { value: None, } ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), ( "billing.email".to_string(), RequiredFieldInfo { @@ -9559,6 +9978,15 @@ impl Default for super::settings::RequiredFields { value: None, } ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), ( "billing.email".to_string(), RequiredFieldInfo { @@ -9662,6 +10090,15 @@ impl Default for super::settings::RequiredFields { value: None, } ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), ( "billing.email".to_string(), RequiredFieldInfo { @@ -9756,6 +10193,15 @@ impl Default for super::settings::RequiredFields { value: None, } ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), ( "billing.email".to_string(), RequiredFieldInfo { @@ -9869,6 +10315,15 @@ impl Default for super::settings::RequiredFields { value: None, } ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), ( "billing.email".to_string(), RequiredFieldInfo { @@ -10221,6 +10676,15 @@ impl Default for super::settings::RequiredFields { value: None, } ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ) ] ), common : HashMap::new(), @@ -10274,6 +10738,15 @@ impl Default for super::settings::RequiredFields { value: None, } ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), ] ), common : HashMap::new(), @@ -10327,6 +10800,15 @@ impl Default for super::settings::RequiredFields { value: None, } ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), ] ), common : HashMap::new(), @@ -10380,12 +10862,21 @@ impl Default for super::settings::RequiredFields { value: None, } ), - ] - ), - common : HashMap::new(), - } - ) - ]), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), + ] + ), + common : HashMap::new(), + } + ) + ]), }, ), ( @@ -10433,6 +10924,15 @@ impl Default for super::settings::RequiredFields { value: None, } ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), ] ), common : HashMap::new(), @@ -10486,6 +10986,15 @@ impl Default for super::settings::RequiredFields { value: None, } ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), ] ), common : HashMap::new(), @@ -10537,7 +11046,7 @@ impl Default for super::settings::RequiredFields { RequiredFieldFinal { mandate: HashMap::new(), non_mandate: HashMap::new(), - common: HashMap::from([ ( + common: HashMap::from([( "billing.address.first_name".to_string(), RequiredFieldInfo { required_field: "payment_method_data.billing.address.first_name".to_string(), @@ -10545,7 +11054,35 @@ impl Default for super::settings::RequiredFields { field_type: enums::FieldType::UserBillingName, value: None, } - )]), + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "owner_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "payment_method_data.bank_debit.ach.account_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_debit.ach.account_number".to_string(), + display_name: "bank_account_number".to_string(), + field_type: enums::FieldType::Text, + value: None, + } + ), + ( + "payment_method_data.bank_debit.ach.routing_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_debit.ach.routing_number".to_string(), + display_name: "bank_routing_number".to_string(), + field_type: enums::FieldType::Text, + value: None, + } + ) + ]), }), ( enums::Connector::Adyen, @@ -10609,7 +11146,35 @@ impl Default for super::settings::RequiredFields { field_type: enums::FieldType::UserBillingName, value: None, } - )]), + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "owner_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "payment_method_data.bank_debit.sepa.iban".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_debit.bacs.iban".to_string(), + display_name: "bank_account_number".to_string(), + field_type: enums::FieldType::Text, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ) + ]), } ), ( @@ -10644,8 +11209,43 @@ impl Default for super::settings::RequiredFields { } ) ]), - }) - ]), + } + ), + ( + enums::Connector::Deutschebank, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "owner_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + }), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "owner_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "payment_method_data.bank_debit.sepa.iban".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_debit.bacs.iban".to_string(), + display_name: "bank_account_number".to_string(), + field_type: enums::FieldType::Text, + value: None, + } + ) + ]), + } + ) + ]), }, ), ( @@ -10665,7 +11265,157 @@ impl Default for super::settings::RequiredFields { field_type: enums::FieldType::UserBillingName, value: None, } - )]), + ), + ( + "payment_method_data.bank_debit.bacs.account_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_debit.bacs.account_number".to_string(), + display_name: "bank_account_number".to_string(), + field_type: enums::FieldType::Text, + value: None, + } + ), + ( + "payment_method_data.bank_debit.bacs.sort_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_debit.bacs.sort_code".to_string(), + display_name: "bank_sort_code".to_string(), + field_type: enums::FieldType::Text, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry { + options: vec!["UK".to_string()], + }, + value: None, + }, + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + }, + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + }, + ) + ]), + } + ), + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "owner_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + }), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "owner_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "payment_method_data.bank_debit.bacs.account_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_debit.bacs.account_number".to_string(), + display_name: "bank_account_number".to_string(), + field_type: enums::FieldType::Text, + value: None, + } + ), + ( + "payment_method_data.bank_debit.bacs.sort_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_debit.bacs.sort_code".to_string(), + display_name: "bank_sort_code".to_string(), + field_type: enums::FieldType::Text, + value: None, + } + ) + ]), + }) + ]), + }, + ), + ( + enums::PaymentMethodType::Becs, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "owner_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "payment_method_data.bank_debit.becs.account_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_debit.becs.account_number".to_string(), + display_name: "bank_account_number".to_string(), + field_type: enums::FieldType::Text, + value: None, + } + ), + ( + "payment_method_data.bank_debit.becs.bsb_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_debit.becs.bsb_number".to_string(), + display_name: "bsb_number".to_string(), + field_type: enums::FieldType::Text, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ) + ]), } ), ( @@ -10712,7 +11462,8 @@ impl Default for super::settings::RequiredFields { }) ]), }, - )]))), + ), + ]))), ( enums::PaymentMethod::BankTransfer, PaymentMethodType(HashMap::from([( @@ -10723,7 +11474,17 @@ impl Default for super::settings::RequiredFields { enums::Connector::Stripe, RequiredFieldFinal { mandate: HashMap::new(), - non_mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ) + ]), common: HashMap::new(), } ), @@ -10747,7 +11508,17 @@ impl Default for super::settings::RequiredFields { }, value: None, } - )]), + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + }, + ), + ]), common: HashMap::new(), } ), @@ -10760,7 +11531,17 @@ impl Default for super::settings::RequiredFields { RequiredFieldFinal { mandate: HashMap::new(), non_mandate: HashMap::new(), - common: HashMap::new(), + common: HashMap::from([ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ) + ]) } ), ])}), @@ -10801,6 +11582,24 @@ impl Default for super::settings::RequiredFields { value: None, } ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), ] ), } @@ -11115,6 +11914,140 @@ impl Default for super::settings::RequiredFields { ]), }, ), + ( + enums::PaymentMethodType::Sepa, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate : HashMap::new(), + non_mandate : HashMap::new(), + common : HashMap::from([ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry { + options: vec![ + "AT".to_string(), + "BE".to_string(), + "BG".to_string(), + "HR".to_string(), + "CY".to_string(), + "CZ".to_string(), + "DK".to_string(), + "EE".to_string(), + "FI".to_string(), + "FR".to_string(), + "DE".to_string(), + "GR".to_string(), + "HU".to_string(), + "IE".to_string(), + "IT".to_string(), + "LV".to_string(), + "LT".to_string(), + "LU".to_string(), + "MT".to_string(), + "NL".to_string(), + "PL".to_string(), + "PT".to_string(), + "RO".to_string(), + "SI".to_string(), + "SK".to_string(), + "ES".to_string(), + "SE".to_string(), + "AD".to_string(), + "IS".to_string(), + "LI".to_string(), + "MC".to_string(), + "NO".to_string(), + "SM".to_string(), + "CH".to_string(), + "GB".to_string(), + "VA".to_string(), + ], + }, + value: None, + }, + ), + ]), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::Bacs, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate : HashMap::new(), + non_mandate : HashMap::new(), + common : HashMap::from([ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ) + ]), + } + ) + ]), + }, + ), ]))), ( enums::PaymentMethod::GiftCard, @@ -11162,7 +12095,7 @@ impl Default for super::settings::RequiredFields { value: None, } ), - ]), + ]), common: HashMap::new(), } ), @@ -11210,6 +12143,15 @@ impl Default for super::settings::RequiredFields { value: None, } ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), ( "billing.email".to_string(), RequiredFieldInfo { @@ -11263,6 +12205,15 @@ impl Default for super::settings::RequiredFields { value: None, } ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), ( "billing.email".to_string(), RequiredFieldInfo { diff --git a/crates/router/src/configs/defaults/payout_required_fields.rs b/crates/router/src/configs/defaults/payout_required_fields.rs new file mode 100644 index 000000000000..0b8d380f7a31 --- /dev/null +++ b/crates/router/src/configs/defaults/payout_required_fields.rs @@ -0,0 +1,603 @@ +use std::collections::HashMap; + +use api_models::{ + enums::{ + CountryAlpha2, FieldType, + PaymentMethod::{BankTransfer, Card, Wallet}, + PaymentMethodType, PayoutConnectors, + }, + payment_methods::RequiredFieldInfo, +}; + +use crate::settings::{ + ConnectorFields, PaymentMethodType as PaymentMethodTypeInfo, PayoutRequiredFields, + RequiredFieldFinal, +}; + +impl Default for PayoutRequiredFields { + fn default() -> Self { + Self(HashMap::from([ + ( + Card, + PaymentMethodTypeInfo(HashMap::from([ + // Adyen + get_connector_payment_method_type_fields( + PayoutConnectors::Adyenplatform, + PaymentMethodType::Debit, + ), + get_connector_payment_method_type_fields( + PayoutConnectors::Adyenplatform, + PaymentMethodType::Credit, + ), + ])), + ), + ( + BankTransfer, + PaymentMethodTypeInfo(HashMap::from([ + // Adyen + get_connector_payment_method_type_fields( + PayoutConnectors::Adyenplatform, + PaymentMethodType::Sepa, + ), + // Ebanx + get_connector_payment_method_type_fields( + PayoutConnectors::Ebanx, + PaymentMethodType::Pix, + ), + // Wise + get_connector_payment_method_type_fields( + PayoutConnectors::Wise, + PaymentMethodType::Bacs, + ), + ])), + ), + ( + Wallet, + PaymentMethodTypeInfo(HashMap::from([ + // Adyen + get_connector_payment_method_type_fields( + PayoutConnectors::Adyenplatform, + PaymentMethodType::Paypal, + ), + ])), + ), + ])) + } +} + +fn get_connector_payment_method_type_fields( + connector: PayoutConnectors, + payment_method_type: PaymentMethodType, +) -> (PaymentMethodType, ConnectorFields) { + let mut common_fields = get_billing_details(connector); + match payment_method_type { + // Card + PaymentMethodType::Debit => { + common_fields.extend(get_card_fields()); + ( + payment_method_type, + ConnectorFields { + fields: HashMap::from([( + connector.into(), + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: common_fields, + }, + )]), + }, + ) + } + PaymentMethodType::Credit => { + common_fields.extend(get_card_fields()); + ( + payment_method_type, + ConnectorFields { + fields: HashMap::from([( + connector.into(), + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: common_fields, + }, + )]), + }, + ) + } + + // Banks + PaymentMethodType::Bacs => { + common_fields.extend(get_bacs_fields()); + ( + payment_method_type, + ConnectorFields { + fields: HashMap::from([( + connector.into(), + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: common_fields, + }, + )]), + }, + ) + } + PaymentMethodType::Pix => { + common_fields.extend(get_pix_bank_transfer_fields()); + ( + payment_method_type, + ConnectorFields { + fields: HashMap::from([( + connector.into(), + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: common_fields, + }, + )]), + }, + ) + } + PaymentMethodType::Sepa => { + common_fields.extend(get_sepa_fields()); + ( + payment_method_type, + ConnectorFields { + fields: HashMap::from([( + connector.into(), + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: common_fields, + }, + )]), + }, + ) + } + + // Wallets + PaymentMethodType::Paypal => { + common_fields.extend(get_paypal_fields()); + ( + payment_method_type, + ConnectorFields { + fields: HashMap::from([( + connector.into(), + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: common_fields, + }, + )]), + }, + ) + } + + _ => ( + payment_method_type, + ConnectorFields { + fields: HashMap::new(), + }, + ), + } +} + +fn get_card_fields() -> HashMap { + HashMap::from([ + ( + "payout_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payout_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: FieldType::UserCardNumber, + value: None, + }, + ), + ( + "payout_method_data.card.expiry_month".to_string(), + RequiredFieldInfo { + required_field: "payout_method_data.card.expiry_month".to_string(), + display_name: "exp_month".to_string(), + field_type: FieldType::UserCardExpiryMonth, + value: None, + }, + ), + ( + "payout_method_data.card.expiry_year".to_string(), + RequiredFieldInfo { + required_field: "payout_method_data.card.expiry_year".to_string(), + display_name: "exp_year".to_string(), + field_type: FieldType::UserCardExpiryYear, + value: None, + }, + ), + ( + "payout_method_data.card.card_holder_name".to_string(), + RequiredFieldInfo { + required_field: "payout_method_data.card.card_holder_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: FieldType::UserFullName, + value: None, + }, + ), + ]) +} + +fn get_bacs_fields() -> HashMap { + HashMap::from([ + ( + "payout_method_data.bank.bank_sort_code".to_string(), + RequiredFieldInfo { + required_field: "payout_method_data.bank.bank_sort_code".to_string(), + display_name: "bank_sort_code".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ( + "payout_method_data.bank.bank_account_number".to_string(), + RequiredFieldInfo { + required_field: "payout_method_data.bank.bank_account_number".to_string(), + display_name: "bank_account_number".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ]) +} + +fn get_pix_bank_transfer_fields() -> HashMap { + HashMap::from([ + ( + "payout_method_data.bank.bank_account_number".to_string(), + RequiredFieldInfo { + required_field: "payout_method_data.bank.bank_account_number".to_string(), + display_name: "bank_account_number".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ( + "payout_method_data.bank.pix_key".to_string(), + RequiredFieldInfo { + required_field: "payout_method_data.bank.pix_key".to_string(), + display_name: "pix_key".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ]) +} + +fn get_sepa_fields() -> HashMap { + HashMap::from([ + ( + "payout_method_data.bank.iban".to_string(), + RequiredFieldInfo { + required_field: "payout_method_data.bank.iban".to_string(), + display_name: "iban".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ( + "payout_method_data.bank.bic".to_string(), + RequiredFieldInfo { + required_field: "payout_method_data.bank.bic".to_string(), + display_name: "bic".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ]) +} + +fn get_paypal_fields() -> HashMap { + HashMap::from([( + "payout_method_data.wallet.telephone_number".to_string(), + RequiredFieldInfo { + required_field: "payout_method_data.wallet.telephone_number".to_string(), + display_name: "telephone_number".to_string(), + field_type: FieldType::Text, + value: None, + }, + )]) +} + +fn get_countries_for_connector(connector: PayoutConnectors) -> Vec { + match connector { + PayoutConnectors::Adyenplatform => vec![ + CountryAlpha2::ES, + CountryAlpha2::SK, + CountryAlpha2::AT, + CountryAlpha2::NL, + CountryAlpha2::DE, + CountryAlpha2::BE, + CountryAlpha2::FR, + CountryAlpha2::FI, + CountryAlpha2::PT, + CountryAlpha2::IE, + CountryAlpha2::EE, + CountryAlpha2::LT, + CountryAlpha2::LV, + CountryAlpha2::IT, + CountryAlpha2::CZ, + CountryAlpha2::DE, + CountryAlpha2::HU, + CountryAlpha2::NO, + CountryAlpha2::PL, + CountryAlpha2::SE, + CountryAlpha2::GB, + CountryAlpha2::CH, + ], + PayoutConnectors::Stripe => vec![CountryAlpha2::US], + _ => vec![], + } +} + +fn get_billing_details(connector: PayoutConnectors) -> HashMap { + match connector { + PayoutConnectors::Adyen => HashMap::from([ + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "billing.address.line1".to_string(), + display_name: "billing_address_line1".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ( + "billing.address.line2".to_string(), + RequiredFieldInfo { + required_field: "billing.address.line2".to_string(), + display_name: "billing_address_line2".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "billing.address.city".to_string(), + display_name: "billing_address_city".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "billing.address.zip".to_string(), + display_name: "billing_address_zip".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "billing.address.country".to_string(), + display_name: "billing_address_country".to_string(), + field_type: FieldType::UserAddressCountry { + options: get_countries_for_connector(connector) + .iter() + .map(|country| country.to_string()) + .collect::>(), + }, + value: None, + }, + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.first_name".to_string(), + display_name: "billing_address_first_name".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.last_name".to_string(), + display_name: "billing_address_last_name".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ]), + PayoutConnectors::Adyenplatform => HashMap::from([ + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "billing.address.line1".to_string(), + display_name: "billing_address_line1".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ( + "billing.address.line2".to_string(), + RequiredFieldInfo { + required_field: "billing.address.line2".to_string(), + display_name: "billing_address_line2".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "billing.address.city".to_string(), + display_name: "billing_address_city".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "billing.address.zip".to_string(), + display_name: "billing_address_zip".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "billing.address.country".to_string(), + display_name: "billing_address_country".to_string(), + field_type: FieldType::UserAddressCountry { + options: get_countries_for_connector(connector) + .iter() + .map(|country| country.to_string()) + .collect::>(), + }, + value: None, + }, + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.first_name".to_string(), + display_name: "billing_address_first_name".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ]), + PayoutConnectors::Wise => HashMap::from([ + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "billing.address.line1".to_string(), + display_name: "billing_address_line1".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "billing.address.city".to_string(), + display_name: "billing_address_city".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "billing.address.state".to_string(), + display_name: "billing_address_state".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "billing.address.zip".to_string(), + display_name: "billing_address_zip".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "billing.address.country".to_string(), + display_name: "billing_address_country".to_string(), + field_type: FieldType::UserAddressCountry { + options: get_countries_for_connector(connector) + .iter() + .map(|country| country.to_string()) + .collect::>(), + }, + value: None, + }, + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.first_name".to_string(), + display_name: "billing_address_first_name".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ]), + _ => HashMap::from([ + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "billing.address.line1".to_string(), + display_name: "billing_address_line1".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ( + "billing.address.line2".to_string(), + RequiredFieldInfo { + required_field: "billing.address.line2".to_string(), + display_name: "billing_address_line2".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "billing.address.city".to_string(), + display_name: "billing_address_city".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "billing.address.zip".to_string(), + display_name: "billing_address_zip".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "billing.address.country".to_string(), + display_name: "billing_address_country".to_string(), + field_type: FieldType::UserAddressCountry { + options: get_countries_for_connector(connector) + .iter() + .map(|country| country.to_string()) + .collect::>(), + }, + value: None, + }, + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.first_name".to_string(), + display_name: "billing_address_first_name".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "billing.address.last_name".to_string(), + display_name: "billing_address_last_name".to_string(), + field_type: FieldType::Text, + value: None, + }, + ), + ]), + } +} diff --git a/crates/router/src/configs/secrets_transformers.rs b/crates/router/src/configs/secrets_transformers.rs index 56a1cd451101..ce9c030333b7 100644 --- a/crates/router/src/configs/secrets_transformers.rs +++ b/crates/router/src/configs/secrets_transformers.rs @@ -115,16 +115,22 @@ impl SecretsHandler for settings::ApiKeys { let expiry_reminder_days = api_keys.expiry_reminder_days.clone(); #[cfg(feature = "partial-auth")] - let checksum_auth_context = secret_management_client - .get_secret(api_keys.checksum_auth_context.clone()) - .await?; - #[cfg(feature = "partial-auth")] - let checksum_auth_key = secret_management_client - .get_secret(api_keys.checksum_auth_key.clone()) - .await?; + let enable_partial_auth = api_keys.enable_partial_auth; #[cfg(feature = "partial-auth")] - let enable_partial_auth = api_keys.enable_partial_auth; + let (checksum_auth_context, checksum_auth_key) = { + if enable_partial_auth { + let checksum_auth_context = secret_management_client + .get_secret(api_keys.checksum_auth_context.clone()) + .await?; + let checksum_auth_key = secret_management_client + .get_secret(api_keys.checksum_auth_key.clone()) + .await?; + (checksum_auth_context, checksum_auth_key) + } else { + (String::new().into(), String::new().into()) + } + }; Ok(value.transition_state(|_| Self { hash_key, @@ -252,17 +258,15 @@ impl SecretsHandler for settings::Secrets { secret_management_client: &dyn SecretManagementInterface, ) -> CustomResult, SecretsManagementError> { let secrets = value.get_inner(); - let (jwt_secret, admin_api_key, recon_admin_api_key, master_enc_key) = tokio::try_join!( + let (jwt_secret, admin_api_key, master_enc_key) = tokio::try_join!( secret_management_client.get_secret(secrets.jwt_secret.clone()), secret_management_client.get_secret(secrets.admin_api_key.clone()), - secret_management_client.get_secret(secrets.recon_admin_api_key.clone()), secret_management_client.get_secret(secrets.master_enc_key.clone()) )?; Ok(value.transition_state(|_| Self { jwt_secret, admin_api_key, - recon_admin_api_key, master_enc_key, })) } @@ -454,5 +458,6 @@ pub(crate) async fn fetch_raw_secrets( user_auth_methods, decision: conf.decision, locker_based_open_banking_connectors: conf.locker_based_open_banking_connectors, + recipient_emails: conf.recipient_emails, } } diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index 79be0bf21d3a..0a65d935f500 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -6,7 +6,7 @@ use std::{ #[cfg(feature = "olap")] use analytics::{opensearch::OpenSearchConfig, ReportConfig}; use api_models::{enums, payment_methods::RequiredFieldInfo}; -use common_utils::ext_traits::ConfigExt; +use common_utils::{ext_traits::ConfigExt, pii::Email}; use config::{Environment, File}; use error_stack::ResultExt; #[cfg(feature = "email")] @@ -120,6 +120,7 @@ pub struct Settings { pub user_auth_methods: SecretStateContainer, pub decision: Option, pub locker_based_open_banking_connectors: LockerBasedRecipientConnectorList, + pub recipient_emails: RecipientMails, } #[derive(Debug, Deserialize, Clone, Default)] @@ -490,6 +491,10 @@ pub struct NotAvailableFlows { pub capture_method: Option, } +#[cfg(feature = "payouts")] +#[derive(Debug, Deserialize, Clone)] +pub struct PayoutRequiredFields(pub HashMap); + #[derive(Debug, Deserialize, Clone)] pub struct RequiredFields(pub HashMap); @@ -513,7 +518,6 @@ pub struct RequiredFieldFinal { pub struct Secrets { pub jwt_secret: Secret, pub admin_api_key: Secret, - pub recon_admin_api_key: Secret, pub master_enc_key: Secret, } @@ -841,6 +845,8 @@ impl Settings { #[derive(Debug, Deserialize, Clone, Default)] pub struct Payouts { pub payout_eligibility: bool, + #[serde(default)] + pub required_fields: PayoutRequiredFields, } #[derive(Debug, Clone, Default)] @@ -900,6 +906,11 @@ pub struct ServerTls { pub certificate: PathBuf, } +#[derive(Debug, Deserialize, Clone, Default)] +pub struct RecipientMails { + pub recon: Email, +} + fn deserialize_hashmap_inner( value: HashMap, ) -> Result>, String> diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs index 1fea8015def6..caa5b13b51c9 100644 --- a/crates/router/src/connector.rs +++ b/crates/router/src/connector.rs @@ -68,7 +68,7 @@ pub use hyperswitch_connectors::connectors::{ fiserv, fiserv::Fiserv, fiservemea, fiservemea::Fiservemea, fiuu, fiuu::Fiuu, globepay, globepay::Globepay, helcim, helcim::Helcim, nexixpay, nexixpay::Nexixpay, novalnet, novalnet::Novalnet, powertranz, powertranz::Powertranz, stax, stax::Stax, taxjar, - taxjar::Taxjar, tsys, tsys::Tsys, worldline, worldline::Worldline, + taxjar::Taxjar, thunes, thunes::Thunes, tsys, tsys::Tsys, worldline, worldline::Worldline, }; #[cfg(feature = "dummy_connector")] diff --git a/crates/router/src/connector/aci/transformers.rs b/crates/router/src/connector/aci/transformers.rs index da31eae22a1e..5842143ad445 100644 --- a/crates/router/src/connector/aci/transformers.rs +++ b/crates/router/src/connector/aci/transformers.rs @@ -225,7 +225,7 @@ impl customer_email: Some(item.router_data.get_billing_email()?), })) } - domain::BankRedirectData::Interac {} => { + domain::BankRedirectData::Interac { .. } => { Self::BankRedirect(Box::new(BankRedirectionPMData { payment_brand: PaymentBrand::InteracOnline, bank_account_country: Some(item.router_data.get_billing_country()?), @@ -238,7 +238,7 @@ impl customer_email: Some(item.router_data.get_billing_email()?), })) } - domain::BankRedirectData::Trustly {} => { + domain::BankRedirectData::Trustly { .. } => { Self::BankRedirect(Box::new(BankRedirectionPMData { payment_brand: PaymentBrand::Trustly, bank_account_country: None, diff --git a/crates/router/src/connector/adyen/transformers.rs b/crates/router/src/connector/adyen/transformers.rs index a9431d957ea8..2539c5c52f97 100644 --- a/crates/router/src/connector/adyen/transformers.rs +++ b/crates/router/src/connector/adyen/transformers.rs @@ -942,6 +942,7 @@ impl TryFrom<&common_enums::BankNames> for OpenBankingUKIssuer { | common_enums::BankNames::AllianceBank | common_enums::BankNames::AmBank | common_enums::BankNames::BankOfAmerica + | common_enums::BankNames::BankOfChina | common_enums::BankNames::BankIslam | common_enums::BankNames::BankMuamalat | common_enums::BankNames::BankRakyat @@ -2951,7 +2952,7 @@ fn get_redirect_extra_details( let country = item.get_optional_billing_country(); Ok((preferred_language.clone(), country)) } - domain::BankRedirectData::Trustly {} + domain::BankRedirectData::Trustly { .. } | domain::BankRedirectData::OpenBankingUk { .. } => { let country = item.get_optional_billing_country(); Ok((None, country)) diff --git a/crates/router/src/connector/cybersource/transformers.rs b/crates/router/src/connector/cybersource/transformers.rs index d1f15082ee3c..6af3c6006577 100644 --- a/crates/router/src/connector/cybersource/transformers.rs +++ b/crates/router/src/connector/cybersource/transformers.rs @@ -83,7 +83,7 @@ pub struct CybersourceZeroMandateRequest { impl TryFrom<&types::SetupMandateRouterData> for CybersourceZeroMandateRequest { type Error = error_stack::Report; fn try_from(item: &types::SetupMandateRouterData) -> Result { - let email = item.request.get_email()?; + let email = item.get_billing_email().or(item.request.get_email())?; let bill_to = build_bill_to(item.get_optional_billing(), email)?; let order_information = OrderInformationWithBill { @@ -1033,7 +1033,10 @@ impl domain::Card, ), ) -> Result { - let email = item.router_data.request.get_email()?; + let email = item + .router_data + .get_billing_email() + .or(item.router_data.request.get_email())?; let bill_to = build_bill_to(item.router_data.get_optional_billing(), email)?; let order_information = OrderInformationWithBill::from((item, Some(bill_to))); @@ -1124,7 +1127,10 @@ impl domain::Card, ), ) -> Result { - let email = item.router_data.request.get_email()?; + let email = item + .router_data + .get_billing_email() + .or(item.router_data.request.get_email())?; let bill_to = build_bill_to(item.router_data.get_optional_billing(), email)?; let order_information = OrderInformationWithBill::from((item, bill_to)); @@ -1207,7 +1213,10 @@ impl domain::ApplePayWalletData, ), ) -> Result { - let email = item.router_data.request.get_email()?; + let email = item + .router_data + .get_billing_email() + .or(item.router_data.request.get_email())?; let bill_to = build_bill_to(item.router_data.get_optional_billing(), email)?; let order_information = OrderInformationWithBill::from((item, Some(bill_to))); let processing_information = ProcessingInformation::try_from(( @@ -1276,7 +1285,10 @@ impl domain::GooglePayWalletData, ), ) -> Result { - let email = item.router_data.request.get_email()?; + let email = item + .router_data + .get_billing_email() + .or(item.router_data.request.get_email())?; let bill_to = build_bill_to(item.router_data.get_optional_billing(), email)?; let order_information = OrderInformationWithBill::from((item, Some(bill_to))); @@ -1338,7 +1350,10 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> } }, None => { - let email = item.router_data.request.get_email()?; + let email = item + .router_data + .get_billing_email() + .or(item.router_data.request.get_email())?; let bill_to = build_bill_to( item.router_data.get_optional_billing(), email, @@ -1490,10 +1505,11 @@ impl let payment_instrument = CybersoucrePaymentInstrument { id: connector_mandate_id.into(), }; - let bill_to = - item.router_data.request.get_email().ok().and_then(|email| { - build_bill_to(item.router_data.get_optional_billing(), email).ok() - }); + let bill_to = item + .router_data + .get_optional_billing_email() + .or(item.router_data.request.get_optional_email()) + .and_then(|email| build_bill_to(item.router_data.get_optional_billing(), email).ok()); let order_information = OrderInformationWithBill::from((item, bill_to)); let payment_information = PaymentInformation::MandatePayment(Box::new(MandatePaymentInformation { @@ -2321,7 +2337,10 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsPreProcessingRouterData>> })? .1 .to_string(); - let email = item.router_data.request.get_email()?; + let email = item + .router_data + .get_billing_email() + .or(item.router_data.request.get_email())?; let bill_to = build_bill_to(item.router_data.get_optional_billing(), email)?; let order_information = OrderInformationWithBill { amount_details, diff --git a/crates/router/src/connector/globalpay.rs b/crates/router/src/connector/globalpay.rs index 7d5553bb25c4..2707ef4b6f2e 100644 --- a/crates/router/src/connector/globalpay.rs +++ b/crates/router/src/connector/globalpay.rs @@ -1017,11 +1017,12 @@ impl api::IncomingWebhook for Globalpay { &self, request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { - let details = std::str::from_utf8(request.body) - .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; - Ok(Box::new(serde_json::from_str(details).change_context( - errors::ConnectorError::WebhookResourceObjectNotFound, - )?)) + Ok(Box::new( + request + .body + .parse_struct::("GlobalpayPaymentsResponse") + .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?, + )) } } diff --git a/crates/router/src/connector/gocardless/transformers.rs b/crates/router/src/connector/gocardless/transformers.rs index 572e4efa33fd..f3250602dbcf 100644 --- a/crates/router/src/connector/gocardless/transformers.rs +++ b/crates/router/src/connector/gocardless/transformers.rs @@ -285,6 +285,7 @@ impl TryFrom<(&domain::BankDebitData, &types::TokenizationRouterData)> for Custo domain::BankDebitData::BecsBankDebit { account_number, bsb_number, + .. } => { let country_code = item.get_billing_country()?; let account_holder_name = item.get_billing_full_name()?; diff --git a/crates/router/src/connector/iatapay/transformers.rs b/crates/router/src/connector/iatapay/transformers.rs index f6b8c266ce24..fde80abd259b 100644 --- a/crates/router/src/connector/iatapay/transformers.rs +++ b/crates/router/src/connector/iatapay/transformers.rs @@ -160,7 +160,7 @@ impl | domain::BankRedirectData::Giropay { .. } | domain::BankRedirectData::Interac { .. } | domain::BankRedirectData::OnlineBankingCzechRepublic { .. } - | domain::BankRedirectData::OnlineBankingFinland {} + | domain::BankRedirectData::OnlineBankingFinland { .. } | domain::BankRedirectData::OnlineBankingPoland { .. } | domain::BankRedirectData::OnlineBankingSlovakia { .. } | domain::BankRedirectData::OpenBankingUk { .. } diff --git a/crates/router/src/connector/multisafepay/transformers.rs b/crates/router/src/connector/multisafepay/transformers.rs index 5466d56c6429..c44cf14ab65c 100644 --- a/crates/router/src/connector/multisafepay/transformers.rs +++ b/crates/router/src/connector/multisafepay/transformers.rs @@ -243,6 +243,7 @@ impl TryFrom<&BankNames> for MultisafepayBankNames { | BankNames::AllianceBank | BankNames::AmBank | BankNames::BankOfAmerica + | BankNames::BankOfChina | BankNames::BankIslam | BankNames::BankMuamalat | BankNames::BankRakyat diff --git a/crates/router/src/connector/nuvei/transformers.rs b/crates/router/src/connector/nuvei/transformers.rs index e67116408267..2547443484e6 100644 --- a/crates/router/src/connector/nuvei/transformers.rs +++ b/crates/router/src/connector/nuvei/transformers.rs @@ -618,6 +618,7 @@ impl TryFrom for NuveiBIC { | common_enums::enums::BankNames::AllianceBank | common_enums::enums::BankNames::AmBank | common_enums::enums::BankNames::BankOfAmerica + | common_enums::enums::BankNames::BankOfChina | common_enums::enums::BankNames::BankIslam | common_enums::enums::BankNames::BankMuamalat | common_enums::enums::BankNames::BankRakyat diff --git a/crates/router/src/connector/paypal/transformers.rs b/crates/router/src/connector/paypal/transformers.rs index 24b2f2b88e98..4f5ab1337355 100644 --- a/crates/router/src/connector/paypal/transformers.rs +++ b/crates/router/src/connector/paypal/transformers.rs @@ -150,14 +150,10 @@ pub struct ShippingAddress { name: Option, } -impl TryFrom<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for ShippingAddress { - type Error = error_stack::Report; - - fn try_from( - item: &PaypalRouterData<&types::PaymentsAuthorizeRouterData>, - ) -> Result { - Ok(Self { - address: get_address_info(item.router_data.get_optional_shipping())?, +impl From<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for ShippingAddress { + fn from(item: &PaypalRouterData<&types::PaymentsAuthorizeRouterData>) -> Self { + Self { + address: get_address_info(item.router_data.get_optional_shipping()), name: Some(ShippingName { full_name: item .router_data @@ -165,7 +161,7 @@ impl TryFrom<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for Shippin .and_then(|inner_data| inner_data.address.as_ref()) .and_then(|inner_data| inner_data.first_name.clone()), }), - }) + } } } @@ -252,34 +248,31 @@ pub struct PaypalPaymentsRequest { payment_source: Option, } -fn get_address_info( - payment_address: Option<&api_models::payments::Address>, -) -> Result, error_stack::Report> { +fn get_address_info(payment_address: Option<&api_models::payments::Address>) -> Option
{ let address = payment_address.and_then(|payment_address| payment_address.address.as_ref()); - let address = match address { - Some(address) => Some(Address { - country_code: address.get_country()?.to_owned(), + match address { + Some(address) => address.get_optional_country().map(|country| Address { + country_code: country.to_owned(), address_line_1: address.line1.clone(), postal_code: address.zip.clone(), admin_area_2: address.city.clone(), }), None => None, - }; - Ok(address) + } } fn get_payment_source( item: &types::PaymentsAuthorizeRouterData, bank_redirection_data: &domain::BankRedirectData, ) -> Result> { match bank_redirection_data { - domain::BankRedirectData::Eps { bank_name: _ } => { + domain::BankRedirectData::Eps { bank_name: _, .. } => { Ok(PaymentSourceItem::Eps(RedirectRequest { name: item.get_billing_full_name()?, country_code: item.get_billing_country()?, experience_context: ContextStruct { return_url: item.request.complete_authorize_url.clone(), cancel_url: item.request.complete_authorize_url.clone(), - shipping_preference: if item.get_optional_shipping().is_some() { + shipping_preference: if item.get_optional_shipping_country().is_some() { ShippingPreference::SetProvidedAddress } else { ShippingPreference::GetFromFile @@ -295,7 +288,7 @@ fn get_payment_source( experience_context: ContextStruct { return_url: item.request.complete_authorize_url.clone(), cancel_url: item.request.complete_authorize_url.clone(), - shipping_preference: if item.get_optional_shipping().is_some() { + shipping_preference: if item.get_optional_shipping_country().is_some() { ShippingPreference::SetProvidedAddress } else { ShippingPreference::GetFromFile @@ -311,7 +304,7 @@ fn get_payment_source( experience_context: ContextStruct { return_url: item.request.complete_authorize_url.clone(), cancel_url: item.request.complete_authorize_url.clone(), - shipping_preference: if item.get_optional_shipping().is_some() { + shipping_preference: if item.get_optional_shipping_country().is_some() { ShippingPreference::SetProvidedAddress } else { ShippingPreference::GetFromFile @@ -322,13 +315,14 @@ fn get_payment_source( } domain::BankRedirectData::Sofort { preferred_language: _, + .. } => Ok(PaymentSourceItem::Sofort(RedirectRequest { name: item.get_billing_full_name()?, country_code: item.get_billing_country()?, experience_context: ContextStruct { return_url: item.request.complete_authorize_url.clone(), cancel_url: item.request.complete_authorize_url.clone(), - shipping_preference: if item.get_optional_shipping().is_some() { + shipping_preference: if item.get_optional_shipping_country().is_some() { ShippingPreference::SetProvidedAddress } else { ShippingPreference::GetFromFile @@ -392,7 +386,7 @@ impl TryFrom<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for PaypalP let connector_request_reference_id = item.router_data.connector_request_reference_id.clone(); - let shipping_address = ShippingAddress::try_from(item)?; + let shipping_address = ShippingAddress::from(item); let item_details = vec![ItemDetails::from(item)]; let purchase_units = vec![PurchaseUnitRequest { @@ -420,7 +414,7 @@ impl TryFrom<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for PaypalP }; let payment_source = Some(PaymentSourceItem::Card(CardRequest { - billing_address: get_address_info(item.router_data.get_optional_billing())?, + billing_address: get_address_info(item.router_data.get_optional_billing()), expiry, name: item .router_data @@ -446,7 +440,7 @@ impl TryFrom<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for PaypalP cancel_url: item.router_data.request.complete_authorize_url.clone(), shipping_preference: if item .router_data - .get_optional_shipping() + .get_optional_shipping_country() .is_some() { ShippingPreference::SetProvidedAddress diff --git a/crates/router/src/connector/stripe.rs b/crates/router/src/connector/stripe.rs index 82ed9577a83c..2af38249e949 100644 --- a/crates/router/src/connector/stripe.rs +++ b/crates/router/src/connector/stripe.rs @@ -157,6 +157,8 @@ impl ConnectorValidation for Stripe { PaymentMethodDataType::ApplePay, PaymentMethodDataType::GooglePay, PaymentMethodDataType::AchBankDebit, + PaymentMethodDataType::BacsBankDebit, + PaymentMethodDataType::BecsBankDebit, PaymentMethodDataType::SepaBankDebit, PaymentMethodDataType::Sofort, PaymentMethodDataType::Ideal, diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index 6b26818c2927..167219940745 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -1093,7 +1093,7 @@ fn get_bank_debit_data( (StripePaymentMethodType::Ach, ach_data) } domain::BankDebitData::SepaBankDebit { iban, .. } => { - let sepa_data = BankDebitData::Sepa { + let sepa_data: BankDebitData = BankDebitData::Sepa { iban: iban.to_owned(), }; (StripePaymentMethodType::Sepa, sepa_data) @@ -1594,35 +1594,36 @@ impl TryFrom<(&types::PaymentsAuthorizeRouterData, MinorUnit)> for PaymentIntent let shipping_address = match item.get_optional_shipping() { Some(shipping_details) => { let shipping_address = shipping_details.address.as_ref(); - Some(StripeShippingAddress { - city: shipping_address.and_then(|a| a.city.clone()), - country: shipping_address.and_then(|a| a.country), - line1: shipping_address.and_then(|a| a.line1.clone()), - line2: shipping_address.and_then(|a| a.line2.clone()), - zip: shipping_address.and_then(|a| a.zip.clone()), - state: shipping_address.and_then(|a| a.state.clone()), - name: shipping_address - .and_then(|a| { - a.first_name.as_ref().map(|first_name| { + shipping_address.and_then(|shipping_detail| { + shipping_detail + .first_name + .as_ref() + .map(|first_name| StripeShippingAddress { + city: shipping_address.and_then(|a| a.city.clone()), + country: shipping_address.and_then(|a| a.country), + line1: shipping_address.and_then(|a| a.line1.clone()), + line2: shipping_address.and_then(|a| a.line2.clone()), + zip: shipping_address.and_then(|a| a.zip.clone()), + state: shipping_address.and_then(|a| a.state.clone()), + name: format!( + "{} {}", + first_name.clone().expose(), + shipping_detail + .last_name + .clone() + .expose_option() + .unwrap_or_default() + ) + .into(), + phone: shipping_details.phone.as_ref().map(|p| { format!( - "{} {}", - first_name.clone().expose(), - a.last_name.clone().expose_option().unwrap_or_default() + "{}{}", + p.country_code.clone().unwrap_or_default(), + p.number.clone().expose_option().unwrap_or_default() ) .into() - }) + }), }) - .ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "shipping_address.first_name", - })?, - phone: shipping_details.phone.as_ref().map(|p| { - format!( - "{}{}", - p.country_code.clone().unwrap_or_default(), - p.number.clone().expose_option().unwrap_or_default() - ) - .into() - }), }) } None => None, @@ -1785,67 +1786,61 @@ impl TryFrom<(&types::PaymentsAuthorizeRouterData, MinorUnit)> for PaymentIntent _ => payment_data, }; - let setup_mandate_details = item + let customer_acceptance = item .request .setup_mandate_details .as_ref() - .and_then(|mandate_details| { - mandate_details - .customer_acceptance - .as_ref() - .map(|customer_acceptance| { - Ok::<_, error_stack::Report>( - match customer_acceptance.acceptance_type { - AcceptanceType::Online => { - let online_mandate = customer_acceptance - .online - .clone() - .get_required_value("online") + .and_then(|mandate_details| mandate_details.customer_acceptance.clone()) + .or(item.request.customer_acceptance.clone()); + + let setup_mandate_details = customer_acceptance + .as_ref() + .map(|customer_acceptance| { + Ok::<_, error_stack::Report>( + match customer_acceptance.acceptance_type { + AcceptanceType::Online => { + let online_mandate = customer_acceptance + .online + .clone() + .get_required_value("online") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "online", + })?; + StripeMandateRequest { + mandate_type: StripeMandateType::Online { + ip_address: online_mandate + .ip_address + .get_required_value("ip_address") .change_context( errors::ConnectorError::MissingRequiredField { - field_name: "online", + field_name: "ip_address", }, - )?; - StripeMandateRequest { - mandate_type: StripeMandateType::Online { - ip_address: online_mandate - .ip_address - .get_required_value("ip_address") - .change_context( - errors::ConnectorError::MissingRequiredField { - field_name: "ip_address", - }, - )?, - user_agent: online_mandate.user_agent, - }, - } - } - AcceptanceType::Offline => StripeMandateRequest { - mandate_type: StripeMandateType::Offline, + )?, + user_agent: online_mandate.user_agent, }, - }, - ) - }) + } + } + AcceptanceType::Offline => StripeMandateRequest { + mandate_type: StripeMandateType::Offline, + }, + }, + ) }) .transpose()? - .or_else(|| { + .or({ //stripe requires us to send mandate_data while making recurring payment through saved bank debit - if payment_method.is_some() { - //check if payment is done through saved payment method - match &payment_method_types { - //check if payment method is bank debit - Some( - StripePaymentMethodType::Ach - | StripePaymentMethodType::Sepa - | StripePaymentMethodType::Becs - | StripePaymentMethodType::Bacs, - ) => Some(StripeMandateRequest { - mandate_type: StripeMandateType::Offline, - }), - _ => None, - } - } else { - None + //check if payment is done through saved payment method + match &payment_method_types { + //check if payment method is bank debit + Some( + StripePaymentMethodType::Ach + | StripePaymentMethodType::Sepa + | StripePaymentMethodType::Becs + | StripePaymentMethodType::Bacs, + ) => Some(StripeMandateRequest { + mandate_type: StripeMandateType::Offline, + }), + _ => None, } }); @@ -3252,7 +3247,7 @@ impl transfer_type: StripeCreditTransferTypes::Multibanco, currency, payment_method_data: MultibancoTransferData { - email: item.request.get_email()?, + email: item.get_billing_email().or(item.request.get_email())?, }, amount: Some(amount), return_url: Some(item.get_return_url()?), @@ -3262,7 +3257,7 @@ impl Ok(Self::AchBankTansfer(AchCreditTransferSourceRequest { transfer_type: StripeCreditTransferTypes::AchCreditTransfer, payment_method_data: AchTransferData { - email: item.request.get_email()?, + email: item.get_billing_email().or(item.request.get_email())?, }, currency, })) diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index f8eed11d1611..9670e00ee9d0 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -758,6 +758,7 @@ impl PaymentsSetupMandateRequestData for types::SetupMandateRequestData { pub trait PaymentsAuthorizeRequestData { fn is_auto_capture(&self) -> Result; fn get_email(&self) -> Result; + fn get_optional_email(&self) -> Option; fn get_browser_info(&self) -> Result; fn get_order_details(&self) -> Result, Error>; fn get_card(&self) -> Result; @@ -805,6 +806,9 @@ impl PaymentsAuthorizeRequestData for types::PaymentsAuthorizeData { fn get_email(&self) -> Result { self.email.clone().ok_or_else(missing_field_err("email")) } + fn get_optional_email(&self) -> Option { + self.email.clone() + } fn get_browser_info(&self) -> Result { self.browser_info .clone() @@ -1662,6 +1666,7 @@ pub trait AddressDetailsData { fn to_state_code(&self) -> Result, Error>; fn to_state_code_as_optional(&self) -> Result>, Error>; fn get_optional_line2(&self) -> Option>; + fn get_optional_country(&self) -> Option; } impl AddressDetailsData for api::AddressDetails { @@ -1761,6 +1766,9 @@ impl AddressDetailsData for api::AddressDetails { fn get_optional_line2(&self) -> Option> { self.line2.clone() } + fn get_optional_country(&self) -> Option { + self.country + } } pub trait MandateData { diff --git a/crates/router/src/connector/zsl/transformers.rs b/crates/router/src/connector/zsl/transformers.rs index 60aed55b1f49..b3e7bfb3bfdc 100644 --- a/crates/router/src/connector/zsl/transformers.rs +++ b/crates/router/src/connector/zsl/transformers.rs @@ -385,7 +385,7 @@ pub struct ZslWebhookResponse { pub paid_ccy: api_models::enums::Currency, pub paid_amt: String, pub consr_paid_ccy: Option, - pub consr_paid_amt: Option, + pub consr_paid_amt: String, pub service_fee_ccy: Option, pub service_fee: Option, pub txn_amt: String, @@ -428,7 +428,7 @@ impl ) -> Result { let paid_amount = item .response - .paid_amt + .consr_paid_amt .parse::() .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; let txn_amount = item diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs index d7afe8dafde5..3d4a29e0f30b 100644 --- a/crates/router/src/consts.rs +++ b/crates/router/src/consts.rs @@ -58,10 +58,6 @@ pub(crate) const API_KEY_LENGTH: usize = 64; pub(crate) const APPLEPAY_VALIDATION_URL: &str = "https://apple-pay-gateway-cert.apple.com/paymentservices/startSession"; -// Qr Image data source starts with this string -// The base64 image data will be appended to it to image data source -pub(crate) const QR_IMAGE_DATA_SOURCE_STRING: &str = "data:image/png;base64"; - // OID (Object Identifier) for the merchant ID field extension. pub(crate) const MERCHANT_ID_FIELD_EXTENSION_ID: &str = "1.2.840.113635.100.6.32"; @@ -90,7 +86,7 @@ pub const EMAIL_TOKEN_TIME_IN_SECS: u64 = 60 * 60 * 24; // 1 day #[cfg(feature = "email")] pub const EMAIL_TOKEN_BLACKLIST_PREFIX: &str = "BET_"; -pub const ROLE_CACHE_PREFIX: &str = "CR_"; +pub const ROLE_INFO_CACHE_PREFIX: &str = "CR_INFO_"; #[cfg(feature = "olap")] pub const VERIFY_CONNECTOR_ID_PREFIX: &str = "conn_verify"; @@ -136,3 +132,6 @@ pub const MAX_ALLOWED_AMOUNT: i64 = 999999999; //payment attempt default unified error code and unified error message pub const DEFAULT_UNIFIED_ERROR_CODE: &str = "UE_000"; pub const DEFAULT_UNIFIED_ERROR_MESSAGE: &str = "Something went wrong"; + +// Recon's feature tag +pub const RECON_FEATURE_TAG: &str = "RECONCILIATION AND SETTLEMENT"; diff --git a/crates/router/src/consts/user_role.rs b/crates/router/src/consts/user_role.rs index 672e58300130..3cc2cb26886d 100644 --- a/crates/router/src/consts/user_role.rs +++ b/crates/router/src/consts/user_role.rs @@ -5,5 +5,13 @@ pub const ROLE_ID_MERCHANT_IAM_ADMIN: &str = "merchant_iam_admin"; pub const ROLE_ID_MERCHANT_DEVELOPER: &str = "merchant_developer"; pub const ROLE_ID_MERCHANT_OPERATOR: &str = "merchant_operator"; pub const ROLE_ID_MERCHANT_CUSTOMER_SUPPORT: &str = "merchant_customer_support"; + +pub const ROLE_ID_PROFILE_ADMIN: &str = "profile_admin"; +pub const ROLE_ID_PROFILE_VIEW_ONLY: &str = "profile_view_only"; +pub const ROLE_ID_PROFILE_IAM_ADMIN: &str = "profile_iam_admin"; +pub const ROLE_ID_PROFILE_DEVELOPER: &str = "profile_developer"; +pub const ROLE_ID_PROFILE_OPERATOR: &str = "profile_operator"; +pub const ROLE_ID_PROFILE_CUSTOMER_SUPPORT: &str = "profile_customer_support"; + pub const INTERNAL_USER_MERCHANT_ID: &str = "juspay000"; pub const MAX_ROLE_NAME_LENGTH: usize = 64; diff --git a/crates/router/src/core.rs b/crates/router/src/core.rs index 5dc4e2959194..e2ccca462984 100644 --- a/crates/router/src/core.rs +++ b/crates/router/src/core.rs @@ -1,8 +1,10 @@ pub mod admin; pub mod api_keys; pub mod api_locking; +#[cfg(feature = "v1")] pub mod apple_pay_certificates_migration; pub mod authentication; +#[cfg(feature = "v1")] pub mod blocklist; pub mod cache; pub mod cards_info; @@ -21,6 +23,7 @@ pub mod files; pub mod fraud_check; pub mod gsm; pub mod health_check; +#[cfg(feature = "v1")] pub mod locker_migration; pub mod mandate; pub mod metrics; @@ -33,6 +36,9 @@ pub mod payout_link; pub mod payouts; pub mod pm_auth; pub mod poll; +#[cfg(feature = "recon")] +pub mod recon; +#[cfg(feature = "v1")] pub mod refunds; pub mod routing; pub mod surcharge_decision_config; @@ -45,4 +51,5 @@ pub mod utils; pub mod verification; #[cfg(feature = "olap")] pub mod verify_connector; +#[cfg(feature = "v1")] pub mod webhooks; diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 8101867df445..75b8d922f82b 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -1300,6 +1300,10 @@ impl<'a> ConnectorAuthTypeAndMetadataValidation<'a> { datatrans::transformers::DatatransAuthType::try_from(self.auth_type)?; Ok(()) } + api_enums::Connector::Deutschebank => { + deutschebank::transformers::DeutschebankAuthType::try_from(self.auth_type)?; + Ok(()) + } api_enums::Connector::Dlocal => { dlocal::transformers::DlocalAuthType::try_from(self.auth_type)?; Ok(()) @@ -1393,6 +1397,10 @@ impl<'a> ConnectorAuthTypeAndMetadataValidation<'a> { noon::transformers::NoonAuthType::try_from(self.auth_type)?; Ok(()) } + api_enums::Connector::Novalnet => { + novalnet::transformers::NovalnetAuthType::try_from(self.auth_type)?; + Ok(()) + } api_enums::Connector::Nuvei => { nuvei::transformers::NuveiAuthType::try_from(self.auth_type)?; Ok(()) @@ -1453,6 +1461,10 @@ impl<'a> ConnectorAuthTypeAndMetadataValidation<'a> { stax::transformers::StaxAuthType::try_from(self.auth_type)?; Ok(()) } + api_enums::Connector::Taxjar => { + taxjar::transformers::TaxjarAuthType::try_from(self.auth_type)?; + Ok(()) + } api_enums::Connector::Stripe => { stripe::transformers::StripeAuthType::try_from(self.auth_type)?; Ok(()) @@ -1776,6 +1788,8 @@ impl<'a> ConnectorTypeAndConnectorName<'a> { api_enums::convert_pm_auth_connector(self.connector_name.to_string().as_str()); let authentication_connector = api_enums::convert_authentication_connector(self.connector_name.to_string().as_str()); + let tax_connector = + api_enums::convert_tax_connector(self.connector_name.to_string().as_str()); if pm_auth_connector.is_some() { if self.connector_type != &api_enums::ConnectorType::PaymentMethodAuth @@ -1793,6 +1807,13 @@ impl<'a> ConnectorTypeAndConnectorName<'a> { } .into()); } + } else if tax_connector.is_some() { + if self.connector_type != &api_enums::ConnectorType::TaxProcessor { + return Err(errors::ApiErrorResponse::InvalidRequestData { + message: "Invalid connector type given".to_string(), + } + .into()); + } } else { let routable_connector_option = self .connector_name @@ -2642,6 +2663,7 @@ pub async fn create_connector( state: SessionState, req: api::MerchantConnectorCreate, merchant_account: domain::MerchantAccount, + auth_profile_id: Option, key_store: domain::MerchantKeyStore, ) -> RouterResponse { let store = state.store.as_ref(); @@ -2673,6 +2695,8 @@ pub async fn create_connector( .validate_and_get_business_profile(&merchant_account, store, key_manager_state, &key_store) .await?; + core_utils::validate_profile_id_from_auth_layer(auth_profile_id, &business_profile)?; + let pm_auth_config_validation = PMAuthConfigValidation { connector_type: &req.connector_type, pm_auth_config: &req.pm_auth_config, @@ -4007,13 +4031,10 @@ impl BusinessProfileWrapper { Ok(()) } - pub fn get_routing_algorithm_id<'a, F>( + pub fn get_routing_algorithm_id<'a>( &'a self, - transaction_data: &'a routing::TransactionData<'_, F>, - ) -> Option - where - F: Send + Clone, - { + transaction_data: &'a routing::TransactionData<'_>, + ) -> Option { match transaction_data { routing::TransactionData::Payment(_) => self.profile.routing_algorithm_id.clone(), #[cfg(feature = "payouts")] diff --git a/crates/router/src/core/disputes.rs b/crates/router/src/core/disputes.rs index 24c6c0f8be88..8835f5f95a87 100644 --- a/crates/router/src/core/disputes.rs +++ b/crates/router/src/core/disputes.rs @@ -61,6 +61,19 @@ pub async fn retrieve_disputes_list( Ok(services::ApplicationResponse::Json(disputes_list)) } +#[cfg(feature = "v2")] +#[instrument(skip(state))] +pub async fn accept_dispute( + state: SessionState, + merchant_account: domain::MerchantAccount, + profile_id: Option, + key_store: domain::MerchantKeyStore, + req: disputes::DisputeId, +) -> RouterResponse { + todo!() +} + +#[cfg(feature = "v1")] #[instrument(skip(state))] pub async fn accept_dispute( state: SessionState, @@ -92,6 +105,7 @@ pub async fn accept_dispute( }) }, )?; + let payment_intent = db .find_payment_intent_by_payment_id_merchant_id( &(&state).into(), @@ -102,6 +116,7 @@ pub async fn accept_dispute( ) .await .change_context(errors::ApiErrorResponse::PaymentNotFound)?; + let payment_attempt = db .find_payment_attempt_by_attempt_id_merchant_id( &dispute.attempt_id, @@ -165,6 +180,19 @@ pub async fn accept_dispute( Ok(services::ApplicationResponse::Json(dispute_response)) } +#[cfg(feature = "v2")] +#[instrument(skip(state))] +pub async fn submit_evidence( + state: SessionState, + merchant_account: domain::MerchantAccount, + profile_id: Option, + key_store: domain::MerchantKeyStore, + req: dispute_models::SubmitEvidenceRequest, +) -> RouterResponse { + todo!() +} + +#[cfg(feature = "v1")] #[instrument(skip(state))] pub async fn submit_evidence( state: SessionState, @@ -208,6 +236,7 @@ pub async fn submit_evidence( &dispute, ) .await?; + let payment_intent = db .find_payment_intent_by_payment_id_merchant_id( &(&state).into(), @@ -218,6 +247,7 @@ pub async fn submit_evidence( ) .await .change_context(errors::ApiErrorResponse::PaymentNotFound)?; + let payment_attempt = db .find_payment_attempt_by_attempt_id_merchant_id( &dispute.attempt_id, diff --git a/crates/router/src/core/files/helpers.rs b/crates/router/src/core/files/helpers.rs index bd4fc749758e..22dbfd93f537 100644 --- a/crates/router/src/core/files/helpers.rs +++ b/crates/router/src/core/files/helpers.rs @@ -234,6 +234,27 @@ pub async fn retrieve_file_and_provider_file_id_from_file_id( } } +#[cfg(feature = "v2")] +//Upload file to connector if it supports / store it in S3 and return file_upload_provider, provider_file_id accordingly +pub async fn upload_and_get_provider_provider_file_id_profile_id( + state: &SessionState, + merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, + create_file_request: &api::CreateFileRequest, + file_key: String, +) -> CustomResult< + ( + String, + api_models::enums::FileUploadProvider, + Option, + Option, + ), + errors::ApiErrorResponse, +> { + todo!() +} + +#[cfg(feature = "v1")] //Upload file to connector if it supports / store it in S3 and return file_upload_provider, provider_file_id accordingly pub async fn upload_and_get_provider_provider_file_id_profile_id( state: &SessionState, @@ -279,6 +300,7 @@ pub async fn upload_and_get_provider_provider_file_id_profile_id( ) .await .change_context(errors::ApiErrorResponse::PaymentNotFound)?; + let payment_attempt = state .store .find_payment_attempt_by_attempt_id_merchant_id( diff --git a/crates/router/src/core/fraud_check.rs b/crates/router/src/core/fraud_check.rs index f0d3f79f666b..90c36ba9aa86 100644 --- a/crates/router/src/core/fraud_check.rs +++ b/crates/router/src/core/fraud_check.rs @@ -48,9 +48,9 @@ pub mod operation; pub mod types; #[instrument(skip_all)] -pub async fn call_frm_service( +pub async fn call_frm_service( state: &SessionState, - payment_data: &mut payments::PaymentData, + payment_data: &OperationData, frm_data: &mut FrmData, merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, @@ -59,6 +59,8 @@ pub async fn call_frm_service( where F: Send + Clone, + OperationData: payments::OperationSessionGetters + Send + Sync + Clone, + // To create connector flow specific interface data FrmData: ConstructFlowSpecificData, oss_types::RouterData: FeatureFrm + Send, @@ -80,7 +82,7 @@ where frm_data .payment_attempt .connector_transaction_id - .clone_from(&payment_data.payment_attempt.connector_transaction_id); + .clone_from(&payment_data.get_payment_attempt().connector_transaction_id); let mut router_data = frm_data .construct_router_data( @@ -94,7 +96,7 @@ where ) .await?; - router_data.status = payment_data.payment_attempt.status; + router_data.status = payment_data.get_payment_attempt().status; if matches!( frm_data.fraud_check.frm_transaction_type, FraudCheckType::PreFrm @@ -120,9 +122,9 @@ where } #[cfg(feature = "v2")] -pub async fn should_call_frm( +pub async fn should_call_frm( _merchant_account: &domain::MerchantAccount, - _payment_data: &payments::PaymentData, + _payment_data: &D, _state: &SessionState, _key_store: domain::MerchantKeyStore, ) -> RouterResult<( @@ -130,16 +132,20 @@ pub async fn should_call_frm( Option, Option, Option, -)> { +)> +where + F: Send + Clone, + D: payments::OperationSessionGetters + Send + Sync + Clone, +{ // Frm routing algorithm is not present in the merchant account // it has to be fetched from the business profile todo!() } #[cfg(feature = "v1")] -pub async fn should_call_frm( +pub async fn should_call_frm( merchant_account: &domain::MerchantAccount, - payment_data: &payments::PaymentData, + payment_data: &D, state: &SessionState, key_store: domain::MerchantKeyStore, ) -> RouterResult<( @@ -147,7 +153,11 @@ pub async fn should_call_frm( Option, Option, Option, -)> { +)> +where + F: Send + Clone, + D: payments::OperationSessionGetters + Send + Sync + Clone, +{ use common_utils::ext_traits::OptionExt; use masking::ExposeInterface; @@ -163,7 +173,7 @@ pub async fn should_call_frm( .attach_printable("Data field not found in frm_routing_algorithm")?; let profile_id = payment_data - .payment_intent + .get_payment_intent() .profile_id .as_ref() .get_required_value("profile_id") @@ -235,23 +245,19 @@ pub async fn should_call_frm( let mut is_frm_connector_enabled = false; let mut is_frm_pm_enabled = false; + let connector = payment_data.get_payment_attempt().connector.clone(); let filtered_frm_config = frm_configs_struct .iter() - .filter(|frm_config| { - match ( - &payment_data.clone().payment_attempt.connector, - &frm_config.gateway, - ) { - (Some(current_connector), Some(configured_connector)) => { - let is_enabled = *current_connector - == configured_connector.to_string(); - if is_enabled { - is_frm_connector_enabled = true; - } - is_enabled + .filter(|frm_config| match (&connector, &frm_config.gateway) { + (Some(current_connector), Some(configured_connector)) => { + let is_enabled = + *current_connector == configured_connector.to_string(); + if is_enabled { + is_frm_connector_enabled = true; } - (None, _) | (_, None) => true, + is_enabled } + (None, _) | (_, None) => true, }) .collect::>(); let filtered_payment_methods = filtered_frm_config @@ -262,7 +268,7 @@ pub async fn should_call_frm( .iter() .filter(|frm_config_pm| { match ( - payment_data.payment_attempt.payment_method, + payment_data.get_payment_attempt().payment_method, frm_config_pm.payment_method, ) { ( @@ -345,26 +351,31 @@ pub async fn should_call_frm( } #[allow(clippy::too_many_arguments)] -pub async fn make_frm_data_and_fraud_check_operation<'a, F>( +pub async fn make_frm_data_and_fraud_check_operation<'a, F, D>( _db: &dyn StorageInterface, state: &SessionState, merchant_account: &domain::MerchantAccount, - payment_data: payments::PaymentData, + payment_data: D, frm_routing_algorithm: FrmRoutingAlgorithm, profile_id: common_utils::id_type::ProfileId, frm_configs: FrmConfigsObject, _customer: &Option, -) -> RouterResult> +) -> RouterResult> where F: Send + Clone, + D: payments::OperationSessionGetters + + payments::OperationSessionSetters + + Send + + Sync + + Clone, { let order_details = payment_data - .payment_intent + .get_payment_intent() .order_details .clone() .or_else(|| // when the order_details are present within the meta_data, we need to take those to support backward compatibility - payment_data.payment_intent.metadata.clone().and_then(|meta| { + payment_data.get_payment_intent().metadata.clone().and_then(|meta| { let order_details = meta.get("order_details").to_owned(); order_details.map(|order| vec![masking::Secret::new(order.to_owned())]) })) @@ -387,21 +398,18 @@ where }; let payment_to_frm_data = PaymentToFrmData { - amount: payment_data.amount, - payment_intent: payment_data.payment_intent.to_owned(), - payment_attempt: payment_data.payment_attempt, + amount: payment_data.get_amount(), + payment_intent: payment_data.get_payment_intent().to_owned(), + payment_attempt: payment_data.get_payment_attempt().to_owned(), merchant_account: merchant_account.to_owned(), - address: payment_data.address.clone(), + address: payment_data.get_address().clone(), connector_details: frm_connector_details.clone(), order_details, - frm_metadata: payment_data.payment_intent.frm_metadata, + frm_metadata: payment_data.get_payment_intent().frm_metadata.clone(), }; - let fraud_check_operation: operation::BoxedFraudCheckOperation = - match frm_configs.frm_preferred_flow_type { - api_enums::FrmPreferredFlowTypes::Pre => Box::new(operation::FraudCheckPre), - api_enums::FrmPreferredFlowTypes::Post => Box::new(operation::FraudCheckPost), - }; + let fraud_check_operation: operation::BoxedFraudCheckOperation = + fraud_check_operation_by_frm_preferred_flow_type(frm_configs.frm_preferred_flow_type); let frm_data = fraud_check_operation .to_get_tracker()? .get_trackers(state, payment_to_frm_data, frm_connector_details) @@ -413,21 +421,39 @@ where }) } +fn fraud_check_operation_by_frm_preferred_flow_type( + frm_preferred_flow_type: api_enums::FrmPreferredFlowTypes, +) -> operation::BoxedFraudCheckOperation +where + operation::FraudCheckPost: operation::FraudCheckOperation, + operation::FraudCheckPre: operation::FraudCheckOperation, +{ + match frm_preferred_flow_type { + api_enums::FrmPreferredFlowTypes::Pre => Box::new(operation::FraudCheckPre), + api_enums::FrmPreferredFlowTypes::Post => Box::new(operation::FraudCheckPost), + } +} + #[allow(clippy::too_many_arguments)] -pub async fn pre_payment_frm_core<'a, F, Req>( +pub async fn pre_payment_frm_core<'a, F, Req, D>( state: &SessionState, merchant_account: &domain::MerchantAccount, - payment_data: &mut payments::PaymentData, - frm_info: &mut FrmInfo, + payment_data: &mut D, + frm_info: &mut FrmInfo, frm_configs: FrmConfigsObject, customer: &Option, should_continue_transaction: &mut bool, should_continue_capture: &mut bool, key_store: domain::MerchantKeyStore, - operation: &BoxedOperation<'_, F, Req>, + operation: &BoxedOperation<'_, F, Req, D>, ) -> RouterResult> where F: Send + Clone, + D: payments::OperationSessionGetters + + payments::OperationSessionSetters + + Send + + Sync + + Clone, { let mut frm_data = None; if is_operation_allowed(operation) { @@ -449,7 +475,7 @@ where key_store.clone(), ) .await?; - let _router_data = call_frm_service::( + let _router_data = call_frm_service::( state, payment_data, frm_data, @@ -470,7 +496,7 @@ where ) .await?; let frm_fraud_check = frm_data_updated.fraud_check.clone(); - payment_data.frm_message = Some(frm_fraud_check.clone()); + payment_data.set_frm_message(frm_fraud_check.clone()); if matches!(frm_fraud_check.frm_status, FraudCheckStatus::Fraud) { *should_continue_transaction = false; frm_info.suggested_action = Some(FrmSuggestion::FrmCancelTransaction); @@ -501,12 +527,12 @@ where } #[allow(clippy::too_many_arguments)] -pub async fn post_payment_frm_core<'a, F>( +pub async fn post_payment_frm_core<'a, F, D>( state: &SessionState, req_state: ReqState, merchant_account: &domain::MerchantAccount, - payment_data: &mut payments::PaymentData, - frm_info: &mut FrmInfo, + payment_data: &mut D, + frm_info: &mut FrmInfo, frm_configs: FrmConfigsObject, customer: &Option, key_store: domain::MerchantKeyStore, @@ -514,12 +540,17 @@ pub async fn post_payment_frm_core<'a, F>( ) -> RouterResult> where F: Send + Clone, + D: payments::OperationSessionGetters + + payments::OperationSessionSetters + + Send + + Sync + + Clone, { if let Some(frm_data) = &mut frm_info.frm_data { // Allow the Post flow only if the payment is authorized, // this logic has to be removed if we are going to call /sale or /transaction after failed transaction let fraud_check_operation = &mut frm_info.fraud_check_operation; - if payment_data.payment_attempt.status == AttemptStatus::Authorized { + if payment_data.get_payment_attempt().status == AttemptStatus::Authorized { let frm_router_data_opt = fraud_check_operation .to_domain()? .post_payment_frm( @@ -546,7 +577,7 @@ where .await?; let frm_fraud_check = frm_data.fraud_check.clone(); let mut frm_suggestion = None; - payment_data.frm_message = Some(frm_fraud_check.clone()); + payment_data.set_frm_message(frm_fraud_check.clone()); if matches!(frm_fraud_check.frm_status, FraudCheckStatus::Fraud) { frm_info.suggested_action = Some(FrmSuggestion::FrmCancelTransaction); } else if matches!(frm_fraud_check.frm_status, FraudCheckStatus::ManualReview) { @@ -590,12 +621,12 @@ where } #[allow(clippy::too_many_arguments)] -pub async fn call_frm_before_connector_call<'a, F, Req>( - operation: &BoxedOperation<'_, F, Req>, +pub async fn call_frm_before_connector_call<'a, F, Req, D>( + operation: &BoxedOperation<'_, F, Req, D>, merchant_account: &domain::MerchantAccount, - payment_data: &mut payments::PaymentData, + payment_data: &mut D, state: &SessionState, - frm_info: &mut Option>, + frm_info: &mut Option>, customer: &Option, should_continue_transaction: &mut bool, should_continue_capture: &mut bool, @@ -603,6 +634,11 @@ pub async fn call_frm_before_connector_call<'a, F, Req>( ) -> RouterResult> where F: Send + Clone, + D: payments::OperationSessionGetters + + payments::OperationSessionSetters + + Send + + Sync + + Clone, { let (is_frm_enabled, frm_routing_algorithm, frm_connector_label, frm_configs) = should_call_frm(merchant_account, payment_data, state, key_store.clone()).await?; @@ -648,7 +684,7 @@ where }); if matches!(fraud_capture_method, Some(Some(CaptureMethod::Manual))) && matches!( - payment_data.payment_attempt.status, + payment_data.get_payment_attempt().status, AttemptStatus::Unresolved ) { @@ -688,6 +724,7 @@ impl From for PaymentDetails { } } +#[cfg(feature = "v1")] #[instrument(skip_all)] pub async fn frm_fulfillment_core( state: SessionState, diff --git a/crates/router/src/core/fraud_check/operation.rs b/crates/router/src/core/fraud_check/operation.rs index 2d159d2e2209..d802339b675a 100644 --- a/crates/router/src/core/fraud_check/operation.rs +++ b/crates/router/src/core/fraud_check/operation.rs @@ -10,26 +10,23 @@ use super::{ FrmData, }; use crate::{ - core::{ - errors::{self, RouterResult}, - payments, - }, + core::errors::{self, RouterResult}, routes::{app::ReqState, SessionState}, types::{domain, fraud_check::FrmRouterData}, }; -pub type BoxedFraudCheckOperation = Box + Send + Sync>; +pub type BoxedFraudCheckOperation = Box + Send + Sync>; -pub trait FraudCheckOperation: Send + std::fmt::Debug { +pub trait FraudCheckOperation: Send + std::fmt::Debug { fn to_get_tracker(&self) -> RouterResult<&(dyn GetTracker + Send + Sync)> { Err(report!(errors::ApiErrorResponse::InternalServerError)) .attach_printable_lazy(|| format!("get tracker interface not found for {self:?}")) } - fn to_domain(&self) -> RouterResult<&(dyn Domain)> { + fn to_domain(&self) -> RouterResult<&(dyn Domain)> { Err(report!(errors::ApiErrorResponse::InternalServerError)) .attach_printable_lazy(|| format!("domain interface not found for {self:?}")) } - fn to_update_tracker(&self) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> { + fn to_update_tracker(&self) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> { Err(report!(errors::ApiErrorResponse::InternalServerError)) .attach_printable_lazy(|| format!("get tracker interface not found for {self:?}")) } @@ -47,12 +44,12 @@ pub trait GetTracker: Send { #[async_trait] #[allow(clippy::too_many_arguments)] -pub trait Domain: Send + Sync { +pub trait Domain: Send + Sync { async fn post_payment_frm<'a>( &'a self, state: &'a SessionState, req_state: ReqState, - payment_data: &mut payments::PaymentData, + payment_data: &mut D, frm_data: &mut FrmData, merchant_account: &domain::MerchantAccount, customer: &Option, @@ -64,7 +61,7 @@ pub trait Domain: Send + Sync { async fn pre_payment_frm<'a>( &'a self, state: &'a SessionState, - payment_data: &mut payments::PaymentData, + payment_data: &mut D, frm_data: &mut FrmData, merchant_account: &domain::MerchantAccount, customer: &Option, @@ -85,7 +82,7 @@ pub trait Domain: Send + Sync { _frm_configs: FrmConfigsObject, _frm_suggestion: &mut Option, _key_store: domain::MerchantKeyStore, - _payment_data: &mut payments::PaymentData, + _payment_data: &mut D, _customer: &Option, _should_continue_capture: &mut bool, ) -> RouterResult> @@ -97,14 +94,14 @@ pub trait Domain: Send + Sync { } #[async_trait] -pub trait UpdateTracker: Send { +pub trait UpdateTracker: Send { async fn update_tracker<'b>( &'b self, state: &SessionState, key_store: &domain::MerchantKeyStore, - frm_data: D, - payment_data: &mut payments::PaymentData, + frm_data: Fd, + payment_data: &mut D, _frm_suggestion: Option, frm_router_data: FrmRouterData, - ) -> RouterResult; + ) -> RouterResult; } diff --git a/crates/router/src/core/fraud_check/operation/fraud_check_post.rs b/crates/router/src/core/fraud_check/operation/fraud_check_post.rs index aec538962a95..bfb54c8f4e2e 100644 --- a/crates/router/src/core/fraud_check/operation/fraud_check_post.rs +++ b/crates/router/src/core/fraud_check/operation/fraud_check_post.rs @@ -43,26 +43,42 @@ use crate::{ #[derive(Debug, Clone, Copy)] pub struct FraudCheckPost; -impl FraudCheckOperation for &FraudCheckPost { +impl FraudCheckOperation for &FraudCheckPost +where + F: Clone + Send, + D: payments::OperationSessionGetters + + payments::OperationSessionSetters + + Send + + Sync + + Clone, +{ fn to_get_tracker(&self) -> RouterResult<&(dyn GetTracker + Send + Sync)> { Ok(*self) } - fn to_domain(&self) -> RouterResult<&(dyn Domain)> { + fn to_domain(&self) -> RouterResult<&(dyn Domain)> { Ok(*self) } - fn to_update_tracker(&self) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> { + fn to_update_tracker(&self) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> { Ok(*self) } } -impl FraudCheckOperation for FraudCheckPost { +impl FraudCheckOperation for FraudCheckPost +where + F: Clone + Send, + D: payments::OperationSessionGetters + + payments::OperationSessionSetters + + Send + + Sync + + Clone, +{ fn to_get_tracker(&self) -> RouterResult<&(dyn GetTracker + Send + Sync)> { Ok(self) } - fn to_domain(&self) -> RouterResult<&(dyn Domain)> { + fn to_domain(&self) -> RouterResult<&(dyn Domain)> { Ok(self) } - fn to_update_tracker(&self) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> { + fn to_update_tracker(&self) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> { Ok(self) } } @@ -83,7 +99,7 @@ impl GetTracker for FraudCheckPost { .ok(); let existing_fraud_check = db .find_fraud_check_by_payment_id_if_present( - payment_data.payment_intent.payment_id.clone(), + payment_data.payment_intent.get_id().to_owned(), payment_data.merchant_account.get_id().clone(), ) .await @@ -93,7 +109,7 @@ impl GetTracker for FraudCheckPost { _ => { db.insert_fraud_check_response(FraudCheckNew { frm_id: utils::generate_id(consts::ID_LENGTH, "frm"), - payment_id: payment_data.payment_intent.payment_id.clone(), + payment_id: payment_data.payment_intent.get_id().to_owned(), merchant_id: payment_data.merchant_account.get_id().clone(), attempt_id: payment_data.payment_attempt.attempt_id.clone(), created_at: common_utils::date_time::now(), @@ -137,13 +153,21 @@ impl GetTracker for FraudCheckPost { } #[async_trait] -impl Domain for FraudCheckPost { +impl Domain for FraudCheckPost +where + F: Clone + Send, + D: payments::OperationSessionGetters + + payments::OperationSessionSetters + + Send + + Sync + + Clone, +{ #[instrument(skip_all)] async fn post_payment_frm<'a>( &'a self, state: &'a SessionState, _req_state: ReqState, - payment_data: &mut payments::PaymentData, + payment_data: &mut D, frm_data: &mut FrmData, merchant_account: &domain::MerchantAccount, customer: &Option, @@ -153,7 +177,7 @@ impl Domain for FraudCheckPost { logger::debug!("post_flow::Sale Skipped"); return Ok(None); } - let router_data = frm_core::call_frm_service::( + let router_data = frm_core::call_frm_service::( state, payment_data, &mut frm_data.to_owned(), @@ -178,6 +202,25 @@ impl Domain for FraudCheckPost { })) } + #[cfg(feature = "v2")] + #[instrument(skip_all)] + async fn execute_post_tasks( + &self, + _state: &SessionState, + _req_state: ReqState, + _frm_data: &mut FrmData, + _merchant_account: &domain::MerchantAccount, + _frm_configs: FrmConfigsObject, + _frm_suggestion: &mut Option, + _key_store: domain::MerchantKeyStore, + _payment_data: &mut D, + _customer: &Option, + _should_continue_capture: &mut bool, + ) -> RouterResult> { + todo!() + } + + #[cfg(feature = "v1")] #[instrument(skip_all)] async fn execute_post_tasks( &self, @@ -188,7 +231,7 @@ impl Domain for FraudCheckPost { _frm_configs: FrmConfigsObject, frm_suggestion: &mut Option, key_store: domain::MerchantKeyStore, - payment_data: &mut payments::PaymentData, + payment_data: &mut D, customer: &Option, _should_continue_capture: &mut bool, ) -> RouterResult> { @@ -201,7 +244,7 @@ impl Domain for FraudCheckPost { *frm_suggestion = Some(FrmSuggestion::FrmCancelTransaction); let cancel_req = api_models::payments::PaymentsCancelRequest { - payment_id: frm_data.payment_intent.payment_id.clone(), + payment_id: frm_data.payment_intent.get_id().to_owned(), cancellation_reason: frm_data.fraud_check.frm_error.clone(), merchant_connector_details: None, }; @@ -211,6 +254,7 @@ impl Domain for FraudCheckPost { _, _, _, + payments::PaymentData, >( state.clone(), req_state.clone(), @@ -225,13 +269,13 @@ impl Domain for FraudCheckPost { HeaderPayload::default(), )) .await?; - logger::debug!("payment_id : {:?} has been cancelled since it has been found fraudulent by configured frm connector",payment_data.payment_attempt.payment_id); + logger::debug!("payment_id : {:?} has been cancelled since it has been found fraudulent by configured frm connector",payment_data.get_payment_attempt().payment_id); if let services::ApplicationResponse::JsonWithHeaders((payments_response, _)) = cancel_res { - payment_data.payment_intent.status = payments_response.status; + payment_data.set_payment_intent_status(payments_response.status); } - let _router_data = frm_core::call_frm_service::( + let _router_data = frm_core::call_frm_service::( state, payment_data, &mut frm_data.to_owned(), @@ -253,7 +297,7 @@ impl Domain for FraudCheckPost { ) { let capture_request = api_models::payments::PaymentsCaptureRequest { - payment_id: frm_data.payment_intent.payment_id.clone(), + payment_id: frm_data.payment_intent.get_id().to_owned(), merchant_id: None, amount_to_capture: None, refund_uncaptured_amount: None, @@ -267,6 +311,7 @@ impl Domain for FraudCheckPost { _, _, _, + payments::PaymentData, >( state.clone(), req_state.clone(), @@ -281,11 +326,11 @@ impl Domain for FraudCheckPost { HeaderPayload::default(), )) .await?; - logger::debug!("payment_id : {:?} has been captured since it has been found legit by configured frm connector",payment_data.payment_attempt.payment_id); + logger::debug!("payment_id : {:?} has been captured since it has been found legit by configured frm connector",payment_data.get_payment_attempt().payment_id); if let services::ApplicationResponse::JsonWithHeaders((payments_response, _)) = capture_response { - payment_data.payment_intent.status = payments_response.status; + payment_data.set_payment_intent_status(payments_response.status); } }; return Ok(Some(frm_data.to_owned())); @@ -295,13 +340,13 @@ impl Domain for FraudCheckPost { async fn pre_payment_frm<'a>( &'a self, state: &'a SessionState, - payment_data: &mut payments::PaymentData, + payment_data: &mut D, frm_data: &mut FrmData, merchant_account: &domain::MerchantAccount, customer: &Option, key_store: domain::MerchantKeyStore, ) -> RouterResult { - let router_data = frm_core::call_frm_service::( + let router_data = frm_core::call_frm_service::( state, payment_data, &mut frm_data.to_owned(), @@ -327,13 +372,35 @@ impl Domain for FraudCheckPost { } #[async_trait] -impl UpdateTracker for FraudCheckPost { +impl UpdateTracker for FraudCheckPost +where + F: Clone + Send, + D: payments::OperationSessionGetters + + payments::OperationSessionSetters + + Send + + Sync + + Clone, +{ + #[cfg(feature = "v2")] + async fn update_tracker<'b>( + &'b self, + state: &SessionState, + key_store: &domain::MerchantKeyStore, + mut frm_data: FrmData, + payment_data: &mut D, + frm_suggestion: Option, + frm_router_data: FrmRouterData, + ) -> RouterResult { + todo!() + } + + #[cfg(feature = "v1")] async fn update_tracker<'b>( &'b self, state: &SessionState, key_store: &domain::MerchantKeyStore, mut frm_data: FrmData, - payment_data: &mut payments::PaymentData, + payment_data: &mut D, frm_suggestion: Option, frm_router_data: FrmRouterData, ) -> RouterResult { @@ -501,9 +568,10 @@ impl UpdateTracker for FraudCheckPost { None, ), }; - payment_data.payment_attempt = db + + let payment_attempt = db .update_payment_attempt_with_attempt_id( - payment_data.payment_attempt.clone(), + payment_data.get_payment_attempt().clone(), PaymentAttemptUpdate::RejectUpdate { status: payment_attempt_status, error_code: Some(Some(frm_data.fraud_check.frm_status.to_string())), @@ -515,10 +583,12 @@ impl UpdateTracker for FraudCheckPost { .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; - payment_data.payment_intent = db + payment_data.set_payment_attempt(payment_attempt); + + let payment_intent = db .update_payment_intent( &state.into(), - payment_data.payment_intent.clone(), + payment_data.get_payment_intent().clone(), PaymentIntentUpdate::RejectUpdate { status: payment_intent_status, merchant_decision, @@ -529,6 +599,8 @@ impl UpdateTracker for FraudCheckPost { ) .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + + payment_data.set_payment_intent(payment_intent); } frm_data.fraud_check = match frm_check_update { Some(fraud_check_update) => db diff --git a/crates/router/src/core/fraud_check/operation/fraud_check_pre.rs b/crates/router/src/core/fraud_check/operation/fraud_check_pre.rs index c1a54acc9b3a..666028e5dfdb 100644 --- a/crates/router/src/core/fraud_check/operation/fraud_check_pre.rs +++ b/crates/router/src/core/fraud_check/operation/fraud_check_pre.rs @@ -37,26 +37,34 @@ use crate::{ #[derive(Debug, Clone, Copy)] pub struct FraudCheckPre; -impl FraudCheckOperation for &FraudCheckPre { +impl FraudCheckOperation for &FraudCheckPre +where + F: Clone + Send, + D: payments::OperationSessionGetters + Send + Sync + Clone, +{ fn to_get_tracker(&self) -> RouterResult<&(dyn GetTracker + Send + Sync)> { Ok(*self) } - fn to_domain(&self) -> RouterResult<&(dyn Domain)> { + fn to_domain(&self) -> RouterResult<&(dyn Domain)> { Ok(*self) } - fn to_update_tracker(&self) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> { + fn to_update_tracker(&self) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> { Ok(*self) } } -impl FraudCheckOperation for FraudCheckPre { +impl FraudCheckOperation for FraudCheckPre +where + F: Clone + Send, + D: payments::OperationSessionGetters + Send + Sync + Clone, +{ fn to_get_tracker(&self) -> RouterResult<&(dyn GetTracker + Send + Sync)> { Ok(self) } - fn to_domain(&self) -> RouterResult<&(dyn Domain)> { + fn to_domain(&self) -> RouterResult<&(dyn Domain)> { Ok(self) } - fn to_update_tracker(&self) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> { + fn to_update_tracker(&self) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> { Ok(self) } } @@ -78,7 +86,7 @@ impl GetTracker for FraudCheckPre { let existing_fraud_check = db .find_fraud_check_by_payment_id_if_present( - payment_data.payment_intent.payment_id.clone(), + payment_data.payment_intent.get_id().to_owned(), payment_data.merchant_account.get_id().clone(), ) .await @@ -89,7 +97,7 @@ impl GetTracker for FraudCheckPre { _ => { db.insert_fraud_check_response(FraudCheckNew { frm_id: Uuid::new_v4().simple().to_string(), - payment_id: payment_data.payment_intent.payment_id.clone(), + payment_id: payment_data.payment_intent.get_id().to_owned(), merchant_id: payment_data.merchant_account.get_id().clone(), attempt_id: payment_data.payment_attempt.attempt_id.clone(), created_at: common_utils::date_time::now(), @@ -134,14 +142,18 @@ impl GetTracker for FraudCheckPre { } #[async_trait] -impl Domain for FraudCheckPre { +impl Domain for FraudCheckPre +where + F: Clone + Send, + D: payments::OperationSessionGetters + Send + Sync + Clone, +{ #[cfg(all(feature = "v2", feature = "customer_v2"))] #[instrument(skip_all)] async fn post_payment_frm<'a>( &'a self, _state: &'a SessionState, _req_state: ReqState, - _payment_data: &mut payments::PaymentData, + _payment_data: &mut D, _frm_data: &mut FrmData, _merchant_account: &domain::MerchantAccount, _customer: &Option, @@ -156,13 +168,13 @@ impl Domain for FraudCheckPre { &'a self, state: &'a SessionState, _req_state: ReqState, - payment_data: &mut payments::PaymentData, + payment_data: &mut D, frm_data: &mut FrmData, merchant_account: &domain::MerchantAccount, customer: &Option, key_store: domain::MerchantKeyStore, ) -> RouterResult> { - let router_data = frm_core::call_frm_service::( + let router_data = frm_core::call_frm_service::( state, payment_data, &mut frm_data.to_owned(), @@ -194,13 +206,13 @@ impl Domain for FraudCheckPre { async fn pre_payment_frm<'a>( &'a self, state: &'a SessionState, - payment_data: &mut payments::PaymentData, + payment_data: &mut D, frm_data: &mut FrmData, merchant_account: &domain::MerchantAccount, customer: &Option, key_store: domain::MerchantKeyStore, ) -> RouterResult { - let router_data = frm_core::call_frm_service::( + let router_data = frm_core::call_frm_service::( state, payment_data, &mut frm_data.to_owned(), @@ -230,13 +242,17 @@ impl Domain for FraudCheckPre { } #[async_trait] -impl UpdateTracker for FraudCheckPre { +impl UpdateTracker for FraudCheckPre +where + F: Clone + Send, + D: payments::OperationSessionGetters + Send + Sync + Clone, +{ async fn update_tracker<'b>( &'b self, state: &SessionState, _key_store: &domain::MerchantKeyStore, mut frm_data: FrmData, - payment_data: &mut payments::PaymentData, + payment_data: &mut D, _frm_suggestion: Option, frm_router_data: FrmRouterData, ) -> RouterResult { @@ -309,7 +325,7 @@ impl UpdateTracker for FraudCheckPre { }; let frm_status = payment_data - .frm_message + .get_frm_message() .as_ref() .map_or(status, |frm_data| frm_data.frm_status); diff --git a/crates/router/src/core/fraud_check/types.rs b/crates/router/src/core/fraud_check/types.rs index dc1528eb9b82..3f5988777fd5 100644 --- a/crates/router/src/core/fraud_check/types.rs +++ b/crates/router/src/core/fraud_check/types.rs @@ -60,8 +60,8 @@ pub struct FrmData { } #[derive(Debug)] -pub struct FrmInfo { - pub fraud_check_operation: BoxedFraudCheckOperation, +pub struct FrmInfo { + pub fraud_check_operation: BoxedFraudCheckOperation, pub frm_data: Option, pub suggested_action: Option, } diff --git a/crates/router/src/core/generic_link/payout_link/initiate/script.js b/crates/router/src/core/generic_link/payout_link/initiate/script.js index abbfbb896ac8..2d3592256e25 100644 --- a/crates/router/src/core/generic_link/payout_link/initiate/script.js +++ b/crates/router/src/core/generic_link/payout_link/initiate/script.js @@ -160,12 +160,13 @@ if (!isTestMode && !isFramed) { theme: payoutDetails.theme, collectorName: payoutDetails.merchant_name, logo: payoutDetails.logo, - enabledPaymentMethods: payoutDetails.enabled_payment_methods, + enabledPaymentMethods: payoutDetails.enabled_payment_methods_with_required_fields, returnUrl: payoutDetails.return_url, sessionExpiry, amount: payoutDetails.amount, currency: payoutDetails.currency, flow: "PayoutLinkInitiate", + formLayout: payoutDetails.form_layout, }; payoutWidget = widgets.create("paymentMethodCollect", payoutOptions); diff --git a/crates/router/src/core/generic_link/payout_link/status/script.js b/crates/router/src/core/generic_link/payout_link/status/script.js index 828ea7f7fc44..473af49b2e7e 100644 --- a/crates/router/src/core/generic_link/payout_link/status/script.js +++ b/crates/router/src/core/generic_link/payout_link/status/script.js @@ -121,10 +121,10 @@ function renderStatusDetails(payoutDetails) { "{{i18n_ref_id_text}}": payoutDetails.payout_id, }; if (typeof payoutDetails.error_code === "string") { - resourceInfo["{{i18n_error_code_text}}"] = payoutDetails.error_code; + // resourceInfo["{{i18n_error_code_text}}"] = payoutDetails.error_code; } if (typeof payoutDetails.error_message === "string") { - resourceInfo["{{i18n_error_message}}"] = payoutDetails.error_message; + // resourceInfo["{{i18n_error_message}}"] = payoutDetails.error_message; } var resourceNode = document.createElement("div"); resourceNode.id = "resource-info-container"; @@ -173,7 +173,17 @@ function redirectToEndUrl(returnUrl) { } if (secondsLeft === 0) { setTimeout(function () { - window.location.href = returnUrl.toString(); + try { + window.top.location.href = returnUrl.toString(); + } catch (error) { + console.error( + "CRITICAL ERROR", + "Failed to redirect top document. Error - ", + error + ); + console.info("Redirecting in current document"); + window.location.href = returnUrl.toString(); + } }, 1000); } }, i * 1000); diff --git a/crates/router/src/core/generic_link/payout_link/status/styles.css b/crates/router/src/core/generic_link/payout_link/status/styles.css index ff922298fad6..a10bfe7e3c05 100644 --- a/crates/router/src/core/generic_link/payout_link/status/styles.css +++ b/crates/router/src/core/generic_link/payout_link/status/styles.css @@ -80,7 +80,7 @@ body { #resource-info-container { width: 100%; border-top: 1px solid rgb(231, 234, 241); - padding: 20px 0; + padding: 20px 10px; } #resource-info { display: flex; diff --git a/crates/router/src/core/health_check.rs b/crates/router/src/core/health_check.rs index 6092ac8bbb98..83faee677d43 100644 --- a/crates/router/src/core/health_check.rs +++ b/crates/router/src/core/health_check.rs @@ -52,7 +52,7 @@ impl HealthCheckInterface for app::SessionState { logger::debug!("Redis set_key was successful"); redis_conn - .get_key("test_key") + .get_key::<()>("test_key") .await .change_context(errors::HealthCheckRedisError::GetFailed)?; diff --git a/crates/router/src/core/mandate.rs b/crates/router/src/core/mandate.rs index 9cc252ffd05f..b5707979247b 100644 --- a/crates/router/src/core/mandate.rs +++ b/crates/router/src/core/mandate.rs @@ -58,6 +58,7 @@ pub async fn get_mandate( )) } +#[cfg(feature = "v1")] #[instrument(skip(state))] pub async fn revoke_mandate( state: SessionState, diff --git a/crates/router/src/core/mandate/helpers.rs b/crates/router/src/core/mandate/helpers.rs index 96f00a0d20ed..28ee784f6feb 100644 --- a/crates/router/src/core/mandate/helpers.rs +++ b/crates/router/src/core/mandate/helpers.rs @@ -11,6 +11,7 @@ use crate::{ types::{api, domain}, }; +#[cfg(feature = "v1")] pub async fn get_profile_id_for_mandate( state: &SessionState, merchant_account: &domain::MerchantAccount, @@ -51,6 +52,7 @@ pub fn get_mandate_type( setup_future_usage: Option, customer_acceptance: Option, token: Option, + payment_method: Option, ) -> CustomResult, errors::ValidationError> { match ( mandate_data.clone(), @@ -58,25 +60,28 @@ pub fn get_mandate_type( setup_future_usage, customer_acceptance.or(mandate_data.and_then(|m_data| m_data.customer_acceptance)), token, + payment_method, ) { - (Some(_), Some(_), Some(enums::FutureUsage::OffSession), Some(_), Some(_)) => { + (Some(_), Some(_), Some(enums::FutureUsage::OffSession), Some(_), Some(_), _) => { Err(errors::ValidationError::InvalidValue { message: "Expected one out of recurring_details and mandate_data but got both" .to_string(), } .into()) } - (_, _, Some(enums::FutureUsage::OffSession), Some(_), Some(_)) - | (_, _, Some(enums::FutureUsage::OffSession), Some(_), _) - | (Some(_), _, Some(enums::FutureUsage::OffSession), _, _) => { + (_, _, Some(enums::FutureUsage::OffSession), Some(_), Some(_), _) + | (_, _, Some(enums::FutureUsage::OffSession), Some(_), _, _) + | (Some(_), _, Some(enums::FutureUsage::OffSession), _, _, _) => { Ok(Some(api::MandateTransactionType::NewMandateTransaction)) } - (_, _, Some(enums::FutureUsage::OffSession), _, Some(_)) - | (_, Some(_), _, _, _) - | (_, _, Some(enums::FutureUsage::OffSession), _, _) => Ok(Some( - api::MandateTransactionType::RecurringMandateTransaction, - )), + (_, _, Some(enums::FutureUsage::OffSession), _, Some(_), _) + | (_, Some(_), _, _, _, _) + | (_, _, Some(enums::FutureUsage::OffSession), _, _, Some(enums::PaymentMethod::Wallet)) => { + Ok(Some( + api::MandateTransactionType::RecurringMandateTransaction, + )) + } _ => Ok(None), } diff --git a/crates/router/src/core/payment_link.rs b/crates/router/src/core/payment_link.rs index c56f49564720..0065e5a32b96 100644 --- a/crates/router/src/core/payment_link.rs +++ b/crates/router/src/core/payment_link.rs @@ -9,7 +9,6 @@ use common_utils::{ DEFAULT_ALLOWED_DOMAINS, DEFAULT_BACKGROUND_COLOR, DEFAULT_DISPLAY_SDK_ONLY, DEFAULT_ENABLE_SAVED_PAYMENT_METHOD, DEFAULT_LOCALE, DEFAULT_MERCHANT_LOGO, DEFAULT_PRODUCT_IMG, DEFAULT_SDK_LAYOUT, DEFAULT_SESSION_EXPIRY, - DEFAULT_TRANSACTION_DETAILS, }, ext_traits::{AsyncExt, OptionExt, ValueExt}, types::{AmountConvertor, MinorUnit, StringMajorUnitForCore}, @@ -64,6 +63,19 @@ pub async fn retrieve_payment_link( Ok(services::ApplicationResponse::Json(response)) } +#[cfg(feature = "v2")] +pub async fn form_payment_link_data( + state: &SessionState, + merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, + merchant_id: common_utils::id_type::MerchantId, + payment_id: common_utils::id_type::PaymentId, + locale: Option, +) -> RouterResult<(PaymentLink, PaymentLinkData, PaymentLinkConfig)> { + todo!() +} + +#[cfg(feature = "v1")] pub async fn form_payment_link_data( state: &SessionState, merchant_account: domain::MerchantAccount, @@ -114,7 +126,7 @@ pub async fn form_payment_link_data( display_sdk_only: DEFAULT_DISPLAY_SDK_ONLY, enabled_saved_payment_method: DEFAULT_ENABLE_SAVED_PAYMENT_METHOD, allowed_domains: DEFAULT_ALLOWED_DOMAINS, - transaction_details: DEFAULT_TRANSACTION_DETAILS, + transaction_details: None, } }; @@ -616,24 +628,8 @@ pub fn get_payment_link_config_based_on_priority( display_sdk_only, enabled_saved_payment_method, allowed_domains, - transaction_details: payment_create_link_config.and_then(|payment_link_config| { - payment_link_config - .theme_config - .transaction_details - .and_then(|transaction_details| { - match serde_json::to_string(&transaction_details).change_context( - errors::ApiErrorResponse::InvalidDataValue { - field_name: "transaction_details", - }, - ) { - Ok(details) => Some(details), - Err(err) => { - logger::error!("Failed to serialize transaction details: {:?}", err); - None - } - } - }) - }), + transaction_details: payment_create_link_config + .and_then(|payment_link_config| payment_link_config.theme_config.transaction_details), }; Ok((payment_link_config, domain_name)) @@ -659,6 +655,19 @@ fn check_payment_link_invalid_conditions( not_allowed_statuses.contains(intent_status) } +#[cfg(feature = "v2")] +pub async fn get_payment_link_status( + _state: SessionState, + _merchant_account: domain::MerchantAccount, + _key_store: domain::MerchantKeyStore, + _merchant_id: common_utils::id_type::MerchantId, + _payment_id: common_utils::id_type::PaymentId, + _request_headers: &header::HeaderMap, +) -> RouterResponse { + todo!() +} + +#[cfg(feature = "v1")] pub async fn get_payment_link_status( state: SessionState, merchant_account: domain::MerchantAccount, @@ -721,7 +730,7 @@ pub async fn get_payment_link_status( display_sdk_only: DEFAULT_DISPLAY_SDK_ONLY, enabled_saved_payment_method: DEFAULT_ENABLE_SAVED_PAYMENT_METHOD, allowed_domains: DEFAULT_ALLOWED_DOMAINS, - transaction_details: DEFAULT_TRANSACTION_DETAILS, + transaction_details: None, } }; diff --git a/crates/router/src/core/payment_link/payment_link_initiate/payment_link.css b/crates/router/src/core/payment_link/payment_link_initiate/payment_link.css index d560060d8820..92b150fa1386 100644 --- a/crates/router/src/core/payment_link/payment_link_initiate/payment_link.css +++ b/crates/router/src/core/payment_link/payment_link_initiate/payment_link.css @@ -96,6 +96,10 @@ body { #hyper-checkout-payment-merchant-dynamic-details { margin: 20px 20px 10px 20px; + overflow-y: scroll; + max-width: 35vw; + max-height: 10vh; + min-height: 80px; } .hyper-checkout-payment-horizontal-line { @@ -714,6 +718,10 @@ body { flex-flow: column; flex-direction: column-reverse; margin: 0; + max-width: 100%; + overflow-y: scroll; + max-height: 10vh; + min-height: 80px; } .hyper-checkout-payment-horizontal-line { diff --git a/crates/router/src/core/payment_link/payment_link_initiate/payment_link.html b/crates/router/src/core/payment_link/payment_link_initiate/payment_link.html index b66242305b6a..54e48a5241f1 100644 --- a/crates/router/src/core/payment_link/payment_link_initiate/payment_link.html +++ b/crates/router/src/core/payment_link/payment_link_initiate/payment_link.html @@ -325,10 +325,10 @@ {{ hyperloader_sdk_link }} diff --git a/crates/router/src/core/payment_link/payment_link_initiate/payment_link.js b/crates/router/src/core/payment_link/payment_link_initiate/payment_link.js index db6a1d717627..ee2357c6695d 100644 --- a/crates/router/src/core/payment_link/payment_link_initiate/payment_link.js +++ b/crates/router/src/core/payment_link/payment_link_initiate/payment_link.js @@ -617,26 +617,57 @@ function renderDynamicMerchantDetails(paymentDetails) { } function appendMerchantDetails(paymentDetails, merchantDynamicDetails) { - if (!(typeof paymentDetails.transaction_details === "string" && paymentDetails.transaction_details.length > 0)) { + if ( + !( + typeof paymentDetails.transaction_details === "object" && + Object.keys(paymentDetails.transaction_details).length > 0 + ) + ) { return; } try { - let merchantDetailsObject = JSON.parse(paymentDetails.transaction_details); + let merchantDetailsObject = paymentDetails.transaction_details; + // sort the merchant details based on the position + // if position is null, then it will be shown at the end + merchantDetailsObject.sort((a, b) => { + if (a.ui_configuration === null || a.ui_configuration.position === null) + return 1; + if (b.ui_configuration === null || b.ui_configuration.position === null) + return -1; + + if (typeof a.ui_configuration.position === "number" && typeof b.ui_configuration.position === "number") { + return a.ui_configuration.position - b.ui_configuration.position; + } + else return 0; + }); - if (Object.keys(merchantDetailsObject).length > 0) { + if (merchantDetailsObject.length > 0) { // render a horizontal line above dynamic merchant details - var horizontalLineContainer = document.getElementById("hyper-checkout-payment-horizontal-line-container"); + var horizontalLineContainer = document.getElementById( + "hyper-checkout-payment-horizontal-line-container", + ); var horizontalLine = document.createElement("hr"); horizontalLine.className = "hyper-checkout-payment-horizontal-line"; horizontalLineContainer.append(horizontalLine); // max number of items to show in the merchant details - let maxItemsInDetails = 5; - for (var key in merchantDetailsObject) { + let maxItemsInDetails = 50; + for (var item of merchantDetailsObject) { var merchantData = document.createElement("div"); merchantData.className = "hyper-checkout-payment-merchant-dynamic-data"; - merchantData.innerHTML = key+": "+merchantDetailsObject[key].bold(); + // make the key and value bold if specified in the ui_configuration + var key = item.ui_configuration + ? item.ui_configuration.is_key_bold + ? item.key.bold() + : item.key + : item.key; + var value = item.ui_configuration + ? item.ui_configuration.is_value_bold + ? item.value.bold() + : item.value + : item.value; + merchantData.innerHTML = key + " : " + value; merchantDynamicDetails.append(merchantData); if (--maxItemsInDetails === 0) { @@ -667,7 +698,7 @@ function renderCart(paymentDetails) { var MAX_ITEMS_VISIBLE_AFTER_COLLAPSE = paymentDetails.max_items_visible_after_collapse; var yourCartText = document.createElement("span"); - var yourCartText = document.getElementById('your-cart-text'); + var yourCartText = document.getElementById("your-cart-text"); if (yourCartText) { yourCartText.textContent = translations.yourCart; } diff --git a/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js b/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js index 777cfd8cadd8..48974a564d5e 100644 --- a/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js +++ b/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js @@ -57,7 +57,7 @@ if (!isFramed) { }); var type = paymentDetails.sdk_layout === "spaced_accordion" || - paymentDetails.sdk_layout === "accordion" + paymentDetails.sdk_layout === "accordion" ? "accordion" : paymentDetails.sdk_layout; @@ -103,6 +103,35 @@ if (!isFramed) { arr.splice(0, 3); arr.unshift("status"); arr.unshift("payment_link"); - window.location.href = window.location.origin + "/" + arr.join("/")+ "?locale=" + paymentDetails.locale; + let returnUrl = + window.location.origin + + "/" + + arr.join("/") + + "?locale=" + + paymentDetails.locale; + try { + window.top.location.href = returnUrl; + + // Push logs to logs endpoint + } catch (error) { + var url = window.location.href; + var { paymentId, merchantId, attemptId, connector } = parseRoute(url); + var urlToPost = getEnvRoute(url); + var message = { + message: "CRITICAL ERROR - Failed to redirect top document. Falling back to redirecting using window.location", + reason: error.message, + } + var log = { + message, + url, + paymentId, + merchantId, + attemptId, + connector, + }; + postLog(log, urlToPost); + + window.location.href = returnUrl; + } } } diff --git a/crates/router/src/core/payment_link/payment_link_status/status.html b/crates/router/src/core/payment_link/payment_link_status/status.html index fdc0c3012bba..c78a0bb556da 100644 --- a/crates/router/src/core/payment_link/payment_link_status/status.html +++ b/crates/router/src/core/payment_link/payment_link_status/status.html @@ -11,9 +11,9 @@ href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700;800" /> diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs index 88624cfd092d..9a39a3c341b7 100644 --- a/crates/router/src/core/payment_methods.rs +++ b/crates/router/src/core/payment_methods.rs @@ -34,13 +34,15 @@ use masking::PeekInterface; use router_env::{instrument, tracing}; use time::Duration; -use super::errors::{RouterResponse, StorageErrorExt}; +use super::{ + errors::{RouterResponse, StorageErrorExt}, + pm_auth, +}; use crate::{ consts, core::{ errors::{self, RouterResult}, payments::helpers, - pm_auth as core_pm_auth, }, routes::{app::StorageInterface, SessionState}, services, @@ -77,9 +79,22 @@ pub async fn retrieve_payment_method( Ok((pm_opt.to_owned(), payment_token)) } + pm_opt @ Some(pm @ domain::PaymentMethodData::BankDebit(_)) => { + let payment_token = helpers::store_payment_method_data_in_vault( + state, + payment_attempt, + payment_intent, + enums::PaymentMethod::BankDebit, + pm, + merchant_key_store, + business_profile, + ) + .await?; + + Ok((pm_opt.to_owned(), payment_token)) + } pm @ Some(domain::PaymentMethodData::PayLater(_)) => Ok((pm.to_owned(), None)), pm @ Some(domain::PaymentMethodData::Crypto(_)) => Ok((pm.to_owned(), None)), - pm @ Some(domain::PaymentMethodData::BankDebit(_)) => Ok((pm.to_owned(), None)), pm @ Some(domain::PaymentMethodData::Upi(_)) => Ok((pm.to_owned(), None)), pm @ Some(domain::PaymentMethodData::Voucher(_)) => Ok((pm.to_owned(), None)), pm @ Some(domain::PaymentMethodData::Reward) => Ok((pm.to_owned(), None)), @@ -450,6 +465,21 @@ pub async fn add_payment_method_status_update_task( Ok(()) } +#[cfg(feature = "v2")] +#[instrument(skip_all)] +pub async fn retrieve_payment_method_with_token( + _state: &SessionState, + _merchant_key_store: &domain::MerchantKeyStore, + _token_data: &storage::PaymentTokenData, + _payment_intent: &PaymentIntent, + _card_token_data: Option<&domain::CardToken>, + _customer: &Option, + _storage_scheme: common_enums::enums::MerchantStorageScheme, +) -> RouterResult { + todo!() +} + +#[cfg(feature = "v1")] #[instrument(skip_all)] pub async fn retrieve_payment_method_with_token( state: &SessionState, @@ -562,7 +592,7 @@ pub async fn retrieve_payment_method_with_token( } storage::PaymentTokenData::AuthBankDebit(auth_token) => { - core_pm_auth::retrieve_payment_method_from_auth_service( + pm_auth::retrieve_payment_method_from_auth_service( state, merchant_key_store, auth_token, diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index cee4e9d1dd22..ccd64f066dfe 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -3626,6 +3626,10 @@ pub async fn list_payment_methods( }); } let currency = payment_intent.as_ref().and_then(|pi| pi.currency); + let skip_external_tax_calculation = payment_intent + .as_ref() + .and_then(|intent| intent.skip_external_tax_calculation) + .unwrap_or(false); let request_external_three_ds_authentication = payment_intent .as_ref() .and_then(|intent| intent.request_external_three_ds_authentication) @@ -3652,23 +3656,33 @@ pub async fn list_payment_methods( api_surcharge_decision_configs::MerchantSurchargeConfigs::default() }; - let collect_shipping_details_from_wallets = business_profile - .as_ref() - .and_then(|business_profile| { - business_profile.always_collect_shipping_details_from_wallet_connector - }) - .or(business_profile.as_ref().and_then(|business_profile| { - business_profile.collect_shipping_details_from_wallet_connector - })); + let collect_shipping_details_from_wallets = + business_profile.as_ref().and_then(|business_profile| { + if business_profile + .always_collect_shipping_details_from_wallet_connector + .unwrap_or(false) + { + business_profile.always_collect_shipping_details_from_wallet_connector + } else { + business_profile.collect_shipping_details_from_wallet_connector + } + }); - let collect_billing_details_from_wallets = business_profile - .as_ref() - .and_then(|business_profile| { - business_profile.always_collect_billing_details_from_wallet_connector - }) - .or(business_profile.as_ref().and_then(|business_profile| { - business_profile.collect_billing_details_from_wallet_connector - })); + let collect_billing_details_from_wallets = + business_profile.as_ref().and_then(|business_profile| { + if business_profile + .always_collect_billing_details_from_wallet_connector + .unwrap_or(false) + { + business_profile.always_collect_billing_details_from_wallet_connector + } else { + business_profile.collect_billing_details_from_wallet_connector + } + }); + + let is_tax_connector_enabled = business_profile.as_ref().map_or(false, |business_profile| { + business_profile.get_is_tax_connector_enabled() + }); Ok(services::ApplicationResponse::Json( api::PaymentMethodListResponse { @@ -3710,6 +3724,7 @@ pub async fn list_payment_methods( request_external_three_ds_authentication, collect_shipping_details_from_wallets, collect_billing_details_from_wallets, + is_tax_calculation_enabled: is_tax_connector_enabled && !skip_external_tax_calculation, }, )) } @@ -4643,7 +4658,7 @@ async fn perform_surcharge_ops( state .store .find_payment_attempt_by_payment_id_merchant_id_attempt_id( - &payment_intent.payment_id, + payment_intent.get_id(), merchant_account.get_id(), &payment_intent.active_attempt.get_id(), merchant_account.storage_scheme, diff --git a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs index 477f025c7850..eaf100a0cb6e 100644 --- a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs +++ b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs @@ -27,7 +27,7 @@ use crate::{ errors::{self, ConditionalConfigError as ConfigError}, payments::{ conditional_configs::ConditionalConfigResult, routing::make_dsl_input_for_surcharge, - types, PaymentData, + types, }, }, db::StorageInterface, @@ -88,11 +88,10 @@ impl SurchargeSource { ) }) .transpose()? - .map(|surcharge_details| { + .inspect(|surcharge_details| { let (surcharge_metadata, surcharge_key) = surcharge_metadata_and_key; surcharge_metadata .insert_surcharge_details(surcharge_key, surcharge_details.clone()); - surcharge_details })) } Self::Predetermined(request_surcharge_details) => Ok(Some( @@ -221,19 +220,17 @@ pub async fn perform_surcharge_decision_management_for_payment_method_list( Ok((surcharge_metadata, merchant_surcharge_configs)) } -pub async fn perform_surcharge_decision_management_for_session_flow( +pub async fn perform_surcharge_decision_management_for_session_flow( state: &SessionState, algorithm_ref: routing::RoutingAlgorithmRef, - payment_data: &mut PaymentData, + payment_attempt: &storage::PaymentAttempt, + payment_intent: &storage::PaymentIntent, + billing_address: Option, payment_method_type_list: &Vec, -) -> ConditionalConfigResult -where - O: Send + Clone, -{ - let mut surcharge_metadata = - types::SurchargeMetadata::new(payment_data.payment_attempt.attempt_id.clone()); +) -> ConditionalConfigResult { + let mut surcharge_metadata = types::SurchargeMetadata::new(payment_attempt.attempt_id.clone()); let surcharge_source = match ( - payment_data.payment_attempt.get_surcharge_details(), + payment_attempt.get_surcharge_details(), algorithm_ref.surcharge_config_algo_id, ) { (Some(request_surcharge_details), _) => { @@ -242,7 +239,7 @@ where (None, Some(algorithm_id)) => { let cached_algo = ensure_algorithm_cached( &*state.store, - &payment_data.payment_attempt.merchant_id, + &payment_attempt.merchant_id, algorithm_id.as_str(), ) .await?; @@ -251,19 +248,16 @@ where } (None, None) => return Ok(surcharge_metadata), }; - let mut backend_input = make_dsl_input_for_surcharge( - &payment_data.payment_attempt, - &payment_data.payment_intent, - payment_data.address.get_payment_method_billing().cloned(), - ) - .change_context(ConfigError::InputConstructionError)?; + let mut backend_input = + make_dsl_input_for_surcharge(payment_attempt, payment_intent, billing_address) + .change_context(ConfigError::InputConstructionError)?; for payment_method_type in payment_method_type_list { backend_input.payment_method.payment_method_type = Some(*payment_method_type); // in case of session flow, payment_method will always be wallet backend_input.payment_method.payment_method = Some(payment_method_type.to_owned().into()); surcharge_source.generate_surcharge_details_and_populate_surcharge_metadata( &backend_input, - &payment_data.payment_attempt, + payment_attempt, ( &mut surcharge_metadata, types::SurchargeKey::PaymentMethodData( diff --git a/crates/router/src/core/payment_methods/vault.rs b/crates/router/src/core/payment_methods/vault.rs index 54da4308bfb9..75278010ceb8 100644 --- a/crates/router/src/core/payment_methods/vault.rs +++ b/crates/router/src/core/payment_methods/vault.rs @@ -279,6 +279,58 @@ impl Vaultable for domain::BankRedirectData { } } +impl Vaultable for domain::BankDebitData { + fn get_value1( + &self, + _customer_id: Option, + ) -> CustomResult { + let value1 = domain::TokenizedBankDebitValue1 { + data: self.to_owned(), + }; + + value1 + .encode_to_string_of_json() + .change_context(errors::VaultError::RequestEncodingFailed) + .attach_printable("Failed to encode bank debit data") + } + + fn get_value2( + &self, + customer_id: Option, + ) -> CustomResult { + let value2 = domain::TokenizedBankDebitValue2 { customer_id }; + + value2 + .encode_to_string_of_json() + .change_context(errors::VaultError::RequestEncodingFailed) + .attach_printable("Failed to encode bank debit supplementary data") + } + + fn from_values( + value1: String, + value2: String, + ) -> CustomResult<(Self, SupplementaryVaultData), errors::VaultError> { + let value1: domain::TokenizedBankDebitValue1 = value1 + .parse_struct("TokenizedBankDebitValue1") + .change_context(errors::VaultError::ResponseDeserializationFailed) + .attach_printable("Could not deserialize into bank debit data")?; + + let value2: domain::TokenizedBankDebitValue2 = value2 + .parse_struct("TokenizedBankDebitValue2") + .change_context(errors::VaultError::ResponseDeserializationFailed) + .attach_printable("Could not deserialize into supplementary bank debit data")?; + + let bank_transfer_data = value1.data; + + let supp_data = SupplementaryVaultData { + customer_id: value2.customer_id, + payment_method_id: None, + }; + + Ok((bank_transfer_data, supp_data)) + } +} + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(tag = "type", content = "value", rename_all = "snake_case")] pub enum VaultPaymentMethod { @@ -286,6 +338,7 @@ pub enum VaultPaymentMethod { Wallet(String), BankTransfer(String), BankRedirect(String), + BankDebit(String), } impl Vaultable for domain::PaymentMethodData { @@ -302,6 +355,9 @@ impl Vaultable for domain::PaymentMethodData { Self::BankRedirect(bank_redirect) => { VaultPaymentMethod::BankRedirect(bank_redirect.get_value1(customer_id)?) } + Self::BankDebit(bank_debit) => { + VaultPaymentMethod::BankDebit(bank_debit.get_value1(customer_id)?) + } _ => Err(errors::VaultError::PaymentMethodNotSupported) .attach_printable("Payment method not supported")?, }; @@ -325,6 +381,9 @@ impl Vaultable for domain::PaymentMethodData { Self::BankRedirect(bank_redirect) => { VaultPaymentMethod::BankRedirect(bank_redirect.get_value2(customer_id)?) } + Self::BankDebit(bank_debit) => { + VaultPaymentMethod::BankDebit(bank_debit.get_value2(customer_id)?) + } _ => Err(errors::VaultError::PaymentMethodNotSupported) .attach_printable("Payment method not supported")?, }; @@ -374,6 +433,10 @@ impl Vaultable for domain::PaymentMethodData { domain::BankRedirectData::from_values(mvalue1, mvalue2)?; Ok((Self::BankRedirect(bank_redirect), supp_data)) } + (VaultPaymentMethod::BankDebit(mvalue1), VaultPaymentMethod::BankDebit(mvalue2)) => { + let (bank_debit, supp_data) = domain::BankDebitData::from_values(mvalue1, mvalue2)?; + Ok((Self::BankDebit(bank_debit), supp_data)) + } _ => Err(errors::VaultError::PaymentMethodNotSupported) .attach_printable("Payment method not supported"), diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 8778079e7281..3ff5e649f20b 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -52,16 +52,16 @@ use scheduler::utils as pt_utils; use strum::IntoEnumIterator; use time; +#[cfg(feature = "v1")] pub use self::operations::{ PaymentApprove, PaymentCancel, PaymentCapture, PaymentConfirm, PaymentCreate, - PaymentIncrementalAuthorization, PaymentReject, PaymentResponse, PaymentSession, PaymentStatus, - PaymentUpdate, + PaymentIncrementalAuthorization, PaymentReject, PaymentSession, PaymentSessionUpdate, + PaymentStatus, PaymentUpdate, }; use self::{ conditional_configs::perform_decision_management, flows::{ConstructFlowSpecificData, Feature}, - helpers::get_key_params_for_surcharge_details, - operations::{payment_complete_authorize, BoxedOperation, Operation}, + operations::{BoxedOperation, Operation, PaymentResponse}, routing::{self as self_routing, SessionFlowRoutingInput}, }; use super::{ @@ -75,7 +75,7 @@ use crate::{ core::{ errors::{self, CustomResult, RouterResponse, RouterResult}, payment_methods::cards, - utils, + payouts, routing as core_routing, utils, }, db::StorageInterface, logger, @@ -102,7 +102,7 @@ use crate::{ #[allow(clippy::too_many_arguments, clippy::type_complexity)] #[instrument(skip_all, fields(payment_id, merchant_id))] -pub async fn payments_operation_core( +pub async fn payments_operation_core( state: &SessionState, req_state: ReqState, merchant_account: domain::MerchantAccount, @@ -114,20 +114,15 @@ pub async fn payments_operation_core( auth_flow: services::AuthFlow, eligible_connectors: Option>, header_payload: HeaderPayload, -) -> RouterResult<( - PaymentData, - Req, - Option, - Option, - Option, -)> +) -> RouterResult<(D, Req, Option, Option, Option)> where F: Send + Clone + Sync, Req: Authenticate + Clone, - Op: Operation + Send + Sync, + Op: Operation + Send + Sync, + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, // To create connector flow specific interface data - PaymentData: ConstructFlowSpecificData, + D: ConstructFlowSpecificData, RouterData: Feature, // To construct connector flow specific api @@ -135,17 +130,17 @@ where services::api::ConnectorIntegration, // To perform router related operation for PaymentResponse - PaymentResponse: Operation, + PaymentResponse: Operation, FData: Send + Sync + Clone, { - let operation: BoxedOperation<'_, F, Req> = Box::new(operation); + let operation: BoxedOperation<'_, F, Req, D> = Box::new(operation); tracing::Span::current().record("merchant_id", merchant_account.get_id().get_string_repr()); let (operation, validate_result) = operation .to_validate_request()? .validate_request(&req, &merchant_account)?; - tracing::Span::current().record("payment_id", &format!("{}", validate_result.payment_id)); + tracing::Span::current().record("payment_id", format!("{}", validate_result.payment_id)); let operations::GetTrackerResponse { operation, @@ -168,7 +163,7 @@ where utils::validate_profile_id_from_auth_layer( profile_id_from_auth_layer, - &payment_data.payment_intent, + &payment_data.get_payment_intent().clone(), )?; let (operation, customer) = operation @@ -184,13 +179,10 @@ where .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound) .attach_printable("Failed while fetching/creating customer")?; - call_decision_manager( - state, - &merchant_account, - &business_profile, - &mut payment_data, - ) - .await?; + let authentication_type = + call_decision_manager(state, &merchant_account, &business_profile, &payment_data).await?; + + payment_data.set_authentication_type_in_attempt(authentication_type); let connector = get_connector_choice( &operation, @@ -267,23 +259,36 @@ where mandate_type, ) .await?; + + operation + .to_domain()? + .payments_dynamic_tax_calculation( + state, + &mut payment_data, + &connector_details, + &business_profile, + &key_store, + &merchant_account, + ) + .await?; + if should_continue_transaction { #[cfg(feature = "frm")] match ( should_continue_capture, - payment_data.payment_attempt.capture_method, + payment_data.get_payment_attempt().capture_method, ) { (false, Some(storage_enums::CaptureMethod::Automatic)) | (false, Some(storage_enums::CaptureMethod::Scheduled)) => { if let Some(info) = &mut frm_info { if let Some(frm_data) = &mut info.frm_data { frm_data.fraud_check.payment_capture_method = - payment_data.payment_attempt.capture_method; + payment_data.get_payment_attempt().capture_method; } } - payment_data.payment_attempt.capture_method = - Some(storage_enums::CaptureMethod::Manual); - logger::debug!("payment_id : {:?} capture method has been changed to manual, since it has configured Post FRM flow",payment_data.payment_attempt.payment_id); + payment_data + .set_capture_method_in_attempt(storage_enums::CaptureMethod::Manual); + logger::debug!("payment_id : {:?} capture method has been changed to manual, since it has configured Post FRM flow",payment_data.get_payment_attempt().payment_id); } _ => (), }; @@ -506,7 +511,9 @@ where state, &merchant_account, &business_profile, - &mut payment_data, + payment_data.get_payment_attempt(), + payment_data.get_payment_intent(), + payment_data.get_billing_address(), &connectors, ) .await?; @@ -528,6 +535,7 @@ where #[cfg(feature = "frm")] if let Some(fraud_info) = &mut frm_info { + #[cfg(feature = "v1")] Box::pin(frm_core::post_payment_frm_core( state, req_state, @@ -566,15 +574,17 @@ where .await?; } + let payment_intent_status = payment_data.get_payment_intent().status; + payment_data - .payment_attempt + .get_payment_attempt() .payment_token .as_ref() - .zip(payment_data.payment_attempt.payment_method) + .zip(payment_data.get_payment_attempt().payment_method) .map(ParentPaymentMethodToken::create_key_for_token) .async_map(|key_for_hyperswitch_token| async move { if key_for_hyperswitch_token - .should_delete_payment_method_token(payment_data.payment_intent.status) + .should_delete_payment_method_token(payment_intent_status) { let _ = key_for_hyperswitch_token.delete(state).await; } @@ -604,9 +614,9 @@ where .to_domain()? .store_extended_card_info_temporarily( state, - &payment_data.payment_intent.payment_id, + payment_data.get_payment_intent().get_id(), &business_profile, - &payment_data.payment_method_data, + payment_data.get_payment_method_data(), ) .await?; @@ -633,16 +643,28 @@ where } #[instrument(skip_all)] -pub async fn call_decision_manager( +#[cfg(feature = "v1")] +pub async fn call_decision_manager( state: &SessionState, merchant_account: &domain::MerchantAccount, _business_profile: &domain::BusinessProfile, - payment_data: &mut PaymentData, -) -> RouterResult<()> + payment_data: &D, +) -> RouterResult> where - O: Send + Clone, + F: Clone, + D: OperationSessionGetters, { - #[cfg(feature = "v1")] + let setup_mandate = payment_data.get_setup_mandate(); + let payment_method_data = payment_data.get_payment_method_data(); + let payment_dsl_data = core_routing::PaymentsDslInput::new( + setup_mandate, + payment_data.get_payment_attempt(), + payment_data.get_payment_intent(), + payment_method_data, + payment_data.get_address(), + payment_data.get_recurring_details(), + payment_data.get_currency(), + ); let algorithm_ref: api::routing::RoutingAlgorithmRef = merchant_account .routing_algorithm .clone() @@ -652,25 +674,36 @@ where .attach_printable("Could not decode the routing algorithm")? .unwrap_or_default(); - // TODO: Move to business profile surcharge column - #[cfg(feature = "v2")] - let algorithm_ref: api::routing::RoutingAlgorithmRef = todo!(); - let output = perform_decision_management( state, algorithm_ref, merchant_account.get_id(), - payment_data, + &payment_dsl_data, ) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Could not decode the conditional config")?; - payment_data.payment_attempt.authentication_type = payment_data + Ok(payment_dsl_data .payment_attempt .authentication_type .or(output.override_3ds.map(ForeignInto::foreign_into)) - .or(Some(storage_enums::AuthenticationType::NoThreeDs)); - Ok(()) + .or(Some(storage_enums::AuthenticationType::NoThreeDs))) +} + +// TODO: Move to business profile surcharge column +#[instrument(skip_all)] +#[cfg(feature = "v2")] +pub async fn call_decision_manager( + state: &SessionState, + merchant_account: &domain::MerchantAccount, + _business_profile: &domain::BusinessProfile, + payment_data: &D, +) -> RouterResult> +where + F: Clone, + D: OperationSessionGetters, +{ + todo!() } #[instrument(skip_all)] @@ -697,7 +730,7 @@ where let raw_card_key = payment_data .payment_method_data .as_ref() - .and_then(get_key_params_for_surcharge_details) + .and_then(helpers::get_key_params_for_surcharge_details) .map(|(payment_method, payment_method_type, card_network)| { types::SurchargeKey::PaymentMethodData( payment_method, @@ -756,23 +789,21 @@ pub fn get_connector_data( } #[instrument(skip_all)] -pub async fn call_surcharge_decision_management_for_session_flow( +pub async fn call_surcharge_decision_management_for_session_flow( state: &SessionState, _merchant_account: &domain::MerchantAccount, _business_profile: &domain::BusinessProfile, - payment_data: &mut PaymentData, + payment_attempt: &storage::PaymentAttempt, + payment_intent: &storage::PaymentIntent, + billing_address: Option, session_connector_data: &[api::SessionConnectorData], -) -> RouterResult> -where - O: Send + Clone + Sync, -{ - if let Some(surcharge_amount) = payment_data.payment_attempt.surcharge_amount { - let tax_on_surcharge_amount = payment_data.payment_attempt.tax_amount.unwrap_or_default(); - let final_amount = - payment_data.payment_attempt.amount + surcharge_amount + tax_on_surcharge_amount; +) -> RouterResult> { + if let Some(surcharge_amount) = payment_attempt.surcharge_amount { + let tax_on_surcharge_amount = payment_attempt.tax_amount.unwrap_or_default(); + let final_amount = payment_attempt.amount + surcharge_amount + tax_on_surcharge_amount; Ok(Some(api::SessionSurchargeDetails::PreDetermined( types::SurchargeDetails { - original_amount: payment_data.payment_attempt.amount, + original_amount: payment_attempt.amount, surcharge: Surcharge::Fixed(surcharge_amount), tax_on_surcharge: None, surcharge_amount, @@ -804,7 +835,9 @@ where surcharge_decision_configs::perform_surcharge_decision_management_for_session_flow( state, algorithm_ref, - payment_data, + payment_attempt, + payment_intent, + billing_address, &payment_method_type_list, ) .await @@ -819,7 +852,7 @@ where } } #[allow(clippy::too_many_arguments)] -pub async fn payments_core( +pub async fn payments_core( state: SessionState, req_state: ReqState, merchant_account: domain::MerchantAccount, @@ -835,11 +868,12 @@ pub async fn payments_core( where F: Send + Clone + Sync, FData: Send + Sync + Clone, - Op: Operation + Send + Sync + Clone, + Op: Operation + Send + Sync + Clone, Req: Debug + Authenticate + Clone, - Res: transformers::ToResponse, Op>, + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, + Res: transformers::ToResponse, // To create connector flow specific interface data - PaymentData: ConstructFlowSpecificData, + D: ConstructFlowSpecificData, RouterData: Feature, // To construct connector flow specific api @@ -847,7 +881,7 @@ where services::api::ConnectorIntegration, // To perform router related operation for PaymentResponse - PaymentResponse: Operation, + PaymentResponse: Operation, { let eligible_routable_connectors = eligible_connectors.map(|connectors| { connectors @@ -856,7 +890,7 @@ where .collect() }); let (payment_data, _req, customer, connector_http_status_code, external_latency) = - payments_operation_core::<_, _, _, _>( + payments_operation_core::<_, _, _, _, _>( &state, req_state, merchant_account, @@ -997,6 +1031,7 @@ pub trait PaymentRedirectFlow: Sync { #[derive(Clone, Debug)] pub struct PaymentRedirectCompleteAuthorize; +#[cfg(feature = "v1")] #[async_trait::async_trait] impl PaymentRedirectFlow for PaymentRedirectCompleteAuthorize { type PaymentFlowResponse = router_types::RedirectPaymentFlowResponse; @@ -1033,13 +1068,14 @@ impl PaymentRedirectFlow for PaymentRedirectCompleteAuthorize { _, _, _, + _, >( state.clone(), req_state, merchant_account, None, merchant_key_store.clone(), - payment_complete_authorize::CompleteAuthorize, + operations::payment_complete_authorize::CompleteAuthorize, payment_confirm_req, services::api::AuthFlow::Merchant, connector_action, @@ -1133,6 +1169,7 @@ impl PaymentRedirectFlow for PaymentRedirectCompleteAuthorize { #[derive(Clone, Debug)] pub struct PaymentRedirectSync; +#[cfg(feature = "v1")] #[async_trait::async_trait] impl PaymentRedirectFlow for PaymentRedirectSync { type PaymentFlowResponse = router_types::RedirectPaymentFlowResponse; @@ -1167,19 +1204,21 @@ impl PaymentRedirectFlow for PaymentRedirectSync { expand_attempts: None, expand_captures: None, }; - let response = Box::pin(payments_core::( - state.clone(), - req_state, - merchant_account, - None, - merchant_key_store.clone(), - PaymentStatus, - payment_sync_req, - services::api::AuthFlow::Merchant, - connector_action, - None, - HeaderPayload::default(), - )) + let response = Box::pin( + payments_core::( + state.clone(), + req_state, + merchant_account, + None, + merchant_key_store.clone(), + PaymentStatus, + payment_sync_req, + services::api::AuthFlow::Merchant, + connector_action, + None, + HeaderPayload::default(), + ), + ) .await?; let payments_response = match response { services::ApplicationResponse::Json(response) => Ok(response), @@ -1227,6 +1266,7 @@ impl PaymentRedirectFlow for PaymentRedirectSync { #[derive(Clone, Debug)] pub struct PaymentAuthenticateCompleteAuthorize; +#[cfg(feature = "v1")] #[async_trait::async_trait] impl PaymentRedirectFlow for PaymentAuthenticateCompleteAuthorize { type PaymentFlowResponse = router_types::AuthenticatePaymentFlowResponse; @@ -1327,6 +1367,7 @@ impl PaymentRedirectFlow for PaymentAuthenticateCompleteAuthorize { _, _, _, + _, >( state.clone(), req_state, @@ -1358,19 +1399,21 @@ impl PaymentRedirectFlow for PaymentAuthenticateCompleteAuthorize { expand_attempts: None, expand_captures: None, }; - Box::pin(payments_core::( - state.clone(), - req_state, - merchant_account.clone(), - None, - merchant_key_store.clone(), - PaymentStatus, - payment_sync_req, - services::api::AuthFlow::Merchant, - connector_action, - None, - HeaderPayload::default(), - )) + Box::pin( + payments_core::( + state.clone(), + req_state, + merchant_account.clone(), + None, + merchant_key_store.clone(), + PaymentStatus, + payment_sync_req, + services::api::AuthFlow::Merchant, + connector_action, + None, + HeaderPayload::default(), + ), + ) .await? }; let payments_response = match response { @@ -1472,14 +1515,14 @@ impl PaymentRedirectFlow for PaymentAuthenticateCompleteAuthorize { #[allow(clippy::too_many_arguments)] #[instrument(skip_all)] -pub async fn call_connector_service( +pub async fn call_connector_service( state: &SessionState, req_state: ReqState, merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, connector: api::ConnectorData, - operation: &BoxedOperation<'_, F, ApiRequest>, - payment_data: &mut PaymentData, + operation: &BoxedOperation<'_, F, ApiRequest, D>, + payment_data: &mut D, customer: &Option, call_connector_action: CallConnectorAction, validate_result: &operations::ValidateResult, @@ -1497,7 +1540,8 @@ where RouterDReq: Send + Sync, // To create connector flow specific interface data - PaymentData: ConstructFlowSpecificData, + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, + D: ConstructFlowSpecificData, RouterData: Feature + Send, // To construct connector flow specific api dyn api::Connector: @@ -1516,9 +1560,12 @@ where ) .await?; - if payment_data.payment_attempt.merchant_connector_id.is_none() { - payment_data.payment_attempt.merchant_connector_id = - merchant_connector_account.get_mca_id(); + if payment_data + .get_payment_attempt() + .merchant_connector_id + .is_none() + { + payment_data.set_merchant_connector_id_in_attempt(merchant_connector_account.get_mca_id()); } operation @@ -1552,20 +1599,22 @@ where ) .await?; - let merchant_recipient_data = - if let Some(true) = payment_data.payment_intent.is_payment_processor_token_flow { - None - } else { - payment_data - .get_merchant_recipient_data( - state, - merchant_account, - key_store, - &merchant_connector_account, - &connector, - ) - .await? - }; + let merchant_recipient_data = if let Some(true) = payment_data + .get_payment_intent() + .is_payment_processor_token_flow + { + None + } else { + payment_data + .get_merchant_recipient_data( + state, + merchant_account, + key_store, + &merchant_connector_account, + &connector, + ) + .await? + }; let mut router_data = payment_data .construct_router_data( @@ -1584,7 +1633,7 @@ where state, &connector, merchant_account, - payment_data.creds_identifier.as_ref(), + payment_data.get_creds_identifier(), ) .await?; @@ -1603,11 +1652,11 @@ where | TokenizationAction::TokenizeInConnectorAndApplepayPreDecrypt( payment_processing_details, ) => { - let apple_pay_data = match payment_data.payment_method_data.clone() { + let apple_pay_data = match payment_data.get_payment_method_data() { Some(domain::PaymentMethodData::Wallet(domain::WalletData::ApplePay( wallet_data, ))) => Some( - ApplePayData::token_json(domain::WalletData::ApplePay(wallet_data)) + ApplePayData::token_json(domain::WalletData::ApplePay(wallet_data.clone())) .change_context(errors::ApiErrorResponse::InternalServerError)? .decrypt( &payment_processing_details.payment_processing_certificate, @@ -1666,7 +1715,7 @@ where .. }) = router_data.response.to_owned() { - payment_data.sessions_token.push(session_token); + payment_data.push_sessions_token(session_token); }; // In case of authorize flow, pre-task and post-tasks are being called in build request @@ -1685,7 +1734,7 @@ where .to_domain()? .add_task_to_process_tracker( state, - &payment_data.payment_attempt, + payment_data.get_payment_attempt(), validate_result.requeue, schedule_time, ) @@ -1717,7 +1766,7 @@ where // update this in router_data. // This is added because few connector integrations do not update the status, // and rely on previous status set in router_data - router_data.status = payment_data.payment_attempt.status; + router_data.status = payment_data.get_payment_attempt().status; router_data .decide_flows( state, @@ -1802,17 +1851,18 @@ pub async fn get_merchant_bank_data_for_open_banking_connectors( Ok(final_recipient_data) } -async fn blocklist_guard( +async fn blocklist_guard( state: &SessionState, merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, - operation: &BoxedOperation<'_, F, ApiRequest>, - payment_data: &mut PaymentData, + operation: &BoxedOperation<'_, F, ApiRequest, D>, + payment_data: &mut D, ) -> CustomResult where F: Send + Clone + Sync, + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, { - let merchant_id = &payment_data.payment_attempt.merchant_id; + let merchant_id = &payment_data.get_payment_attempt().merchant_id; let blocklist_enabled_key = merchant_id.get_blocklist_guard_key(); let blocklist_guard_enabled = state .store @@ -1842,24 +1892,25 @@ where } #[allow(clippy::too_many_arguments)] -pub async fn call_multiple_connectors_service( +pub async fn call_multiple_connectors_service( state: &SessionState, merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, connectors: Vec, _operation: &Op, - mut payment_data: PaymentData, + mut payment_data: D, customer: &Option, session_surcharge_details: Option, business_profile: &domain::BusinessProfile, header_payload: HeaderPayload, -) -> RouterResult> +) -> RouterResult where Op: Debug, F: Send + Clone, // To create connector flow specific interface data - PaymentData: ConstructFlowSpecificData, + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, + D: ConstructFlowSpecificData, RouterData: Feature, // To construct connector flow specific api @@ -1877,7 +1928,7 @@ where let merchant_connector_account = construct_profile_id_and_get_mca( state, merchant_account, - &mut payment_data, + &payment_data, &session_connector_data.connector.connector_name.to_string(), session_connector_data .connector @@ -1888,16 +1939,15 @@ where ) .await?; - payment_data.surcharge_details = - session_surcharge_details - .as_ref() - .and_then(|session_surcharge_details| { - session_surcharge_details.fetch_surcharge_details( - &session_connector_data.payment_method_type.into(), - &session_connector_data.payment_method_type, - None, - ) - }); + payment_data.set_surcharge_details(session_surcharge_details.as_ref().and_then( + |session_surcharge_details| { + session_surcharge_details.fetch_surcharge_details( + &session_connector_data.payment_method_type.into(), + &session_connector_data.payment_method_type, + None, + ) + }, + )); let router_data = payment_data .construct_router_data( @@ -1940,7 +1990,7 @@ where session_token, api_models::payments::SessionToken::NoSessionTokenReceived, ) { - payment_data.sessions_token.push(session_token); + payment_data.push_sessions_token(session_token); } } if let Err(connector_error_response) = connector_response.response { @@ -1965,27 +2015,28 @@ where Ok(payment_data) } -pub async fn call_create_connector_customer_if_required( +pub async fn call_create_connector_customer_if_required( state: &SessionState, customer: &Option, merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, merchant_connector_account: &helpers::MerchantConnectorAccountType, - payment_data: &mut PaymentData, + payment_data: &mut D, ) -> RouterResult> where F: Send + Clone + Sync, Req: Send + Sync, // To create connector flow specific interface data - PaymentData: ConstructFlowSpecificData, + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, + D: ConstructFlowSpecificData, RouterData: Feature + Send, // To construct connector flow specific api dyn api::Connector: services::api::ConnectorIntegration, { - let connector_name = payment_data.payment_attempt.connector.clone(); + let connector_name = payment_data.get_payment_attempt().connector.clone(); match connector_name { Some(connector_name) => { @@ -1996,37 +2047,49 @@ where merchant_connector_account.get_mca_id(), )?; - let connector_label = utils::get_connector_label( - payment_data.payment_intent.business_country, - payment_data.payment_intent.business_label.as_ref(), - payment_data.payment_attempt.business_sub_label.as_ref(), - &connector_name, - ); + #[cfg(feature = "v1")] + let label = { + let connector_label = utils::get_connector_label( + payment_data.get_payment_intent().business_country, + payment_data.get_payment_intent().business_label.as_ref(), + payment_data + .get_payment_attempt() + .business_sub_label + .as_ref(), + &connector_name, + ); - let connector_label = if let Some(connector_label) = merchant_connector_account - .get_mca_id() - .map(|mca_id| mca_id.get_string_repr().to_string()) - .or(connector_label) - { - connector_label - } else { - let profile_id = payment_data - .payment_intent - .profile_id - .as_ref() - .get_required_value("profile_id") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("profile_id is not set in payment_intent")?; + if let Some(connector_label) = merchant_connector_account + .get_mca_id() + .map(|mca_id| mca_id.get_string_repr().to_string()) + .or(connector_label) + { + connector_label + } else { + let profile_id = payment_data + .get_payment_intent() + .profile_id + .as_ref() + .get_required_value("profile_id") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("profile_id is not set in payment_intent")?; + + format!("{connector_name}_{}", profile_id.get_string_repr()) + } + }; - format!("{connector_name}_{}", profile_id.get_string_repr()) + #[cfg(feature = "v2")] + let label = { + merchant_connector_account + .get_mca_id() + .get_required_value("merchant_connector_account_id")? + .get_string_repr() + .to_owned() }; let (should_call_connector, existing_connector_customer_id) = customers::should_call_connector_create_customer( - state, - &connector, - customer, - &connector_label, + state, &connector, customer, &label, ); if should_call_connector { @@ -2048,18 +2111,19 @@ where .await?; let customer_update = customers::update_connector_customer_in_customers( - &connector_label, + &label, customer.as_ref(), &connector_customer_id, ) .await; - payment_data.connector_customer_id = connector_customer_id; + payment_data.set_connector_customer_id(connector_customer_id); Ok(customer_update) } else { // Customer already created in previous calls use the same value, no need to update - payment_data.connector_customer_id = - existing_connector_customer_id.map(ToOwned::to_owned); + payment_data.set_connector_customer_id( + existing_connector_customer_id.map(ToOwned::to_owned), + ); Ok(None) } } @@ -2067,16 +2131,17 @@ where } } -async fn complete_preprocessing_steps_if_required( +async fn complete_preprocessing_steps_if_required( state: &SessionState, connector: &api::ConnectorData, - payment_data: &PaymentData, + payment_data: &D, mut router_data: RouterData, - operation: &BoxedOperation<'_, F, Q>, + operation: &BoxedOperation<'_, F, Q, D>, should_continue_payment: bool, ) -> RouterResult<(RouterData, bool)> where F: Send + Clone + Sync, + D: OperationSessionGetters + Send + Sync + Clone, Req: Send + Sync, RouterData: Feature + Send, dyn api::Connector: @@ -2091,13 +2156,17 @@ where return Ok((router_data, should_continue_payment)); } //TODO: For ACH transfers, if preprocessing_step is not required for connectors encountered in future, add the check - let router_data_and_should_continue_payment = match payment_data.payment_method_data.clone() { + let router_data_and_should_continue_payment = match payment_data.get_payment_method_data() { Some(domain::PaymentMethodData::BankTransfer(data)) => match data.deref() { domain::BankTransferData::AchBankTransfer { .. } | domain::BankTransferData::MultibancoBankTransfer { .. } if connector.connector_name == router_types::Connector::Stripe => { - if payment_data.payment_attempt.preprocessing_step_id.is_none() { + if payment_data + .get_payment_attempt() + .preprocessing_step_id + .is_none() + { ( router_data.preprocessing_steps(state, connector).await?, false, @@ -2132,7 +2201,7 @@ where && router_data.auth_type == storage_enums::AuthenticationType::ThreeDs && !matches!( payment_data - .payment_attempt + .get_payment_attempt() .external_three_ds_authentication_attempted, Some(true) ) @@ -2191,7 +2260,7 @@ where _ => { // 3DS validation for paypal cards after verification (authorize call) if connector.connector_name == router_types::Connector::Paypal - && payment_data.payment_attempt.payment_method + && payment_data.get_payment_attempt().payment_method == Some(storage_enums::PaymentMethod::Card) && matches!(format!("{operation:?}").as_str(), "CompleteAuthorize") { @@ -2209,24 +2278,25 @@ where } #[allow(clippy::too_many_arguments)] -async fn complete_postprocessing_steps_if_required( +async fn complete_postprocessing_steps_if_required( state: &SessionState, merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, customer: &Option, merchant_conn_account: &helpers::MerchantConnectorAccountType, connector: &api::ConnectorData, - payment_data: &mut PaymentData, - _operation: &BoxedOperation<'_, F, Q>, + payment_data: &mut D, + _operation: &BoxedOperation<'_, F, Q, D>, ) -> RouterResult> where F: Send + Clone + Sync, RouterDReq: Send + Sync, + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, RouterData: Feature + Send, dyn api::Connector: services::api::ConnectorIntegration, - PaymentData: ConstructFlowSpecificData, + D: ConstructFlowSpecificData, { let mut router_data = payment_data .construct_router_data( @@ -2240,7 +2310,7 @@ where ) .await?; - match payment_data.payment_method_data.clone() { + match payment_data.get_payment_method_data() { Some(domain::PaymentMethodData::OpenBanking(domain::OpenBankingData::OpenBankingPIS { .. })) => { @@ -2259,7 +2329,7 @@ where None }; if let Some(t) = token { - payment_data.sessions_token.push(t); + payment_data.push_sessions_token(t); } Ok(router_data) @@ -2276,10 +2346,10 @@ pub fn is_preprocessing_required_for_wallets(connector_name: String) -> bool { } #[instrument(skip_all)] -pub async fn construct_profile_id_and_get_mca<'a, F>( +pub async fn construct_profile_id_and_get_mca<'a, F, D>( state: &'a SessionState, merchant_account: &domain::MerchantAccount, - payment_data: &mut PaymentData, + payment_data: &D, connector_name: &str, merchant_connector_id: Option<&id_type::MerchantConnectorAccountId>, key_store: &domain::MerchantKeyStore, @@ -2287,9 +2357,10 @@ pub async fn construct_profile_id_and_get_mca<'a, F>( ) -> RouterResult where F: Clone, + D: OperationSessionGetters + Send + Sync + Clone, { let profile_id = payment_data - .payment_intent + .get_payment_intent() .profile_id .as_ref() .get_required_value("profile_id") @@ -2300,7 +2371,7 @@ where let merchant_connector_account = helpers::get_merchant_connector_account( state, merchant_account.get_id(), - payment_data.creds_identifier.to_owned(), + payment_data.get_creds_identifier(), key_store, &profile_id, connector_name, @@ -2467,7 +2538,7 @@ async fn decide_payment_method_tokenize_action( state: &SessionState, connector_name: &str, payment_method: &storage::enums::PaymentMethod, - pm_parent_token: Option<&String>, + pm_parent_token: Option<&str>, is_connector_tokenization_enabled: bool, apple_pay_flow: Option, ) -> RouterResult { @@ -2535,23 +2606,24 @@ pub enum TokenizationAction { } #[allow(clippy::too_many_arguments)] -pub async fn get_connector_tokenization_action_when_confirm_true( +pub async fn get_connector_tokenization_action_when_confirm_true( state: &SessionState, - operation: &BoxedOperation<'_, F, Req>, - payment_data: &mut PaymentData, + operation: &BoxedOperation<'_, F, Req, D>, + payment_data: &mut D, validate_result: &operations::ValidateResult, merchant_connector_account: &helpers::MerchantConnectorAccountType, merchant_key_store: &domain::MerchantKeyStore, customer: &Option, business_profile: Option<&domain::BusinessProfile>, -) -> RouterResult<(PaymentData, TokenizationAction)> +) -> RouterResult<(D, TokenizationAction)> where F: Send + Clone, + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, { - let connector = payment_data.payment_attempt.connector.to_owned(); + let connector = payment_data.get_payment_attempt().connector.to_owned(); let is_mandate = payment_data - .mandate_id + .get_mandate_id() .as_ref() .and_then(|inner| inner.mandate_reference_id.as_ref()) .map(|mandate_reference| match mandate_reference { @@ -2567,10 +2639,10 @@ where ), Some(connector) if is_operation_confirm(&operation) => { let payment_method = &payment_data - .payment_attempt + .get_payment_attempt() .payment_method .get_required_value("payment_method")?; - let payment_method_type = &payment_data.payment_attempt.payment_method_type; + let payment_method_type = &payment_data.get_payment_attempt().payment_method_type; let apple_pay_flow = decide_apple_pay_flow(state, payment_method_type, Some(merchant_connector_account)); @@ -2586,15 +2658,15 @@ where add_apple_pay_flow_metrics( &apple_pay_flow, - payment_data.payment_attempt.connector.clone(), - payment_data.payment_attempt.merchant_id.clone(), + payment_data.get_payment_attempt().connector.clone(), + payment_data.get_payment_attempt().merchant_id.clone(), ); let payment_method_action = decide_payment_method_tokenize_action( state, &connector, payment_method, - payment_data.token.as_ref(), + payment_data.get_token(), is_connector_tokenization_enabled, apple_pay_flow, ) @@ -2613,8 +2685,8 @@ where business_profile, ) .await?; - payment_data.payment_method_data = payment_method_data; - payment_data.payment_attempt.payment_method_id = pm_id; + payment_data.set_payment_method_data(payment_method_data); + payment_data.set_payment_method_id_in_attempt(pm_id); TokenizationAction::SkipConnectorTokenization } @@ -2633,12 +2705,12 @@ where ) .await?; - payment_data.payment_method_data = payment_method_data; - payment_data.payment_attempt.payment_method_id = pm_id; + payment_data.set_payment_method_data(payment_method_data); + payment_data.set_payment_method_id_in_attempt(pm_id); TokenizationAction::TokenizeInConnector } TokenizationAction::ConnectorToken(token) => { - payment_data.pm_token = Some(token); + payment_data.set_pm_token(token); TokenizationAction::SkipConnectorTokenization } TokenizationAction::SkipConnectorTokenization => { @@ -2664,21 +2736,22 @@ where Ok(payment_data_and_tokenization_action) } -pub async fn tokenize_in_router_when_confirm_false_or_external_authentication( +pub async fn tokenize_in_router_when_confirm_false_or_external_authentication( state: &SessionState, - operation: &BoxedOperation<'_, F, Req>, - payment_data: &mut PaymentData, + operation: &BoxedOperation<'_, F, Req, D>, + payment_data: &mut D, validate_result: &operations::ValidateResult, merchant_key_store: &domain::MerchantKeyStore, customer: &Option, business_profile: Option<&domain::BusinessProfile>, -) -> RouterResult> +) -> RouterResult where F: Send + Clone, + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, { // On confirm is false and only router related let is_external_authentication_requested = payment_data - .payment_intent + .get_payment_intent() .request_external_three_ds_authentication; let payment_data = if !is_operation_confirm(operation) || is_external_authentication_requested == Some(true) { @@ -2693,9 +2766,9 @@ where business_profile, ) .await?; - payment_data.payment_method_data = payment_method_data; + payment_data.set_payment_method_data(payment_method_data); if let Some(payment_method_id) = pm_id { - payment_data.payment_attempt.payment_method_id = Some(payment_method_id); + payment_data.set_payment_method_id_in_attempt(Some(payment_method_id)); } payment_data } else { @@ -2753,6 +2826,13 @@ where pub authentication: Option, pub recurring_details: Option, pub poll_config: Option, + pub tax_data: Option, +} + +#[derive(Clone, serde::Serialize, Debug)] +pub struct TaxData { + pub shipping_details: api_models::payments::Address, + pub payment_method_type: enums::PaymentMethodType, } #[derive(Clone, serde::Serialize, Debug)] @@ -2805,15 +2885,18 @@ impl CustomerDetailsExt for CustomerDetails { } } +#[cfg(feature = "v1")] pub fn if_not_create_change_operation<'a, Op, F>( status: storage_enums::IntentStatus, confirm: Option, current: &'a Op, -) -> BoxedOperation<'_, F, api::PaymentsRequest> +) -> BoxedOperation<'_, F, api::PaymentsRequest, PaymentData> where F: Send + Clone, - Op: Operation + Send + Sync, - &'a Op: Operation, + Op: Operation> + Send + Sync, + &'a Op: Operation>, + PaymentStatus: Operation>, + &'a PaymentStatus: Operation>, { if confirm.unwrap_or(false) { Box::new(PaymentConfirm) @@ -2827,15 +2910,16 @@ where } } +#[cfg(feature = "v1")] pub fn is_confirm<'a, F: Clone + Send, R, Op>( operation: &'a Op, confirm: Option, -) -> BoxedOperation<'_, F, R> +) -> BoxedOperation<'_, F, R, PaymentData> where - PaymentConfirm: Operation, - &'a PaymentConfirm: Operation, - Op: Operation + Send + Sync, - &'a Op: Operation, + PaymentConfirm: Operation>, + &'a PaymentConfirm: Operation>, + Op: Operation> + Send + Sync, + &'a Op: Operation>, { if confirm.unwrap_or(false) { Box::new(&PaymentConfirm) @@ -2844,43 +2928,46 @@ where } } -pub fn should_call_connector( - operation: &Op, - payment_data: &PaymentData, -) -> bool { +pub fn should_call_connector(operation: &Op, payment_data: &D) -> bool +where + D: OperationSessionGetters + Send + Sync + Clone, +{ match format!("{operation:?}").as_str() { "PaymentConfirm" => true, "PaymentStart" => { !matches!( - payment_data.payment_intent.status, + payment_data.get_payment_intent().status, storage_enums::IntentStatus::Failed | storage_enums::IntentStatus::Succeeded - ) && payment_data.payment_attempt.authentication_data.is_none() + ) && payment_data + .get_payment_attempt() + .authentication_data + .is_none() } "PaymentStatus" => { matches!( - payment_data.payment_intent.status, + payment_data.get_payment_intent().status, storage_enums::IntentStatus::Processing | storage_enums::IntentStatus::RequiresCustomerAction | storage_enums::IntentStatus::RequiresMerchantAction | storage_enums::IntentStatus::RequiresCapture | storage_enums::IntentStatus::PartiallyCapturedAndCapturable - ) && payment_data.force_sync.unwrap_or(false) + ) && payment_data.get_force_sync().unwrap_or(false) } "PaymentCancel" => matches!( - payment_data.payment_intent.status, + payment_data.get_payment_intent().status, storage_enums::IntentStatus::RequiresCapture | storage_enums::IntentStatus::PartiallyCapturedAndCapturable ), "PaymentCapture" => { matches!( - payment_data.payment_intent.status, + payment_data.get_payment_intent().status, storage_enums::IntentStatus::RequiresCapture | storage_enums::IntentStatus::PartiallyCapturedAndCapturable ) || (matches!( - payment_data.payment_intent.status, + payment_data.get_payment_intent().status, storage_enums::IntentStatus::Processing ) && matches!( - payment_data.payment_attempt.capture_method, + payment_data.get_payment_attempt().capture_method, Some(storage_enums::CaptureMethod::ManualMultiple) )) } @@ -2888,8 +2975,9 @@ pub fn should_call_connector( "PaymentApprove" => true, "PaymentReject" => true, "PaymentSession" => true, + "PaymentSessionUpdate" => true, "PaymentIncrementalAuthorization" => matches!( - payment_data.payment_intent.status, + payment_data.get_payment_intent().status, storage_enums::IntentStatus::RequiresCapture ), _ => false, @@ -2904,7 +2992,7 @@ pub fn is_operation_complete_authorize(operation: &Op) -> bool { matches!(format!("{operation:?}").as_str(), "CompleteAuthorize") } -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v1"))] pub async fn list_payments( state: SessionState, merchant: domain::MerchantAccount, @@ -2918,15 +3006,13 @@ pub async fn list_payments( let db = state.store.as_ref(); let payment_intents = helpers::filter_by_constraints( &state, - &constraints, + &(constraints, profile_id_list).try_into()?, merchant_id, &key_store, merchant.storage_scheme, ) .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; - let payment_intents = - utils::filter_objects_based_on_profile_id_list(profile_id_list, payment_intents); let collected_futures = payment_intents.into_iter().map(|pi| { async { @@ -2979,7 +3065,8 @@ pub async fn list_payments( }, )) } -#[cfg(feature = "olap")] + +#[cfg(all(feature = "olap", feature = "v1"))] pub async fn apply_filters_on_payments( state: SessionState, merchant: domain::MerchantAccount, @@ -2989,25 +3076,25 @@ pub async fn apply_filters_on_payments( ) -> RouterResponse { let limit = &constraints.limit; helpers::validate_payment_list_request_for_joins(*limit)?; - let db = state.store.as_ref(); + let db: &dyn StorageInterface = state.store.as_ref(); + let pi_fetch_constraints = (constraints.clone(), profile_id_list.clone()).try_into()?; let list: Vec<(storage::PaymentIntent, storage::PaymentAttempt)> = db .get_filtered_payment_intents_attempt( &(&state).into(), merchant.get_id(), - &constraints.clone().into(), + &pi_fetch_constraints, &merchant_key_store, merchant.storage_scheme, ) .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; - let list = utils::filter_objects_based_on_profile_id_list(profile_id_list, list); let data: Vec = list.into_iter().map(ForeignFrom::foreign_from).collect(); let active_attempt_ids = db .get_filtered_active_attempt_ids_for_total_count( merchant.get_id(), - &constraints.clone().into(), + &pi_fetch_constraints, merchant.storage_scheme, ) .await @@ -3022,6 +3109,7 @@ pub async fn apply_filters_on_payments( constraints.payment_method_type, constraints.authentication_type, constraints.merchant_connector_id, + pi_fetch_constraints.get_profile_id_list(), merchant.storage_scheme, ) .await @@ -3036,7 +3124,7 @@ pub async fn apply_filters_on_payments( )) } -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v1"))] pub async fn get_filters_for_payments( state: SessionState, merchant: domain::MerchantAccount, @@ -3077,7 +3165,7 @@ pub async fn get_filters_for_payments( )) } -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v1"))] pub async fn get_payment_filters( state: SessionState, merchant: domain::MerchantAccount, @@ -3157,15 +3245,16 @@ pub async fn get_payment_filters( )) } -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v1"))] pub async fn get_aggregates_for_payments( state: SessionState, merchant: domain::MerchantAccount, + profile_id_list: Option>, time_range: api::TimeRange, ) -> RouterResponse { let db = state.store.as_ref(); let intent_status_with_count = db - .get_intent_status_with_count(merchant.get_id(), &time_range) + .get_intent_status_with_count(merchant.get_id(), profile_id_list, &time_range) .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; @@ -3239,37 +3328,39 @@ pub async fn reset_process_sync_task( Ok(()) } -pub fn update_straight_through_routing( - payment_data: &mut PaymentData, +pub fn update_straight_through_routing( + payment_data: &mut D, request_straight_through: serde_json::Value, ) -> CustomResult<(), errors::ParsingError> where F: Send + Clone, + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, { let _: api_models::routing::RoutingAlgorithm = request_straight_through .clone() .parse_value("RoutingAlgorithm") .attach_printable("Invalid straight through routing rules format")?; - payment_data.payment_attempt.straight_through_algorithm = Some(request_straight_through); + payment_data.set_straight_through_algorithm_in_payment_attempt(request_straight_through); Ok(()) } #[allow(clippy::too_many_arguments)] -pub async fn get_connector_choice( - operation: &BoxedOperation<'_, F, Req>, +pub async fn get_connector_choice( + operation: &BoxedOperation<'_, F, Req, D>, state: &SessionState, req: &Req, merchant_account: &domain::MerchantAccount, business_profile: &domain::BusinessProfile, key_store: &domain::MerchantKeyStore, - payment_data: &mut PaymentData, + payment_data: &mut D, eligible_connectors: Option>, mandate_type: Option, ) -> RouterResult> where F: Send + Clone, + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, { let connector_choice = operation .to_domain()? @@ -3277,7 +3368,7 @@ where merchant_account, &state.clone(), req, - &payment_data.payment_intent, + payment_data.get_payment_intent(), key_store, ) .await?; @@ -3337,18 +3428,19 @@ where } #[allow(clippy::too_many_arguments)] -pub async fn connector_selection( +pub async fn connector_selection( state: &SessionState, merchant_account: &domain::MerchantAccount, business_profile: &domain::BusinessProfile, key_store: &domain::MerchantKeyStore, - payment_data: &mut PaymentData, + payment_data: &mut D, request_straight_through: Option, eligible_connectors: Option>, mandate_type: Option, ) -> RouterResult where F: Send + Clone, + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, { let request_straight_through: Option = request_straight_through @@ -3358,13 +3450,16 @@ where .attach_printable("Invalid straight through routing rules format")?; let mut routing_data = storage::RoutingData { - routed_through: payment_data.payment_attempt.connector.clone(), + routed_through: payment_data.get_payment_attempt().connector.clone(), - merchant_connector_id: payment_data.payment_attempt.merchant_connector_id.clone(), + merchant_connector_id: payment_data + .get_payment_attempt() + .merchant_connector_id + .clone(), algorithm: request_straight_through.clone(), routing_info: payment_data - .payment_attempt + .get_payment_attempt() .straight_through_algorithm .clone() .map(|val| val.parse_value("PaymentRoutingInfo")) @@ -3396,21 +3491,42 @@ where .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("error serializing payment routing info to serde value")?; - payment_data.payment_attempt.connector = routing_data.routed_through; + payment_data.set_connector_in_payment_attempt(routing_data.routed_through); - payment_data.payment_attempt.merchant_connector_id = routing_data.merchant_connector_id; - payment_data.payment_attempt.straight_through_algorithm = Some(encoded_info); + payment_data.set_merchant_connector_id_in_attempt(routing_data.merchant_connector_id); + payment_data.set_straight_through_algorithm_in_payment_attempt(encoded_info); Ok(decided_connector) } #[allow(clippy::too_many_arguments)] -pub async fn decide_connector( +#[cfg(feature = "v2")] +pub async fn decide_connector( state: SessionState, merchant_account: &domain::MerchantAccount, business_profile: &domain::BusinessProfile, key_store: &domain::MerchantKeyStore, - payment_data: &mut PaymentData, + payment_data: &mut D, + request_straight_through: Option, + routing_data: &mut storage::RoutingData, + eligible_connectors: Option>, + mandate_type: Option, +) -> RouterResult +where + F: Send + Clone, + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, +{ + todo!() +} + +#[allow(clippy::too_many_arguments)] +#[cfg(feature = "v1")] +pub async fn decide_connector( + state: SessionState, + merchant_account: &domain::MerchantAccount, + business_profile: &domain::BusinessProfile, + key_store: &domain::MerchantKeyStore, + payment_data: &mut D, request_straight_through: Option, routing_data: &mut storage::RoutingData, eligible_connectors: Option>, @@ -3418,17 +3534,21 @@ pub async fn decide_connector( ) -> RouterResult where F: Send + Clone, + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, { // If the connector was already decided previously, use the same connector // This is in case of flows like payments_sync, payments_cancel where the successive operations // with the connector have to be made using the same connector account. - if let Some(ref connector_name) = payment_data.payment_attempt.connector { + if let Some(ref connector_name) = payment_data.get_payment_attempt().connector { // Connector was already decided previously, use the same connector let connector_data = api::ConnectorData::get_connector_by_name( &state.conf.connectors, connector_name, api::GetToken::Connector, - payment_data.payment_attempt.merchant_connector_id.clone(), + payment_data + .get_payment_attempt() + .merchant_connector_id + .clone(), ) .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Invalid connector name received in 'routed_through'")?; @@ -3437,7 +3557,7 @@ where return Ok(ConnectorCallType::PreDetermined(connector_data)); } - if let Some(mandate_connector_details) = payment_data.mandate_connector.as_ref() { + if let Some(mandate_connector_details) = payment_data.get_mandate_connector().as_ref() { let connector_data = api::ConnectorData::get_connector_by_name( &state.conf.connectors, &mandate_connector_details.connector, @@ -3456,15 +3576,17 @@ where return Ok(ConnectorCallType::PreDetermined(connector_data)); } - if let Some((pre_routing_results, storage_pm_type)) = routing_data - .routing_info - .pre_routing_results - .as_ref() - .zip(payment_data.payment_attempt.payment_method_type.as_ref()) + if let Some((pre_routing_results, storage_pm_type)) = + routing_data.routing_info.pre_routing_results.as_ref().zip( + payment_data + .get_payment_attempt() + .payment_method_type + .as_ref(), + ) { if let (Some(routable_connector_choice), None) = ( pre_routing_results.get(storage_pm_type), - &payment_data.token_data, + &payment_data.get_token_data(), ) { let routable_connector_list = match routable_connector_choice { storage::PreRoutingConnectorChoice::Single(routable_connector) => { @@ -3505,7 +3627,7 @@ where retry::config_should_call_gsm(&*state.store, merchant_account.get_id()).await; #[cfg(feature = "retry")] - if payment_data.payment_attempt.payment_method_type + if payment_data.get_payment_attempt().payment_method_type == Some(storage_enums::PaymentMethodType::ApplePay) && should_do_retry { @@ -3546,17 +3668,27 @@ where if let Some(routing_algorithm) = request_straight_through { let (mut connectors, check_eligibility) = routing::perform_straight_through_routing( &routing_algorithm, - payment_data.creds_identifier.clone(), + payment_data.get_creds_identifier(), ) .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed execution of straight through routing")?; if check_eligibility { + let transaction_data = core_routing::PaymentsDslInput::new( + payment_data.get_setup_mandate(), + payment_data.get_payment_attempt(), + payment_data.get_payment_intent(), + payment_data.get_payment_method_data(), + payment_data.get_address(), + payment_data.get_recurring_details(), + payment_data.get_currency(), + ); + connectors = routing::perform_eligibility_analysis_with_fallback( &state.clone(), key_store, connectors, - &TransactionData::Payment(payment_data), + &TransactionData::Payment(transaction_data), eligible_connectors, business_profile, ) @@ -3593,17 +3725,27 @@ where if let Some(ref routing_algorithm) = routing_data.routing_info.algorithm { let (mut connectors, check_eligibility) = routing::perform_straight_through_routing( routing_algorithm, - payment_data.creds_identifier.clone(), + payment_data.get_creds_identifier(), ) .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed execution of straight through routing")?; if check_eligibility { + let transaction_data = core_routing::PaymentsDslInput::new( + payment_data.get_setup_mandate(), + payment_data.get_payment_attempt(), + payment_data.get_payment_intent(), + payment_data.get_payment_method_data(), + payment_data.get_address(), + payment_data.get_recurring_details(), + payment_data.get_currency(), + ); + connectors = routing::perform_eligibility_analysis_with_fallback( &state, key_store, connectors, - &TransactionData::Payment(payment_data), + &TransactionData::Payment(transaction_data), eligible_connectors, business_profile, ) @@ -3637,12 +3779,24 @@ where .await; } - route_connector_v1( + let new_pd = payment_data.clone(); + let transaction_data = core_routing::PaymentsDslInput::new( + new_pd.get_setup_mandate(), + new_pd.get_payment_attempt(), + new_pd.get_payment_intent(), + new_pd.get_payment_method_data(), + new_pd.get_address(), + new_pd.get_recurring_details(), + new_pd.get_currency(), + ); + + route_connector_v1_for_payments( &state, merchant_account, business_profile, key_store, - TransactionData::Payment(payment_data), + payment_data, + transaction_data, routing_data, eligible_connectors, mandate_type, @@ -3650,19 +3804,22 @@ where .await } -pub async fn decide_multiplex_connector_for_normal_or_recurring_payment( +pub async fn decide_multiplex_connector_for_normal_or_recurring_payment( state: &SessionState, - payment_data: &mut PaymentData, + payment_data: &mut D, routing_data: &mut storage::RoutingData, connectors: Vec, mandate_type: Option, is_connector_agnostic_mit_enabled: Option, -) -> RouterResult { +) -> RouterResult +where + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, +{ match ( - payment_data.payment_intent.setup_future_usage, - payment_data.token_data.as_ref(), - payment_data.recurring_details.as_ref(), - payment_data.payment_intent.off_session, + payment_data.get_payment_intent().setup_future_usage, + payment_data.get_token_data().as_ref(), + payment_data.get_recurring_details().as_ref(), + payment_data.get_payment_intent().off_session, mandate_type, ) { ( @@ -3683,8 +3840,7 @@ pub async fn decide_multiplex_connector_for_normal_or_recurring_payment(payment_data: &PaymentData) -> bool { - let connector = payment_data.payment_attempt.connector.as_deref(); +pub fn should_add_task_to_process_tracker>( + payment_data: &D, +) -> bool { + let connector = payment_data.get_payment_attempt().connector.as_deref(); !matches!( - (payment_data.payment_attempt.payment_method, connector), + (payment_data.get_payment_attempt().payment_method, connector), ( Some(storage_enums::PaymentMethod::BankTransfer), Some("stripe") @@ -3874,22 +4032,23 @@ pub fn should_add_task_to_process_tracker(payment_data: &PaymentData( +pub async fn perform_session_token_routing( state: SessionState, merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, - payment_data: &mut PaymentData, + payment_data: &D, connectors: Vec, ) -> RouterResult> where F: Clone, + D: OperationSessionGetters, { // Commenting out this code as `list_payment_method_api` and `perform_session_token_routing` // will happen in parallel the behaviour of the session call differ based on filters in // list_payment_method_api // let routing_info: Option = payment_data - // .payment_attempt + // .get_payment_attempt() // .straight_through_algorithm // .clone() // .map(|val| val.parse_value("PaymentRoutingInfo")) @@ -3956,14 +4115,14 @@ where let sfr = SessionFlowRoutingInput { state: &state, country: payment_data - .address + .get_address() .get_payment_method_billing() .and_then(|address| address.address.as_ref()) .and_then(|details| details.country), key_store, merchant_account, - payment_attempt: &payment_data.payment_attempt, - payment_intent: &payment_data.payment_intent, + payment_attempt: payment_data.get_payment_attempt(), + payment_intent: payment_data.get_payment_intent(), chosen, }; @@ -3990,39 +4149,49 @@ where Ok(final_list) } -#[cfg(feature = "v2")] +#[cfg(feature = "v1")] #[allow(clippy::too_many_arguments)] -pub async fn route_connector_v1( +pub async fn route_connector_v1_for_payments( state: &SessionState, merchant_account: &domain::MerchantAccount, business_profile: &domain::BusinessProfile, key_store: &domain::MerchantKeyStore, - transaction_data: TransactionData<'_, F>, + payment_data: &mut D, + transaction_data: core_routing::PaymentsDslInput<'_>, routing_data: &mut storage::RoutingData, eligible_connectors: Option>, mandate_type: Option, ) -> RouterResult where F: Send + Clone, + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, { - let profile_wrapper = super::admin::BusinessProfileWrapper::new(business_profile.clone()); - let routing_algorithm_id = profile_wrapper.get_routing_algorithm_id(&transaction_data); + let routing_algorithm_id = { + let routing_algorithm = business_profile.routing_algorithm.clone(); + + let algorithm_ref = routing_algorithm + .map(|ra| ra.parse_value::("RoutingAlgorithmRef")) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Could not decode merchant routing algorithm ref")? + .unwrap_or_default(); + algorithm_ref.algorithm_id + }; let connectors = routing::perform_static_routing_v1( state, merchant_account.get_id(), - routing_algorithm_id, + routing_algorithm_id.as_ref(), business_profile, - &transaction_data, + &TransactionData::Payment(transaction_data.clone()), ) .await .change_context(errors::ApiErrorResponse::InternalServerError)?; - let connectors = routing::perform_eligibility_analysis_with_fallback( &state.clone(), key_store, connectors, - &transaction_data, + &TransactionData::Payment(transaction_data), eligible_connectors, business_profile, ) @@ -4030,13 +4199,6 @@ where .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("failed eligibility analysis and fallback")?; - #[cfg(feature = "payouts")] - let first_connector_choice = connectors - .first() - .ok_or(errors::ApiErrorResponse::IncorrectPaymentMethodConfiguration) - .attach_printable("Empty connector list returned")? - .clone(); - let connector_data = connectors .into_iter() .map(|conn| { @@ -4051,52 +4213,46 @@ where .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Invalid connector name received")?; - match transaction_data { - TransactionData::Payment(payment_data) => { - decide_multiplex_connector_for_normal_or_recurring_payment( - state, - payment_data, - routing_data, - connector_data, - mandate_type, - business_profile.is_connector_agnostic_mit_enabled, - ) - .await - } - - #[cfg(feature = "payouts")] - TransactionData::Payout(_) => { - routing_data.routed_through = Some(first_connector_choice.connector.to_string()); - - routing_data.merchant_connector_id = first_connector_choice.merchant_connector_id; + decide_multiplex_connector_for_normal_or_recurring_payment( + state, + payment_data, + routing_data, + connector_data, + mandate_type, + business_profile.is_connector_agnostic_mit_enabled, + ) + .await +} - Ok(ConnectorCallType::Retryable(connector_data)) - } - } +#[cfg(feature = "payouts")] +#[cfg(feature = "v2")] +#[allow(clippy::too_many_arguments)] +pub async fn route_connector_v1_for_payouts( + state: &SessionState, + merchant_account: &domain::MerchantAccount, + business_profile: &domain::BusinessProfile, + key_store: &domain::MerchantKeyStore, + transaction_data: &payouts::PayoutData, + routing_data: &mut storage::RoutingData, + eligible_connectors: Option>, +) -> RouterResult { + todo!() } +#[cfg(feature = "payouts")] #[cfg(feature = "v1")] #[allow(clippy::too_many_arguments)] -pub async fn route_connector_v1( +pub async fn route_connector_v1_for_payouts( state: &SessionState, merchant_account: &domain::MerchantAccount, business_profile: &domain::BusinessProfile, key_store: &domain::MerchantKeyStore, - transaction_data: TransactionData<'_, F>, + transaction_data: &payouts::PayoutData, routing_data: &mut storage::RoutingData, eligible_connectors: Option>, - mandate_type: Option, -) -> RouterResult -where - F: Send + Clone, -{ +) -> RouterResult { let routing_algorithm_id = { - let routing_algorithm = match &transaction_data { - TransactionData::Payment(_) => business_profile.routing_algorithm.clone(), - - #[cfg(feature = "payouts")] - TransactionData::Payout(_) => business_profile.payout_routing_algorithm.clone(), - }; + let routing_algorithm = business_profile.payout_routing_algorithm.clone(); let algorithm_ref = routing_algorithm .map(|ra| ra.parse_value::("RoutingAlgorithmRef")) @@ -4110,9 +4266,9 @@ where let connectors = routing::perform_static_routing_v1( state, merchant_account.get_id(), - routing_algorithm_id, + routing_algorithm_id.as_ref(), business_profile, - &transaction_data, + &TransactionData::Payout(transaction_data), ) .await .change_context(errors::ApiErrorResponse::InternalServerError)?; @@ -4120,7 +4276,7 @@ where &state.clone(), key_store, connectors, - &transaction_data, + &TransactionData::Payout(transaction_data), eligible_connectors, business_profile, ) @@ -4128,7 +4284,6 @@ where .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("failed eligibility analysis and fallback")?; - #[cfg(feature = "payouts")] let first_connector_choice = connectors .first() .ok_or(errors::ApiErrorResponse::IncorrectPaymentMethodConfiguration) @@ -4149,28 +4304,11 @@ where .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Invalid connector name received")?; - match transaction_data { - TransactionData::Payment(payment_data) => { - decide_multiplex_connector_for_normal_or_recurring_payment( - state, - payment_data, - routing_data, - connector_data, - mandate_type, - business_profile.is_connector_agnostic_mit_enabled, - ) - .await - } - - #[cfg(feature = "payouts")] - TransactionData::Payout(_) => { - routing_data.routed_through = Some(first_connector_choice.connector.to_string()); + routing_data.routed_through = Some(first_connector_choice.connector.to_string()); - routing_data.merchant_connector_id = first_connector_choice.merchant_connector_id; + routing_data.merchant_connector_id = first_connector_choice.merchant_connector_id; - Ok(ConnectorCallType::Retryable(connector_data)) - } - } + Ok(ConnectorCallType::Retryable(connector_data)) } #[cfg(all(feature = "v2", feature = "customer_v2"))] @@ -4419,7 +4557,7 @@ pub async fn get_extended_card_info( )) } -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v1"))] pub async fn payments_manual_update( state: SessionState, req: api_models::payments::PaymentsManualUpdateRequest, @@ -4464,6 +4602,7 @@ pub async fn payments_manual_update( .attach_printable( "Error while fetching the payment_attempt by payment_id, merchant_id and attempt_id", )?; + let payment_intent = state .store .find_payment_intent_by_payment_id_merchant_id( @@ -4476,6 +4615,7 @@ pub async fn payments_manual_update( .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound) .attach_printable("Error while fetching the payment_intent by payment_id, merchant_id")?; + let option_gsm = if let Some(((code, message), connector_name)) = error_code .as_ref() .zip(error_message.as_ref()) @@ -4547,3 +4687,288 @@ pub async fn payments_manual_update( }, )) } + +pub trait OperationSessionGetters { + fn get_payment_attempt(&self) -> &storage::PaymentAttempt; + fn get_payment_intent(&self) -> &storage::PaymentIntent; + fn get_payment_method_info(&self) -> Option<&domain::PaymentMethod>; + fn get_mandate_id(&self) -> Option<&payments_api::MandateIds>; + fn get_address(&self) -> &PaymentAddress; + fn get_creds_identifier(&self) -> Option<&str>; + fn get_token(&self) -> Option<&str>; + fn get_multiple_capture_data(&self) -> Option<&types::MultipleCaptureData>; + fn get_payment_link_data(&self) -> Option; + fn get_ephemeral_key(&self) -> Option; + fn get_setup_mandate(&self) -> Option<&MandateData>; + fn get_poll_config(&self) -> Option; + fn get_authentication(&self) -> Option<&storage::Authentication>; + fn get_frm_message(&self) -> Option; + fn get_refunds(&self) -> Vec; + fn get_disputes(&self) -> Vec; + fn get_authorizations(&self) -> Vec; + fn get_attempts(&self) -> Option>; + fn get_recurring_details(&self) -> Option<&RecurringDetails>; + fn get_payment_intent_profile_id(&self) -> Option<&id_type::ProfileId>; + fn get_currency(&self) -> storage_enums::Currency; + fn get_amount(&self) -> api::Amount; + fn get_payment_attempt_connector(&self) -> Option<&str>; + fn get_billing_address(&self) -> Option; + fn get_payment_method_data(&self) -> Option<&domain::PaymentMethodData>; + fn get_sessions_token(&self) -> Vec; + fn get_token_data(&self) -> Option<&storage::PaymentTokenData>; + fn get_mandate_connector(&self) -> Option<&MandateConnectorDetails>; + fn get_force_sync(&self) -> Option; +} + +pub trait OperationSessionSetters { + // Setter functions for PaymentData + fn set_payment_intent(&mut self, payment_intent: storage::PaymentIntent); + fn set_payment_attempt(&mut self, payment_attempt: storage::PaymentAttempt); + fn set_payment_method_data(&mut self, payment_method_data: Option); + fn set_email_if_not_present(&mut self, email: pii::Email); + fn set_payment_method_id_in_attempt(&mut self, payment_method_id: Option); + fn set_pm_token(&mut self, token: String); + fn set_connector_customer_id(&mut self, customer_id: Option); + fn push_sessions_token(&mut self, token: api::SessionToken); + fn set_surcharge_details(&mut self, surcharge_details: Option); + fn set_merchant_connector_id_in_attempt( + &mut self, + merchant_connector_id: Option, + ); + fn set_capture_method_in_attempt(&mut self, capture_method: enums::CaptureMethod); + fn set_frm_message(&mut self, frm_message: FraudCheck); + fn set_payment_intent_status(&mut self, status: storage_enums::IntentStatus); + fn set_authentication_type_in_attempt( + &mut self, + authentication_type: Option, + ); + fn set_recurring_mandate_payment_data( + &mut self, + recurring_mandate_payment_data: + hyperswitch_domain_models::router_data::RecurringMandatePaymentData, + ); + fn set_mandate_id(&mut self, mandate_id: api_models::payments::MandateIds); + fn set_setup_future_usage_in_payment_intent( + &mut self, + setup_future_usage: storage_enums::FutureUsage, + ); + fn set_straight_through_algorithm_in_payment_attempt( + &mut self, + straight_through_algorithm: serde_json::Value, + ); + fn set_connector_in_payment_attempt(&mut self, connector: Option); +} + +impl OperationSessionGetters for PaymentData { + fn get_payment_attempt(&self) -> &storage::PaymentAttempt { + &self.payment_attempt + } + + fn get_payment_intent(&self) -> &storage::PaymentIntent { + &self.payment_intent + } + + fn get_payment_method_info(&self) -> Option<&domain::PaymentMethod> { + self.payment_method_info.as_ref() + } + + fn get_mandate_id(&self) -> Option<&payments_api::MandateIds> { + self.mandate_id.as_ref() + } + + // what is this address find out and not required remove this + fn get_address(&self) -> &PaymentAddress { + &self.address + } + + fn get_creds_identifier(&self) -> Option<&str> { + self.creds_identifier.as_deref() + } + + fn get_token(&self) -> Option<&str> { + self.token.as_deref() + } + + fn get_multiple_capture_data(&self) -> Option<&types::MultipleCaptureData> { + self.multiple_capture_data.as_ref() + } + + fn get_payment_link_data(&self) -> Option { + self.payment_link_data.clone() + } + + fn get_ephemeral_key(&self) -> Option { + self.ephemeral_key.clone() + } + + fn get_setup_mandate(&self) -> Option<&MandateData> { + self.setup_mandate.as_ref() + } + + fn get_poll_config(&self) -> Option { + self.poll_config.clone() + } + + fn get_authentication(&self) -> Option<&storage::Authentication> { + self.authentication.as_ref() + } + + fn get_frm_message(&self) -> Option { + self.frm_message.clone() + } + + fn get_refunds(&self) -> Vec { + self.refunds.clone() + } + + fn get_disputes(&self) -> Vec { + self.disputes.clone() + } + + fn get_authorizations(&self) -> Vec { + self.authorizations.clone() + } + + fn get_attempts(&self) -> Option> { + self.attempts.clone() + } + + fn get_recurring_details(&self) -> Option<&RecurringDetails> { + self.recurring_details.as_ref() + } + + fn get_payment_intent_profile_id(&self) -> Option<&id_type::ProfileId> { + self.payment_intent.profile_id.as_ref() + } + + fn get_currency(&self) -> storage_enums::Currency { + self.currency + } + + fn get_amount(&self) -> api::Amount { + self.amount + } + + fn get_payment_attempt_connector(&self) -> Option<&str> { + self.payment_attempt.connector.as_deref() + } + + fn get_billing_address(&self) -> Option { + self.address.get_payment_method_billing().cloned() + } + + fn get_payment_method_data(&self) -> Option<&domain::PaymentMethodData> { + self.payment_method_data.as_ref() + } + + fn get_sessions_token(&self) -> Vec { + self.sessions_token.clone() + } + + fn get_token_data(&self) -> Option<&storage::PaymentTokenData> { + self.token_data.as_ref() + } + + fn get_mandate_connector(&self) -> Option<&MandateConnectorDetails> { + self.mandate_connector.as_ref() + } + + fn get_force_sync(&self) -> Option { + self.force_sync + } +} + +impl OperationSessionSetters for PaymentData { + // Setters Implementation + fn set_payment_intent(&mut self, payment_intent: storage::PaymentIntent) { + self.payment_intent = payment_intent; + } + + fn set_payment_attempt(&mut self, payment_attempt: storage::PaymentAttempt) { + self.payment_attempt = payment_attempt; + } + + fn set_payment_method_data(&mut self, payment_method_data: Option) { + self.payment_method_data = payment_method_data; + } + + fn set_payment_method_id_in_attempt(&mut self, payment_method_id: Option) { + self.payment_attempt.payment_method_id = payment_method_id; + } + + fn set_email_if_not_present(&mut self, email: pii::Email) { + self.email = self.email.clone().or(Some(email)); + } + + fn set_pm_token(&mut self, token: String) { + self.pm_token = Some(token); + } + + fn set_connector_customer_id(&mut self, customer_id: Option) { + self.connector_customer_id = customer_id; + } + + fn push_sessions_token(&mut self, token: api::SessionToken) { + self.sessions_token.push(token); + } + + fn set_surcharge_details(&mut self, surcharge_details: Option) { + self.surcharge_details = surcharge_details; + } + + fn set_merchant_connector_id_in_attempt( + &mut self, + merchant_connector_id: Option, + ) { + self.payment_attempt.merchant_connector_id = merchant_connector_id; + } + + fn set_capture_method_in_attempt(&mut self, capture_method: enums::CaptureMethod) { + self.payment_attempt.capture_method = Some(capture_method); + } + + fn set_frm_message(&mut self, frm_message: FraudCheck) { + self.frm_message = Some(frm_message); + } + + fn set_payment_intent_status(&mut self, status: storage_enums::IntentStatus) { + self.payment_intent.status = status; + } + + fn set_authentication_type_in_attempt( + &mut self, + authentication_type: Option, + ) { + self.payment_attempt.authentication_type = authentication_type; + } + + fn set_recurring_mandate_payment_data( + &mut self, + recurring_mandate_payment_data: + hyperswitch_domain_models::router_data::RecurringMandatePaymentData, + ) { + self.recurring_mandate_payment_data = Some(recurring_mandate_payment_data); + } + + fn set_mandate_id(&mut self, mandate_id: api_models::payments::MandateIds) { + self.mandate_id = Some(mandate_id); + } + + fn set_setup_future_usage_in_payment_intent( + &mut self, + setup_future_usage: storage_enums::FutureUsage, + ) { + self.payment_intent.setup_future_usage = Some(setup_future_usage); + } + + fn set_straight_through_algorithm_in_payment_attempt( + &mut self, + straight_through_algorithm: serde_json::Value, + ) { + self.payment_attempt.straight_through_algorithm = Some(straight_through_algorithm); + } + + fn set_connector_in_payment_attempt(&mut self, connector: Option) { + self.payment_attempt.connector = connector; + } +} diff --git a/crates/router/src/core/payments/access_token.rs b/crates/router/src/core/payments/access_token.rs index e6e79a6c70a1..42d0823a256c 100644 --- a/crates/router/src/core/payments/access_token.rs +++ b/crates/router/src/core/payments/access_token.rs @@ -58,7 +58,7 @@ pub async fn add_access_token< connector: &api_types::ConnectorData, merchant_account: &domain::MerchantAccount, router_data: &types::RouterData, - creds_identifier: Option<&String>, + creds_identifier: Option<&str>, ) -> RouterResult { if connector .connector_name @@ -77,7 +77,7 @@ pub async fn add_access_token< .merchant_connector_id .clone() .map(|mca_id| mca_id.get_string_repr().to_string()) - .or(creds_identifier.cloned()) + .or(creds_identifier.map(|id| id.to_string())) .unwrap_or(connector.connector_name.to_string()); let old_access_token = store diff --git a/crates/router/src/core/payments/conditional_configs.rs b/crates/router/src/core/payments/conditional_configs.rs index 4f5b84af7626..cc6cc9cd7437 100644 --- a/crates/router/src/core/payments/conditional_configs.rs +++ b/crates/router/src/core/payments/conditional_configs.rs @@ -12,17 +12,17 @@ use storage_impl::redis::cache::{self, DECISION_MANAGER_CACHE}; use super::routing::make_dsl_input; use crate::{ - core::{errors, errors::ConditionalConfigError as ConfigError, payments}, + core::{errors, errors::ConditionalConfigError as ConfigError, routing as core_routing}, routes, }; pub type ConditionalConfigResult = errors::CustomResult; #[instrument(skip_all)] -pub async fn perform_decision_management( +pub async fn perform_decision_management( state: &routes::SessionState, algorithm_ref: routing::RoutingAlgorithmRef, merchant_id: &common_utils::id_type::MerchantId, - payment_data: &mut payments::PaymentData, + payment_data: &core_routing::PaymentsDslInput<'_>, ) -> ConditionalConfigResult { let algorithm_id = if let Some(id) = algorithm_ref.config_algo_id { id diff --git a/crates/router/src/core/payments/connector_integration_v2_impls.rs b/crates/router/src/core/payments/connector_integration_v2_impls.rs index a0b7b530bfcf..19d67733d3b3 100644 --- a/crates/router/src/core/payments/connector_integration_v2_impls.rs +++ b/crates/router/src/core/payments/connector_integration_v2_impls.rs @@ -42,6 +42,10 @@ mod dummy_connector_default_impl { impl api::PaymentsPostProcessingV2 for connector::DummyConnector {} + impl api::TaxCalculationV2 for connector::DummyConnector {} + + impl api::PaymentSessionUpdateV2 for connector::DummyConnector {} + impl services::ConnectorIntegrationV2< api::Authorize, @@ -181,6 +185,24 @@ mod dummy_connector_default_impl { > for connector::DummyConnector { } + impl + services::ConnectorIntegrationV2< + api::CalculateTax, + types::PaymentFlowData, + types::PaymentsTaxCalculationData, + types::TaxCalculationResponseData, + > for connector::DummyConnector + { + } + impl + services::ConnectorIntegrationV2< + api::SdkSessionUpdate, + types::PaymentFlowData, + types::SdkPaymentsSessionUpdateData, + types::PaymentsResponseData, + > for connector::DummyConnector + { + } impl services::ConnectorIntegrationV2< @@ -557,6 +579,8 @@ macro_rules! default_imp_for_new_connector_integration_payment { impl api::ConnectorCustomerV2 for $path::$connector{} impl api::PaymentsPreProcessingV2 for $path::$connector{} impl api::PaymentsPostProcessingV2 for $path::$connector{} + impl api::TaxCalculationV2 for $path::$connector{} + impl api::PaymentSessionUpdateV2 for $path::$connector{} impl services::ConnectorIntegrationV2 for $path::$connector{} @@ -629,6 +653,19 @@ macro_rules! default_imp_for_new_connector_integration_payment { types::AuthorizeSessionTokenData, types::PaymentsResponseData > for $path::$connector{} + impl services::ConnectorIntegrationV2< + api::CalculateTax, + types::PaymentFlowData, + types::PaymentsTaxCalculationData, + types::TaxCalculationResponseData, + > for $path::$connector{} + + impl services::ConnectorIntegrationV2< + api::SdkSessionUpdate, + types::PaymentFlowData, + types::SdkPaymentsSessionUpdateData, + types::PaymentsResponseData, + > for $path::$connector{} )* }; } @@ -1250,6 +1287,7 @@ default_imp_for_new_connector_integration_payouts!( connector::Taxjar, connector::Trustpay, connector::Threedsecureio, + connector::Thunes, connector::Tsys, connector::Volt, connector::Wellsfargo, @@ -2063,6 +2101,7 @@ default_imp_for_new_connector_integration_frm!( connector::Taxjar, connector::Trustpay, connector::Threedsecureio, + connector::Thunes, connector::Tsys, connector::Volt, connector::Wellsfargo, @@ -2667,6 +2706,7 @@ default_imp_for_new_connector_integration_connector_authentication!( connector::Taxjar, connector::Trustpay, connector::Threedsecureio, + connector::Thunes, connector::Tsys, connector::Volt, connector::Wellsfargo, diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index c7045733cb4b..1dec8911d31d 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -7,6 +7,7 @@ pub mod incremental_authorization_flow; pub mod psync_flow; pub mod reject_flow; pub mod session_flow; +pub mod session_update_flow; pub mod setup_mandate_flow; use async_trait::async_trait; @@ -83,7 +84,7 @@ pub trait Feature { state: &SessionState, connector: &api::ConnectorData, merchant_account: &domain::MerchantAccount, - creds_identifier: Option<&String>, + creds_identifier: Option<&str>, ) -> RouterResult where F: Clone, @@ -3084,3 +3085,181 @@ default_imp_for_authorize_session_token!( connector::Zen, connector::Zsl ); + +macro_rules! default_imp_for_calculate_tax { + ($($path:ident::$connector:ident),*) => { + $( impl api::TaxCalculation for $path::$connector {} + impl + services::ConnectorIntegration< + api::CalculateTax, + types::PaymentsTaxCalculationData, + types::TaxCalculationResponseData + > for $path::$connector + {} + )* + }; +} +#[cfg(feature = "dummy_connector")] +impl api::TaxCalculation for connector::DummyConnector {} +#[cfg(feature = "dummy_connector")] +impl + services::ConnectorIntegration< + api::CalculateTax, + types::PaymentsTaxCalculationData, + types::TaxCalculationResponseData, + > for connector::DummyConnector +{ +} + +default_imp_for_calculate_tax!( + connector::Aci, + connector::Adyen, + connector::Adyenplatform, + connector::Airwallex, + connector::Authorizedotnet, + connector::Bamboraapac, + connector::Bankofamerica, + connector::Billwerk, + connector::Bluesnap, + connector::Boku, + connector::Braintree, + connector::Cashtocode, + connector::Checkout, + connector::Cryptopay, + connector::Coinbase, + connector::Cybersource, + connector::Datatrans, + connector::Dlocal, + connector::Ebanx, + connector::Forte, + connector::Globalpay, + connector::Gocardless, + connector::Gpayments, + connector::Iatapay, + connector::Itaubank, + connector::Klarna, + connector::Mifinity, + connector::Mollie, + connector::Multisafepay, + connector::Netcetera, + connector::Nexinets, + connector::Nuvei, + connector::Nmi, + connector::Noon, + connector::Opayo, + connector::Opennode, + connector::Paybox, + connector::Payeezy, + connector::Payme, + connector::Payone, + connector::Paypal, + connector::Payu, + connector::Placetopay, + connector::Plaid, + connector::Prophetpay, + connector::Rapyd, + connector::Razorpay, + connector::Riskified, + connector::Square, + connector::Signifyd, + connector::Stripe, + connector::Shift4, + connector::Threedsecureio, + connector::Trustpay, + connector::Volt, + connector::Wellsfargo, + connector::Wellsfargopayout, + connector::Wise, + connector::Worldpay, + connector::Zen, + connector::Zsl +); + +macro_rules! default_imp_for_session_update { + ($($path:ident::$connector:ident),*) => { + $( impl api::PaymentSessionUpdate for $path::$connector {} + impl + services::ConnectorIntegration< + api::SdkSessionUpdate, + types::SdkPaymentsSessionUpdateData, + types::PaymentsResponseData + > for $path::$connector + {} + )* + }; +} +#[cfg(feature = "dummy_connector")] +impl api::PaymentSessionUpdate for connector::DummyConnector {} +#[cfg(feature = "dummy_connector")] +impl + services::ConnectorIntegration< + api::SdkSessionUpdate, + types::SdkPaymentsSessionUpdateData, + types::PaymentsResponseData, + > for connector::DummyConnector +{ +} + +default_imp_for_session_update!( + connector::Aci, + connector::Adyen, + connector::Adyenplatform, + connector::Airwallex, + connector::Authorizedotnet, + connector::Bamboraapac, + connector::Bankofamerica, + connector::Billwerk, + connector::Bluesnap, + connector::Boku, + connector::Braintree, + connector::Cashtocode, + connector::Checkout, + connector::Cryptopay, + connector::Coinbase, + connector::Cybersource, + connector::Datatrans, + connector::Dlocal, + connector::Ebanx, + connector::Forte, + connector::Globalpay, + connector::Gocardless, + connector::Gpayments, + connector::Iatapay, + connector::Itaubank, + connector::Klarna, + connector::Mifinity, + connector::Mollie, + connector::Multisafepay, + connector::Netcetera, + connector::Nexinets, + connector::Nuvei, + connector::Nmi, + connector::Noon, + connector::Opayo, + connector::Opennode, + connector::Paybox, + connector::Payeezy, + connector::Payme, + connector::Payone, + connector::Paypal, + connector::Payu, + connector::Placetopay, + connector::Plaid, + connector::Prophetpay, + connector::Rapyd, + connector::Razorpay, + connector::Riskified, + connector::Square, + connector::Signifyd, + connector::Stripe, + connector::Shift4, + connector::Threedsecureio, + connector::Trustpay, + connector::Volt, + connector::Wellsfargo, + connector::Wellsfargopayout, + connector::Wise, + connector::Worldpay, + connector::Zen, + connector::Zsl +); diff --git a/crates/router/src/core/payments/flows/approve_flow.rs b/crates/router/src/core/payments/flows/approve_flow.rs index 694b8d40fc2d..e425abce3365 100644 --- a/crates/router/src/core/payments/flows/approve_flow.rs +++ b/crates/router/src/core/payments/flows/approve_flow.rs @@ -78,7 +78,7 @@ impl Feature state: &SessionState, connector: &api::ConnectorData, merchant_account: &domain::MerchantAccount, - creds_identifier: Option<&String>, + creds_identifier: Option<&str>, ) -> RouterResult { access_token::add_access_token(state, connector, merchant_account, self, creds_identifier) .await diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index 21f55cce3155..0a55cefb4273 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -139,7 +139,7 @@ impl Feature for types::PaymentsAu state: &SessionState, connector: &api::ConnectorData, merchant_account: &domain::MerchantAccount, - creds_identifier: Option<&String>, + creds_identifier: Option<&str>, ) -> RouterResult { access_token::add_access_token(state, connector, merchant_account, self, creds_identifier) .await diff --git a/crates/router/src/core/payments/flows/cancel_flow.rs b/crates/router/src/core/payments/flows/cancel_flow.rs index 488818021ecf..a56c49611a69 100644 --- a/crates/router/src/core/payments/flows/cancel_flow.rs +++ b/crates/router/src/core/payments/flows/cancel_flow.rs @@ -97,7 +97,7 @@ impl Feature state: &SessionState, connector: &api::ConnectorData, merchant_account: &domain::MerchantAccount, - creds_identifier: Option<&String>, + creds_identifier: Option<&str>, ) -> RouterResult { access_token::add_access_token(state, connector, merchant_account, self, creds_identifier) .await diff --git a/crates/router/src/core/payments/flows/capture_flow.rs b/crates/router/src/core/payments/flows/capture_flow.rs index c22f9e0b2173..cb37a75813bf 100644 --- a/crates/router/src/core/payments/flows/capture_flow.rs +++ b/crates/router/src/core/payments/flows/capture_flow.rs @@ -98,7 +98,7 @@ impl Feature state: &SessionState, connector: &api::ConnectorData, merchant_account: &domain::MerchantAccount, - creds_identifier: Option<&String>, + creds_identifier: Option<&str>, ) -> RouterResult { access_token::add_access_token(state, connector, merchant_account, self, creds_identifier) .await diff --git a/crates/router/src/core/payments/flows/complete_authorize_flow.rs b/crates/router/src/core/payments/flows/complete_authorize_flow.rs index bd7307eb4c89..212fb4bc240b 100644 --- a/crates/router/src/core/payments/flows/complete_authorize_flow.rs +++ b/crates/router/src/core/payments/flows/complete_authorize_flow.rs @@ -105,7 +105,7 @@ impl Feature state: &SessionState, connector: &api::ConnectorData, merchant_account: &domain::MerchantAccount, - creds_identifier: Option<&String>, + creds_identifier: Option<&str>, ) -> RouterResult { access_token::add_access_token(state, connector, merchant_account, self, creds_identifier) .await diff --git a/crates/router/src/core/payments/flows/incremental_authorization_flow.rs b/crates/router/src/core/payments/flows/incremental_authorization_flow.rs index 8cdb07d39092..226852e4a3c0 100644 --- a/crates/router/src/core/payments/flows/incremental_authorization_flow.rs +++ b/crates/router/src/core/payments/flows/incremental_authorization_flow.rs @@ -98,7 +98,7 @@ impl Feature, + creds_identifier: Option<&str>, ) -> RouterResult { access_token::add_access_token(state, connector, merchant_account, self, creds_identifier) .await diff --git a/crates/router/src/core/payments/flows/psync_flow.rs b/crates/router/src/core/payments/flows/psync_flow.rs index f862fd571122..428d170633ea 100644 --- a/crates/router/src/core/payments/flows/psync_flow.rs +++ b/crates/router/src/core/payments/flows/psync_flow.rs @@ -134,7 +134,7 @@ impl Feature state: &SessionState, connector: &api::ConnectorData, merchant_account: &domain::MerchantAccount, - creds_identifier: Option<&String>, + creds_identifier: Option<&str>, ) -> RouterResult { access_token::add_access_token(state, connector, merchant_account, self, creds_identifier) .await diff --git a/crates/router/src/core/payments/flows/reject_flow.rs b/crates/router/src/core/payments/flows/reject_flow.rs index f54b8d14ddde..d2e65c8a638f 100644 --- a/crates/router/src/core/payments/flows/reject_flow.rs +++ b/crates/router/src/core/payments/flows/reject_flow.rs @@ -77,7 +77,7 @@ impl Feature state: &SessionState, connector: &api::ConnectorData, merchant_account: &domain::MerchantAccount, - creds_identifier: Option<&String>, + creds_identifier: Option<&str>, ) -> RouterResult { access_token::add_access_token(state, connector, merchant_account, self, creds_identifier) .await diff --git a/crates/router/src/core/payments/flows/session_flow.rs b/crates/router/src/core/payments/flows/session_flow.rs index 1dee68423608..54d8dc230c73 100644 --- a/crates/router/src/core/payments/flows/session_flow.rs +++ b/crates/router/src/core/payments/flows/session_flow.rs @@ -101,7 +101,7 @@ impl Feature for types::PaymentsSessio state: &routes::SessionState, connector: &api::ConnectorData, merchant_account: &domain::MerchantAccount, - creds_identifier: Option<&String>, + creds_identifier: Option<&str>, ) -> RouterResult { access_token::add_access_token(state, connector, merchant_account, self, creds_identifier) .await diff --git a/crates/router/src/core/payments/flows/session_update_flow.rs b/crates/router/src/core/payments/flows/session_update_flow.rs new file mode 100644 index 000000000000..c12667e19a21 --- /dev/null +++ b/crates/router/src/core/payments/flows/session_update_flow.rs @@ -0,0 +1,130 @@ +use async_trait::async_trait; + +use super::ConstructFlowSpecificData; +use crate::{ + core::{ + errors::{ConnectorErrorExt, RouterResult}, + payments::{self, access_token, helpers, transformers, Feature, PaymentData}, + }, + routes::SessionState, + services, + types::{self, api, domain}, +}; + +#[async_trait] +impl + ConstructFlowSpecificData< + api::SdkSessionUpdate, + types::SdkPaymentsSessionUpdateData, + types::PaymentsResponseData, + > for PaymentData +{ + async fn construct_router_data<'a>( + &self, + state: &SessionState, + connector_id: &str, + merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, + customer: &Option, + merchant_connector_account: &helpers::MerchantConnectorAccountType, + _merchant_recipient_data: Option, + ) -> RouterResult { + Box::pin( + transformers::construct_router_data_to_update_calculated_tax::< + api::SdkSessionUpdate, + types::SdkPaymentsSessionUpdateData, + >( + state, + self.clone(), + connector_id, + merchant_account, + key_store, + customer, + merchant_connector_account, + ), + ) + .await + } + + async fn get_merchant_recipient_data<'a>( + &self, + _state: &SessionState, + _merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, + _merchant_connector_account: &helpers::MerchantConnectorAccountType, + _connector: &api::ConnectorData, + ) -> RouterResult> { + Ok(None) + } +} + +#[async_trait] +impl Feature + for types::RouterData< + api::SdkSessionUpdate, + types::SdkPaymentsSessionUpdateData, + types::PaymentsResponseData, + > +{ + async fn decide_flows<'a>( + self, + state: &SessionState, + connector: &api::ConnectorData, + call_connector_action: payments::CallConnectorAction, + connector_request: Option, + _business_profile: &domain::BusinessProfile, + _header_payload: api_models::payments::HeaderPayload, + ) -> RouterResult { + let connector_integration: services::BoxedPaymentConnectorIntegrationInterface< + api::SdkSessionUpdate, + types::SdkPaymentsSessionUpdateData, + types::PaymentsResponseData, + > = connector.connector.get_connector_integration(); + + let resp = services::execute_connector_processing_step( + state, + connector_integration, + &self, + call_connector_action, + connector_request, + ) + .await + .to_payment_failed_response()?; + Ok(resp) + } + + async fn add_access_token<'a>( + &self, + state: &SessionState, + connector: &api::ConnectorData, + merchant_account: &domain::MerchantAccount, + creds_identifier: Option<&str>, + ) -> RouterResult { + access_token::add_access_token(state, connector, merchant_account, self, creds_identifier) + .await + } + + async fn build_flow_specific_connector_request( + &mut self, + state: &SessionState, + connector: &api::ConnectorData, + call_connector_action: payments::CallConnectorAction, + ) -> RouterResult<(Option, bool)> { + let request = match call_connector_action { + payments::CallConnectorAction::Trigger => { + let connector_integration: services::BoxedPaymentConnectorIntegrationInterface< + api::SdkSessionUpdate, + types::SdkPaymentsSessionUpdateData, + types::PaymentsResponseData, + > = connector.connector.get_connector_integration(); + + connector_integration + .build_request(self, &state.conf.connectors) + .to_payment_failed_response()? + } + _ => None, + }; + + Ok((request, true)) + } +} diff --git a/crates/router/src/core/payments/flows/setup_mandate_flow.rs b/crates/router/src/core/payments/flows/setup_mandate_flow.rs index fb7719725f4c..c412fd2b37fa 100644 --- a/crates/router/src/core/payments/flows/setup_mandate_flow.rs +++ b/crates/router/src/core/payments/flows/setup_mandate_flow.rs @@ -109,7 +109,7 @@ impl Feature for types::Setup state: &SessionState, connector: &api::ConnectorData, merchant_account: &domain::MerchantAccount, - creds_identifier: Option<&String>, + creds_identifier: Option<&str>, ) -> RouterResult { access_token::add_access_token(state, connector, merchant_account, self, creds_identifier) .await diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 8ce183d3e3d2..6cb4532d6179 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -4,14 +4,19 @@ use std::{borrow::Cow, str::FromStr}; use api_models::customers::CustomerRequestWithEmail; use api_models::{ mandates::RecurringDetails, - payments::{AddressDetailsWithPhone, RequestSurchargeDetails}, + payments::{ + additional_info as payment_additional_types, AddressDetailsWithPhone, + RequestSurchargeDetails, + }, }; use base64::Engine; use common_enums::ConnectorType; use common_utils::{ crypto::Encryptable, ext_traits::{AsyncExt, ByteSliceExt, Encode, ValueExt}, - fp_utils, generate_id, id_type, pii, type_name, + fp_utils, generate_id, id_type, + new_type::{MaskedIban, MaskedSortCode}, + pii, type_name, types::{ keymanager::{Identifier, KeyManagerState, ToEncryptable}, MinorUnit, @@ -26,7 +31,10 @@ use hyperswitch_domain_models::payments::payment_intent::CustomerData; use hyperswitch_domain_models::{ mandates::MandateData, payment_method_data::GetPaymentMethodType, - payments::{payment_attempt::PaymentAttempt, PaymentIntent}, + payments::{ + payment_attempt::PaymentAttempt, payment_intent::PaymentIntentFetchConstraints, + PaymentIntent, + }, router_data::KlarnaSdkResponse, }; use hyperswitch_interfaces::integrity::{CheckIntegrity, FlowIntegrity, GetIntegrityObject}; @@ -680,6 +688,7 @@ pub async fn get_token_pm_type_mandate_details( }) } +#[cfg(feature = "v1")] pub async fn get_token_for_recurring_mandate( state: &SessionState, req: &api::PaymentsRequest, @@ -1139,7 +1148,7 @@ pub fn create_startpay_url( format!( "{}/payments/redirect/{}/{}/{}", base_url, - payment_intent.payment_id.get_string_repr(), + payment_intent.get_id().get_string_repr(), payment_intent.merchant_id.get_string_repr(), payment_attempt.attempt_id ) @@ -1372,74 +1381,14 @@ where } } -pub fn response_operation<'a, F, R>() -> BoxedOperation<'a, F, R> +pub fn response_operation<'a, F, R, D>() -> BoxedOperation<'a, F, R, D> where F: Send + Clone, - PaymentResponse: Operation, + PaymentResponse: Operation, { Box::new(PaymentResponse) } -#[cfg(all(feature = "v2", feature = "customer_v2"))] -pub async fn get_customer_from_details( - _state: &SessionState, - _customer_id: Option, - _merchant_id: &id_type::MerchantId, - _payment_data: &mut PaymentData, - _merchant_key_store: &domain::MerchantKeyStore, - _storage_scheme: enums::MerchantStorageScheme, -) -> CustomResult, errors::StorageError> { - todo!() -} - -#[cfg(all(feature = "v2", feature = "customer_v2"))] -pub async fn get_customer_details_even_for_redacted_customer( - _state: &SessionState, - _customer_id: Option, - _merchant_id: &id_type::MerchantId, - _payment_data: &mut PaymentData, - _merchant_key_store: &domain::MerchantKeyStore, - _storage_scheme: enums::MerchantStorageScheme, -) -> CustomResult, errors::StorageError> { - todo!() -} - -#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] -pub async fn get_customer_from_details( - state: &SessionState, - customer_id: Option, - merchant_id: &id_type::MerchantId, - payment_data: &mut PaymentData, - merchant_key_store: &domain::MerchantKeyStore, - storage_scheme: enums::MerchantStorageScheme, -) -> CustomResult, errors::StorageError> { - match customer_id { - None => Ok(None), - Some(customer_id) => { - let db = &*state.store; - let customer = db - .find_customer_optional_by_customer_id_merchant_id( - &state.into(), - &customer_id, - merchant_id, - merchant_key_store, - storage_scheme, - ) - .await?; - - payment_data.email = payment_data.email.clone().or_else(|| { - customer.as_ref().and_then(|inner| { - inner - .email - .clone() - .map(|encrypted_value| encrypted_value.into()) - }) - }); - Ok(customer) - } - } -} - pub fn validate_max_amount( amount: api_models::payments::Amount, ) -> CustomResult<(), errors::ApiErrorResponse> { @@ -1529,30 +1478,30 @@ pub async fn get_connector_default( #[cfg(all(feature = "v2", feature = "customer_v2"))] #[instrument(skip_all)] #[allow(clippy::type_complexity)] -pub async fn create_customer_if_not_exist<'a, F: Clone, R>( +pub async fn create_customer_if_not_exist<'a, F: Clone, R, D>( _state: &SessionState, - _operation: BoxedOperation<'a, F, R>, + _operation: BoxedOperation<'a, F, R, D>, _payment_data: &mut PaymentData, _req: Option, _merchant_id: &id_type::MerchantId, _key_store: &domain::MerchantKeyStore, _storage_scheme: common_enums::enums::MerchantStorageScheme, -) -> CustomResult<(BoxedOperation<'a, F, R>, Option), errors::StorageError> { +) -> CustomResult<(BoxedOperation<'a, F, R, D>, Option), errors::StorageError> { todo!() } #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] #[instrument(skip_all)] #[allow(clippy::type_complexity)] -pub async fn create_customer_if_not_exist<'a, F: Clone, R>( +pub async fn create_customer_if_not_exist<'a, F: Clone, R, D>( state: &SessionState, - operation: BoxedOperation<'a, F, R>, + operation: BoxedOperation<'a, F, R, D>, payment_data: &mut PaymentData, req: Option, merchant_id: &id_type::MerchantId, key_store: &domain::MerchantKeyStore, storage_scheme: common_enums::enums::MerchantStorageScheme, -) -> CustomResult<(BoxedOperation<'a, F, R>, Option), errors::StorageError> { +) -> CustomResult<(BoxedOperation<'a, F, R, D>, Option), errors::StorageError> { let request_customer_details = req .get_required_value("customer") .change_context(errors::StorageError::ValueNotFound("customer".to_owned()))?; @@ -1744,44 +1693,6 @@ pub async fn create_customer_if_not_exist<'a, F: Clone, R>( )) } -// This function is to retrieve customer details. If the customer is deleted, it returns -// customer details that contains the fields as Redacted -#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] -pub async fn get_customer_details_even_for_redacted_customer( - state: &SessionState, - customer_id: Option, - merchant_id: &id_type::MerchantId, - payment_data: &mut PaymentData, - merchant_key_store: &domain::MerchantKeyStore, - storage_scheme: enums::MerchantStorageScheme, -) -> CustomResult, errors::StorageError> { - match customer_id { - None => Ok(None), - Some(customer_id) => { - let db = &*state.store; - let customer_details = db - .find_customer_optional_with_redacted_customer_details_by_customer_id_merchant_id( - &state.into(), - &customer_id, - merchant_id, - merchant_key_store, - storage_scheme, - ) - .await?; - - payment_data.email = payment_data.email.clone().or_else(|| { - customer_details.as_ref().and_then(|inner| { - inner - .email - .clone() - .map(|encrypted_value| encrypted_value.into()) - }) - }); - Ok(customer_details) - } - } -} - pub async fn retrieve_payment_method_with_temporary_token( state: &SessionState, token: &str, @@ -1859,6 +1770,10 @@ pub async fn retrieve_payment_method_with_temporary_token( Some((the_pm, enums::PaymentMethod::BankRedirect)) } + Some(the_pm @ domain::PaymentMethodData::BankDebit(_)) => { + Some((the_pm, enums::PaymentMethod::BankDebit)) + } + Some(_) => Err(errors::ApiErrorResponse::InternalServerError) .attach_printable("Payment method received from locker is unsupported by locker")?, @@ -2027,8 +1942,8 @@ pub async fn retrieve_payment_token_data( Ok(token_data) } -pub async fn make_pm_data<'a, F: Clone, R>( - operation: BoxedOperation<'a, F, R>, +pub async fn make_pm_data<'a, F: Clone, R, D>( + operation: BoxedOperation<'a, F, R, D>, state: &'a SessionState, payment_data: &mut PaymentData, merchant_key_store: &domain::MerchantKeyStore, @@ -2036,7 +1951,7 @@ pub async fn make_pm_data<'a, F: Clone, R>( storage_scheme: common_enums::enums::MerchantStorageScheme, business_profile: Option<&domain::BusinessProfile>, ) -> RouterResult<( - BoxedOperation<'a, F, R>, + BoxedOperation<'a, F, R, D>, Option, Option, )> { @@ -2535,10 +2450,10 @@ where Some(func(option1?, option2?)) } -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v1"))] pub(super) async fn filter_by_constraints( state: &SessionState, - constraints: &api::PaymentListConstraints, + constraints: &PaymentIntentFetchConstraints, merchant_id: &id_type::MerchantId, key_store: &domain::MerchantKeyStore, storage_scheme: storage_enums::MerchantStorageScheme, @@ -2548,7 +2463,7 @@ pub(super) async fn filter_by_constraints( .filter_payment_intent_by_constraints( &(state).into(), merchant_id, - &constraints.clone().into(), + constraints, key_store, storage_scheme, ) @@ -2979,6 +2894,7 @@ pub async fn verify_payment_intent_time_and_client_secret( }, )?; + #[cfg(feature = "v1")] let payment_intent = db .find_payment_intent_by_payment_id_merchant_id( &state.into(), @@ -2990,6 +2906,17 @@ pub async fn verify_payment_intent_time_and_client_secret( .await .change_context(errors::ApiErrorResponse::PaymentNotFound)?; + #[cfg(feature = "v2")] + let payment_intent = db + .find_payment_intent_by_id( + &state.into(), + &payment_id, + key_store, + merchant_account.storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::PaymentNotFound)?; + authenticate_client_secret(Some(&cs), &payment_intent)?; Ok(payment_intent) }) @@ -3038,6 +2965,7 @@ pub(crate) fn get_payment_id_from_client_secret(cs: &str) -> RouterResult, + creds_identifier: Option<&str>, key_store: &domain::MerchantKeyStore, profile_id: &id_type::ProfileId, connector_name: &str, @@ -3365,7 +3302,7 @@ pub async fn get_merchant_connector_account( let key_manager_state: &KeyManagerState = &state.into(); match creds_identifier { Some(creds_identifier) => { - let key = merchant_id.get_creds_identifier_key(&creds_identifier); + let key = merchant_id.get_creds_identifier_key(creds_identifier); let cloned_key = key.clone(); let redis_fetch = || async { db.get_redis_conn() @@ -3760,6 +3697,8 @@ impl AttemptType { customer_acceptance: old_payment_attempt.customer_acceptance, organization_id: old_payment_attempt.organization_id, profile_id: old_payment_attempt.profile_id, + shipping_cost: old_payment_attempt.shipping_cost, + order_tax_amount: None, } } @@ -3794,7 +3733,7 @@ impl AttemptType { ) .await .to_duplicate_response(errors::ApiErrorResponse::DuplicatePayment { - payment_id: fetched_payment_intent.payment_id.to_owned(), + payment_id: fetched_payment_intent.get_id().to_owned(), })?; let updated_payment_intent = db @@ -3822,7 +3761,7 @@ impl AttemptType { logger::info!( "manual_retry payment for {:?} with attempt_id {}", - updated_payment_intent.payment_id, + updated_payment_intent.get_id(), new_payment_attempt.attempt_id ); @@ -4019,16 +3958,69 @@ pub async fn get_additional_payment_data( domain::BankRedirectData::Eps { bank_name, .. } => { Some(api_models::payments::AdditionalPaymentData::BankRedirect { bank_name: bank_name.to_owned(), + details: None, }) } domain::BankRedirectData::Ideal { bank_name, .. } => { Some(api_models::payments::AdditionalPaymentData::BankRedirect { bank_name: bank_name.to_owned(), + details: None, }) } - _ => { - Some(api_models::payments::AdditionalPaymentData::BankRedirect { bank_name: None }) + domain::BankRedirectData::BancontactCard { + card_number, + card_exp_month, + card_exp_year, + card_holder_name, + } => Some(api_models::payments::AdditionalPaymentData::BankRedirect { + bank_name: None, + details: Some( + payment_additional_types::BankRedirectDetails::BancontactCard(Box::new( + payment_additional_types::BancontactBankRedirectAdditionalData { + last4: card_number.as_ref().map(|c| c.get_last4()), + card_exp_month: card_exp_month.clone(), + card_exp_year: card_exp_year.clone(), + card_holder_name: card_holder_name.clone(), + }, + )), + ), + }), + domain::BankRedirectData::Blik { blik_code } => { + Some(api_models::payments::AdditionalPaymentData::BankRedirect { + bank_name: None, + details: blik_code.as_ref().map(|blik_code| { + payment_additional_types::BankRedirectDetails::Blik(Box::new( + payment_additional_types::BlikBankRedirectAdditionalData { + blik_code: Some(blik_code.to_owned()), + }, + )) + }), + }) } + domain::BankRedirectData::Giropay { + bank_account_bic, + bank_account_iban, + country, + } => Some(api_models::payments::AdditionalPaymentData::BankRedirect { + bank_name: None, + details: Some(payment_additional_types::BankRedirectDetails::Giropay( + Box::new( + payment_additional_types::GiropayBankRedirectAdditionalData { + bic: bank_account_bic + .as_ref() + .map(|bic| MaskedSortCode::from(bic.to_owned())), + iban: bank_account_iban + .as_ref() + .map(|iban| MaskedIban::from(iban.to_owned())), + country: *country, + }, + ), + )), + }), + _ => Some(api_models::payments::AdditionalPaymentData::BankRedirect { + bank_name: None, + details: None, + }), }, domain::PaymentMethodData::Wallet(wallet) => match wallet { domain::WalletData::ApplePay(apple_pay_wallet_data) => { @@ -4045,14 +4037,20 @@ pub async fn get_additional_payment_data( domain::PaymentMethodData::PayLater(_) => { Some(api_models::payments::AdditionalPaymentData::PayLater { klarna_sdk: None }) } - domain::PaymentMethodData::BankTransfer(_) => { - Some(api_models::payments::AdditionalPaymentData::BankTransfer {}) + domain::PaymentMethodData::BankTransfer(bank_transfer) => { + Some(api_models::payments::AdditionalPaymentData::BankTransfer { + details: Some((*(bank_transfer.to_owned())).into()), + }) } - domain::PaymentMethodData::Crypto(_) => { - Some(api_models::payments::AdditionalPaymentData::Crypto {}) + domain::PaymentMethodData::Crypto(crypto) => { + Some(api_models::payments::AdditionalPaymentData::Crypto { + details: Some(crypto.to_owned().into()), + }) } - domain::PaymentMethodData::BankDebit(_) => { - Some(api_models::payments::AdditionalPaymentData::BankDebit {}) + domain::PaymentMethodData::BankDebit(bank_debit) => { + Some(api_models::payments::AdditionalPaymentData::BankDebit { + details: Some(bank_debit.to_owned().into()), + }) } domain::PaymentMethodData::MandatePayment => { Some(api_models::payments::AdditionalPaymentData::MandatePayment {}) @@ -4060,26 +4058,40 @@ pub async fn get_additional_payment_data( domain::PaymentMethodData::Reward => { Some(api_models::payments::AdditionalPaymentData::Reward {}) } - domain::PaymentMethodData::RealTimePayment(_) => { - Some(api_models::payments::AdditionalPaymentData::RealTimePayment {}) - } - domain::PaymentMethodData::Upi(_) => { - Some(api_models::payments::AdditionalPaymentData::Upi {}) + domain::PaymentMethodData::RealTimePayment(realtime_payment) => Some( + api_models::payments::AdditionalPaymentData::RealTimePayment { + details: Some((*(realtime_payment.to_owned())).into()), + }, + ), + domain::PaymentMethodData::Upi(upi) => { + Some(api_models::payments::AdditionalPaymentData::Upi { + details: Some(upi.to_owned().into()), + }) } - domain::PaymentMethodData::CardRedirect(_) => { - Some(api_models::payments::AdditionalPaymentData::CardRedirect {}) + domain::PaymentMethodData::CardRedirect(card_redirect) => { + Some(api_models::payments::AdditionalPaymentData::CardRedirect { + details: Some(card_redirect.to_owned().into()), + }) } - domain::PaymentMethodData::Voucher(_) => { - Some(api_models::payments::AdditionalPaymentData::Voucher {}) + domain::PaymentMethodData::Voucher(voucher) => { + Some(api_models::payments::AdditionalPaymentData::Voucher { + details: Some(voucher.to_owned().into()), + }) } - domain::PaymentMethodData::GiftCard(_) => { - Some(api_models::payments::AdditionalPaymentData::GiftCard {}) + domain::PaymentMethodData::GiftCard(gift_card) => { + Some(api_models::payments::AdditionalPaymentData::GiftCard { + details: Some((*(gift_card.to_owned())).into()), + }) } - domain::PaymentMethodData::CardToken(_) => { - Some(api_models::payments::AdditionalPaymentData::CardToken {}) + domain::PaymentMethodData::CardToken(card_token) => { + Some(api_models::payments::AdditionalPaymentData::CardToken { + details: Some(card_token.to_owned().into()), + }) } - domain::PaymentMethodData::OpenBanking(_) => { - Some(api_models::payments::AdditionalPaymentData::OpenBanking {}) + domain::PaymentMethodData::OpenBanking(open_banking) => { + Some(api_models::payments::AdditionalPaymentData::OpenBanking { + details: Some(open_banking.to_owned().into()), + }) } domain::PaymentMethodData::NetworkToken(_) => None, } @@ -4282,10 +4294,10 @@ pub fn get_applepay_metadata( } #[cfg(feature = "retry")] -pub async fn get_apple_pay_retryable_connectors( +pub async fn get_apple_pay_retryable_connectors( state: &SessionState, merchant_account: &domain::MerchantAccount, - payment_data: &mut PaymentData, + payment_data: &D, key_store: &domain::MerchantKeyStore, pre_routing_connector_data_list: &[api::ConnectorData], merchant_connector_id: Option<&id_type::MerchantConnectorAccountId>, @@ -4293,6 +4305,7 @@ pub async fn get_apple_pay_retryable_connectors( ) -> CustomResult>, errors::ApiErrorResponse> where F: Send + Clone, + D: payments::OperationSessionGetters + Send, { let profile_id = business_profile.get_id(); @@ -4303,7 +4316,7 @@ where let merchant_connector_account_type = get_merchant_connector_account( state, merchant_account.get_id(), - payment_data.creds_identifier.to_owned(), + payment_data.get_creds_identifier(), key_store, profile_id, &pre_decided_connector_data_first.connector_name.to_string(), @@ -5213,21 +5226,30 @@ pub async fn config_skip_saving_wallet_at_connector( }) } -pub async fn override_setup_future_usage_to_on_session( +pub async fn override_setup_future_usage_to_on_session( db: &dyn StorageInterface, - payment_data: &mut PaymentData, -) -> CustomResult<(), errors::ApiErrorResponse> { - if payment_data.payment_intent.setup_future_usage == Some(enums::FutureUsage::OffSession) { - let skip_saving_wallet_at_connector_optional = - config_skip_saving_wallet_at_connector(db, &payment_data.payment_intent.merchant_id) - .await?; + payment_data: &mut D, +) -> CustomResult<(), errors::ApiErrorResponse> +where + F: Clone, + D: payments::OperationSessionGetters + payments::OperationSessionSetters + Send, +{ + if payment_data.get_payment_intent().setup_future_usage == Some(enums::FutureUsage::OffSession) + { + let skip_saving_wallet_at_connector_optional = config_skip_saving_wallet_at_connector( + db, + &payment_data.get_payment_intent().merchant_id, + ) + .await?; if let Some(skip_saving_wallet_at_connector) = skip_saving_wallet_at_connector_optional { - if let Some(payment_method_type) = payment_data.payment_attempt.payment_method_type { + if let Some(payment_method_type) = + payment_data.get_payment_attempt().payment_method_type + { if skip_saving_wallet_at_connector.contains(&payment_method_type) { logger::debug!("Override setup_future_usage from off_session to on_session based on the merchant's skip_saving_wallet_at_connector configuration to avoid creating a connector mandate."); - payment_data.payment_intent.setup_future_usage = - Some(enums::FutureUsage::OnSession); + payment_data + .set_setup_future_usage_in_payment_intent(enums::FutureUsage::OnSession); } } }; diff --git a/crates/router/src/core/payments/operations.rs b/crates/router/src/core/payments/operations.rs index 0eef08c104cf..e4692075914b 100644 --- a/crates/router/src/core/payments/operations.rs +++ b/crates/router/src/core/payments/operations.rs @@ -1,31 +1,47 @@ +#[cfg(feature = "v1")] pub mod payment_approve; +#[cfg(feature = "v1")] pub mod payment_cancel; +#[cfg(feature = "v1")] pub mod payment_capture; +#[cfg(feature = "v1")] pub mod payment_complete_authorize; +#[cfg(feature = "v1")] pub mod payment_confirm; +#[cfg(feature = "v1")] pub mod payment_create; +#[cfg(feature = "v1")] pub mod payment_reject; pub mod payment_response; +#[cfg(feature = "v1")] pub mod payment_session; +#[cfg(feature = "v1")] pub mod payment_start; +#[cfg(feature = "v1")] pub mod payment_status; +#[cfg(feature = "v1")] pub mod payment_update; +#[cfg(feature = "v1")] pub mod payments_incremental_authorization; +#[cfg(feature = "v1")] +pub mod tax_calculation; use api_models::enums::FrmSuggestion; use async_trait::async_trait; use error_stack::{report, ResultExt}; use router_env::{instrument, tracing}; +pub use self::payment_response::PaymentResponse; +#[cfg(feature = "v1")] pub use self::{ payment_approve::PaymentApprove, payment_cancel::PaymentCancel, payment_capture::PaymentCapture, payment_confirm::PaymentConfirm, - payment_create::PaymentCreate, payment_reject::PaymentReject, - payment_response::PaymentResponse, payment_session::PaymentSession, + payment_create::PaymentCreate, payment_reject::PaymentReject, payment_session::PaymentSession, payment_start::PaymentStart, payment_status::PaymentStatus, payment_update::PaymentUpdate, payments_incremental_authorization::PaymentIncrementalAuthorization, + tax_calculation::PaymentSessionUpdate, }; -use super::{helpers, CustomerDetails, PaymentData}; +use super::{helpers, CustomerDetails, OperationSessionGetters, OperationSessionSetters}; use crate::{ core::errors::{self, CustomResult, RouterResult}, routes::{app::ReqState, SessionState}, @@ -39,32 +55,33 @@ use crate::{ }, }; -pub type BoxedOperation<'a, F, T> = Box + Send + Sync + 'a>; +pub type BoxedOperation<'a, F, T, D> = Box + Send + Sync + 'a>; pub trait Operation: Send + std::fmt::Debug { - fn to_validate_request(&self) -> RouterResult<&(dyn ValidateRequest + Send + Sync)> { + type Data; + fn to_validate_request( + &self, + ) -> RouterResult<&(dyn ValidateRequest + Send + Sync)> { Err(report!(errors::ApiErrorResponse::InternalServerError)) .attach_printable_lazy(|| format!("validate request interface not found for {self:?}")) } - fn to_get_tracker( - &self, - ) -> RouterResult<&(dyn GetTracker, T> + Send + Sync)> { + fn to_get_tracker(&self) -> RouterResult<&(dyn GetTracker + Send + Sync)> { Err(report!(errors::ApiErrorResponse::InternalServerError)) .attach_printable_lazy(|| format!("get tracker interface not found for {self:?}")) } - fn to_domain(&self) -> RouterResult<&dyn Domain> { + fn to_domain(&self) -> RouterResult<&dyn Domain> { Err(report!(errors::ApiErrorResponse::InternalServerError)) .attach_printable_lazy(|| format!("domain interface not found for {self:?}")) } fn to_update_tracker( &self, - ) -> RouterResult<&(dyn UpdateTracker, T> + Send + Sync)> { + ) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> { Err(report!(errors::ApiErrorResponse::InternalServerError)) .attach_printable_lazy(|| format!("update tracker interface not found for {self:?}")) } fn to_post_update_tracker( &self, - ) -> RouterResult<&(dyn PostUpdateTracker, T> + Send + Sync)> { + ) -> RouterResult<&(dyn PostUpdateTracker + Send + Sync)> { Err(report!(errors::ApiErrorResponse::InternalServerError)).attach_printable_lazy(|| { format!("post connector update tracker not found for {self:?}") }) @@ -80,18 +97,18 @@ pub struct ValidateResult { } #[allow(clippy::type_complexity)] -pub trait ValidateRequest { +pub trait ValidateRequest { fn validate_request<'b>( &'b self, request: &R, merchant_account: &domain::MerchantAccount, - ) -> RouterResult<(BoxedOperation<'b, F, R>, ValidateResult)>; + ) -> RouterResult<(BoxedOperation<'b, F, R, D>, ValidateResult)>; } -pub struct GetTrackerResponse<'a, F: Clone, R> { - pub operation: BoxedOperation<'a, F, R>, +pub struct GetTrackerResponse<'a, F: Clone, R, D> { + pub operation: BoxedOperation<'a, F, R, D>, pub customer_details: Option, - pub payment_data: PaymentData, + pub payment_data: D, pub business_profile: domain::BusinessProfile, pub mandate_type: Option, } @@ -108,32 +125,32 @@ pub trait GetTracker: Send { mechant_key_store: &domain::MerchantKeyStore, auth_flow: services::AuthFlow, header_payload: &api::HeaderPayload, - ) -> RouterResult>; + ) -> RouterResult>; } #[async_trait] -pub trait Domain: Send + Sync { +pub trait Domain: Send + Sync { /// This will fetch customer details, (this operation is flow specific) async fn get_or_create_customer_details<'a>( &'a self, state: &SessionState, - payment_data: &mut PaymentData, + payment_data: &mut D, request: Option, merchant_key_store: &domain::MerchantKeyStore, storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult<(BoxedOperation<'a, F, R>, Option), errors::StorageError>; + ) -> CustomResult<(BoxedOperation<'a, F, R, D>, Option), errors::StorageError>; #[allow(clippy::too_many_arguments)] async fn make_pm_data<'a>( &'a self, state: &'a SessionState, - payment_data: &mut PaymentData, + payment_data: &mut D, storage_scheme: enums::MerchantStorageScheme, merchant_key_store: &domain::MerchantKeyStore, customer: &Option, business_profile: Option<&domain::BusinessProfile>, ) -> RouterResult<( - BoxedOperation<'a, F, R>, + BoxedOperation<'a, F, R, D>, Option, Option, )>; @@ -160,7 +177,7 @@ pub trait Domain: Send + Sync { async fn populate_payment_data<'a>( &'a self, _state: &SessionState, - _payment_data: &mut PaymentData, + _payment_data: &mut D, _merchant_account: &domain::MerchantAccount, ) -> CustomResult<(), errors::ApiErrorResponse> { Ok(()) @@ -170,7 +187,7 @@ pub trait Domain: Send + Sync { async fn call_external_three_ds_authentication_if_eligible<'a>( &'a self, _state: &SessionState, - _payment_data: &mut PaymentData, + _payment_data: &mut D, _should_continue_confirm_transaction: &mut bool, _connector_call_type: &ConnectorCallType, _merchant_account: &domain::BusinessProfile, @@ -180,13 +197,26 @@ pub trait Domain: Send + Sync { Ok(()) } + #[allow(clippy::too_many_arguments)] + async fn payments_dynamic_tax_calculation<'a>( + &'a self, + _state: &SessionState, + _payment_data: &mut D, + _connector_call_type: &ConnectorCallType, + _business_profile: &domain::BusinessProfile, + _key_store: &domain::MerchantKeyStore, + _merchant_account: &domain::MerchantAccount, + ) -> CustomResult<(), errors::ApiErrorResponse> { + Ok(()) + } + #[instrument(skip_all)] async fn guard_payment_against_blocklist<'a>( &'a self, _state: &SessionState, _merchant_account: &domain::MerchantAccount, _key_store: &domain::MerchantKeyStore, - _payment_data: &mut PaymentData, + _payment_data: &mut D, ) -> CustomResult { Ok(false) } @@ -196,7 +226,7 @@ pub trait Domain: Send + Sync { _state: &SessionState, _payment_id: &common_utils::id_type::PaymentId, _business_profile: &domain::BusinessProfile, - _payment_method_data: &Option, + _payment_method_data: Option<&domain::PaymentMethodData>, ) -> CustomResult<(), errors::ApiErrorResponse> { Ok(()) } @@ -216,7 +246,7 @@ pub trait UpdateTracker: Send { mechant_key_store: &domain::MerchantKeyStore, frm_suggestion: Option, header_payload: api::HeaderPayload, - ) -> RouterResult<(BoxedOperation<'b, F, Req>, D)> + ) -> RouterResult<(BoxedOperation<'b, F, Req, D>, D)> where F: 'b + Send; } @@ -243,7 +273,7 @@ pub trait PostUpdateTracker: Send { _resp: &types::RouterData, _merchant_account: &domain::MerchantAccount, _key_store: &domain::MerchantKeyStore, - _payment_data: &mut PaymentData, + _payment_data: &mut D, _business_profile: &domain::BusinessProfile, ) -> CustomResult<(), errors::ApiErrorResponse> where @@ -254,38 +284,72 @@ pub trait PostUpdateTracker: Send { } #[async_trait] -impl> - Domain for Op +impl< + D, + F: Clone + Send, + Op: Send + Sync + Operation, + > Domain for Op where - for<'a> &'a Op: Operation, + for<'a> &'a Op: Operation, + D: OperationSessionGetters + OperationSessionSetters + Send, { #[instrument(skip_all)] + #[cfg(feature = "v1")] + async fn get_or_create_customer_details<'a>( + &'a self, + state: &SessionState, + payment_data: &mut D, + _request: Option, + merchant_key_store: &domain::MerchantKeyStore, + storage_scheme: enums::MerchantStorageScheme, + ) -> CustomResult< + ( + BoxedOperation<'a, F, api::PaymentsRetrieveRequest, D>, + Option, + ), + errors::StorageError, + > { + let db = &*state.store; + let customer = match payment_data.get_payment_intent().customer_id.as_ref() { + None => None, + Some(customer_id) => { + // This function is to retrieve customer details. If the customer is deleted, it returns + // customer details that contains the fields as Redacted + db.find_customer_optional_with_redacted_customer_details_by_customer_id_merchant_id( + &state.into(), + customer_id, + &merchant_key_store.merchant_id, + merchant_key_store, + storage_scheme, + ) + .await? + } + }; + + if let Some(email) = customer.as_ref().and_then(|inner| inner.email.clone()) { + payment_data.set_email_if_not_present(email.into()); + } + + Ok((Box::new(self), customer)) + } + + #[instrument(skip_all)] + #[cfg(feature = "v2")] async fn get_or_create_customer_details<'a>( &'a self, state: &SessionState, - payment_data: &mut PaymentData, + payment_data: &mut D, _request: Option, merchant_key_store: &domain::MerchantKeyStore, storage_scheme: enums::MerchantStorageScheme, ) -> CustomResult< ( - BoxedOperation<'a, F, api::PaymentsRetrieveRequest>, + BoxedOperation<'a, F, api::PaymentsRetrieveRequest, D>, Option, ), errors::StorageError, > { - Ok(( - Box::new(self), - helpers::get_customer_details_even_for_redacted_customer( - state, - payment_data.payment_intent.customer_id.clone(), - &merchant_key_store.merchant_id, - payment_data, - merchant_key_store, - storage_scheme, - ) - .await?, - )) + todo!() } async fn get_connector<'a>( @@ -303,13 +367,13 @@ where async fn make_pm_data<'a>( &'a self, _state: &'a SessionState, - _payment_data: &mut PaymentData, + _payment_data: &mut D, _storage_scheme: enums::MerchantStorageScheme, _merchant_key_store: &domain::MerchantKeyStore, _customer: &Option, _business_profile: Option<&domain::BusinessProfile>, ) -> RouterResult<( - BoxedOperation<'a, F, api::PaymentsRetrieveRequest>, + BoxedOperation<'a, F, api::PaymentsRetrieveRequest, D>, Option, Option, )> { @@ -322,57 +386,88 @@ where _state: &SessionState, _merchant_account: &domain::MerchantAccount, _key_store: &domain::MerchantKeyStore, - _payment_data: &mut PaymentData, + _payment_data: &mut D, ) -> CustomResult { Ok(false) } } #[async_trait] -impl> - Domain for Op +impl> + Domain for Op where - for<'a> &'a Op: Operation, + for<'a> &'a Op: Operation, + D: OperationSessionGetters + OperationSessionSetters + Send, { #[instrument(skip_all)] + #[cfg(feature = "v1")] async fn get_or_create_customer_details<'a>( &'a self, state: &SessionState, - payment_data: &mut PaymentData, + payment_data: &mut D, _request: Option, merchant_key_store: &domain::MerchantKeyStore, storage_scheme: enums::MerchantStorageScheme, ) -> CustomResult< ( - BoxedOperation<'a, F, api::PaymentsCaptureRequest>, + BoxedOperation<'a, F, api::PaymentsCaptureRequest, D>, Option, ), errors::StorageError, > { - Ok(( - Box::new(self), - helpers::get_customer_from_details( - state, - payment_data.payment_intent.customer_id.clone(), - &merchant_key_store.merchant_id, - payment_data, - merchant_key_store, - storage_scheme, - ) - .await?, - )) + let db = &*state.store; + + let customer = match payment_data.get_payment_intent().customer_id.as_ref() { + None => None, + Some(customer_id) => { + db.find_customer_optional_by_customer_id_merchant_id( + &state.into(), + customer_id, + &merchant_key_store.merchant_id, + merchant_key_store, + storage_scheme, + ) + .await? + } + }; + + if let Some(email) = customer.as_ref().and_then(|inner| inner.email.clone()) { + payment_data.set_email_if_not_present(email.into()); + } + + Ok((Box::new(self), customer)) } + + #[instrument(skip_all)] + #[cfg(feature = "v2")] + async fn get_or_create_customer_details<'a>( + &'a self, + state: &SessionState, + payment_data: &mut D, + _request: Option, + merchant_key_store: &domain::MerchantKeyStore, + storage_scheme: enums::MerchantStorageScheme, + ) -> CustomResult< + ( + BoxedOperation<'a, F, api::PaymentsCaptureRequest, D>, + Option, + ), + errors::StorageError, + > { + todo!() + } + #[instrument(skip_all)] async fn make_pm_data<'a>( &'a self, _state: &'a SessionState, - _payment_data: &mut PaymentData, + _payment_data: &mut D, _storage_scheme: enums::MerchantStorageScheme, _merchant_key_store: &domain::MerchantKeyStore, _customer: &Option, _business_profile: Option<&domain::BusinessProfile>, ) -> RouterResult<( - BoxedOperation<'a, F, api::PaymentsCaptureRequest>, + BoxedOperation<'a, F, api::PaymentsCaptureRequest, D>, Option, Option, )> { @@ -396,58 +491,88 @@ where _state: &SessionState, _merchant_account: &domain::MerchantAccount, _key_store: &domain::MerchantKeyStore, - _payment_data: &mut PaymentData, + _payment_data: &mut D, ) -> CustomResult { Ok(false) } } #[async_trait] -impl> - Domain for Op +impl> + Domain for Op where - for<'a> &'a Op: Operation, + for<'a> &'a Op: Operation, + D: OperationSessionGetters + OperationSessionSetters + Send, { #[instrument(skip_all)] + #[cfg(feature = "v1")] + async fn get_or_create_customer_details<'a>( + &'a self, + state: &SessionState, + payment_data: &mut D, + _request: Option, + merchant_key_store: &domain::MerchantKeyStore, + storage_scheme: enums::MerchantStorageScheme, + ) -> CustomResult< + ( + BoxedOperation<'a, F, api::PaymentsCancelRequest, D>, + Option, + ), + errors::StorageError, + > { + let db = &*state.store; + + let customer = match payment_data.get_payment_intent().customer_id.as_ref() { + None => None, + Some(customer_id) => { + db.find_customer_optional_by_customer_id_merchant_id( + &state.into(), + customer_id, + &merchant_key_store.merchant_id, + merchant_key_store, + storage_scheme, + ) + .await? + } + }; + + if let Some(email) = customer.as_ref().and_then(|inner| inner.email.clone()) { + payment_data.set_email_if_not_present(email.into()); + } + + Ok((Box::new(self), customer)) + } + + #[instrument(skip_all)] + #[cfg(feature = "v2")] async fn get_or_create_customer_details<'a>( &'a self, state: &SessionState, - payment_data: &mut PaymentData, + payment_data: &mut D, _request: Option, merchant_key_store: &domain::MerchantKeyStore, storage_scheme: enums::MerchantStorageScheme, ) -> CustomResult< ( - BoxedOperation<'a, F, api::PaymentsCancelRequest>, + BoxedOperation<'a, F, api::PaymentsCancelRequest, D>, Option, ), errors::StorageError, > { - Ok(( - Box::new(self), - helpers::get_customer_from_details( - state, - payment_data.payment_intent.customer_id.clone(), - &merchant_key_store.merchant_id, - payment_data, - merchant_key_store, - storage_scheme, - ) - .await?, - )) + todo!() } #[instrument(skip_all)] async fn make_pm_data<'a>( &'a self, _state: &'a SessionState, - _payment_data: &mut PaymentData, + _payment_data: &mut D, _storage_scheme: enums::MerchantStorageScheme, _merchant_key_store: &domain::MerchantKeyStore, _customer: &Option, _business_profile: Option<&domain::BusinessProfile>, ) -> RouterResult<( - BoxedOperation<'a, F, api::PaymentsCancelRequest>, + BoxedOperation<'a, F, api::PaymentsCancelRequest, D>, Option, Option, )> { @@ -471,29 +596,29 @@ where _state: &SessionState, _merchant_account: &domain::MerchantAccount, _key_store: &domain::MerchantKeyStore, - _payment_data: &mut PaymentData, + _payment_data: &mut D, ) -> CustomResult { Ok(false) } } #[async_trait] -impl> - Domain for Op +impl> + Domain for Op where - for<'a> &'a Op: Operation, + for<'a> &'a Op: Operation, { #[instrument(skip_all)] async fn get_or_create_customer_details<'a>( &'a self, _state: &SessionState, - _payment_data: &mut PaymentData, + _payment_data: &mut D, _request: Option, _merchant_key_store: &domain::MerchantKeyStore, _storage_scheme: enums::MerchantStorageScheme, ) -> CustomResult< ( - BoxedOperation<'a, F, api::PaymentsRejectRequest>, + BoxedOperation<'a, F, api::PaymentsRejectRequest, D>, Option, ), errors::StorageError, @@ -505,13 +630,13 @@ where async fn make_pm_data<'a>( &'a self, _state: &'a SessionState, - _payment_data: &mut PaymentData, + _payment_data: &mut D, _storage_scheme: enums::MerchantStorageScheme, _merchant_key_store: &domain::MerchantKeyStore, _customer: &Option, _business_profile: Option<&domain::BusinessProfile>, ) -> RouterResult<( - BoxedOperation<'a, F, api::PaymentsRejectRequest>, + BoxedOperation<'a, F, api::PaymentsRejectRequest, D>, Option, Option, )> { @@ -535,7 +660,7 @@ where _state: &SessionState, _merchant_account: &domain::MerchantAccount, _key_store: &domain::MerchantKeyStore, - _payment_data: &mut PaymentData, + _payment_data: &mut D, ) -> CustomResult { Ok(false) } diff --git a/crates/router/src/core/payments/operations/payment_approve.rs b/crates/router/src/core/payments/operations/payment_approve.rs index 3ad4cff9ef0f..38cf72a3e2f7 100644 --- a/crates/router/src/core/payments/operations/payment_approve.rs +++ b/crates/router/src/core/payments/operations/payment_approve.rs @@ -27,6 +27,9 @@ use crate::{ #[operation(operations = "all", flow = "capture")] pub struct PaymentApprove; +type PaymentApproveOperation<'a, F> = + BoxedOperation<'a, F, api::PaymentsCaptureRequest, PaymentData>; + #[async_trait] impl GetTracker, api::PaymentsCaptureRequest> for PaymentApprove @@ -41,7 +44,9 @@ impl GetTracker, api::PaymentsCaptureRequest> key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, _header_payload: &api::HeaderPayload, - ) -> RouterResult> { + ) -> RouterResult< + operations::GetTrackerResponse<'a, F, api::PaymentsCaptureRequest, PaymentData>, + > { let db = &*state.store; let key_manager_state = &state.into(); let merchant_id = merchant_account.get_id(); @@ -182,6 +187,7 @@ impl GetTracker, api::PaymentsCaptureRequest> authentication: None, recurring_details: None, poll_config: None, + tax_data: None, }; let get_trackers_response = operations::GetTrackerResponse { @@ -210,10 +216,7 @@ impl UpdateTracker, api::PaymentsCaptureRequest> for key_store: &domain::MerchantKeyStore, frm_suggestion: Option, _header_payload: api::HeaderPayload, - ) -> RouterResult<( - BoxedOperation<'b, F, api::PaymentsCaptureRequest>, - PaymentData, - )> + ) -> RouterResult<(PaymentApproveOperation<'b, F>, PaymentData)> where F: 'b + Send, { @@ -254,16 +257,15 @@ impl UpdateTracker, api::PaymentsCaptureRequest> for } } -impl ValidateRequest for PaymentApprove { +impl ValidateRequest> + for PaymentApprove +{ #[instrument(skip_all)] fn validate_request<'a, 'b>( &'b self, request: &api::PaymentsCaptureRequest, merchant_account: &'a domain::MerchantAccount, - ) -> RouterResult<( - BoxedOperation<'b, F, api::PaymentsCaptureRequest>, - operations::ValidateResult, - )> { + ) -> RouterResult<(PaymentApproveOperation<'b, F>, operations::ValidateResult)> { let request_merchant_id = request.merchant_id.as_ref(); helpers::validate_merchant_id(merchant_account.get_id(), request_merchant_id) .change_context(errors::ApiErrorResponse::InvalidDataFormat { diff --git a/crates/router/src/core/payments/operations/payment_cancel.rs b/crates/router/src/core/payments/operations/payment_cancel.rs index 689184a619eb..d0a896bc204d 100644 --- a/crates/router/src/core/payments/operations/payment_cancel.rs +++ b/crates/router/src/core/payments/operations/payment_cancel.rs @@ -29,6 +29,9 @@ use crate::{ #[operation(operations = "all", flow = "cancel")] pub struct PaymentCancel; +type PaymentCancelOperation<'b, F> = + BoxedOperation<'b, F, api::PaymentsCancelRequest, PaymentData>; + #[async_trait] impl GetTracker, api::PaymentsCancelRequest> for PaymentCancel { #[instrument(skip_all)] @@ -41,7 +44,9 @@ impl GetTracker, api::PaymentsCancelRequest> key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, _header_payload: &api::HeaderPayload, - ) -> RouterResult> { + ) -> RouterResult< + operations::GetTrackerResponse<'a, F, api::PaymentsCancelRequest, PaymentData>, + > { let db = &*state.store; let key_manager_state = &state.into(); @@ -196,6 +201,7 @@ impl GetTracker, api::PaymentsCancelRequest> authentication: None, recurring_details: None, poll_config: None, + tax_data: None, }; let get_trackers_response = operations::GetTrackerResponse { @@ -224,10 +230,7 @@ impl UpdateTracker, api::PaymentsCancelRequest> for key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, _header_payload: api::HeaderPayload, - ) -> RouterResult<( - BoxedOperation<'b, F, api::PaymentsCancelRequest>, - PaymentData, - )> + ) -> RouterResult<(PaymentCancelOperation<'b, F>, PaymentData)> where F: 'b + Send, { @@ -282,16 +285,15 @@ impl UpdateTracker, api::PaymentsCancelRequest> for } } -impl ValidateRequest for PaymentCancel { +impl ValidateRequest> + for PaymentCancel +{ #[instrument(skip_all)] fn validate_request<'a, 'b>( &'b self, request: &api::PaymentsCancelRequest, merchant_account: &'a domain::MerchantAccount, - ) -> RouterResult<( - BoxedOperation<'b, F, api::PaymentsCancelRequest>, - operations::ValidateResult, - )> { + ) -> RouterResult<(PaymentCancelOperation<'b, F>, operations::ValidateResult)> { Ok(( Box::new(self), operations::ValidateResult { diff --git a/crates/router/src/core/payments/operations/payment_capture.rs b/crates/router/src/core/payments/operations/payment_capture.rs index 5c04fc99eff0..1b1b29e9160c 100644 --- a/crates/router/src/core/payments/operations/payment_capture.rs +++ b/crates/router/src/core/payments/operations/payment_capture.rs @@ -28,6 +28,9 @@ use crate::{ #[operation(operations = "all", flow = "capture")] pub struct PaymentCapture; +type PaymentCaptureOperation<'b, F> = + BoxedOperation<'b, F, api::PaymentsCaptureRequest, payments::PaymentData>; + #[async_trait] impl GetTracker, api::PaymentsCaptureRequest> for PaymentCapture @@ -42,7 +45,14 @@ impl GetTracker, api::PaymentsCaptu key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, _header_payload: &api::HeaderPayload, - ) -> RouterResult> { + ) -> RouterResult< + operations::GetTrackerResponse< + 'a, + F, + api::PaymentsCaptureRequest, + payments::PaymentData, + >, + > { let db = &*state.store; let key_manager_state = &state.into(); @@ -242,6 +252,7 @@ impl GetTracker, api::PaymentsCaptu authentication: None, recurring_details: None, poll_config: None, + tax_data: None, }; let get_trackers_response = operations::GetTrackerResponse { @@ -272,10 +283,7 @@ impl UpdateTracker, api::PaymentsCaptureRe _mechant_key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, _header_payload: api::HeaderPayload, - ) -> RouterResult<( - BoxedOperation<'b, F, api::PaymentsCaptureRequest>, - payments::PaymentData, - )> + ) -> RouterResult<(PaymentCaptureOperation<'b, F>, payments::PaymentData)> where F: 'b + Send, { @@ -317,16 +325,15 @@ impl UpdateTracker, api::PaymentsCaptureRe } } -impl ValidateRequest for PaymentCapture { +impl ValidateRequest> + for PaymentCapture +{ #[instrument(skip_all)] fn validate_request<'a, 'b>( &'b self, request: &api::PaymentsCaptureRequest, merchant_account: &'a domain::MerchantAccount, - ) -> RouterResult<( - BoxedOperation<'b, F, api::PaymentsCaptureRequest>, - operations::ValidateResult, - )> { + ) -> RouterResult<(PaymentCaptureOperation<'b, F>, operations::ValidateResult)> { Ok(( Box::new(self), operations::ValidateResult { diff --git a/crates/router/src/core/payments/operations/payment_complete_authorize.rs b/crates/router/src/core/payments/operations/payment_complete_authorize.rs index f45872c2a883..00eb0a3bc3e2 100644 --- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs +++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs @@ -30,6 +30,9 @@ use crate::{ #[operation(operations = "all", flow = "authorize")] pub struct CompleteAuthorize; +type CompleteAuthorizeOperation<'b, F> = + BoxedOperation<'b, F, api::PaymentsRequest, PaymentData>; + #[async_trait] impl GetTracker, api::PaymentsRequest> for CompleteAuthorize { #[instrument(skip_all)] @@ -42,7 +45,8 @@ impl GetTracker, api::PaymentsRequest> for Co key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, _header_payload: &api::HeaderPayload, - ) -> RouterResult> { + ) -> RouterResult>> + { let db = &*state.store; let key_manager_state = &state.into(); @@ -107,6 +111,7 @@ impl GetTracker, api::PaymentsRequest> for Co payment_intent.setup_future_usage, request.customer_acceptance.clone(), request.payment_token.clone(), + payment_attempt.payment_method, ) .change_context(errors::ApiErrorResponse::MandateValidationFailed { reason: "Expected one out of recurring_details and mandate_data but got both".into(), @@ -338,6 +343,7 @@ impl GetTracker, api::PaymentsRequest> for Co authentication: None, recurring_details, poll_config: None, + tax_data: None, }; let customer_details = Some(CustomerDetails { @@ -361,7 +367,7 @@ impl GetTracker, api::PaymentsRequest> for Co } #[async_trait] -impl Domain for CompleteAuthorize { +impl Domain> for CompleteAuthorize { #[instrument(skip_all)] async fn get_or_create_customer_details<'a>( &'a self, @@ -371,10 +377,7 @@ impl Domain for CompleteAuthorize { key_store: &domain::MerchantKeyStore, storage_scheme: common_enums::enums::MerchantStorageScheme, ) -> CustomResult< - ( - BoxedOperation<'a, F, api::PaymentsRequest>, - Option, - ), + (CompleteAuthorizeOperation<'a, F>, Option), errors::StorageError, > { helpers::create_customer_if_not_exist( @@ -399,7 +402,7 @@ impl Domain for CompleteAuthorize { customer: &Option, business_profile: Option<&domain::BusinessProfile>, ) -> RouterResult<( - BoxedOperation<'a, F, api::PaymentsRequest>, + CompleteAuthorizeOperation<'a, F>, Option, Option, )> { @@ -466,7 +469,7 @@ impl UpdateTracker, api::PaymentsRequest> for Comple key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, _header_payload: api::HeaderPayload, - ) -> RouterResult<(BoxedOperation<'b, F, api::PaymentsRequest>, PaymentData)> + ) -> RouterResult<(CompleteAuthorizeOperation<'b, F>, PaymentData)> where F: 'b + Send, { @@ -493,14 +496,16 @@ impl UpdateTracker, api::PaymentsRequest> for Comple } } -impl ValidateRequest for CompleteAuthorize { +impl ValidateRequest> + for CompleteAuthorize +{ #[instrument(skip_all)] fn validate_request<'a, 'b>( &'b self, request: &api::PaymentsRequest, merchant_account: &'a domain::MerchantAccount, ) -> RouterResult<( - BoxedOperation<'b, F, api::PaymentsRequest>, + CompleteAuthorizeOperation<'b, F>, operations::ValidateResult, )> { let payment_id = request diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 3edefd294664..95e254f3411b 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -52,6 +52,9 @@ use crate::{ #[derive(Debug, Clone, Copy, PaymentOperation)] #[operation(operations = "all", flow = "authorize")] pub struct PaymentConfirm; + +type PaymentConfirmOperation<'b, F> = BoxedOperation<'b, F, api::PaymentsRequest, PaymentData>; + #[async_trait] impl GetTracker, api::PaymentsRequest> for PaymentConfirm { #[instrument(skip_all)] @@ -64,7 +67,8 @@ impl GetTracker, api::PaymentsRequest> for Pa key_store: &domain::MerchantKeyStore, auth_flow: services::AuthFlow, header_payload: &api::HeaderPayload, - ) -> RouterResult> { + ) -> RouterResult>> + { let key_manager_state = &state.into(); let merchant_id = merchant_account.get_id(); @@ -520,6 +524,7 @@ impl GetTracker, api::PaymentsRequest> for Pa payment_intent.setup_future_usage, request.customer_acceptance.clone(), request.payment_token.clone(), + payment_attempt.payment_method.or(request.payment_method), ) .change_context(errors::ApiErrorResponse::MandateValidationFailed { reason: "Expected one out of recurring_details and mandate_data but got both".into(), @@ -722,6 +727,7 @@ impl GetTracker, api::PaymentsRequest> for Pa authentication: None, recurring_details, poll_config: None, + tax_data: None, }; let get_trackers_response = operations::GetTrackerResponse { @@ -737,7 +743,7 @@ impl GetTracker, api::PaymentsRequest> for Pa } #[async_trait] -impl Domain for PaymentConfirm { +impl Domain> for PaymentConfirm { #[instrument(skip_all)] async fn get_or_create_customer_details<'a>( &'a self, @@ -747,10 +753,7 @@ impl Domain for PaymentConfirm { key_store: &domain::MerchantKeyStore, storage_scheme: common_enums::enums::MerchantStorageScheme, ) -> CustomResult< - ( - BoxedOperation<'a, F, api::PaymentsRequest>, - Option, - ), + (PaymentConfirmOperation<'a, F>, Option), errors::StorageError, > { helpers::create_customer_if_not_exist( @@ -775,7 +778,7 @@ impl Domain for PaymentConfirm { customer: &Option, business_profile: Option<&domain::BusinessProfile>, ) -> RouterResult<( - BoxedOperation<'a, F, api::PaymentsRequest>, + PaymentConfirmOperation<'a, F>, Option, Option, )> { @@ -963,7 +966,7 @@ impl Domain for PaymentConfirm { state: &SessionState, payment_id: &common_utils::id_type::PaymentId, business_profile: &domain::BusinessProfile, - payment_method_data: &Option, + payment_method_data: Option<&domain::PaymentMethodData>, ) -> CustomResult<(), errors::ApiErrorResponse> { if let (Some(true), Some(domain::PaymentMethodData::Card(card)), Some(merchant_config)) = ( business_profile.is_extended_card_info_enabled, @@ -1037,7 +1040,10 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen _key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, _header_payload: api::HeaderPayload, - ) -> RouterResult<(BoxedOperation<'b, F, api::PaymentsRequest>, PaymentData)> + ) -> RouterResult<( + BoxedOperation<'b, F, api::PaymentsRequest, PaymentData>, + PaymentData, + )> where F: 'b + Send, { @@ -1060,7 +1066,10 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen key_store: &domain::MerchantKeyStore, frm_suggestion: Option, header_payload: api::HeaderPayload, - ) -> RouterResult<(BoxedOperation<'b, F, api::PaymentsRequest>, PaymentData)> + ) -> RouterResult<( + BoxedOperation<'b, F, api::PaymentsRequest, PaymentData>, + PaymentData, + )> where F: 'b + Send, { @@ -1270,6 +1279,31 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen None => (None, None, None), }; + let shipping_cost = payment_data.payment_intent.shipping_cost; + + let pmt_order_tax_amount = + payment_data + .payment_intent + .tax_details + .clone() + .and_then(|tax| { + if tax.payment_method_type.clone().map(|a| a.pmt) + == payment_data.payment_attempt.payment_method_type + { + tax.payment_method_type.map(|a| a.order_tax_amount) + } else { + None + } + }); + + let order_tax_amount = pmt_order_tax_amount.or_else(|| { + payment_data + .payment_intent + .tax_details + .clone() + .and_then(|tax| tax.default.map(|a| a.order_tax_amount)) + }); + let payment_attempt_fut = tokio::spawn( async move { m_db.update_payment_attempt_with_attempt_id( @@ -1305,6 +1339,8 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen client_source, client_version, customer_acceptance: payment_data.payment_attempt.customer_acceptance, + shipping_cost, + order_tax_amount, }, storage_scheme, ) @@ -1382,6 +1418,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen billing_details, shipping_details, is_payment_processor_token_flow, + tax_details: None, })), &m_key_store, storage_scheme, @@ -1453,16 +1490,13 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen } } -impl ValidateRequest for PaymentConfirm { +impl ValidateRequest> for PaymentConfirm { #[instrument(skip_all)] fn validate_request<'a, 'b>( &'b self, request: &api::PaymentsRequest, merchant_account: &'a domain::MerchantAccount, - ) -> RouterResult<( - BoxedOperation<'b, F, api::PaymentsRequest>, - operations::ValidateResult, - )> { + ) -> RouterResult<(PaymentConfirmOperation<'b, F>, operations::ValidateResult)> { helpers::validate_customer_information(request)?; if let Some(amount) = request.amount { diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index 6c0b748fc43c..d66565e84e58 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -7,7 +7,7 @@ use api_models::{ use async_trait::async_trait; use common_utils::{ ext_traits::{AsyncExt, Encode, ValueExt}, - types::MinorUnit, + types::{keymanager::KeyManagerState, MinorUnit}, }; use diesel_models::ephemeral_key; use error_stack::{self, ResultExt}; @@ -38,7 +38,8 @@ use crate::{ }, services, types::{ - api::{self, PaymentIdTypeExt}, + self, + api::{self, ConnectorCallType, PaymentIdTypeExt}, domain, storage::{ self, @@ -53,6 +54,8 @@ use crate::{ #[operation(operations = "all", flow = "authorize")] pub struct PaymentCreate; +type PaymentCreateOperation<'a, F> = BoxedOperation<'a, F, api::PaymentsRequest, PaymentData>; + /// The `get_trackers` function for `PaymentsCreate` is an entrypoint for new payments /// This will create all the entities required for a new payment from the request #[async_trait] @@ -67,7 +70,8 @@ impl GetTracker, api::PaymentsRequest> for Pa merchant_key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, header_payload: &api::HeaderPayload, - ) -> RouterResult> { + ) -> RouterResult>> + { let db = &*state.store; let key_manager_state = &state.into(); let ephemeral_key = Self::get_ephemeral_key(request, state, merchant_account).await; @@ -146,6 +150,7 @@ impl GetTracker, api::PaymentsRequest> for Pa request.setup_future_usage, request.customer_acceptance.clone(), request.payment_token.clone(), + request.payment_method, ) .change_context(errors::ApiErrorResponse::MandateValidationFailed { reason: "Expected one out of recurring_details and mandate_data but got both".into(), @@ -532,6 +537,7 @@ impl GetTracker, api::PaymentsRequest> for Pa authentication: None, recurring_details, poll_config: None, + tax_data: None, }; let get_trackers_response = operations::GetTrackerResponse { @@ -547,7 +553,7 @@ impl GetTracker, api::PaymentsRequest> for Pa } #[async_trait] -impl Domain for PaymentCreate { +impl Domain> for PaymentCreate { #[instrument(skip_all)] async fn get_or_create_customer_details<'a>( &'a self, @@ -556,13 +562,8 @@ impl Domain for PaymentCreate { request: Option, key_store: &domain::MerchantKeyStore, storage_scheme: enums::MerchantStorageScheme, - ) -> CustomResult< - ( - BoxedOperation<'a, F, api::PaymentsRequest>, - Option, - ), - errors::StorageError, - > { + ) -> CustomResult<(PaymentCreateOperation<'a, F>, Option), errors::StorageError> + { helpers::create_customer_if_not_exist( state, Box::new(self), @@ -575,6 +576,110 @@ impl Domain for PaymentCreate { .await } + async fn payments_dynamic_tax_calculation<'a>( + &'a self, + state: &SessionState, + payment_data: &mut PaymentData, + _connector_call_type: &ConnectorCallType, + business_profile: &domain::BusinessProfile, + key_store: &domain::MerchantKeyStore, + merchant_account: &domain::MerchantAccount, + ) -> CustomResult<(), errors::ApiErrorResponse> { + let is_tax_connector_enabled = business_profile.get_is_tax_connector_enabled(); + let skip_external_tax_calculation = payment_data + .payment_intent + .skip_external_tax_calculation + .unwrap_or(false); + if is_tax_connector_enabled && !skip_external_tax_calculation { + let db = state.store.as_ref(); + + let key_manager_state: &KeyManagerState = &state.into(); + + let merchant_connector_id = business_profile + .tax_connector_id + .as_ref() + .get_required_value("business_profile.tax_connector_id")?; + + #[cfg(feature = "v1")] + let mca = db + .find_by_merchant_connector_account_merchant_id_merchant_connector_id( + key_manager_state, + &business_profile.merchant_id, + merchant_connector_id, + key_store, + ) + .await + .to_not_found_response( + errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: merchant_connector_id.get_string_repr().to_string(), + }, + )?; + + #[cfg(feature = "v2")] + let mca = db + .find_merchant_connector_account_by_id( + key_manager_state, + merchant_connector_id, + key_store, + ) + .await + .to_not_found_response( + errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: merchant_connector_id.get_string_repr().to_string(), + }, + )?; + + let connector_data = + api::TaxCalculateConnectorData::get_connector_by_name(&mca.connector_name)?; + + let router_data = core_utils::construct_payments_dynamic_tax_calculation_router_data( + state, + merchant_account, + key_store, + payment_data, + &mca, + ) + .await?; + let connector_integration: services::BoxedPaymentConnectorIntegrationInterface< + api::CalculateTax, + types::PaymentsTaxCalculationData, + types::TaxCalculationResponseData, + > = connector_data.connector.get_connector_integration(); + + let response = services::execute_connector_processing_step( + state, + connector_integration, + &router_data, + payments::CallConnectorAction::Trigger, + None, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Tax connector Response Failed")?; + + let tax_response = response.response.map_err(|err| { + errors::ApiErrorResponse::ExternalConnectorError { + code: err.code, + message: err.message, + connector: connector_data.connector_name.clone().to_string(), + status_code: err.status_code, + reason: err.reason, + } + })?; + + payment_data.payment_intent.tax_details = Some(diesel_models::TaxDetails { + default: Some(diesel_models::DefaultTax { + order_tax_amount: tax_response.order_tax_amount, + }), + payment_method_type: None, + }); + + Ok(()) + } else { + Ok(()) + } + } + #[instrument(skip_all)] async fn make_pm_data<'a>( &'a self, @@ -585,7 +690,7 @@ impl Domain for PaymentCreate { customer: &Option, business_profile: Option<&domain::BusinessProfile>, ) -> RouterResult<( - BoxedOperation<'a, F, api::PaymentsRequest>, + PaymentCreateOperation<'a, F>, Option, Option, )> { @@ -649,7 +754,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, _header_payload: api::HeaderPayload, - ) -> RouterResult<(BoxedOperation<'b, F, api::PaymentsRequest>, PaymentData)> + ) -> RouterResult<(PaymentCreateOperation<'b, F>, PaymentData)> where F: 'b + Send, { @@ -756,16 +861,13 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen } } -impl ValidateRequest for PaymentCreate { +impl ValidateRequest> for PaymentCreate { #[instrument(skip_all)] fn validate_request<'a, 'b>( &'b self, request: &api::PaymentsRequest, merchant_account: &'a domain::MerchantAccount, - ) -> RouterResult<( - BoxedOperation<'b, F, api::PaymentsRequest>, - operations::ValidateResult, - )> { + ) -> RouterResult<(PaymentCreateOperation<'b, F>, operations::ValidateResult)> { helpers::validate_customer_information(request)?; if let Some(amount) = request.amount { @@ -1053,6 +1155,8 @@ impl PaymentCreate { .map(Secret::new), organization_id: organization_id.clone(), profile_id, + shipping_cost: request.shipping_cost, + order_tax_amount: None, }, additional_pm_data, )) @@ -1181,6 +1285,8 @@ impl PaymentCreate { .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Unable to encrypt customer details")?; + let skip_external_tax_calculation = request.skip_external_tax_calculation; + Ok(storage::PaymentIntent { payment_id: payment_id.to_owned(), merchant_id: merchant_account.get_id().to_owned(), @@ -1234,6 +1340,9 @@ impl PaymentCreate { shipping_details, is_payment_processor_token_flow, organization_id: merchant_account.organization_id.clone(), + shipping_cost: request.shipping_cost, + tax_details: None, + skip_external_tax_calculation, }) } diff --git a/crates/router/src/core/payments/operations/payment_reject.rs b/crates/router/src/core/payments/operations/payment_reject.rs index 928d69dc1c1c..570337a55550 100644 --- a/crates/router/src/core/payments/operations/payment_reject.rs +++ b/crates/router/src/core/payments/operations/payment_reject.rs @@ -26,6 +26,8 @@ use crate::{ #[operation(operations = "all", flow = "cancel")] pub struct PaymentReject; +type PaymentRejectOperation<'b, F> = BoxedOperation<'b, F, PaymentsCancelRequest, PaymentData>; + #[async_trait] impl GetTracker, PaymentsCancelRequest> for PaymentReject { #[instrument(skip_all)] @@ -38,7 +40,8 @@ impl GetTracker, PaymentsCancelRequest> for P key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, _header_payload: &api::HeaderPayload, - ) -> RouterResult> { + ) -> RouterResult>> + { let db = &*state.store; let key_manager_state = &state.into(); @@ -180,6 +183,7 @@ impl GetTracker, PaymentsCancelRequest> for P authentication: None, recurring_details: None, poll_config: None, + tax_data: None, }; let get_trackers_response = operations::GetTrackerResponse { @@ -208,7 +212,7 @@ impl UpdateTracker, PaymentsCancelRequest> for Payme key_store: &domain::MerchantKeyStore, _should_decline_transaction: Option, _header_payload: api::HeaderPayload, - ) -> RouterResult<(BoxedOperation<'b, F, PaymentsCancelRequest>, PaymentData)> + ) -> RouterResult<(PaymentRejectOperation<'b, F>, PaymentData)> where F: 'b + Send, { @@ -260,16 +264,13 @@ impl UpdateTracker, PaymentsCancelRequest> for Payme } } -impl ValidateRequest for PaymentReject { +impl ValidateRequest> for PaymentReject { #[instrument(skip_all)] fn validate_request<'a, 'b>( &'b self, request: &PaymentsCancelRequest, merchant_account: &'a domain::MerchantAccount, - ) -> RouterResult<( - BoxedOperation<'b, F, PaymentsCancelRequest>, - operations::ValidateResult, - )> { + ) -> RouterResult<(PaymentRejectOperation<'b, F>, operations::ValidateResult)> { Ok(( Box::new(self), operations::ValidateResult { diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 675c64aeb988..5471ae355f5c 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -45,7 +45,7 @@ use crate::{ #[derive(Debug, Clone, Copy, router_derive::PaymentOperation)] #[operation( operations = "post_update_tracker", - flow = "sync_data, cancel_data, authorize_data, capture_data, complete_authorize_data, approve_data, reject_data, setup_mandate_data, session_data,incremental_authorization_data" + flow = "sync_data, cancel_data, authorize_data, capture_data, complete_authorize_data, approve_data, reject_data, setup_mandate_data, session_data,incremental_authorization_data, sdk_session_update_data" )] pub struct PaymentResponse; @@ -181,7 +181,7 @@ impl PostUpdateTracker, types::PaymentsAuthor payment_method_id.clone(), merchant_connector_id.clone(), merchant_account.storage_scheme, - &payment_data.payment_intent.payment_id, + payment_data.payment_intent.get_id(), ) .await?; payment_data.payment_attempt.payment_method_id = payment_method_id; @@ -394,7 +394,7 @@ impl PostUpdateTracker, types::PaymentsIncrementalAu .store .find_all_authorizations_by_merchant_id_payment_id( &router_data.merchant_id, - &payment_data.payment_intent.payment_id, + payment_data.payment_intent.get_id(), ) .await .to_not_found_response(errors::ApiErrorResponse::InternalServerError) @@ -489,6 +489,79 @@ impl PostUpdateTracker, types::PaymentsSessionData> } } +#[async_trait] +impl PostUpdateTracker, types::SdkPaymentsSessionUpdateData> + for PaymentResponse +{ + async fn update_tracker<'b>( + &'b self, + _db: &'b SessionState, + _payment_id: &api::PaymentIdType, + payment_data: PaymentData, + _router_data: types::RouterData< + F, + types::SdkPaymentsSessionUpdateData, + types::PaymentsResponseData, + >, + _key_store: &domain::MerchantKeyStore, + _storage_scheme: enums::MerchantStorageScheme, + _locale: &Option, + ) -> RouterResult> + where + F: 'b + Send, + { + // let session_update_details = + // payment_data + // .payment_intent + // .tax_details + // .clone() + // .ok_or_else(|| { + // report!(errors::ApiErrorResponse::InternalServerError) + // .attach_printable("missing tax_details in payment_intent") + // })?; + + // let pmt_amount = session_update_details + // .pmt + // .clone() + // .map(|pmt| pmt.order_tax_amount) + // .ok_or(errors::ApiErrorResponse::InternalServerError) + // .attach_printable("Missing tax_details.order_tax_amount")?; + + // let total_amount = MinorUnit::from(payment_data.amount) + pmt_amount; + + // // if connector_ call successful -> payment_intent.amount update + // match router_data.response.clone() { + // Err(_) => (None, None), + // Ok(types::PaymentsResponseData::SessionUpdateResponse { status }) => { + // if status == SessionUpdateStatus::Success { + // ( + // Some( + // storage::PaymentAttemptUpdate::IncrementalAuthorizationAmountUpdate { + // amount: total_amount, + // amount_capturable: total_amount, + // }, + // ), + // Some( + // storage::PaymentIntentUpdate::IncrementalAuthorizationAmountUpdate { + // amount: pmt_amount, + // }, + // ), + // ) + // } else { + // (None, None) + // } + // } + // _ => Err(errors::ApiErrorResponse::InternalServerError) + // .attach_printable("unexpected response in session_update flow")?, + // }; + + // let _shipping_address = payment_data.address.get_shipping(); + // let _amount = payment_data.amount; + + Ok(payment_data) + } +} + #[async_trait] impl PostUpdateTracker, types::PaymentsCaptureData> for PaymentResponse @@ -709,7 +782,7 @@ impl PostUpdateTracker, types::SetupMandateRequestDa payment_method_id.clone(), merchant_connector_id.clone(), merchant_account.storage_scheme, - &payment_data.payment_intent.payment_id, + payment_data.payment_intent.get_id(), ) .await?; payment_data.payment_attempt.payment_method_id = payment_method_id; @@ -773,6 +846,21 @@ impl PostUpdateTracker, types::CompleteAuthorizeData } } +#[cfg(feature = "v2")] +#[instrument(skip_all)] +async fn payment_response_update_tracker( + state: &SessionState, + _payment_id: &api::PaymentIdType, + mut payment_data: PaymentData, + router_data: types::RouterData, + key_store: &domain::MerchantKeyStore, + storage_scheme: enums::MerchantStorageScheme, + locale: &Option, +) -> RouterResult> { + todo!() +} + +#[cfg(feature = "v1")] #[instrument(skip_all)] async fn payment_response_update_tracker( state: &SessionState, @@ -1178,6 +1266,7 @@ async fn payment_response_update_tracker( types::PaymentsResponseData::IncrementalAuthorizationResponse { .. } => (None, None), + // types::PaymentsResponseData::SessionUpdateResponse { .. } => (None, None), types::PaymentsResponseData::MultipleCaptureResponse { capture_sync_response_list, } => match payment_data.multiple_capture_data { diff --git a/crates/router/src/core/payments/operations/payment_session.rs b/crates/router/src/core/payments/operations/payment_session.rs index a5c27b20420b..e58dd8855fd9 100644 --- a/crates/router/src/core/payments/operations/payment_session.rs +++ b/crates/router/src/core/payments/operations/payment_session.rs @@ -27,6 +27,9 @@ use crate::{ #[operation(operations = "all", flow = "session")] pub struct PaymentSession; +type PaymentSessionOperation<'b, F> = + BoxedOperation<'b, F, api::PaymentsSessionRequest, PaymentData>; + #[async_trait] impl GetTracker, api::PaymentsSessionRequest> for PaymentSession @@ -41,7 +44,9 @@ impl GetTracker, api::PaymentsSessionRequest> key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, _header_payload: &api::HeaderPayload, - ) -> RouterResult> { + ) -> RouterResult< + operations::GetTrackerResponse<'a, F, api::PaymentsSessionRequest, PaymentData>, + > { let payment_id = payment_id .get_payment_intent_id() .change_context(errors::ApiErrorResponse::PaymentNotFound)?; @@ -205,6 +210,7 @@ impl GetTracker, api::PaymentsSessionRequest> authentication: None, recurring_details: None, poll_config: None, + tax_data: None, }; let get_trackers_response = operations::GetTrackerResponse { @@ -233,10 +239,7 @@ impl UpdateTracker, api::PaymentsSessionRequest> for key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, _header_payload: api::HeaderPayload, - ) -> RouterResult<( - BoxedOperation<'b, F, api::PaymentsSessionRequest>, - PaymentData, - )> + ) -> RouterResult<(PaymentSessionOperation<'b, F>, PaymentData)> where F: 'b + Send, { @@ -263,16 +266,15 @@ impl UpdateTracker, api::PaymentsSessionRequest> for } } -impl ValidateRequest for PaymentSession { +impl ValidateRequest> + for PaymentSession +{ #[instrument(skip_all)] fn validate_request<'a, 'b>( &'b self, request: &api::PaymentsSessionRequest, merchant_account: &'a domain::MerchantAccount, - ) -> RouterResult<( - BoxedOperation<'b, F, api::PaymentsSessionRequest>, - operations::ValidateResult, - )> { + ) -> RouterResult<(PaymentSessionOperation<'b, F>, operations::ValidateResult)> { //paymentid is already generated and should be sent in the request let given_payment_id = request.payment_id.clone(); @@ -289,10 +291,12 @@ impl ValidateRequest for Paymen } #[async_trait] -impl> - Domain for Op +impl< + F: Clone + Send, + Op: Send + Sync + Operation>, + > Domain> for Op where - for<'a> &'a Op: Operation, + for<'a> &'a Op: Operation>, { #[instrument(skip_all)] async fn get_or_create_customer_details<'a>( @@ -303,10 +307,7 @@ where key_store: &domain::MerchantKeyStore, storage_scheme: common_enums::enums::MerchantStorageScheme, ) -> errors::CustomResult< - ( - BoxedOperation<'a, F, api::PaymentsSessionRequest>, - Option, - ), + (PaymentSessionOperation<'a, F>, Option), errors::StorageError, > { helpers::create_customer_if_not_exist( @@ -331,7 +332,7 @@ where _customer: &Option, _business_profile: Option<&domain::BusinessProfile>, ) -> RouterResult<( - BoxedOperation<'b, F, api::PaymentsSessionRequest>, + PaymentSessionOperation<'b, F>, Option, Option, )> { diff --git a/crates/router/src/core/payments/operations/payment_start.rs b/crates/router/src/core/payments/operations/payment_start.rs index 2304ab12565f..6895c9e7b06a 100644 --- a/crates/router/src/core/payments/operations/payment_start.rs +++ b/crates/router/src/core/payments/operations/payment_start.rs @@ -26,6 +26,9 @@ use crate::{ #[operation(operations = "all", flow = "start")] pub struct PaymentStart; +type PaymentSessionOperation<'b, F> = + BoxedOperation<'b, F, api::PaymentsStartRequest, PaymentData>; + #[async_trait] impl GetTracker, api::PaymentsStartRequest> for PaymentStart { #[instrument(skip_all)] @@ -38,7 +41,9 @@ impl GetTracker, api::PaymentsStartRequest> f key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, _header_payload: &api::HeaderPayload, - ) -> RouterResult> { + ) -> RouterResult< + operations::GetTrackerResponse<'a, F, api::PaymentsStartRequest, PaymentData>, + > { let (mut payment_intent, payment_attempt, currency, amount); let db = &*state.store; let key_manager_state = &state.into(); @@ -190,6 +195,7 @@ impl GetTracker, api::PaymentsStartRequest> f authentication: None, recurring_details: None, poll_config: None, + tax_data: None, }; let get_trackers_response = operations::GetTrackerResponse { @@ -218,10 +224,7 @@ impl UpdateTracker, api::PaymentsStartRequest> for P _mechant_key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, _header_payload: api::HeaderPayload, - ) -> RouterResult<( - BoxedOperation<'b, F, api::PaymentsStartRequest>, - PaymentData, - )> + ) -> RouterResult<(PaymentSessionOperation<'b, F>, PaymentData)> where F: 'b + Send, { @@ -229,16 +232,15 @@ impl UpdateTracker, api::PaymentsStartRequest> for P } } -impl ValidateRequest for PaymentStart { +impl ValidateRequest> + for PaymentStart +{ #[instrument(skip_all)] fn validate_request<'a, 'b>( &'b self, request: &api::PaymentsStartRequest, merchant_account: &'a domain::MerchantAccount, - ) -> RouterResult<( - BoxedOperation<'b, F, api::PaymentsStartRequest>, - operations::ValidateResult, - )> { + ) -> RouterResult<(PaymentSessionOperation<'b, F>, operations::ValidateResult)> { let request_merchant_id = Some(&request.merchant_id); helpers::validate_merchant_id(merchant_account.get_id(), request_merchant_id) .change_context(errors::ApiErrorResponse::InvalidDataFormat { @@ -261,10 +263,12 @@ impl ValidateRequest for PaymentS } #[async_trait] -impl> - Domain for Op +impl< + F: Clone + Send, + Op: Send + Sync + Operation>, + > Domain> for Op where - for<'a> &'a Op: Operation, + for<'a> &'a Op: Operation>, { #[instrument(skip_all)] async fn get_or_create_customer_details<'a>( @@ -275,10 +279,7 @@ where key_store: &domain::MerchantKeyStore, storage_scheme: common_enums::enums::MerchantStorageScheme, ) -> CustomResult< - ( - BoxedOperation<'a, F, api::PaymentsStartRequest>, - Option, - ), + (PaymentSessionOperation<'a, F>, Option), errors::StorageError, > { helpers::create_customer_if_not_exist( @@ -303,7 +304,7 @@ where customer: &Option, business_profile: Option<&domain::BusinessProfile>, ) -> RouterResult<( - BoxedOperation<'a, F, api::PaymentsStartRequest>, + PaymentSessionOperation<'a, F>, Option, Option, )> { diff --git a/crates/router/src/core/payments/operations/payment_status.rs b/crates/router/src/core/payments/operations/payment_status.rs index 85dab242de43..d7ca01e66610 100644 --- a/crates/router/src/core/payments/operations/payment_status.rs +++ b/crates/router/src/core/payments/operations/payment_status.rs @@ -29,8 +29,11 @@ use crate::{ #[operation(operations = "all", flow = "sync")] pub struct PaymentStatus; +type PaymentStatusOperation<'b, F, R> = BoxedOperation<'b, F, R, PaymentData>; + impl Operation for PaymentStatus { - fn to_domain(&self) -> RouterResult<&dyn Domain> { + type Data = PaymentData; + fn to_domain(&self) -> RouterResult<&dyn Domain>> { Ok(self) } fn to_update_tracker( @@ -41,7 +44,8 @@ impl Operation for PaymentStatus { } } impl Operation for &PaymentStatus { - fn to_domain(&self) -> RouterResult<&dyn Domain> { + type Data = PaymentData; + fn to_domain(&self) -> RouterResult<&dyn Domain>> { Ok(*self) } fn to_update_tracker( @@ -53,7 +57,7 @@ impl Operation for &PaymentStatus { } #[async_trait] -impl Domain for PaymentStatus { +impl Domain> for PaymentStatus { #[instrument(skip_all)] async fn get_or_create_customer_details<'a>( &'a self, @@ -64,7 +68,7 @@ impl Domain for PaymentStatus { storage_scheme: enums::MerchantStorageScheme, ) -> CustomResult< ( - BoxedOperation<'a, F, api::PaymentsRequest>, + PaymentStatusOperation<'a, F, api::PaymentsRequest>, Option, ), errors::StorageError, @@ -91,7 +95,7 @@ impl Domain for PaymentStatus { _customer: &Option, _business_profile: Option<&domain::BusinessProfile>, ) -> RouterResult<( - BoxedOperation<'a, F, api::PaymentsRequest>, + PaymentStatusOperation<'a, F, api::PaymentsRequest>, Option, Option, )> { @@ -145,7 +149,10 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen _key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, _header_payload: api::HeaderPayload, - ) -> RouterResult<(BoxedOperation<'b, F, api::PaymentsRequest>, PaymentData)> + ) -> RouterResult<( + PaymentStatusOperation<'b, F, api::PaymentsRequest>, + PaymentData, + )> where F: 'b + Send, { @@ -167,7 +174,7 @@ impl UpdateTracker, api::PaymentsRetrieveRequest> fo _frm_suggestion: Option, _header_payload: api::HeaderPayload, ) -> RouterResult<( - BoxedOperation<'b, F, api::PaymentsRetrieveRequest>, + PaymentStatusOperation<'b, F, api::PaymentsRetrieveRequest>, PaymentData, )> where @@ -191,7 +198,9 @@ impl GetTracker, api::PaymentsRetrieveRequest key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, _header_payload: &api::HeaderPayload, - ) -> RouterResult> { + ) -> RouterResult< + operations::GetTrackerResponse<'a, F, api::PaymentsRetrieveRequest, PaymentData>, + > { get_tracker_for_sync( payment_id, merchant_account, @@ -208,7 +217,7 @@ impl GetTracker, api::PaymentsRetrieveRequest async fn get_tracker_for_sync< 'a, F: Send + Clone, - Op: Operation + 'a + Send + Sync, + Op: Operation> + 'a + Send + Sync, >( payment_id: &api::PaymentIdType, merchant_account: &domain::MerchantAccount, @@ -217,7 +226,8 @@ async fn get_tracker_for_sync< request: &api::PaymentsRetrieveRequest, operation: Op, storage_scheme: enums::MerchantStorageScheme, -) -> RouterResult> { +) -> RouterResult>> +{ let (payment_intent, mut payment_attempt, currency, amount); (payment_intent, payment_attempt) = get_payment_intent_payment_attempt( @@ -468,6 +478,7 @@ async fn get_tracker_for_sync< authentication, recurring_details: None, poll_config: None, + tax_data: None, }; let get_trackers_response = operations::GetTrackerResponse { @@ -481,13 +492,15 @@ async fn get_tracker_for_sync< Ok(get_trackers_response) } -impl ValidateRequest for PaymentStatus { +impl ValidateRequest> + for PaymentStatus +{ fn validate_request<'b>( &'b self, request: &api::PaymentsRetrieveRequest, merchant_account: &domain::MerchantAccount, ) -> RouterResult<( - BoxedOperation<'b, F, api::PaymentsRetrieveRequest>, + PaymentStatusOperation<'b, F, api::PaymentsRetrieveRequest>, operations::ValidateResult, )> { let request_merchant_id = request.merchant_id.as_ref(); diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index e42f0ccdfdd5..484bec14f484 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -7,6 +7,7 @@ use async_trait::async_trait; use common_utils::{ ext_traits::{AsyncExt, Encode, ValueExt}, pii::Email, + types::keymanager::KeyManagerState, }; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::payments::payment_intent::{ @@ -22,11 +23,13 @@ use crate::{ mandate::helpers as m_helpers, payment_methods::cards::create_encrypted_data, payments::{self, helpers, operations, CustomerDetails, PaymentAddress, PaymentData}, + utils as core_utils, }, routes::{app::ReqState, SessionState}, services, types::{ - api::{self, PaymentIdTypeExt}, + self, + api::{self, ConnectorCallType, PaymentIdTypeExt}, domain, storage::{self, enums as storage_enums, payment_attempt::PaymentAttemptExt}, transformers::ForeignTryFrom, @@ -38,6 +41,8 @@ use crate::{ #[operation(operations = "all", flow = "authorize")] pub struct PaymentUpdate; +type PaymentUpdateOperation<'a, F> = BoxedOperation<'a, F, api::PaymentsRequest, PaymentData>; + #[async_trait] impl GetTracker, api::PaymentsRequest> for PaymentUpdate { #[instrument(skip_all)] @@ -50,7 +55,8 @@ impl GetTracker, api::PaymentsRequest> for Pa key_store: &domain::MerchantKeyStore, auth_flow: services::AuthFlow, _header_payload: &api::HeaderPayload, - ) -> RouterResult> { + ) -> RouterResult>> + { let (mut payment_intent, mut payment_attempt, currency): (_, _, storage_enums::Currency); let payment_id = payment_id @@ -130,6 +136,7 @@ impl GetTracker, api::PaymentsRequest> for Pa payment_intent.setup_future_usage, request.customer_acceptance.clone(), request.payment_token.clone(), + payment_attempt.payment_method.or(request.payment_method), ) .change_context(errors::ApiErrorResponse::MandateValidationFailed { reason: "Expected one out of recurring_details and mandate_data but got both".into(), @@ -335,7 +342,7 @@ impl GetTracker, api::PaymentsRequest> for Pa }) .await .transpose()?; - let (next_operation, amount): (BoxedOperation<'a, F, api::PaymentsRequest>, _) = + let (next_operation, amount): (PaymentUpdateOperation<'a, F>, _) = if request.confirm.unwrap_or(false) { let amount = { let amount = request @@ -471,6 +478,7 @@ impl GetTracker, api::PaymentsRequest> for Pa authentication: None, recurring_details, poll_config: None, + tax_data: None, }; let get_trackers_response = operations::GetTrackerResponse { @@ -486,7 +494,7 @@ impl GetTracker, api::PaymentsRequest> for Pa } #[async_trait] -impl Domain for PaymentUpdate { +impl Domain> for PaymentUpdate { #[instrument(skip_all)] async fn get_or_create_customer_details<'a>( &'a self, @@ -495,13 +503,8 @@ impl Domain for PaymentUpdate { request: Option, key_store: &domain::MerchantKeyStore, storage_scheme: common_enums::enums::MerchantStorageScheme, - ) -> CustomResult< - ( - BoxedOperation<'a, F, api::PaymentsRequest>, - Option, - ), - errors::StorageError, - > { + ) -> CustomResult<(PaymentUpdateOperation<'a, F>, Option), errors::StorageError> + { helpers::create_customer_if_not_exist( state, Box::new(self), @@ -514,6 +517,109 @@ impl Domain for PaymentUpdate { .await } + async fn payments_dynamic_tax_calculation<'a>( + &'a self, + state: &SessionState, + payment_data: &mut PaymentData, + _connector_call_type: &ConnectorCallType, + business_profile: &domain::BusinessProfile, + key_store: &domain::MerchantKeyStore, + merchant_account: &domain::MerchantAccount, + ) -> CustomResult<(), errors::ApiErrorResponse> { + let is_tax_connector_enabled = business_profile.get_is_tax_connector_enabled(); + let skip_external_tax_calculation = payment_data + .payment_intent + .skip_external_tax_calculation + .unwrap_or(false); + if is_tax_connector_enabled && !skip_external_tax_calculation { + let db = state.store.as_ref(); + let key_manager_state: &KeyManagerState = &state.into(); + + let merchant_connector_id = business_profile + .tax_connector_id + .as_ref() + .get_required_value("business_profile.tax_connector_id")?; + + #[cfg(feature = "v1")] + let mca = db + .find_by_merchant_connector_account_merchant_id_merchant_connector_id( + key_manager_state, + &business_profile.merchant_id, + merchant_connector_id, + key_store, + ) + .await + .to_not_found_response( + errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: merchant_connector_id.get_string_repr().to_string(), + }, + )?; + + #[cfg(feature = "v2")] + let mca = db + .find_merchant_connector_account_by_id( + key_manager_state, + merchant_connector_id, + key_store, + ) + .await + .to_not_found_response( + errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: merchant_connector_id.get_string_repr().to_string(), + }, + )?; + + let connector_data = + api::TaxCalculateConnectorData::get_connector_by_name(&mca.connector_name)?; + + let router_data = core_utils::construct_payments_dynamic_tax_calculation_router_data( + state, + merchant_account, + key_store, + payment_data, + &mca, + ) + .await?; + let connector_integration: services::BoxedPaymentConnectorIntegrationInterface< + api::CalculateTax, + types::PaymentsTaxCalculationData, + types::TaxCalculationResponseData, + > = connector_data.connector.get_connector_integration(); + + let response = services::execute_connector_processing_step( + state, + connector_integration, + &router_data, + payments::CallConnectorAction::Trigger, + None, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Tax connector Response Failed")?; + + let tax_response = response.response.map_err(|err| { + errors::ApiErrorResponse::ExternalConnectorError { + code: err.code, + message: err.message, + connector: connector_data.connector_name.clone().to_string(), + status_code: err.status_code, + reason: err.reason, + } + })?; + + payment_data.payment_intent.tax_details = Some(diesel_models::TaxDetails { + default: Some(diesel_models::DefaultTax { + order_tax_amount: tax_response.order_tax_amount, + }), + payment_method_type: None, + }); + + Ok(()) + } else { + Ok(()) + } + } + #[instrument(skip_all)] async fn make_pm_data<'a>( &'a self, @@ -524,7 +630,7 @@ impl Domain for PaymentUpdate { customer: &Option, business_profile: Option<&domain::BusinessProfile>, ) -> RouterResult<( - BoxedOperation<'a, F, api::PaymentsRequest>, + PaymentUpdateOperation<'a, F>, Option, Option, )> { @@ -589,7 +695,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen _key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, _header_payload: api::HeaderPayload, - ) -> RouterResult<(BoxedOperation<'b, F, api::PaymentsRequest>, PaymentData)> + ) -> RouterResult<(PaymentUpdateOperation<'b, F>, PaymentData)> where F: 'b + Send, { @@ -609,7 +715,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, _header_payload: api::HeaderPayload, - ) -> RouterResult<(BoxedOperation<'b, F, api::PaymentsRequest>, PaymentData)> + ) -> RouterResult<(PaymentUpdateOperation<'b, F>, PaymentData)> where F: 'b + Send, { @@ -792,6 +898,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen billing_details, shipping_details, is_payment_processor_token_flow: None, + tax_details: None, })), key_store, storage_scheme, @@ -818,16 +925,13 @@ impl ForeignTryFrom for CustomerData { } } -impl ValidateRequest for PaymentUpdate { +impl ValidateRequest> for PaymentUpdate { #[instrument(skip_all)] fn validate_request<'a, 'b>( &'b self, request: &api::PaymentsRequest, merchant_account: &'a domain::MerchantAccount, - ) -> RouterResult<( - BoxedOperation<'b, F, api::PaymentsRequest>, - operations::ValidateResult, - )> { + ) -> RouterResult<(PaymentUpdateOperation<'b, F>, operations::ValidateResult)> { helpers::validate_customer_information(request)?; if let Some(amount) = request.amount { diff --git a/crates/router/src/core/payments/operations/payments_incremental_authorization.rs b/crates/router/src/core/payments/operations/payments_incremental_authorization.rs index 72ae1d6190dd..29ae20a30496 100644 --- a/crates/router/src/core/payments/operations/payments_incremental_authorization.rs +++ b/crates/router/src/core/payments/operations/payments_incremental_authorization.rs @@ -30,6 +30,9 @@ use crate::{ #[operation(operations = "all", flow = "incremental_authorization")] pub struct PaymentIncrementalAuthorization; +type PaymentIncrementalAuthorizationOperation<'b, F> = + BoxedOperation<'b, F, PaymentsIncrementalAuthorizationRequest, payments::PaymentData>; + #[async_trait] impl GetTracker, PaymentsIncrementalAuthorizationRequest> @@ -45,8 +48,14 @@ impl key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, _header_payload: &api::HeaderPayload, - ) -> RouterResult> - { + ) -> RouterResult< + operations::GetTrackerResponse< + 'a, + F, + PaymentsIncrementalAuthorizationRequest, + payments::PaymentData, + >, + > { let db = &*state.store; let key_manager_state = &state.into(); @@ -158,6 +167,7 @@ impl authentication: None, recurring_details: None, poll_config: None, + tax_data: None, }; let get_trackers_response = operations::GetTrackerResponse { @@ -189,7 +199,7 @@ impl UpdateTracker, PaymentsIncrementalAut _frm_suggestion: Option, _header_payload: api::HeaderPayload, ) -> RouterResult<( - BoxedOperation<'b, F, PaymentsIncrementalAuthorizationRequest>, + PaymentIncrementalAuthorizationOperation<'b, F>, payments::PaymentData, )> where @@ -265,7 +275,8 @@ impl UpdateTracker, PaymentsIncrementalAut } } -impl ValidateRequest +impl + ValidateRequest> for PaymentIncrementalAuthorization { #[instrument(skip_all)] @@ -274,7 +285,7 @@ impl ValidateRequest RouterResult<( - BoxedOperation<'b, F, PaymentsIncrementalAuthorizationRequest>, + PaymentIncrementalAuthorizationOperation<'b, F>, operations::ValidateResult, )> { Ok(( @@ -290,7 +301,7 @@ impl ValidateRequest Domain +impl Domain> for PaymentIncrementalAuthorization { #[instrument(skip_all)] @@ -303,7 +314,12 @@ impl Domain _storage_scheme: enums::MerchantStorageScheme, ) -> CustomResult< ( - BoxedOperation<'a, F, PaymentsIncrementalAuthorizationRequest>, + BoxedOperation< + 'a, + F, + PaymentsIncrementalAuthorizationRequest, + payments::PaymentData, + >, Option, ), errors::StorageError, @@ -321,7 +337,7 @@ impl Domain _customer: &Option, _business_profile: Option<&domain::BusinessProfile>, ) -> RouterResult<( - BoxedOperation<'a, F, PaymentsIncrementalAuthorizationRequest>, + PaymentIncrementalAuthorizationOperation<'a, F>, Option, Option, )> { diff --git a/crates/router/src/core/payments/operations/tax_calculation.rs b/crates/router/src/core/payments/operations/tax_calculation.rs new file mode 100644 index 000000000000..1df836124402 --- /dev/null +++ b/crates/router/src/core/payments/operations/tax_calculation.rs @@ -0,0 +1,460 @@ +use std::marker::PhantomData; + +use api_models::enums::FrmSuggestion; +use async_trait::async_trait; +use common_utils::{ext_traits::AsyncExt, types::keymanager::KeyManagerState}; +use error_stack::ResultExt; +use masking::PeekInterface; +use router_derive::PaymentOperation; +use router_env::{instrument, tracing}; + +use super::{BoxedOperation, Domain, GetTracker, Operation, UpdateTracker, ValidateRequest}; +use crate::{ + core::{ + errors::{self, RouterResult, StorageErrorExt}, + payment_methods::cards::create_encrypted_data, + payments::{self, helpers, operations, PaymentData}, + utils as core_utils, + }, + routes::{app::ReqState, SessionState}, + services, + types::{ + self, + api::{self, ConnectorCallType, PaymentIdTypeExt}, + domain, + storage::{self, enums as storage_enums}, + }, + utils::OptionExt, +}; + +#[derive(Debug, Clone, Copy, PaymentOperation)] +#[operation(operations = "all", flow = "sdk_session_update")] +pub struct PaymentSessionUpdate; + +type PaymentSessionUpdateOperation<'b, F> = + BoxedOperation<'b, F, api::PaymentsDynamicTaxCalculationRequest, PaymentData>; + +#[async_trait] +impl GetTracker, api::PaymentsDynamicTaxCalculationRequest> + for PaymentSessionUpdate +{ + #[instrument(skip_all)] + async fn get_trackers<'a>( + &'a self, + state: &'a SessionState, + payment_id: &api::PaymentIdType, + request: &api::PaymentsDynamicTaxCalculationRequest, + merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, + _auth_flow: services::AuthFlow, + _header_payload: &api::HeaderPayload, + ) -> RouterResult< + operations::GetTrackerResponse< + 'a, + F, + api::PaymentsDynamicTaxCalculationRequest, + PaymentData, + >, + > { + let payment_id = payment_id + .get_payment_intent_id() + .change_context(errors::ApiErrorResponse::PaymentNotFound)?; + + let db = &*state.store; + let key_manager_state: &KeyManagerState = &state.into(); + let merchant_id = merchant_account.get_id(); + let storage_scheme = merchant_account.storage_scheme; + + let payment_intent = db + .find_payment_intent_by_payment_id_merchant_id( + &state.into(), + &payment_id, + merchant_id, + key_store, + storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + + helpers::validate_payment_status_against_not_allowed_statuses( + &payment_intent.status, + &[ + storage_enums::IntentStatus::Failed, + storage_enums::IntentStatus::Succeeded, + ], + "create a session update for", + )?; + + helpers::authenticate_client_secret(Some(request.client_secret.peek()), &payment_intent)?; + + let mut payment_attempt = db + .find_payment_attempt_by_payment_id_merchant_id_attempt_id( + &payment_intent.payment_id, + merchant_id, + payment_intent.active_attempt.get_id().as_str(), + storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + + let currency = payment_intent.currency.get_required_value("currency")?; + + let amount = payment_attempt.get_total_amount().into(); + + payment_attempt.payment_method_type = Some(request.payment_method_type); + + let shipping_address = helpers::get_address_by_id( + state, + payment_intent.shipping_address_id.clone(), + key_store, + &payment_intent.payment_id, + merchant_id, + merchant_account.storage_scheme, + ) + .await?; + + let profile_id = payment_intent + .profile_id + .as_ref() + .get_required_value("profile_id") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("'profile_id' not set in payment intent")?; + + let business_profile = db + .find_business_profile_by_profile_id(key_manager_state, key_store, profile_id) + .await + .to_not_found_response(errors::ApiErrorResponse::BusinessProfileNotFound { + id: profile_id.get_string_repr().to_owned(), + })?; + + let tax_data = payments::TaxData { + shipping_details: request.shipping.clone(), + payment_method_type: request.payment_method_type, + }; + + let payment_data = PaymentData { + flow: PhantomData, + payment_intent, + payment_attempt, + currency, + amount, + email: None, + mandate_id: None, + mandate_connector: None, + customer_acceptance: None, + token: None, + token_data: None, + setup_mandate: None, + address: payments::PaymentAddress::new( + shipping_address.as_ref().map(From::from), + None, + None, + business_profile.use_billing_as_payment_method_billing, + ), + confirm: None, + payment_method_data: None, + payment_method_info: None, + force_sync: None, + refunds: vec![], + disputes: vec![], + attempts: None, + sessions_token: vec![], + card_cvc: None, + creds_identifier: None, + pm_token: None, + connector_customer_id: None, + recurring_mandate_payment_data: None, + ephemeral_key: None, + multiple_capture_data: None, + redirect_response: None, + surcharge_details: None, + frm_message: None, + payment_link_data: None, + incremental_authorization_details: None, + authorizations: vec![], + authentication: None, + recurring_details: None, + poll_config: None, + tax_data: Some(tax_data), + }; + let get_trackers_response = operations::GetTrackerResponse { + operation: Box::new(self), + customer_details: None, + payment_data, + business_profile, + mandate_type: None, + }; + + Ok(get_trackers_response) + } +} + +#[async_trait] +impl Domain> + for PaymentSessionUpdate +{ + #[instrument(skip_all)] + async fn get_or_create_customer_details<'a>( + &'a self, + _state: &SessionState, + _payment_data: &mut PaymentData, + _request: Option, + _merchant_key_store: &domain::MerchantKeyStore, + _storage_scheme: storage_enums::MerchantStorageScheme, + ) -> errors::CustomResult< + ( + PaymentSessionUpdateOperation<'a, F>, + Option, + ), + errors::StorageError, + > { + Ok((Box::new(self), None)) + } + + async fn payments_dynamic_tax_calculation<'a>( + &'a self, + state: &SessionState, + payment_data: &mut PaymentData, + _connector_call_type: &ConnectorCallType, + business_profile: &domain::BusinessProfile, + key_store: &domain::MerchantKeyStore, + merchant_account: &domain::MerchantAccount, + ) -> errors::CustomResult<(), errors::ApiErrorResponse> { + let is_tax_connector_enabled = business_profile.get_is_tax_connector_enabled(); + let skip_external_tax_calculation = payment_data + .payment_intent + .skip_external_tax_calculation + .unwrap_or(false); + if is_tax_connector_enabled && !skip_external_tax_calculation { + let db = state.store.as_ref(); + let key_manager_state: &KeyManagerState = &state.into(); + + let merchant_connector_id = business_profile + .tax_connector_id + .as_ref() + .get_required_value("business_profile.tax_connector_id")?; + + #[cfg(feature = "v1")] + let mca = db + .find_by_merchant_connector_account_merchant_id_merchant_connector_id( + key_manager_state, + &business_profile.merchant_id, + merchant_connector_id, + key_store, + ) + .await + .to_not_found_response( + errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: merchant_connector_id.get_string_repr().to_string(), + }, + )?; + + #[cfg(feature = "v2")] + let mca = db + .find_merchant_connector_account_by_id( + key_manager_state, + merchant_connector_id, + key_store, + ) + .await + .to_not_found_response( + errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: merchant_connector_id.get_string_repr().to_string(), + }, + )?; + + let connector_data = + api::TaxCalculateConnectorData::get_connector_by_name(&mca.connector_name)?; + + let router_data = core_utils::construct_payments_dynamic_tax_calculation_router_data( + state, + merchant_account, + key_store, + payment_data, + &mca, + ) + .await?; + let connector_integration: services::BoxedPaymentConnectorIntegrationInterface< + api::CalculateTax, + types::PaymentsTaxCalculationData, + types::TaxCalculationResponseData, + > = connector_data.connector.get_connector_integration(); + + let response = services::execute_connector_processing_step( + state, + connector_integration, + &router_data, + payments::CallConnectorAction::Trigger, + None, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Tax connector Response Failed")?; + + let tax_response = response.response.map_err(|err| { + errors::ApiErrorResponse::ExternalConnectorError { + code: err.code, + message: err.message, + connector: connector_data.connector_name.clone().to_string(), + status_code: err.status_code, + reason: err.reason, + } + })?; + + let payment_method_type = payment_data + .tax_data + .clone() + .map(|tax_data| tax_data.payment_method_type) + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("missing tax_data.payment_method_type")?; + + payment_data.payment_intent.tax_details = Some(diesel_models::TaxDetails { + payment_method_type: Some(diesel_models::PaymentMethodTypeTax { + order_tax_amount: tax_response.order_tax_amount, + pmt: payment_method_type, + }), + default: None, + }); + Ok(()) + } else { + Ok(()) + } + } + + #[instrument(skip_all)] + async fn make_pm_data<'a>( + &'a self, + _state: &'a SessionState, + _payment_data: &mut PaymentData, + _storage_scheme: storage_enums::MerchantStorageScheme, + _merchant_key_store: &domain::MerchantKeyStore, + _customer: &Option, + _business_profile: Option<&domain::BusinessProfile>, + ) -> RouterResult<( + PaymentSessionUpdateOperation<'a, F>, + Option, + Option, + )> { + Ok((Box::new(self), None, None)) + } + + async fn get_connector<'a>( + &'a self, + _merchant_account: &domain::MerchantAccount, + state: &SessionState, + _request: &api::PaymentsDynamicTaxCalculationRequest, + _payment_intent: &storage::PaymentIntent, + _merchant_key_store: &domain::MerchantKeyStore, + ) -> errors::CustomResult { + helpers::get_connector_default(state, None).await + } + + #[instrument(skip_all)] + async fn guard_payment_against_blocklist<'a>( + &'a self, + _state: &SessionState, + _merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, + _payment_data: &mut PaymentData, + ) -> errors::CustomResult { + Ok(false) + } +} + +#[async_trait] +impl UpdateTracker, api::PaymentsDynamicTaxCalculationRequest> + for PaymentSessionUpdate +{ + #[instrument(skip_all)] + async fn update_trackers<'b>( + &'b self, + state: &'b SessionState, + _req_state: ReqState, + mut payment_data: PaymentData, + _customer: Option, + storage_scheme: storage_enums::MerchantStorageScheme, + _updated_customer: Option, + key_store: &domain::MerchantKeyStore, + _frm_suggestion: Option, + _header_payload: api::HeaderPayload, + ) -> RouterResult<(PaymentSessionUpdateOperation<'b, F>, PaymentData)> + where + F: 'b + Send, + { + let shipping_address = payment_data + .tax_data + .clone() + .map(|tax_data| tax_data.shipping_details); + + let shipping_details = shipping_address + .clone() + .async_map(|shipping_details| create_encrypted_data(state, key_store, shipping_details)) + .await + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to encrypt shipping details")?; + + let shipping_address = helpers::create_or_update_address_for_payment_by_request( + state, + shipping_address.as_ref(), + payment_data.payment_intent.shipping_address_id.as_deref(), + &payment_data.payment_intent.merchant_id, + payment_data.payment_intent.customer_id.as_ref(), + key_store, + &payment_data.payment_intent.payment_id, + storage_scheme, + ) + .await?; + + let payment_intent_update = hyperswitch_domain_models::payments::payment_intent::PaymentIntentUpdate::SessionResponseUpdate { + tax_details: payment_data.payment_intent.tax_details.clone().ok_or(errors::ApiErrorResponse::InternalServerError).attach_printable("payment_intent.tax_details not found")?, + shipping_address_id: shipping_address.map(|address| address.address_id), + updated_by: payment_data.payment_intent.updated_by.clone(), + shipping_details, + }; + + let db = &*state.store; + let payment_intent = payment_data.payment_intent.clone(); + + let updated_payment_intent = db + .update_payment_intent( + &state.into(), + payment_intent, + payment_intent_update, + key_store, + storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + + payment_data.payment_intent = updated_payment_intent; + Ok((Box::new(self), payment_data)) + } +} + +impl ValidateRequest> + for PaymentSessionUpdate +{ + #[instrument(skip_all)] + fn validate_request<'a, 'b>( + &'b self, + request: &api::PaymentsDynamicTaxCalculationRequest, + merchant_account: &'a domain::MerchantAccount, + ) -> RouterResult<( + PaymentSessionUpdateOperation<'b, F>, + operations::ValidateResult, + )> { + //paymentid is already generated and should be sent in the request + let given_payment_id = request.payment_id.clone(); + + Ok(( + Box::new(self), + operations::ValidateResult { + merchant_id: merchant_account.get_id().to_owned(), + payment_id: api::PaymentIdType::PaymentIntentId(given_payment_id), + storage_scheme: merchant_account.storage_scheme, + requeue: false, + }, + )) + } +} diff --git a/crates/router/src/core/payments/retry.rs b/crates/router/src/core/payments/retry.rs index 5fed8bf22261..75c0fb6d77fa 100644 --- a/crates/router/src/core/payments/retry.rs +++ b/crates/router/src/core/payments/retry.rs @@ -29,16 +29,16 @@ use crate::{ #[instrument(skip_all)] #[allow(clippy::too_many_arguments)] -pub async fn do_gsm_actions( +pub async fn do_gsm_actions( state: &app::SessionState, req_state: ReqState, - payment_data: &mut payments::PaymentData, + payment_data: &mut D, mut connectors: IntoIter, original_connector_data: api::ConnectorData, mut router_data: types::RouterData, merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, - operation: &operations::BoxedOperation<'_, F, ApiRequest>, + operation: &operations::BoxedOperation<'_, F, ApiRequest, D>, customer: &Option, validate_result: &operations::ValidateResult, schedule_time: Option, @@ -49,8 +49,12 @@ where F: Clone + Send + Sync, FData: Send + Sync, payments::PaymentResponse: operations::Operation, - - payments::PaymentData: ConstructFlowSpecificData, + D: payments::OperationSessionGetters + + payments::OperationSessionSetters + + Send + + Sync + + Clone, + D: ConstructFlowSpecificData, types::RouterData: Feature, dyn api::Connector: services::api::ConnectorIntegration, { @@ -66,7 +70,7 @@ where .map(|gsm| gsm.step_up_possible) .unwrap_or(false); let is_no_three_ds_payment = matches!( - payment_data.payment_attempt.authentication_type, + payment_data.get_payment_attempt().authentication_type, Some(storage_enums::AuthenticationType::NoThreeDs) ); let should_step_up = if step_up_possible && is_no_three_ds_payment { @@ -268,15 +272,15 @@ fn get_flow_name() -> RouterResult { #[allow(clippy::too_many_arguments)] #[instrument(skip_all)] -pub async fn do_retry( +pub async fn do_retry( state: &routes::SessionState, req_state: ReqState, connector: api::ConnectorData, - operation: &operations::BoxedOperation<'_, F, ApiRequest>, + operation: &operations::BoxedOperation<'_, F, ApiRequest, D>, customer: &Option, merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, - payment_data: &mut payments::PaymentData, + payment_data: &mut D, router_data: types::RouterData, validate_result: &operations::ValidateResult, schedule_time: Option, @@ -288,8 +292,12 @@ where F: Clone + Send + Sync, FData: Send + Sync, payments::PaymentResponse: operations::Operation, - - payments::PaymentData: ConstructFlowSpecificData, + D: payments::OperationSessionGetters + + payments::OperationSessionSetters + + Send + + Sync + + Clone, + D: ConstructFlowSpecificData, types::RouterData: Feature, dyn api::Connector: services::api::ConnectorIntegration, { @@ -329,10 +337,10 @@ where } #[instrument(skip_all)] -pub async fn modify_trackers( +pub async fn modify_trackers( state: &routes::SessionState, connector: String, - payment_data: &mut payments::PaymentData, + payment_data: &mut D, key_store: &domain::MerchantKeyStore, storage_scheme: storage_enums::MerchantStorageScheme, router_data: types::RouterData, @@ -341,11 +349,12 @@ pub async fn modify_trackers( where F: Clone + Send, FData: Send, + D: payments::OperationSessionGetters + payments::OperationSessionSetters + Send + Sync, { - let new_attempt_count = payment_data.payment_intent.attempt_count + 1; + let new_attempt_count = payment_data.get_payment_intent().attempt_count + 1; let new_payment_attempt = make_new_payment_attempt( connector, - payment_data.payment_attempt.clone(), + payment_data.get_payment_attempt().clone(), new_attempt_count, is_step_up, ); @@ -353,7 +362,10 @@ where let db = &*state.store; let additional_payment_method_data = payments::helpers::update_additional_payment_data_with_connector_response_pm_data( - payment_data.payment_attempt.payment_method_data.clone(), + payment_data + .get_payment_attempt() + .payment_method_data + .clone(), router_data .connector_response .clone() @@ -368,7 +380,7 @@ where charge_id, .. }) => { - let encoded_data = payment_data.payment_attempt.encoded_data.clone(); + let encoded_data = payment_data.get_payment_attempt().encoded_data.clone(); let authentication_data = redirection_data .as_ref() @@ -378,7 +390,7 @@ where .attach_printable("Could not parse the connector response")?; db.update_payment_attempt_with_attempt_id( - payment_data.payment_attempt.clone(), + payment_data.get_payment_attempt().clone(), storage::PaymentAttemptUpdate::ResponseUpdate { status: router_data.status, connector: None, @@ -388,15 +400,14 @@ where | types::ResponseId::EncodedData(id) => Some(id), }, connector_response_reference_id: payment_data - .payment_attempt + .get_payment_attempt() .connector_response_reference_id .clone(), authentication_type: None, - payment_method_id: payment_data.payment_attempt.payment_method_id.clone(), + payment_method_id: payment_data.get_payment_attempt().payment_method_id.clone(), mandate_id: payment_data - .mandate_id - .clone() - .and_then(|mandate| mandate.mandate_id), + .get_mandate_id() + .and_then(|mandate| mandate.mandate_id.clone()), connector_metadata, payment_token: None, error_code: None, @@ -427,7 +438,7 @@ where Err(ref error_response) => { let option_gsm = get_gsm(state, &router_data).await?; let auth_update = if Some(router_data.auth_type) - != payment_data.payment_attempt.authentication_type + != payment_data.get_payment_attempt().authentication_type { Some(router_data.auth_type) } else { @@ -435,7 +446,7 @@ where }; db.update_payment_attempt_with_attempt_id( - payment_data.payment_attempt.clone(), + payment_data.get_payment_attempt().clone(), storage::PaymentAttemptUpdate::ErrorUpdate { connector: None, error_code: Some(Some(error_response.code.clone())), @@ -461,18 +472,18 @@ where .insert_payment_attempt(new_payment_attempt, storage_scheme) .await .to_duplicate_response(errors::ApiErrorResponse::DuplicatePayment { - payment_id: payment_data.payment_intent.payment_id.clone(), + payment_id: payment_data.get_payment_intent().get_id().to_owned(), })?; // update payment_attempt, connector_response and payment_intent in payment_data - payment_data.payment_attempt = payment_attempt; + payment_data.set_payment_attempt(payment_attempt); - payment_data.payment_intent = db + let payment_intent = db .update_payment_intent( &state.into(), - payment_data.payment_intent.clone(), + payment_data.get_payment_intent().clone(), storage::PaymentIntentUpdate::PaymentAttemptAndAttemptCountUpdate { - active_attempt_id: payment_data.payment_attempt.attempt_id.clone(), + active_attempt_id: payment_data.get_payment_attempt().attempt_id.clone(), attempt_count: new_attempt_count, updated_by: storage_scheme.to_string(), }, @@ -482,6 +493,8 @@ where .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + payment_data.set_payment_intent(payment_intent); + Ok(()) } @@ -558,6 +571,8 @@ pub fn make_new_payment_attempt( customer_acceptance: Default::default(), profile_id: old_payment_attempt.profile_id, organization_id: old_payment_attempt.organization_id, + shipping_cost: old_payment_attempt.shipping_cost, + order_tax_amount: None, } } diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index 5303366dbe3d..7e703967a507 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -77,7 +77,7 @@ pub struct SessionRoutingPmTypeInput<'a> { routing_algorithm: &'a MerchantAccountRoutingAlgorithm, backend_input: dsl_inputs::BackendInput, allowed_connectors: FxHashMap, - profile_id: common_utils::id_type::ProfileId, + profile_id: &'a common_utils::id_type::ProfileId, } type RoutingResult = oss_errors::CustomResult; @@ -158,17 +158,20 @@ pub fn make_dsl_input_for_payouts( }) } -pub fn make_dsl_input( - payment_data: &payments_oss::PaymentData, -) -> RoutingResult -where - F: Clone, -{ +#[cfg(feature = "v2")] +pub fn make_dsl_input( + payments_dsl_input: &routing::PaymentsDslInput<'_>, +) -> RoutingResult { + todo!() +} + +#[cfg(feature = "v1")] +pub fn make_dsl_input( + payments_dsl_input: &routing::PaymentsDslInput<'_>, +) -> RoutingResult { let mandate_data = dsl_inputs::MandateData { - mandate_acceptance_type: payment_data - .setup_mandate - .as_ref() - .and_then(|mandate_data| { + mandate_acceptance_type: payments_dsl_input.setup_mandate.as_ref().and_then( + |mandate_data| { mandate_data .customer_acceptance .clone() @@ -180,8 +183,9 @@ where euclid_enums::MandateAcceptanceType::Offline } }) - }), - mandate_type: payment_data + }, + ), + mandate_type: payments_dsl_input .setup_mandate .as_ref() .and_then(|mandate_data| { @@ -195,15 +199,19 @@ where }) }), payment_type: Some( - if payment_data.recurring_details.as_ref().is_some_and(|data| { - matches!( - data, - api_models::mandates::RecurringDetails::ProcessorPaymentToken(_) - ) - }) { + if payments_dsl_input + .recurring_details + .as_ref() + .is_some_and(|data| { + matches!( + data, + api_models::mandates::RecurringDetails::ProcessorPaymentToken(_) + ) + }) + { euclid_enums::PaymentType::PptMandate } else { - payment_data.setup_mandate.clone().map_or_else( + payments_dsl_input.setup_mandate.map_or_else( || euclid_enums::PaymentType::NonMandate, |_| euclid_enums::PaymentType::SetupMandate, ) @@ -211,9 +219,9 @@ where ), }; let payment_method_input = dsl_inputs::PaymentMethodInput { - payment_method: payment_data.payment_attempt.payment_method, - payment_method_type: payment_data.payment_attempt.payment_method_type, - card_network: payment_data + payment_method: payments_dsl_input.payment_attempt.payment_method, + payment_method_type: payments_dsl_input.payment_attempt.payment_method_type, + card_network: payments_dsl_input .payment_method_data .as_ref() .and_then(|pm_data| match pm_data { @@ -224,37 +232,36 @@ where }; let payment_input = dsl_inputs::PaymentInput { - amount: payment_data.payment_intent.amount, - card_bin: payment_data - .payment_method_data - .as_ref() - .and_then(|pm_data| match pm_data { + amount: payments_dsl_input.payment_intent.amount, + card_bin: payments_dsl_input.payment_method_data.as_ref().and_then( + |pm_data| match pm_data { domain::PaymentMethodData::Card(card) => { Some(card.card_number.peek().chars().take(6).collect()) } _ => None, - }), - currency: payment_data.currency, - authentication_type: payment_data.payment_attempt.authentication_type, - capture_method: payment_data + }, + ), + currency: payments_dsl_input.currency, + authentication_type: payments_dsl_input.payment_attempt.authentication_type, + capture_method: payments_dsl_input .payment_attempt .capture_method .and_then(|cm| cm.foreign_into()), - business_country: payment_data + business_country: payments_dsl_input .payment_intent .business_country .map(api_enums::Country::from_alpha2), - billing_country: payment_data + billing_country: payments_dsl_input .address .get_payment_method_billing() .and_then(|bic| bic.address.as_ref()) .and_then(|add| add.country) .map(api_enums::Country::from_alpha2), - business_label: payment_data.payment_intent.business_label.clone(), - setup_future_usage: payment_data.payment_intent.setup_future_usage, + business_label: payments_dsl_input.payment_intent.business_label.clone(), + setup_future_usage: payments_dsl_input.payment_intent.setup_future_usage, }; - let metadata = payment_data + let metadata = payments_dsl_input .payment_intent .metadata .clone() @@ -272,12 +279,12 @@ where }) } -pub async fn perform_static_routing_v1( +pub async fn perform_static_routing_v1( state: &SessionState, merchant_id: &common_utils::id_type::MerchantId, - algorithm_id: Option, + algorithm_id: Option<&common_utils::id_type::RoutingId>, business_profile: &domain::BusinessProfile, - transaction_data: &routing::TransactionData<'_, F>, + transaction_data: &routing::TransactionData<'_>, ) -> RoutingResult> { let algorithm_id = if let Some(id) = algorithm_id { id @@ -300,8 +307,8 @@ pub async fn perform_static_routing_v1( let cached_algorithm = ensure_algorithm_cached_v1( state, merchant_id, - &algorithm_id, - business_profile.get_id().to_owned(), + algorithm_id, + business_profile.get_id(), &api_enums::TransactionType::from(transaction_data), ) .await?; @@ -332,7 +339,7 @@ async fn ensure_algorithm_cached_v1( state: &SessionState, merchant_id: &common_utils::id_type::MerchantId, algorithm_id: &common_utils::id_type::RoutingId, - profile_id: common_utils::id_type::ProfileId, + profile_id: &common_utils::id_type::ProfileId, transaction_type: &api_enums::TransactionType, ) -> RoutingResult> { let key = { @@ -373,7 +380,7 @@ async fn ensure_algorithm_cached_v1( pub fn perform_straight_through_routing( algorithm: &routing_types::StraightThroughAlgorithm, - creds_identifier: Option, + creds_identifier: Option<&str>, ) -> RoutingResult<(Vec, bool)> { Ok(match algorithm { routing_types::StraightThroughAlgorithm::Single(conn) => { @@ -417,12 +424,12 @@ pub async fn refresh_routing_cache_v1( state: &SessionState, key: String, algorithm_id: &common_utils::id_type::RoutingId, - profile_id: common_utils::id_type::ProfileId, + profile_id: &common_utils::id_type::ProfileId, ) -> RoutingResult> { let algorithm = { let algorithm = state .store - .find_routing_algorithm_by_profile_id_algorithm_id(&profile_id, algorithm_id) + .find_routing_algorithm_by_profile_id_algorithm_id(profile_id, algorithm_id) .await .change_context(errors::RoutingError::DslMissingInDb)?; let algorithm: routing_types::RoutingAlgorithm = algorithm @@ -499,7 +506,7 @@ pub fn perform_volume_split( pub async fn get_merchant_cgraph<'a>( state: &SessionState, key_store: &domain::MerchantKeyStore, - profile_id: common_utils::id_type::ProfileId, + profile_id: &common_utils::id_type::ProfileId, transaction_type: &api_enums::TransactionType, ) -> RoutingResult>> { let merchant_id = &key_store.merchant_id; @@ -546,7 +553,7 @@ pub async fn refresh_cgraph_cache<'a>( state: &SessionState, key_store: &domain::MerchantKeyStore, key: String, - profile_id: common_utils::id_type::ProfileId, + profile_id: &common_utils::id_type::ProfileId, transaction_type: &api_enums::TransactionType, ) -> RoutingResult>> { let mut merchant_connector_accounts = state @@ -585,7 +592,7 @@ pub async fn refresh_cgraph_cache<'a>( let merchant_connector_accounts = payments_oss::helpers::filter_mca_based_on_profile_and_connector_type( merchant_connector_accounts, - &profile_id, + profile_id, connector_type, ); @@ -645,7 +652,7 @@ async fn perform_cgraph_filtering( chosen: Vec, backend_input: dsl_inputs::BackendInput, eligible_connectors: Option<&Vec>, - profile_id: common_utils::id_type::ProfileId, + profile_id: &common_utils::id_type::ProfileId, transaction_type: &api_enums::TransactionType, ) -> RoutingResult> { let context = euclid_graph::AnalysisContext::from_dir_values( @@ -683,13 +690,13 @@ async fn perform_cgraph_filtering( Ok(final_selection) } -pub async fn perform_eligibility_analysis( +pub async fn perform_eligibility_analysis( state: &SessionState, key_store: &domain::MerchantKeyStore, chosen: Vec, - transaction_data: &routing::TransactionData<'_, F>, + transaction_data: &routing::TransactionData<'_>, eligible_connectors: Option<&Vec>, - profile_id: common_utils::id_type::ProfileId, + profile_id: &common_utils::id_type::ProfileId, ) -> RoutingResult> { let backend_input = match transaction_data { routing::TransactionData::Payment(payment_data) => make_dsl_input(payment_data)?, @@ -709,10 +716,10 @@ pub async fn perform_eligibility_analysis( .await } -pub async fn perform_fallback_routing( +pub async fn perform_fallback_routing( state: &SessionState, key_store: &domain::MerchantKeyStore, - transaction_data: &routing::TransactionData<'_, F>, + transaction_data: &routing::TransactionData<'_>, eligible_connectors: Option<&Vec>, business_profile: &domain::BusinessProfile, ) -> RoutingResult> { @@ -752,17 +759,17 @@ pub async fn perform_fallback_routing( fallback_config, backend_input, eligible_connectors, - business_profile.get_id().to_owned(), + business_profile.get_id(), &api_enums::TransactionType::from(transaction_data), ) .await } -pub async fn perform_eligibility_analysis_with_fallback( +pub async fn perform_eligibility_analysis_with_fallback( state: &SessionState, key_store: &domain::MerchantKeyStore, chosen: Vec, - transaction_data: &routing::TransactionData<'_, F>, + transaction_data: &routing::TransactionData<'_>, eligible_connectors: Option>, business_profile: &domain::BusinessProfile, ) -> RoutingResult> { @@ -772,7 +779,7 @@ pub async fn perform_eligibility_analysis_with_fallback( chosen, transaction_data, eligible_connectors.as_ref(), - business_profile.get_id().to_owned(), + business_profile.get_id(), ) .await?; @@ -850,6 +857,7 @@ pub async fn perform_session_flow_routing( card_network: None, }; + #[cfg(feature = "v1")] let payment_input = dsl_inputs::PaymentInput { amount: session_input.payment_intent.amount, currency: session_input @@ -876,6 +884,30 @@ pub async fn perform_session_flow_routing( setup_future_usage: session_input.payment_intent.setup_future_usage, }; + #[cfg(feature = "v2")] + let payment_input = dsl_inputs::PaymentInput { + amount: session_input.payment_intent.amount, + currency: session_input + .payment_intent + .currency + .get_required_value("Currency") + .change_context(errors::RoutingError::DslMissingRequiredField { + field_name: "currency".to_string(), + })?, + authentication_type: session_input.payment_attempt.authentication_type, + card_bin: None, + capture_method: session_input + .payment_attempt + .capture_method + .and_then(|cm| cm.foreign_into()), + business_country: None, + business_label: None, + billing_country: session_input + .country + .map(storage_enums::Country::from_alpha2), + setup_future_usage: session_input.payment_intent.setup_future_usage, + }; + let metadata = session_input .payment_intent .metadata @@ -926,7 +958,7 @@ pub async fn perform_session_flow_routing( routing_algorithm: &routing_algorithm, backend_input: backend_input.clone(), allowed_connectors, - profile_id: profile_id.clone(), + profile_id: &profile_id, }; let routable_connector_choice_option = perform_session_routing_for_pm_type( &session_pm_input, @@ -981,7 +1013,7 @@ async fn perform_session_routing_for_pm_type( &session_pm_input.state.clone(), merchant_id, algorithm_id, - session_pm_input.profile_id.clone(), + session_pm_input.profile_id, transaction_type, ) .await?; @@ -1014,7 +1046,7 @@ async fn perform_session_routing_for_pm_type( chosen_connectors, session_pm_input.backend_input.clone(), None, - session_pm_input.profile_id.clone(), + session_pm_input.profile_id, transaction_type, ) .await?; @@ -1034,7 +1066,7 @@ async fn perform_session_routing_for_pm_type( fallback, session_pm_input.backend_input.clone(), None, - session_pm_input.profile_id.clone(), + session_pm_input.profile_id, transaction_type, ) .await?; @@ -1063,7 +1095,7 @@ async fn perform_session_routing_for_pm_type( &session_pm_input.state.clone(), merchant_id, algorithm_id, - session_pm_input.profile_id.clone(), + &session_pm_input.profile_id, transaction_type, ) .await?; @@ -1092,7 +1124,7 @@ async fn perform_session_routing_for_pm_type( chosen_connectors, session_pm_input.backend_input.clone(), None, - session_pm_input.profile_id.clone(), + &session_pm_input.profile_id, transaction_type, ) .await?; @@ -1108,7 +1140,7 @@ async fn perform_session_routing_for_pm_type( fallback, session_pm_input.backend_input.clone(), None, - session_pm_input.profile_id.clone(), + &session_pm_input.profile_id, transaction_type, ) .await?; @@ -1120,6 +1152,17 @@ async fn perform_session_routing_for_pm_type( Ok(Some(final_selection)) } } + +#[cfg(feature = "v2")] +pub fn make_dsl_input_for_surcharge( + payment_attempt: &oss_storage::PaymentAttempt, + payment_intent: &oss_storage::PaymentIntent, + billing_address: Option
, +) -> RoutingResult { + todo!() +} + +#[cfg(feature = "v1")] pub fn make_dsl_input_for_surcharge( payment_attempt: &oss_storage::PaymentAttempt, payment_intent: &oss_storage::PaymentIntent, @@ -1130,6 +1173,7 @@ pub fn make_dsl_input_for_surcharge( mandate_type: None, payment_type: None, }; + let payment_input = dsl_inputs::PaymentInput { amount: payment_attempt.amount, // currency is always populated in payment_attempt during payment create @@ -1152,6 +1196,7 @@ pub fn make_dsl_input_for_surcharge( business_label: payment_intent.business_label.clone(), setup_future_usage: payment_intent.setup_future_usage, }; + let metadata = payment_intent .metadata .clone() diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index a41205a496d1..853706327942 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -12,7 +12,7 @@ use hyperswitch_domain_models::{payments::payment_intent::CustomerData, router_r use masking::{ExposeInterface, Maskable, PeekInterface, Secret}; use router_env::{instrument, metrics::add_attributes, tracing}; -use super::{flows::Feature, types::AuthenticationData, PaymentData}; +use super::{flows::Feature, types::AuthenticationData, OperationSessionGetters, PaymentData}; use crate::{ configs::settings::ConnectorRequestReferenceIdConfig, connector::{Helcim, Nexinets}, @@ -35,6 +35,121 @@ use crate::{ utils::{OptionExt, ValueExt}, }; +pub async fn construct_router_data_to_update_calculated_tax<'a, F, T>( + state: &'a SessionState, + payment_data: PaymentData, + connector_id: &str, + merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, + customer: &'a Option, + merchant_connector_account: &helpers::MerchantConnectorAccountType, +) -> RouterResult> +where + T: TryFrom>, + types::RouterData: Feature, + F: Clone, + error_stack::Report: + From<>>::Error>, +{ + fp_utils::when(merchant_connector_account.is_disabled(), || { + Err(errors::ApiErrorResponse::MerchantConnectorAccountDisabled) + })?; + + let test_mode = merchant_connector_account.is_test_mode_on(); + + let auth_type: types::ConnectorAuthType = merchant_connector_account + .get_connector_account_details() + .parse_value("ConnectorAuthType") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while parsing value for ConnectorAuthType")?; + + let resource_id = match payment_data + .payment_attempt + .connector_transaction_id + .clone() + { + Some(id) => types::ResponseId::ConnectorTransactionId(id), + None => types::ResponseId::NoResponseId, + }; + + // [#44]: why should response be filled during request + let response = Ok(types::PaymentsResponseData::TransactionResponse { + resource_id, + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }); + let additional_data = PaymentAdditionalData { + router_base_url: state.base_url.clone(), + connector_name: connector_id.to_string(), + payment_data: payment_data.clone(), + state, + customer_data: customer, + }; + + let router_data = types::RouterData { + flow: PhantomData, + merchant_id: merchant_account.get_id().clone(), + customer_id: None, + connector: connector_id.to_owned(), + payment_id: payment_data + .payment_attempt + .payment_id + .get_string_repr() + .to_owned(), + attempt_id: payment_data.payment_attempt.attempt_id.clone(), + status: payment_data.payment_attempt.status, + payment_method: diesel_models::enums::PaymentMethod::default(), + connector_auth_type: auth_type, + description: None, + return_url: None, + address: payment_data.address.clone(), + auth_type: payment_data + .payment_attempt + .authentication_type + .unwrap_or_default(), + connector_meta_data: None, + connector_wallets_details: None, + request: T::try_from(additional_data)?, + response, + amount_captured: None, + minor_amount_captured: None, + access_token: None, + session_token: None, + reference_id: None, + payment_method_status: None, + payment_method_token: None, + connector_customer: None, + recurring_mandate_payment_data: None, + connector_request_reference_id: core_utils::get_connector_request_reference_id( + &state.conf, + merchant_account.get_id(), + &payment_data.payment_attempt, + ), + preprocessing_id: None, + #[cfg(feature = "payouts")] + payout_method_data: None, + #[cfg(feature = "payouts")] + quote_id: None, + test_mode, + payment_method_balance: None, + connector_api_version: None, + connector_http_status_code: None, + external_latency: None, + apple_pay_flow: None, + frm_metadata: None, + refund_id: None, + dispute_id: None, + connector_response: None, + integrity_check: Ok(()), + }; + Ok(router_data) +} + #[cfg(all(feature = "v2", feature = "customer_v2"))] #[instrument(skip_all)] #[allow(clippy::too_many_arguments)] @@ -249,10 +364,11 @@ where Ok(router_data) } -pub trait ToResponse +pub trait ToResponse where Self: Sized, Op: Debug, + D: OperationSessionGetters, { #[allow(clippy::too_many_arguments)] fn generate_response( @@ -268,14 +384,15 @@ where ) -> RouterResponse; } -impl ToResponse, Op> for api::PaymentsResponse +impl ToResponse for api::PaymentsResponse where F: Clone, Op: Debug, + D: OperationSessionGetters, { #[allow(clippy::too_many_arguments)] fn generate_response( - payment_data: PaymentData, + payment_data: D, customer: Option, auth_flow: services::AuthFlow, base_url: &str, @@ -285,23 +402,21 @@ where external_latency: Option, is_latency_header_enabled: Option, ) -> RouterResponse { - let captures = - payment_data - .multiple_capture_data - .clone() - .and_then(|multiple_capture_data| { - multiple_capture_data - .expand_captures - .and_then(|should_expand| { - should_expand.then_some( - multiple_capture_data - .get_all_captures() - .into_iter() - .cloned() - .collect(), - ) - }) - }); + let captures = payment_data + .get_multiple_capture_data() + .and_then(|multiple_capture_data| { + multiple_capture_data + .expand_captures + .and_then(|should_expand| { + should_expand.then_some( + multiple_capture_data + .get_all_captures() + .into_iter() + .cloned() + .collect(), + ) + }) + }); payments_to_payments_response( payment_data, @@ -318,14 +433,15 @@ where } } -impl ToResponse, Op> for api::PaymentsSessionResponse +impl ToResponse for api::PaymentsSessionResponse where F: Clone, Op: Debug, + D: OperationSessionGetters, { #[allow(clippy::too_many_arguments)] fn generate_response( - payment_data: PaymentData, + payment_data: D, _customer: Option, _auth_flow: services::AuthFlow, _base_url: &str, @@ -337,11 +453,12 @@ where ) -> RouterResponse { Ok(services::ApplicationResponse::JsonWithHeaders(( Self { - session_token: payment_data.sessions_token, - payment_id: payment_data.payment_attempt.payment_id, + session_token: payment_data.get_sessions_token(), + payment_id: payment_data.get_payment_attempt().payment_id.clone(), client_secret: payment_data - .payment_intent + .get_payment_intent() .client_secret + .clone() .get_required_value("client_secret")? .into(), }, @@ -350,15 +467,64 @@ where } } -impl ToResponse, Op> for api::VerifyResponse +impl ToResponse for api::PaymentsDynamicTaxCalculationResponse +where + F: Clone, + Op: Debug, + D: OperationSessionGetters, +{ + #[allow(clippy::too_many_arguments)] + fn generate_response( + payment_data: D, + _customer: Option, + _auth_flow: services::AuthFlow, + _base_url: &str, + _operation: Op, + _connector_request_reference_id_config: &ConnectorRequestReferenceIdConfig, + _connector_http_status_code: Option, + _external_latency: Option, + _is_latency_header_enabled: Option, + ) -> RouterResponse { + let mut amount = payment_data.get_payment_intent().amount; + let shipping_cost = payment_data.get_payment_intent().shipping_cost; + if let Some(shipping_cost) = shipping_cost { + amount = amount + shipping_cost; + } + let order_tax_amount = payment_data + .get_payment_intent() + .tax_details + .clone() + .and_then(|tax| { + tax.payment_method_type + .map(|a| a.order_tax_amount) + .or_else(|| tax.default.map(|a| a.order_tax_amount)) + }); + if let Some(tax_amount) = order_tax_amount { + amount = amount + tax_amount; + } + + Ok(services::ApplicationResponse::JsonWithHeaders(( + Self { + net_amount: amount, + payment_id: payment_data.get_payment_attempt().payment_id.clone(), + order_tax_amount, + shipping_cost, + }, + vec![], + ))) + } +} + +impl ToResponse for api::VerifyResponse where F: Clone, Op: Debug, + D: OperationSessionGetters, { #[cfg(all(feature = "v2", feature = "customer_v2"))] #[allow(clippy::too_many_arguments)] fn generate_response( - _data: PaymentData, + _data: D, _customer: Option, _auth_flow: services::AuthFlow, _base_url: &str, @@ -374,7 +540,7 @@ where #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] #[allow(clippy::too_many_arguments)] fn generate_response( - data: PaymentData, + payment_data: D, customer: Option, _auth_flow: services::AuthFlow, _base_url: &str, @@ -385,7 +551,8 @@ where _is_latency_header_enabled: Option, ) -> RouterResponse { let additional_payment_method_data: Option = - data.payment_attempt + payment_data + .get_payment_attempt() .payment_method_data .clone() .map(|data| data.parse_value("payment_method_data")) @@ -397,9 +564,13 @@ where additional_payment_method_data.map(api::PaymentMethodDataResponse::from); Ok(services::ApplicationResponse::JsonWithHeaders(( Self { - verify_id: Some(data.payment_intent.payment_id), - merchant_id: Some(data.payment_intent.merchant_id), - client_secret: data.payment_intent.client_secret.map(Secret::new), + verify_id: Some(payment_data.get_payment_intent().payment_id.clone()), + merchant_id: Some(payment_data.get_payment_intent().merchant_id.clone()), + client_secret: payment_data + .get_payment_intent() + .client_secret + .clone() + .map(Secret::new), customer_id: customer.as_ref().map(|x| x.customer_id.clone()), email: customer .as_ref() @@ -410,14 +581,14 @@ where phone: customer .as_ref() .and_then(|cus| cus.phone.as_ref().map(|s| s.to_owned())), - mandate_id: data - .mandate_id - .and_then(|mandate_ids| mandate_ids.mandate_id), - payment_method: data.payment_attempt.payment_method, + mandate_id: payment_data + .get_mandate_id() + .and_then(|mandate_ids| mandate_ids.mandate_id.clone()), + payment_method: payment_data.get_payment_attempt().payment_method, payment_method_data: payment_method_data_response, - payment_token: data.token, - error_code: data.payment_attempt.error_code, - error_message: data.payment_attempt.error_message, + payment_token: payment_data.get_token().map(ToString::to_string), + error_code: payment_data.get_payment_attempt().clone().error_code, + error_message: payment_data.get_payment_attempt().clone().error_message, }, vec![], ))) @@ -429,8 +600,8 @@ where // try to use router data here so that already validated things , we don't want to repeat the validations. // Add internal value not found and external value not found so that we can give 500 / Internal server error for internal value not found #[allow(clippy::too_many_arguments)] -pub fn payments_to_payments_response( - _payment_data: PaymentData, +pub fn payments_to_payments_response( + _payment_data: D, _captures: Option>, _customer: Option, _auth_flow: services::AuthFlow, @@ -443,6 +614,7 @@ pub fn payments_to_payments_response( ) -> RouterResponse where Op: Debug, + D: OperationSessionGetters, { todo!() } @@ -452,8 +624,8 @@ where // try to use router data here so that already validated things , we don't want to repeat the validations. // Add internal value not found and external value not found so that we can give 500 / Internal server error for internal value not found #[allow(clippy::too_many_arguments)] -pub fn payments_to_payments_response( - payment_data: PaymentData, +pub fn payments_to_payments_response( + payment_data: D, captures: Option>, customer: Option, _auth_flow: services::AuthFlow, @@ -466,10 +638,13 @@ pub fn payments_to_payments_response( ) -> RouterResponse where Op: Debug, + D: OperationSessionGetters, { - let payment_attempt = payment_data.payment_attempt; - let payment_intent = payment_data.payment_intent; - let payment_link_data = payment_data.payment_link_data; + use std::ops::Not; + + let payment_attempt = payment_data.get_payment_attempt().clone(); + let payment_intent = payment_data.get_payment_intent().clone(); + let payment_link_data = payment_data.get_payment_link_data(); let currency = payment_attempt .currency @@ -481,48 +656,37 @@ where field_name: "amount", })?; let mandate_id = payment_attempt.mandate_id.clone(); - let refunds_response = if payment_data.refunds.is_empty() { - None - } else { - Some( - payment_data - .refunds - .into_iter() - .map(ForeignInto::foreign_into) - .collect(), - ) - }; - let disputes_response = if payment_data.disputes.is_empty() { - None - } else { - Some( - payment_data - .disputes - .into_iter() - .map(ForeignInto::foreign_into) - .collect(), - ) - }; + let refunds_response = payment_data.get_refunds().is_empty().not().then(|| { + payment_data + .get_refunds() + .into_iter() + .map(ForeignInto::foreign_into) + .collect() + }); - let incremental_authorizations_response = if payment_data.authorizations.is_empty() { - None - } else { - Some( + let disputes_response = payment_data.get_disputes().is_empty().not().then(|| { + payment_data + .get_disputes() + .into_iter() + .map(ForeignInto::foreign_into) + .collect() + }); + + let incremental_authorizations_response = + payment_data.get_authorizations().is_empty().not().then(|| { payment_data - .authorizations + .get_authorizations() .into_iter() .map(ForeignInto::foreign_into) - .collect(), - ) - }; + .collect() + }); let external_authentication_details = payment_data - .authentication - .as_ref() + .get_authentication() .map(ForeignInto::foreign_into); - let attempts_response = payment_data.attempts.map(|attempts| { + let attempts_response = payment_data.get_attempts().map(|attempts| { attempts .into_iter() .map(ForeignInto::foreign_into) @@ -564,20 +728,20 @@ where tax_amount: payment_attempt.tax_amount, }); let merchant_decision = payment_intent.merchant_decision.to_owned(); - let frm_message = payment_data.frm_message.map(FrmMessage::foreign_from); + let frm_message = payment_data.get_frm_message().map(FrmMessage::foreign_from); let payment_method_data = additional_payment_method_data.map(api::PaymentMethodDataResponse::from); let payment_method_data_response = (payment_method_data.is_some() || payment_data - .address + .get_address() .get_request_payment_method_billing() .is_some()) .then_some(api_models::payments::PaymentMethodDataResponseWithBilling { payment_method_data, billing: payment_data - .address + .get_address() .get_request_payment_method_billing() .cloned(), }); @@ -669,6 +833,7 @@ where { let redirection_data = payment_attempt .authentication_data + .clone() .get_required_value("redirection_data")?; let form: RedirectForm = serde_json::from_value(redirection_data) @@ -676,7 +841,7 @@ where services::ApplicationResponse::Form(Box::new(services::RedirectionFormData { redirect_form: form, - payment_method_data: payment_data.payment_method_data.map(Into::into), + payment_method_data: payment_data.get_payment_method_data().cloned(), amount, currency: currency.to_string(), })) @@ -704,7 +869,7 @@ where || next_action_containing_wait_screen.is_some() || papal_sdk_next_action.is_some() || next_action_containing_fetch_qr_code_url.is_some() - || payment_data.authentication.is_some() + || payment_data.get_authentication().is_some() { next_action_response = bank_transfer_next_steps .map(|bank_transfer| { @@ -745,11 +910,11 @@ where ), } })) - .or(match payment_data.authentication.as_ref(){ + .or(match payment_data.get_authentication().as_ref(){ Some(authentication) => { if payment_intent.status == common_enums::IntentStatus::RequiresCustomerAction && authentication.cavv.is_none() && authentication.is_separate_authn_required(){ // if preAuthn and separate authentication needed. - let poll_config = payment_data.poll_config.unwrap_or_default(); + let poll_config = payment_data.get_poll_config().unwrap_or_default(); let request_poll_id = core_utils::get_external_authentication_request_poll_id(&payment_intent.payment_id); let payment_connector_name = payment_attempt.connector .as_ref() @@ -791,7 +956,7 @@ where if third_party_sdk_session_next_action(&payment_attempt, operation) { next_action_response = Some( api_models::payments::NextActionData::ThirdPartySdkSessionToken { - session_token: payment_data.sessions_token.first().cloned(), + session_token: payment_data.get_sessions_token().first().cloned(), }, ) } @@ -829,23 +994,26 @@ where } }; - let mandate_data = payment_data.setup_mandate.map(|d| api::MandateData { - customer_acceptance: d.customer_acceptance.map(|d| api::CustomerAcceptance { - acceptance_type: match d.acceptance_type { - hyperswitch_domain_models::mandates::AcceptanceType::Online => { - api::AcceptanceType::Online - } - hyperswitch_domain_models::mandates::AcceptanceType::Offline => { - api::AcceptanceType::Offline - } - }, - accepted_at: d.accepted_at, - online: d.online.map(|d| api::OnlineMandate { - ip_address: d.ip_address, - user_agent: d.user_agent, + let mandate_data = payment_data.get_setup_mandate().map(|d| api::MandateData { + customer_acceptance: d + .customer_acceptance + .clone() + .map(|d| api::CustomerAcceptance { + acceptance_type: match d.acceptance_type { + hyperswitch_domain_models::mandates::AcceptanceType::Online => { + api::AcceptanceType::Online + } + hyperswitch_domain_models::mandates::AcceptanceType::Offline => { + api::AcceptanceType::Offline + } + }, + accepted_at: d.accepted_at, + online: d.online.map(|d| api::OnlineMandate { + ip_address: d.ip_address, + user_agent: d.user_agent, + }), }), - }), - mandate_type: d.mandate_type.map(|d| match d { + mandate_type: d.mandate_type.clone().map(|d| match d { hyperswitch_domain_models::mandates::MandateDataType::MultiUse(Some(i)) => { api::MandateType::MultiUse(Some(api::MandateAmountData { amount: i.amount, @@ -868,9 +1036,24 @@ where api::MandateType::MultiUse(None) } }), - update_mandate_id: d.update_mandate_id, + update_mandate_id: d.update_mandate_id.clone(), }); + let order_tax_amount = payment_data + .get_payment_attempt() + .order_tax_amount + .or_else(|| { + payment_data + .get_payment_intent() + .tax_details + .clone() + .and_then(|tax| { + tax.payment_method_type + .map(|a| a.order_tax_amount) + .or_else(|| tax.default.map(|a| a.order_tax_amount)) + }) + }); + let payments_response = api::PaymentsResponse { payment_id: payment_intent.payment_id, merchant_id: payment_intent.merchant_id, @@ -899,8 +1082,8 @@ where payment_method: payment_attempt.payment_method, payment_method_data: payment_method_data_response, payment_token: payment_attempt.payment_token, - shipping: payment_data.address.get_shipping().cloned(), - billing: payment_data.address.get_payment_billing().cloned(), + shipping: payment_data.get_address().get_shipping().cloned(), + billing: payment_data.get_address().get_payment_billing().cloned(), order_details: payment_intent.order_details, email: customer .as_ref() @@ -930,7 +1113,9 @@ where business_label: payment_intent.business_label, business_sub_label: payment_attempt.business_sub_label, allowed_payment_method_types: payment_intent.allowed_payment_method_types, - ephemeral_key: payment_data.ephemeral_key.map(ForeignFrom::foreign_from), + ephemeral_key: payment_data + .get_ephemeral_key() + .map(ForeignFrom::foreign_from), manual_retry_allowed: helpers::is_manual_retry_allowed( &payment_intent.status, &payment_attempt.status, @@ -959,11 +1144,14 @@ where fingerprint: payment_intent.fingerprint_id, browser_info: payment_attempt.browser_info, payment_method_id: payment_attempt.payment_method_id, - payment_method_status: payment_data.payment_method_info.map(|info| info.status), + payment_method_status: payment_data + .get_payment_method_info() + .map(|info| info.status), updated: Some(payment_intent.modified_at), charges: charges_response, frm_metadata: payment_intent.frm_metadata, merchant_order_reference_id: payment_intent.merchant_order_reference_id, + order_tax_amount, }; services::ApplicationResponse::JsonWithHeaders((payments_response, headers)) @@ -1094,6 +1282,7 @@ pub fn wait_screen_next_steps_check( Ok(display_info_with_timer_instructions) } +#[cfg(feature = "v1")] impl ForeignFrom<(storage::PaymentIntent, storage::PaymentAttempt)> for api::PaymentsResponse { fn foreign_from((pi, pa): (storage::PaymentIntent, storage::PaymentAttempt)) -> Self { Self { @@ -1215,6 +1404,7 @@ impl ForeignFrom<(storage::PaymentIntent, storage::PaymentAttempt)> for api::Pay updated: None, charges: None, frm_metadata: None, + order_tax_amount: None, } } } @@ -1297,6 +1487,7 @@ pub fn change_order_details_to_new_type( sub_category: order_details.sub_category, brand: order_details.brand, product_type: order_details.product_type, + product_tax_code: order_details.product_tax_code, }]) } @@ -1721,6 +1912,28 @@ impl TryFrom> for types::PaymentsApproveD } } +impl TryFrom> for types::SdkPaymentsSessionUpdateData { + type Error = error_stack::Report; + + fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result { + let payment_data = additional_data.payment_data; + let order_tax_amount = payment_data + .payment_intent + .tax_details + .clone() + .and_then(|tax| tax.payment_method_type.map(|pmt| pmt.order_tax_amount)) + .ok_or(errors::ApiErrorResponse::MissingRequiredField { + field_name: "order_tax_amount", + })?; + let amount = payment_data.payment_intent.amount; + + Ok(Self { + net_amount: amount + order_tax_amount, //need to change after we move to connector module + order_tax_amount, + }) + } +} + impl TryFrom> for types::PaymentsRejectData { type Error = error_stack::Report; @@ -1781,6 +1994,7 @@ impl TryFrom> for types::PaymentsSessionD } } +#[cfg(feature = "v1")] impl TryFrom> for types::SetupMandateRequestData { type Error = error_stack::Report; @@ -1846,6 +2060,15 @@ impl TryFrom> for types::SetupMandateRequ } } +#[cfg(feature = "v2")] +impl TryFrom> for types::SetupMandateRequestData { + type Error = error_stack::Report; + + fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result { + todo!() + } +} + impl ForeignTryFrom for storage::CaptureUpdate { type Error = error_stack::Report; @@ -1888,6 +2111,7 @@ impl ForeignTryFrom for storage::CaptureUpdate { } } +#[cfg(feature = "v1")] impl TryFrom> for types::CompleteAuthorizeData { type Error = error_stack::Report; @@ -1946,6 +2170,15 @@ impl TryFrom> for types::CompleteAuthoriz } } +#[cfg(feature = "v2")] +impl TryFrom> for types::CompleteAuthorizeData { + type Error = error_stack::Report; + + fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result { + todo!() + } +} + impl TryFrom> for types::PaymentsPreProcessingData { type Error = error_stack::Report; diff --git a/crates/router/src/core/payout_link.rs b/crates/router/src/core/payout_link.rs index 210d0e6d38b5..a153d071088c 100644 --- a/crates/router/src/core/payout_link.rs +++ b/crates/router/src/core/payout_link.rs @@ -21,7 +21,7 @@ use crate::{ errors, routes::{app::StorageInterface, SessionState}, services, - types::domain, + types::{api, domain, transformers::ForeignFrom}, }; #[cfg(all(feature = "v2", feature = "customer_v2"))] @@ -156,9 +156,31 @@ pub async fn initiate_payout_link( .attach_printable_lazy(|| { format!("customer [{}] not found", payout_link.primary_reference) })?; + let address = payout + .address_id + .as_ref() + .async_map(|address_id| async { + db.find_address_by_address_id(&(&state).into(), address_id, &key_store) + .await + }) + .await + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable_lazy(|| { + format!( + "Failed while fetching address [id - {:?}] for payout [id - {}]", + payout.address_id, payout.payout_id + ) + })?; - let enabled_payout_methods = - filter_payout_methods(&state, &merchant_account, &key_store, &payout).await?; + let enabled_payout_methods = filter_payout_methods( + &state, + &merchant_account, + &key_store, + &payout, + address.as_ref(), + ) + .await?; // Fetch default enabled_payout_methods let mut default_enabled_payout_methods: Vec = vec![]; for (payment_method, payment_method_types) in @@ -188,6 +210,16 @@ pub async fn initiate_payout_link( _ => Ordering::Equal, }); + let required_field_override = api::RequiredFieldsOverrideRequest { + billing: address.as_ref().map(From::from), + }; + + let enabled_payment_methods_with_required_fields = ForeignFrom::foreign_from(( + &state.conf.payouts.required_fields, + enabled_payment_methods.clone(), + required_field_override, + )); + let js_data = payouts::PayoutLinkDetails { publishable_key: masking::Secret::new(merchant_account.publishable_key), client_secret: link_data.client_secret.clone(), @@ -204,9 +236,11 @@ pub async fn initiate_payout_link( .attach_printable("Failed to parse payout status link's return URL")?, ui_config: ui_config_data, enabled_payment_methods, + enabled_payment_methods_with_required_fields, amount, currency: payout.destination_currency, locale: locale.clone(), + form_layout: link_data.form_layout, test_mode: link_data.test_mode.unwrap_or(false), }; @@ -287,6 +321,7 @@ pub async fn filter_payout_methods( merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, payout: &hyperswitch_domain_models::payouts::payouts::Payouts, + address: Option<&domain::Address>, ) -> errors::RouterResult> { use masking::ExposeInterface; @@ -308,22 +343,6 @@ pub async fn filter_payout_methods( &payout.profile_id, common_enums::ConnectorType::PayoutProcessor, ); - let address = payout - .address_id - .as_ref() - .async_map(|address_id| async { - db.find_address_by_address_id(key_manager_state, address_id, key_store) - .await - }) - .await - .transpose() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable_lazy(|| { - format!( - "Failed while fetching address [id - {:?}] for payout [id - {}]", - payout.address_id, payout.payout_id - ) - })?; let mut response: Vec = vec![]; let mut payment_method_list_hm: HashMap< diff --git a/crates/router/src/core/payouts.rs b/crates/router/src/core/payouts.rs index e7acdd18006a..85d5b78521f7 100644 --- a/crates/router/src/core/payouts.rs +++ b/crates/router/src/core/payouts.rs @@ -6,16 +6,16 @@ pub mod transformers; pub mod validator; use std::{collections::HashSet, vec::IntoIter}; +#[cfg(feature = "olap")] +use api_models::payments as payment_enums; use api_models::{self, enums as api_enums, payouts::PayoutLinkResponse}; #[cfg(feature = "payout_retry")] use common_enums::PayoutRetryType; use common_utils::{ consts, - crypto::Encryptable, ext_traits::{AsyncExt, ValueExt}, id_type::CustomerId, link_utils::{GenericLinkStatus, GenericLinkUiConfig, PayoutLinkData, PayoutLinkStatus}, - pii, types::MinorUnit, }; use diesel_models::{ @@ -35,6 +35,8 @@ use time::Duration; #[cfg(feature = "olap")] use crate::types::domain::behaviour::Conversion; +#[cfg(feature = "olap")] +use crate::types::PayoutActionData; use crate::{ core::{ errors::{ @@ -772,7 +774,9 @@ pub async fn payouts_list_core( .to_not_found_response(errors::ApiErrorResponse::PayoutNotFound)?; let payouts = core_utils::filter_objects_based_on_profile_id_list(profile_id_list, payouts); - let collected_futures = payouts.into_iter().map(|payout| async { + let mut pi_pa_tuple_vec = PayoutActionData::new(); + + for payout in payouts { match db .find_payout_attempt_by_merchant_id_payout_attempt_id( merchant_id, @@ -780,73 +784,80 @@ pub async fn payouts_list_core( storage_enums::MerchantStorageScheme::PostgresOnly, ) .await + .change_context(errors::ApiErrorResponse::InternalServerError) { - Ok(ref payout_attempt) => match payout.customer_id.clone() { - Some(ref customer_id) => { + Ok(payout_attempt) => { + let domain_customer = match payout.customer_id.clone() { #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] - match db + Some(customer_id) => db .find_customer_by_customer_id_merchant_id( &(&state).into(), - customer_id, + &customer_id, merchant_id, &key_store, merchant_account.storage_scheme, ) .await - { - Ok(customer) => Ok((payout, payout_attempt.to_owned(), Some(customer))), - Err(err) => { + .map_err(|err| { let err_msg = format!( "failed while fetching customer for customer_id - {:?}", customer_id ); logger::warn!(?err, err_msg); - if err.current_context().is_db_not_found() { - Ok((payout, payout_attempt.to_owned(), None)) - } else { - Err(err - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable(err_msg)) - } - } - } - } - None => Ok((payout.to_owned(), payout_attempt.to_owned(), None)), - }, + }) + .ok(), + _ => None, + }; + + let payout_id_as_payment_id_type = + common_utils::id_type::PaymentId::wrap(payout.payout_id.clone()) + .change_context(errors::ApiErrorResponse::InvalidRequestData { + message: "payout_id contains invalid data".to_string(), + }) + .attach_printable("Error converting payout_id to PaymentId type")?; + + let payment_addr = payment_helpers::create_or_find_address_for_payment_by_request( + &state, + None, + payout.address_id.as_deref(), + merchant_id, + payout.customer_id.as_ref(), + &key_store, + &payout_id_as_payment_id_type, + merchant_account.storage_scheme, + ) + .await + .transpose() + .and_then(|addr| { + addr.map_err(|err| { + let err_msg = format!( + "billing_address missing for address_id : {:?}", + payout.address_id + ); + logger::warn!(?err, err_msg); + }) + .ok() + .map(payment_enums::Address::foreign_from) + }); + + pi_pa_tuple_vec.push(( + payout.to_owned(), + payout_attempt.to_owned(), + domain_customer, + payment_addr, + )); + } Err(err) => { let err_msg = format!( - "failed while fetching payout_attempt for payout_id - {}", - payout.payout_id.clone(), + "failed while fetching payout_attempt for payout_id - {:?}", + payout.payout_id ); logger::warn!(?err, err_msg); - Err(err - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable(err_msg)) } } - }); - - let pi_pa_tuple_vec: Result< - Vec<( - storage::Payouts, - storage::PayoutAttempt, - Option, - )>, - _, - > = join_all(collected_futures) - .await - .into_iter() - .collect::, - )>, - _, - >>(); + } let data: Vec = pi_pa_tuple_vec - .change_context(errors::ApiErrorResponse::InternalServerError)? .into_iter() .map(ForeignFrom::foreign_from) .collect(); @@ -876,6 +887,7 @@ pub async fn payouts_filtered_list_core( storage::Payouts, storage::PayoutAttempt, Option, + Option, )> = db .filter_payouts_and_attempts( merchant_account.get_id(), @@ -885,30 +897,50 @@ pub async fn payouts_filtered_list_core( .await .to_not_found_response(errors::ApiErrorResponse::PayoutNotFound)?; let list = core_utils::filter_objects_based_on_profile_id_list(profile_id_list, list); - let data: Vec = join_all(list.into_iter().map(|(p, pa, c)| async { - let domain_cust = c - .async_and_then(|cust| async { - domain::Customer::convert_back( - &(&state).into(), - cust, - &key_store.key, - key_store.merchant_id.clone().into(), - ) - .await - .map_err(|err| { - let msg = format!("failed to convert customer for id: {:?}", p.customer_id); - logger::warn!(?err, msg); + let data: Vec = + join_all(list.into_iter().map(|(p, pa, customer, address)| async { + let customer: Option = customer + .async_and_then(|cust| async { + domain::Customer::convert_back( + &(&state).into(), + cust, + &key_store.key, + key_store.merchant_id.clone().into(), + ) + .await + .map_err(|err| { + let msg = format!("failed to convert customer for id: {:?}", p.customer_id); + logger::warn!(?err, msg); + }) + .ok() }) - .ok() - }) - .await; - Some((p, pa, domain_cust)) - })) - .await - .into_iter() - .flatten() - .map(ForeignFrom::foreign_from) - .collect(); + .await; + + let payout_addr: Option = address + .async_and_then(|addr| async { + domain::Address::convert_back( + &(&state).into(), + addr, + &key_store.key, + key_store.merchant_id.clone().into(), + ) + .await + .map(ForeignFrom::foreign_from) + .map_err(|err| { + let msg = format!("failed to convert address for id: {:?}", p.address_id); + logger::warn!(?err, msg); + }) + .ok() + }) + .await; + + Some((p, pa, customer, payout_addr)) + })) + .await + .into_iter() + .flatten() + .map(ForeignFrom::foreign_from) + .collect(); let active_payout_ids = db .filter_active_payout_ids_by_constraints(merchant_account.get_id(), &constraints) @@ -947,10 +979,11 @@ pub async fn payouts_filtered_list_core( pub async fn payouts_list_available_filters_core( state: SessionState, merchant_account: domain::MerchantAccount, + profile_id_list: Option>, time_range: api::TimeRange, ) -> RouterResponse { let db = state.store.as_ref(); - let payout = db + let payouts = db .filter_payouts_by_time_range_constraints( merchant_account.get_id(), &time_range, @@ -959,9 +992,11 @@ pub async fn payouts_list_available_filters_core( .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + let payouts = core_utils::filter_objects_based_on_profile_id_list(profile_id_list, payouts); + let filters = db .get_filters_for_payouts( - payout.as_slice(), + payouts.as_slice(), merchant_account.get_id(), storage_enums::MerchantStorageScheme::PostgresOnly, ) @@ -2139,29 +2174,7 @@ pub async fn response_handler( let billing_address = payout_data.billing_address.to_owned(); let customer_details = payout_data.customer_details.to_owned(); let customer_id = payouts.customer_id; - - let address = billing_address.as_ref().map(|a| { - let phone_details = payment_api_types::PhoneDetails { - number: a.phone_number.to_owned().map(Encryptable::into_inner), - country_code: a.country_code.to_owned(), - }; - let address_details = payment_api_types::AddressDetails { - city: a.city.to_owned(), - country: a.country.to_owned(), - line1: a.line1.to_owned().map(Encryptable::into_inner), - line2: a.line2.to_owned().map(Encryptable::into_inner), - line3: a.line3.to_owned().map(Encryptable::into_inner), - zip: a.zip.to_owned().map(Encryptable::into_inner), - first_name: a.first_name.to_owned().map(Encryptable::into_inner), - last_name: a.last_name.to_owned().map(Encryptable::into_inner), - state: a.state.to_owned().map(Encryptable::into_inner), - }; - api::payments::Address { - phone: Some(phone_details), - address: Some(address_details), - email: a.email.to_owned().map(pii::Email::from), - } - }); + let billing = billing_address.as_ref().map(From::from); let response = api::PayoutCreateResponse { payout_id: payouts.payout_id.to_owned(), @@ -2170,7 +2183,7 @@ pub async fn response_handler( currency: payouts.destination_currency.to_owned(), connector: payout_attempt.connector.to_owned(), payout_type: payouts.payout_type.to_owned(), - billing: address, + billing, auto_fulfill: payouts.auto_fulfill, customer_id, email: customer_details.as_ref().and_then(|c| c.email.clone()), @@ -2743,6 +2756,14 @@ pub async fn create_payout_link( .and_then(|config| config.payout_link_id.clone()), "payout_link", )?; + let form_layout = payout_link_config_req + .as_ref() + .and_then(|config| config.form_layout.to_owned()) + .or_else(|| { + profile_config + .as_ref() + .and_then(|config| config.form_layout.to_owned()) + }); let data = PayoutLinkData { payout_link_id: payout_link_id.clone(), @@ -2756,6 +2777,7 @@ pub async fn create_payout_link( amount: MinorUnit::from(*amount), currency: *currency, allowed_domains, + form_layout, test_mode: test_mode_in_config, }; diff --git a/crates/router/src/core/payouts/helpers.rs b/crates/router/src/core/payouts/helpers.rs index 7408a7ea2be7..11cb0c117611 100644 --- a/crates/router/src/core/payouts/helpers.rs +++ b/crates/router/src/core/payouts/helpers.rs @@ -19,6 +19,8 @@ use masking::{PeekInterface, Secret}; use router_env::logger; use super::PayoutData; +#[cfg(feature = "payouts")] +use crate::core::payments::route_connector_v1_for_payouts; use crate::{ consts, core::{ @@ -30,7 +32,7 @@ use crate::{ }, payments::{ customers::get_connector_customer_details_if_present, helpers as payment_helpers, - route_connector_v1, routing, CustomerDetails, + routing, CustomerDetails, }, routing::TransactionData, utils as core_utils, @@ -800,7 +802,7 @@ pub async fn decide_payout_connector( state, key_store, connectors, - &TransactionData::<()>::Payout(payout_data), + &TransactionData::Payout(payout_data), eligible_connectors, &business_profile, ) @@ -848,7 +850,7 @@ pub async fn decide_payout_connector( state, key_store, connectors, - &TransactionData::<()>::Payout(payout_data), + &TransactionData::Payout(payout_data), eligible_connectors, &business_profile, ) @@ -886,15 +888,14 @@ pub async fn decide_payout_connector( } // 4. Route connector - route_connector_v1( + route_connector_v1_for_payouts( state, merchant_account, &payout_data.business_profile, key_store, - TransactionData::<()>::Payout(payout_data), + payout_data, routing_data, eligible_connectors, - None, ) .await } diff --git a/crates/router/src/core/payouts/transformers.rs b/crates/router/src/core/payouts/transformers.rs index d10e56cff432..ed17eb71afaf 100644 --- a/crates/router/src/core/payouts/transformers.rs +++ b/crates/router/src/core/payouts/transformers.rs @@ -1,3 +1,7 @@ +use std::collections::HashMap; + +use common_utils::link_utils::EnabledPaymentMethod; + #[cfg(all( any(feature = "v1", feature = "v2"), not(feature = "customer_v2"), @@ -5,7 +9,11 @@ ))] use crate::types::transformers::ForeignInto; #[cfg(feature = "olap")] -use crate::types::{api, domain, storage, transformers::ForeignFrom}; +use crate::types::{api::payments, domain, storage}; +use crate::{ + settings::PayoutRequiredFields, + types::{api, transformers::ForeignFrom}, +}; #[cfg(all(feature = "v2", feature = "customer_v2", feature = "olap"))] impl @@ -13,6 +21,7 @@ impl storage::Payouts, storage::PayoutAttempt, Option, + Option, )> for api::PayoutCreateResponse { fn foreign_from( @@ -20,6 +29,7 @@ impl storage::Payouts, storage::PayoutAttempt, Option, + Option, ), ) -> Self { todo!() @@ -36,6 +46,7 @@ impl storage::Payouts, storage::PayoutAttempt, Option, + Option, )> for api::PayoutCreateResponse { fn foreign_from( @@ -43,9 +54,10 @@ impl storage::Payouts, storage::PayoutAttempt, Option, + Option, ), ) -> Self { - let (payout, payout_attempt, customer) = item; + let (payout, payout_attempt, customer, address) = item; let attempt = api::PayoutAttemptResponse { attempt_id: payout_attempt.payout_attempt_id, status: payout_attempt.status, @@ -87,7 +99,7 @@ impl connector_transaction_id: attempt.connector_transaction_id.clone(), priority: payout.priority, attempts: Some(vec![attempt]), - billing: None, + billing: address, client_secret: None, payout_link: None, email: customer @@ -103,3 +115,72 @@ impl } } } + +impl + ForeignFrom<( + &PayoutRequiredFields, + Vec, + api::RequiredFieldsOverrideRequest, + )> for Vec +{ + fn foreign_from( + (payout_required_fields, enabled_payout_methods, value_overrides): ( + &PayoutRequiredFields, + Vec, + api::RequiredFieldsOverrideRequest, + ), + ) -> Self { + let value_overrides = value_overrides.flat_struct(); + + enabled_payout_methods + .into_iter() + .map(|enabled_payout_method| { + let payment_method = enabled_payout_method.payment_method; + let payment_method_types_info = enabled_payout_method + .payment_method_types + .into_iter() + .filter_map(|pmt| { + payout_required_fields + .0 + .get(&payment_method) + .and_then(|pmt_info| { + pmt_info.0.get(&pmt).map(|connector_fields| { + let mut required_fields = HashMap::new(); + + for required_field_final in connector_fields.fields.values() { + required_fields.extend(required_field_final.common.clone()); + } + + for (key, value) in &value_overrides { + required_fields.entry(key.to_string()).and_modify( + |required_field| { + required_field.value = + Some(masking::Secret::new(value.to_string())); + }, + ); + } + api::PaymentMethodTypeInfo { + payment_method_type: pmt, + required_fields: if required_fields.is_empty() { + None + } else { + Some(required_fields) + }, + } + }) + }) + .or(Some(api::PaymentMethodTypeInfo { + payment_method_type: pmt, + required_fields: None, + })) + }) + .collect(); + + api::PayoutEnabledPaymentMethodsInfo { + payment_method, + payment_method_types_info, + } + }) + .collect() + } +} diff --git a/crates/router/src/core/pm_auth.rs b/crates/router/src/core/pm_auth.rs index 4380d60955bf..bb47a54b1256 100644 --- a/crates/router/src/core/pm_auth.rs +++ b/crates/router/src/core/pm_auth.rs @@ -45,6 +45,7 @@ use crate::{ types::{self, domain, storage, transformers::ForeignTryFrom}, }; +#[cfg(feature = "v1")] pub async fn create_link_token( state: SessionState, merchant_account: domain::MerchantAccount, @@ -201,6 +202,16 @@ pub async fn create_link_token( Ok(ApplicationResponse::Json(response)) } +#[cfg(feature = "v2")] +pub async fn create_link_token( + _state: SessionState, + _merchant_account: domain::MerchantAccount, + _key_store: domain::MerchantKeyStore, + _payload: api_models::pm_auth::LinkTokenCreateRequest, +) -> RouterResponse { + todo!() +} + impl ForeignTryFrom<&types::ConnectorAuthType> for PlaidAuthType { type Error = errors::ConnectorError; @@ -289,6 +300,7 @@ pub async fn exchange_token_core( Ok(ApplicationResponse::StatusOk) } +#[cfg(feature = "v1")] async fn store_bank_details_in_payment_methods( key_store: domain::MerchantKeyStore, payload: api_models::pm_auth::ExchangeTokenCreateRequest, @@ -558,6 +570,19 @@ async fn store_bank_details_in_payment_methods( Ok(()) } +#[cfg(feature = "v2")] +async fn store_bank_details_in_payment_methods( + _key_store: domain::MerchantKeyStore, + _payload: api_models::pm_auth::ExchangeTokenCreateRequest, + _merchant_account: domain::MerchantAccount, + _state: SessionState, + _bank_account_details_resp: pm_auth_types::BankAccountCredentialsResponse, + _connector_details: (&str, Secret), + _mca_id: common_utils::id_type::MerchantConnectorAccountId, +) -> RouterResult<()> { + todo!() +} + async fn store_in_db( state: &SessionState, key_store: &domain::MerchantKeyStore, @@ -860,17 +885,21 @@ pub async fn retrieve_payment_method_from_auth_service( bank_name: None, bank_type, bank_holder_type: None, + card_holder_name: None, + bank_account_holder_name: None, }) } pm_auth_types::PaymentMethodTypeDetails::Bacs(bacs) => { domain::PaymentMethodData::BankDebit(domain::BankDebitData::BacsBankDebit { account_number: bacs.account_number.clone(), sort_code: bacs.sort_code.clone(), + bank_account_holder_name: None, }) } pm_auth_types::PaymentMethodTypeDetails::Sepa(sepa) => { domain::PaymentMethodData::BankDebit(domain::BankDebitData::SepaBankDebit { iban: sepa.iban.clone(), + bank_account_holder_name: None, }) } }; diff --git a/crates/router/src/core/recon.rs b/crates/router/src/core/recon.rs new file mode 100644 index 000000000000..fa9944ee8eed --- /dev/null +++ b/crates/router/src/core/recon.rs @@ -0,0 +1,172 @@ +use api_models::recon as recon_api; +use common_utils::ext_traits::AsyncExt; +use error_stack::ResultExt; +use masking::{ExposeInterface, PeekInterface, Secret}; + +use crate::{ + consts, + core::errors::{self, RouterResponse, UserErrors}, + services::{api as service_api, authentication, email::types as email_types}, + types::{ + api::{self as api_types, enums}, + domain, storage, + transformers::ForeignTryFrom, + }, + SessionState, +}; + +pub async fn send_recon_request( + state: SessionState, + user_with_auth_data: authentication::UserFromTokenWithAuthData, +) -> RouterResponse { + let user = user_with_auth_data.0; + let user_in_db = &user_with_auth_data.1.user; + let merchant_id = user.merchant_id; + + let user_email = user_in_db.email.clone(); + let email_contents = email_types::ProFeatureRequest { + feature_name: consts::RECON_FEATURE_TAG.to_string(), + merchant_id: merchant_id.clone(), + user_name: domain::UserName::new(user_in_db.name.clone()) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to form username")?, + user_email: domain::UserEmail::from_pii_email(user_email.clone()) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to convert recipient's email to UserEmail")?, + recipient_email: domain::UserEmail::from_pii_email( + state.conf.recipient_emails.recon.clone(), + ) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to convert recipient's email to UserEmail")?, + settings: state.conf.clone(), + subject: format!( + "Dashboard Pro Feature Request by {}", + user_email.expose().peek() + ), + }; + + state + .email_client + .compose_and_send_email( + Box::new(email_contents), + state.conf.proxy.https_url.as_ref(), + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to compose and send email for ProFeatureRequest [Recon]") + .async_and_then(|_| async { + let auth = user_with_auth_data.1; + let updated_merchant_account = storage::MerchantAccountUpdate::ReconUpdate { + recon_status: enums::ReconStatus::Requested, + }; + let db = &*state.store; + let key_manager_state = &(&state).into(); + + let response = db + .update_merchant( + key_manager_state, + auth.merchant_account, + updated_merchant_account, + &auth.key_store, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable_lazy(|| { + format!("Failed while updating merchant's recon status: {merchant_id:?}") + })?; + + Ok(service_api::ApplicationResponse::Json( + recon_api::ReconStatusResponse { + recon_status: response.recon_status, + }, + )) + }) + .await +} + +pub async fn generate_recon_token( + state: SessionState, + user: authentication::UserFromToken, +) -> RouterResponse { + let token = authentication::AuthToken::new_token( + user.user_id.clone(), + user.merchant_id.clone(), + user.role_id.clone(), + &state.conf, + user.org_id.clone(), + user.profile_id.clone(), + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable_lazy(|| { + format!( + "Failed to create recon token for params [user_id, org_id, mid, pid] [{}, {:?}, {:?}, {:?}]", + user.user_id, user.org_id, user.merchant_id, user.profile_id, + ) + })?; + + Ok(service_api::ApplicationResponse::Json( + recon_api::ReconTokenResponse { + token: token.into(), + }, + )) +} + +pub async fn recon_merchant_account_update( + state: SessionState, + auth: authentication::AuthenticationData, + req: recon_api::ReconUpdateMerchantRequest, +) -> RouterResponse { + let db = &*state.store; + let key_manager_state = &(&state).into(); + + let updated_merchant_account = storage::MerchantAccountUpdate::ReconUpdate { + recon_status: req.recon_status, + }; + let merchant_id = auth.merchant_account.get_id().clone(); + + let updated_merchant_account = db + .update_merchant( + key_manager_state, + auth.merchant_account, + updated_merchant_account, + &auth.key_store, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable_lazy(|| { + format!("Failed while updating merchant's recon status: {merchant_id:?}") + })?; + + let user_email = &req.user_email.clone(); + let email_contents = email_types::ReconActivation { + recipient_email: domain::UserEmail::from_pii_email(user_email.clone()) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to convert recipient's email to UserEmail from pii::Email")?, + user_name: domain::UserName::new(Secret::new("HyperSwitch User".to_string())) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to form username")?, + settings: state.conf.clone(), + subject: "Approval of Recon Request - Access Granted to Recon Dashboard", + }; + + if req.recon_status == enums::ReconStatus::Active { + let _ = state + .email_client + .compose_and_send_email( + Box::new(email_contents), + state.conf.proxy.https_url.as_ref(), + ) + .await + .change_context(UserErrors::InternalServerError) + .attach_printable("Failed to compose and send email for ReconActivation") + .is_ok(); + } + + Ok(service_api::ApplicationResponse::Json( + api_types::MerchantAccountResponse::foreign_try_from(updated_merchant_account) + .change_context(errors::ApiErrorResponse::InvalidDataValue { + field_name: "merchant_account", + })?, + )) +} diff --git a/crates/router/src/core/refunds.rs b/crates/router/src/core/refunds.rs index 1761c0bad030..c30ef7556553 100644 --- a/crates/router/src/core/refunds.rs +++ b/crates/router/src/core/refunds.rs @@ -191,7 +191,7 @@ pub async fn trigger_refund_to_gateway( &connector, merchant_account, &router_data, - creds_identifier.as_ref(), + creds_identifier.as_deref(), ) .await?; @@ -533,7 +533,7 @@ pub async fn sync_refund_with_gateway( &connector, merchant_account, &router_data, - creds_identifier.as_ref(), + creds_identifier.as_deref(), ) .await?; @@ -869,7 +869,7 @@ pub async fn validate_and_create_refund( pub async fn refund_list( state: SessionState, merchant_account: domain::MerchantAccount, - _profile_id_list: Option>, + profile_id_list: Option>, req: api_models::refunds::RefundListRequest, ) -> RouterResponse { let db = state.store; @@ -879,7 +879,7 @@ pub async fn refund_list( let refund_list = db .filter_refund_by_constraints( merchant_account.get_id(), - &req, + &(req.clone(), profile_id_list.clone()).try_into()?, merchant_account.storage_scheme, limit, offset, @@ -895,7 +895,7 @@ pub async fn refund_list( let total_count = db .get_total_count_of_refunds( merchant_account.get_id(), - &req, + &(req, profile_id_list).try_into()?, merchant_account.storage_scheme, ) .await @@ -1034,6 +1034,32 @@ pub async fn get_filters_for_refunds( )) } +#[instrument(skip_all)] +#[cfg(feature = "olap")] +pub async fn get_aggregates_for_refunds( + state: SessionState, + merchant: domain::MerchantAccount, + time_range: api::TimeRange, +) -> RouterResponse { + let db = state.store.as_ref(); + let refund_status_with_count = db + .get_refund_status_with_count(merchant.get_id(), &time_range, merchant.storage_scheme) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to find status count")?; + let mut status_map: HashMap = + refund_status_with_count.into_iter().collect(); + for status in enums::RefundStatus::iter() { + status_map.entry(status).or_default(); + } + + Ok(services::ApplicationResponse::Json( + api_models::refunds::RefundAggregateResponse { + status_with_count: status_map, + }, + )) +} + impl ForeignFrom for api::RefundResponse { fn foreign_from(refund: storage::Refund) -> Self { let refund = refund; diff --git a/crates/router/src/core/routing.rs b/crates/router/src/core/routing.rs index e431ae7b51be..fd58a2c571a7 100644 --- a/crates/router/src/core/routing.rs +++ b/crates/router/src/core/routing.rs @@ -2,14 +2,14 @@ pub mod helpers; pub mod transformers; use api_models::{ - enums, + enums, mandates as mandates_api, routing::{self as routing_types, RoutingRetrieveQuery}, }; use diesel_models::routing_algorithm::RoutingAlgorithm; use error_stack::ResultExt; +use hyperswitch_domain_models::{mandates, payment_address}; use rustc_hash::FxHashSet; -use super::payments; #[cfg(feature = "payouts")] use super::payouts; #[cfg(feature = "v1")] @@ -28,19 +28,50 @@ use crate::{ services::api as service_api, types::{ domain, + storage::{self, enums as storage_enums}, transformers::{ForeignInto, ForeignTryFrom}, }, utils::{self, OptionExt}, }; -pub enum TransactionData<'a, F> -where - F: Clone, -{ - Payment(&'a mut payments::PaymentData), +pub enum TransactionData<'a> { + Payment(PaymentsDslInput<'a>), #[cfg(feature = "payouts")] Payout(&'a payouts::PayoutData), } +#[derive(Clone)] +pub struct PaymentsDslInput<'a> { + pub setup_mandate: Option<&'a mandates::MandateData>, + pub payment_attempt: &'a storage::PaymentAttempt, + pub payment_intent: &'a storage::PaymentIntent, + pub payment_method_data: Option<&'a domain::PaymentMethodData>, + pub address: &'a payment_address::PaymentAddress, + pub recurring_details: Option<&'a mandates_api::RecurringDetails>, + pub currency: storage_enums::Currency, +} + +impl<'a> PaymentsDslInput<'a> { + pub fn new( + setup_mandate: Option<&'a mandates::MandateData>, + payment_attempt: &'a storage::PaymentAttempt, + payment_intent: &'a storage::PaymentIntent, + payment_method_data: Option<&'a domain::PaymentMethodData>, + address: &'a payment_address::PaymentAddress, + recurring_details: Option<&'a mandates_api::RecurringDetails>, + currency: storage_enums::Currency, + ) -> Self { + Self { + setup_mandate, + payment_attempt, + payment_intent, + payment_method_data, + address, + recurring_details, + currency, + } + } +} + #[cfg(feature = "v2")] struct RoutingAlgorithmUpdate(RoutingAlgorithm); diff --git a/crates/router/src/core/routing/transformers.rs b/crates/router/src/core/routing/transformers.rs index 2e45605edb6a..6bffde12a9b8 100644 --- a/crates/router/src/core/routing/transformers.rs +++ b/crates/router/src/core/routing/transformers.rs @@ -87,8 +87,8 @@ impl ForeignFrom for storage_enums::RoutingAlgorithmKind { } } -impl From<&routing::TransactionData<'_, F>> for storage_enums::TransactionType { - fn from(value: &routing::TransactionData<'_, F>) -> Self { +impl From<&routing::TransactionData<'_>> for storage_enums::TransactionType { + fn from(value: &routing::TransactionData<'_>) -> Self { match value { routing::TransactionData::Payment(_) => Self::Payment, #[cfg(feature = "payouts")] diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index 1d235dceaea0..31ab0e8ff222 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -100,6 +100,14 @@ pub async fn get_user_details( ) -> UserResponse { let user = user_from_token.get_user_from_db(&state).await?; let verification_days_left = utils::user::get_verification_days_left(&state, &user)?; + let role_info = roles::RoleInfo::from_role_id( + &state, + &user_from_token.role_id, + &user_from_token.merchant_id, + &user_from_token.org_id, + ) + .await + .change_context(UserErrors::InternalServerError)?; Ok(ApplicationResponse::Json( user_api::GetUserDetailsResponse { @@ -112,6 +120,10 @@ pub async fn get_user_details( org_id: user_from_token.org_id, is_two_factor_auth_setup: user.get_totp_status() == TotpStatus::Set, recovery_codes_left: user.get_recovery_codes().map(|codes| codes.len()), + profile_id: user_from_token + .profile_id + .ok_or(UserErrors::JwtProfileIdMissing)?, + entity_type: role_info.get_entity_type(), }, )) } @@ -1185,13 +1197,12 @@ pub async fn switch_merchant_id( })? .organization_id; - let token = utils::user::generate_jwt_auth_token_with_attributes( + let token = utils::user::generate_jwt_auth_token_with_attributes_without_profile( &state, user_from_token.user_id, request.merchant_id.clone(), org_id.clone(), user_from_token.role_id.clone(), - None, ) .await?; @@ -1810,30 +1821,23 @@ pub async fn send_verification_mail( #[cfg(feature = "recon")] pub async fn verify_token( state: SessionState, - req: auth::ReconUser, + user: auth::UserFromToken, ) -> UserResponse { - let user = state + let user_in_db = state .global_store - .find_user_by_id(&req.user_id) + .find_user_by_id(&user.user_id) .await - .map_err(|e| { - if e.current_context().is_db_not_found() { - e.change_context(UserErrors::UserNotFound) - } else { - e.change_context(UserErrors::InternalServerError) - } + .change_context(UserErrors::InternalServerError) + .attach_printable_lazy(|| { + format!( + "Failed to fetch the user from DB for user_id - {}", + user.user_id + ) })?; - let merchant_id = state - .store - .find_user_role_by_user_id(&req.user_id, UserRoleVersion::V1) - .await - .change_context(UserErrors::InternalServerError)? - .merchant_id - .ok_or(UserErrors::InternalServerError)?; Ok(ApplicationResponse::Json(user_api::VerifyTokenResponse { - merchant_id: merchant_id.to_owned(), - user_email: user.email, + merchant_id: user.merchant_id.to_owned(), + user_email: user_in_db.email, })) } @@ -2792,7 +2796,6 @@ pub async fn switch_org_for_user( .into()); } - let key_manager_state = &(&state).into(); let role_info = roles::RoleInfo::from_role_id( &state, &user_from_token.role_id, @@ -2830,38 +2833,8 @@ pub async fn switch_org_for_user( "No user role found for the requested org_id".to_string(), ))?; - let merchant_id = utils::user_role::get_single_merchant_id(&state, &user_role).await?; - - let profile_id = if let Some(profile_id) = &user_role.profile_id { - profile_id.clone() - } else { - let merchant_key_store = state - .store - .get_merchant_key_store_by_merchant_id( - key_manager_state, - &merchant_id, - &state.store.get_master_key().to_vec().into(), - ) - .await - .change_context(UserErrors::InternalServerError) - .attach_printable("Failed to retrieve merchant key store by merchant_id")?; - - state - .store - .list_business_profile_by_merchant_id( - key_manager_state, - &merchant_key_store, - &merchant_id, - ) - .await - .change_context(UserErrors::InternalServerError) - .attach_printable("Failed to list business profiles by merchant_id")? - .pop() - .ok_or(UserErrors::InternalServerError) - .attach_printable("No business profile found for the merchant_id")? - .get_id() - .to_owned() - }; + let (merchant_id, profile_id) = + utils::user_role::get_single_merchant_id_and_profile_id(&state, &user_role).await?; let token = utils::user::generate_jwt_auth_token_with_attributes( &state, @@ -2869,7 +2842,7 @@ pub async fn switch_org_for_user( merchant_id.clone(), request.org_id.clone(), user_role.role_id.clone(), - Some(profile_id.clone()), + profile_id.clone(), ) .await?; @@ -3078,7 +3051,7 @@ pub async fn switch_merchant_for_user_in_org( merchant_id.clone(), org_id.clone(), role_id.clone(), - Some(profile_id), + profile_id, ) .await?; @@ -3183,7 +3156,7 @@ pub async fn switch_profile_for_user_in_org_and_merchant( user_from_token.merchant_id.clone(), user_from_token.org_id.clone(), role_id.clone(), - Some(profile_id), + profile_id, ) .await?; diff --git a/crates/router/src/core/user/sample_data.rs b/crates/router/src/core/user/sample_data.rs index ce7143338f4c..1851c563d0c7 100644 --- a/crates/router/src/core/user/sample_data.rs +++ b/crates/router/src/core/user/sample_data.rs @@ -10,16 +10,17 @@ use crate::{ core::errors::sample_data::{SampleDataError, SampleDataResult}, routes::{app::ReqState, SessionState}, services::{authentication::UserFromToken, ApplicationResponse}, - utils::user::sample_data::generate_sample_data, + utils, }; +#[cfg(feature = "v1")] pub async fn generate_sample_data_for_user( state: SessionState, user_from_token: UserFromToken, req: SampleDataRequest, _req_state: ReqState, ) -> SampleDataApiResponse<()> { - let sample_data = generate_sample_data( + let sample_data = utils::user::sample_data::generate_sample_data( &state, req, &user_from_token.merchant_id, diff --git a/crates/router/src/core/user_role.rs b/crates/router/src/core/user_role.rs index 8c955a095177..369ee97a02a7 100644 --- a/crates/router/src/core/user_role.rs +++ b/crates/router/src/core/user_role.rs @@ -744,30 +744,6 @@ pub async fn list_users_in_lineage( .map(|user| (user.user_id.clone(), user.email)) .collect::>(); - let role_info_map = - futures::future::try_join_all(user_roles_set.iter().map(|user_role| async { - roles::RoleInfo::from_role_id( - &state, - &user_role.role_id, - &user_from_token.merchant_id, - &user_from_token.org_id, - ) - .await - .map(|role_info| { - ( - user_role.role_id.clone(), - user_role_api::role::MinimalRoleInfo { - role_id: user_role.role_id.clone(), - role_name: role_info.get_role_name().to_string(), - }, - ) - }) - })) - .await - .change_context(UserErrors::InternalServerError)? - .into_iter() - .collect::>(); - let user_role_map = user_roles_set .into_iter() .fold(HashMap::new(), |mut map, user_role| { @@ -787,13 +763,11 @@ pub async fn list_users_in_lineage( .ok_or(UserErrors::InternalServerError)?, roles: role_id_vec .into_iter() - .map(|role_id| { - role_info_map - .get(&role_id) - .cloned() - .ok_or(UserErrors::InternalServerError) + .map(|role_id| user_role_api::role::MinimalRoleInfo { + role_id, + role_name: None, }) - .collect::, _>>()?, + .collect(), }) }) .collect::, _>>()?, diff --git a/crates/router/src/core/user_role/role.rs b/crates/router/src/core/user_role/role.rs index 1a81c49d5a93..8c9cd1da0abc 100644 --- a/crates/router/src/core/user_role/role.rs +++ b/crates/router/src/core/user_role/role.rs @@ -348,7 +348,7 @@ pub async fn list_roles_at_entity_level( if check_type && role_info.get_entity_type() == req.entity_type { Some(role_api::MinimalRoleInfo { role_id: role_info.get_role_id().to_string(), - role_name: role_info.get_role_name().to_string(), + role_name: Some(role_info.get_role_name().to_string()), }) } else { None diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index a6cf890dc184..0e8b21f3a8a2 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -1,12 +1,19 @@ use std::{collections::HashSet, marker::PhantomData, str::FromStr}; -use api_models::enums::{DisputeStage, DisputeStatus}; #[cfg(feature = "payouts")] use api_models::payouts::PayoutVendorAccountDetails; +use api_models::{ + enums::{DisputeStage, DisputeStatus}, + payments::OrderDetailsWithAmount, +}; use common_enums::{IntentStatus, RequestIncrementalAuthorization}; #[cfg(feature = "payouts")] use common_utils::{crypto::Encryptable, pii::Email}; -use common_utils::{errors::CustomResult, ext_traits::AsyncExt, types::MinorUnit}; +use common_utils::{ + errors::CustomResult, + ext_traits::AsyncExt, + types::{keymanager::KeyManagerState, MinorUnit}, +}; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ merchant_connector_account::MerchantConnectorAccount, payment_address::PaymentAddress, @@ -26,7 +33,10 @@ use crate::core::payments; use crate::{ configs::Settings, consts, - core::errors::{self, RouterResult, StorageErrorExt}, + core::{ + errors::{self, RouterResult, StorageErrorExt}, + payments::PaymentData, + }, db::StorageInterface, routes::SessionState, types::{ @@ -234,7 +244,7 @@ pub async fn construct_refund_router_data<'a, F>( let merchant_connector_account = helpers::get_merchant_connector_account( state, merchant_account.get_id(), - creds_identifier, + creds_identifier.as_deref(), key_store, profile_id, connector_id, @@ -861,6 +871,113 @@ pub async fn construct_upload_file_router_data<'a>( Ok(router_data) } +pub async fn construct_payments_dynamic_tax_calculation_router_data<'a, F: Clone>( + state: &'a SessionState, + merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, + payment_data: &mut PaymentData, + merchant_connector_account: &MerchantConnectorAccount, +) -> RouterResult { + let payment_intent = &payment_data.payment_intent.clone(); + let payment_attempt = &payment_data.payment_attempt.clone(); + + #[cfg(feature = "v1")] + let test_mode: Option = merchant_connector_account.test_mode; + + #[cfg(feature = "v2")] + let test_mode = None; + + let connector_auth_type: types::ConnectorAuthType = merchant_connector_account + .connector_account_details + .clone() + .parse_value("ConnectorAuthType") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while parsing value for ConnectorAuthType")?; + + let shipping_address = payment_data + .tax_data + .clone() + .map(|tax_data| tax_data.shipping_details) + .clone() + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Missing shipping_details")?; + + let order_details: Option> = payment_intent + .order_details + .clone() + .map(|order_details| { + order_details + .iter() + .map(|data| { + data.to_owned() + .parse_value("OrderDetailsWithAmount") + .change_context(errors::ApiErrorResponse::InvalidDataValue { + field_name: "OrderDetailsWithAmount", + }) + .attach_printable("Unable to parse OrderDetailsWithAmount") + }) + .collect::, _>>() + }) + .transpose()?; + + let router_data = types::RouterData { + flow: PhantomData, + merchant_id: merchant_account.get_id().to_owned(), + customer_id: None, + connector_customer: None, + connector: merchant_connector_account.connector_name.clone(), + payment_id: payment_attempt.payment_id.get_string_repr().to_owned(), + attempt_id: payment_attempt.attempt_id.clone(), + status: payment_attempt.status, + payment_method: diesel_models::enums::PaymentMethod::default(), + connector_auth_type, + description: None, + return_url: None, + address: payment_data.address.clone(), + auth_type: payment_attempt.authentication_type.unwrap_or_default(), + connector_meta_data: None, + connector_wallets_details: None, + amount_captured: None, + access_token: None, + session_token: None, + reference_id: None, + payment_method_token: None, + recurring_mandate_payment_data: None, + preprocessing_id: None, + payment_method_balance: None, + connector_api_version: None, + request: types::PaymentsTaxCalculationData { + amount: payment_intent.amount, + shipping_cost: payment_intent.shipping_cost, + order_details, + currency: payment_data.currency, + shipping_address, + }, + response: Err(ErrorResponse::default()), + connector_request_reference_id: get_connector_request_reference_id( + &state.conf, + merchant_account.get_id(), + payment_attempt, + ), + #[cfg(feature = "payouts")] + payout_method_data: None, + #[cfg(feature = "payouts")] + quote_id: None, + test_mode, + connector_http_status_code: None, + external_latency: None, + apple_pay_flow: None, + frm_metadata: None, + refund_id: None, + dispute_id: None, + connector_response: None, + payment_method_status: None, + minor_amount_captured: None, + integrity_check: Ok(()), + }; + Ok(router_data) +} + #[instrument(skip_all)] pub async fn construct_defend_dispute_router_data<'a>( state: &'a SessionState, @@ -1074,7 +1191,7 @@ pub fn get_connector_request_reference_id( /// Validate whether the profile_id exists and is associated with the merchant_id pub async fn validate_and_get_business_profile( db: &dyn StorageInterface, - key_manager_state: &common_utils::types::keymanager::KeyManagerState, + key_manager_state: &KeyManagerState, merchant_key_store: &domain::MerchantKeyStore, profile_id: Option<&common_utils::id_type::ProfileId>, merchant_id: &common_utils::id_type::MerchantId, @@ -1151,7 +1268,7 @@ pub fn get_connector_label( /// or return a `MissingRequiredField` error #[allow(clippy::too_many_arguments)] pub async fn get_profile_id_from_business_details( - key_manager_state: &common_utils::types::keymanager::KeyManagerState, + key_manager_state: &KeyManagerState, merchant_key_store: &domain::MerchantKeyStore, business_country: Option, business_label: Option<&String>, @@ -1404,7 +1521,7 @@ impl GetProfileId for storage::Payouts { } } #[cfg(feature = "payouts")] -impl GetProfileId for (storage::Payouts, T, F) { +impl GetProfileId for (storage::Payouts, T, F, R) { fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> { self.0.get_profile_id() } diff --git a/crates/router/src/core/verification/utils.rs b/crates/router/src/core/verification/utils.rs index 404019e8097f..787220651f15 100644 --- a/crates/router/src/core/verification/utils.rs +++ b/crates/router/src/core/verification/utils.rs @@ -1,4 +1,3 @@ -use api_models::payments::PaymentIdType; use common_utils::{errors::CustomResult, id_type::PaymentId}; use error_stack::{Report, ResultExt}; @@ -11,7 +10,6 @@ use crate::{ routes::SessionState, services::authentication::AuthenticationData, types::{self, storage}, - utils::find_payment_intent_from_payment_id_type, }; pub async fn check_existence_and_add_domain_to_db( @@ -137,14 +135,28 @@ pub async fn check_if_profile_id_is_present_in_payment_intent( state: &SessionState, auth_data: &AuthenticationData, ) -> CustomResult<(), errors::ApiErrorResponse> { - let payment_id_type = PaymentIdType::PaymentIntentId(payment_id); - let payment_intent = find_payment_intent_from_payment_id_type( - state, - payment_id_type, - &auth_data.merchant_account, - &auth_data.key_store, - ) - .await - .change_context(errors::ApiErrorResponse::Unauthorized)?; + let db = &*state.store; + #[cfg(feature = "v1")] + let payment_intent = db + .find_payment_intent_by_payment_id_merchant_id( + &state.into(), + &payment_id, + auth_data.merchant_account.get_id(), + &auth_data.key_store, + auth_data.merchant_account.storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::Unauthorized)?; + + #[cfg(feature = "v2")] + let payment_intent = db + .find_payment_intent_by_id( + &state.into(), + &payment_id, + &auth_data.key_store, + auth_data.merchant_account.storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::Unauthorized)?; utils::validate_profile_id_from_auth_layer(auth_data.profile_id.clone(), &payment_intent) } diff --git a/crates/router/src/core/webhooks/incoming.rs b/crates/router/src/core/webhooks/incoming.rs index 056a85d9c87a..20b07d8669fc 100644 --- a/crates/router/src/core/webhooks/incoming.rs +++ b/crates/router/src/core/webhooks/incoming.rs @@ -537,6 +537,7 @@ async fn payments_incoming_webhook_flow( _, _, _, + payments::PaymentData, >( state.clone(), req_state, @@ -1068,6 +1069,7 @@ async fn external_authentication_incoming_webhook_flow( _, _, _, + payments::PaymentData, >( state.clone(), req_state, @@ -1258,6 +1260,7 @@ async fn frm_incoming_webhook_flow( _, _, _, + payments::PaymentData, >( state.clone(), req_state, @@ -1284,6 +1287,7 @@ async fn frm_incoming_webhook_flow( _, _, _, + payments::PaymentData, >( state.clone(), req_state, @@ -1447,6 +1451,7 @@ async fn bank_transfer_webhook_flow( _, _, _, + payments::PaymentData, >( state.clone(), req_state, diff --git a/crates/router/src/core/webhooks/outgoing.rs b/crates/router/src/core/webhooks/outgoing.rs index f7dcfb737317..7b6f703f6f74 100644 --- a/crates/router/src/core/webhooks/outgoing.rs +++ b/crates/router/src/core/webhooks/outgoing.rs @@ -889,7 +889,7 @@ async fn error_response_handler( ); let error = report!(errors::WebhooksFlowError::NotReceivedByMerchant); - logger::warn!(?error, ?delivery_attempt, ?status_code, %log_message); + logger::warn!(?error, ?delivery_attempt, status_code, %log_message); if let ScheduleWebhookRetry::WithProcessTracker(process_tracker) = schedule_webhook_retry { // Schedule a retry attempt for webhook delivery diff --git a/crates/router/src/db/address.rs b/crates/router/src/db/address.rs index af2fffa50918..335110248ab3 100644 --- a/crates/router/src/db/address.rs +++ b/crates/router/src/db/address.rs @@ -375,9 +375,12 @@ mod storage { .await .map_err(|error| report!(errors::StorageError::from(error))) }; - let storage_scheme = - decide_storage_scheme::<_, storage_types::Address>(self, storage_scheme, Op::Find) - .await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, storage_types::Address>( + self, + storage_scheme, + Op::Find, + )) + .await; let address = match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, MerchantStorageScheme::RedisKv => { @@ -458,11 +461,11 @@ mod storage { payment_id: &payment_id, }; let field = format!("add_{}", address.address_id); - let storage_scheme = decide_storage_scheme::<_, storage_types::Address>( + let storage_scheme = Box::pin(decide_storage_scheme::<_, storage_types::Address>( self, storage_scheme, Op::Update(key.clone(), &field, Some(address.updated_by.as_str())), - ) + )) .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { @@ -539,11 +542,11 @@ mod storage { .await .change_context(errors::StorageError::EncryptionError)?; let merchant_id = address_new.merchant_id.clone(); - let storage_scheme = decide_storage_scheme::<_, storage_types::Address>( + let storage_scheme = Box::pin(decide_storage_scheme::<_, storage_types::Address>( self, storage_scheme, Op::Insert, - ) + )) .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { diff --git a/crates/router/src/db/customers.rs b/crates/router/src/db/customers.rs index 01bfc7ce0ad0..9cd044f69eb1 100644 --- a/crates/router/src/db/customers.rs +++ b/crates/router/src/db/customers.rs @@ -202,9 +202,12 @@ mod storage { .await .map_err(|err| report!(errors::StorageError::from(err))) }; - let storage_scheme = - decide_storage_scheme::<_, diesel_models::Customer>(self, storage_scheme, Op::Find) - .await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, diesel_models::Customer>( + self, + storage_scheme, + Op::Find, + )) + .await; let maybe_customer = match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, MerchantStorageScheme::RedisKv => { @@ -273,9 +276,12 @@ mod storage { .await .map_err(|err| report!(errors::StorageError::from(err))) }; - let storage_scheme = - decide_storage_scheme::<_, diesel_models::Customer>(self, storage_scheme, Op::Find) - .await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, diesel_models::Customer>( + self, + storage_scheme, + Op::Find, + )) + .await; let maybe_customer = match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, MerchantStorageScheme::RedisKv => { @@ -338,9 +344,12 @@ mod storage { .await .map_err(|err| report!(errors::StorageError::from(err))) }; - let storage_scheme = - decide_storage_scheme::<_, diesel_models::Customer>(self, storage_scheme, Op::Find) - .await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, diesel_models::Customer>( + self, + storage_scheme, + Op::Find, + )) + .await; let maybe_customer = match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, MerchantStorageScheme::RedisKv => { @@ -419,11 +428,11 @@ mod storage { customer_id: &customer_id, }; let field = format!("cust_{}", customer_id.get_string_repr()); - let storage_scheme = decide_storage_scheme::<_, diesel_models::Customer>( + let storage_scheme = Box::pin(decide_storage_scheme::<_, diesel_models::Customer>( self, storage_scheme, Op::Update(key.clone(), &field, customer.updated_by.as_deref()), - ) + )) .await; let updated_object = match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, @@ -491,9 +500,12 @@ mod storage { .await .map_err(|error| report!(errors::StorageError::from(error))) }; - let storage_scheme = - decide_storage_scheme::<_, diesel_models::Customer>(self, storage_scheme, Op::Find) - .await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, diesel_models::Customer>( + self, + storage_scheme, + Op::Find, + )) + .await; let customer = match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, MerchantStorageScheme::RedisKv => { @@ -556,9 +568,12 @@ mod storage { .await .map_err(|error| report!(errors::StorageError::from(error))) }; - let storage_scheme = - decide_storage_scheme::<_, diesel_models::Customer>(self, storage_scheme, Op::Find) - .await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, diesel_models::Customer>( + self, + storage_scheme, + Op::Find, + )) + .await; let customer = match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, MerchantStorageScheme::RedisKv => { @@ -653,11 +668,11 @@ mod storage { .construct_new() .await .change_context(errors::StorageError::EncryptionError)?; - let storage_scheme = decide_storage_scheme::<_, diesel_models::Customer>( + let storage_scheme = Box::pin(decide_storage_scheme::<_, diesel_models::Customer>( self, storage_scheme, Op::Insert, - ) + )) .await; new_customer.update_storage_scheme(storage_scheme); let create_customer = match storage_scheme { @@ -729,11 +744,11 @@ mod storage { .construct_new() .await .change_context(errors::StorageError::EncryptionError)?; - let storage_scheme = decide_storage_scheme::<_, diesel_models::Customer>( + let storage_scheme = Box::pin(decide_storage_scheme::<_, diesel_models::Customer>( self, storage_scheme, Op::Insert, - ) + )) .await; new_customer.update_storage_scheme(storage_scheme); let create_customer = match storage_scheme { @@ -826,9 +841,12 @@ mod storage { .await .map_err(|error| report!(errors::StorageError::from(error))) }; - let storage_scheme = - decide_storage_scheme::<_, diesel_models::Customer>(self, storage_scheme, Op::Find) - .await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, diesel_models::Customer>( + self, + storage_scheme, + Op::Find, + )) + .await; let customer = match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, MerchantStorageScheme::RedisKv => { @@ -895,11 +913,11 @@ mod storage { }; let key = PartitionKey::GlobalId { id: &id }; let field = format!("cust_{}", id); - let storage_scheme = decide_storage_scheme::<_, diesel_models::Customer>( + let storage_scheme = Box::pin(decide_storage_scheme::<_, diesel_models::Customer>( self, storage_scheme, Op::Update(key.clone(), &field, customer.updated_by.as_deref()), - ) + )) .await; let updated_object = match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index 31ed4ad93e2a..122e9ee00f09 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -9,13 +9,14 @@ use diesel_models::{ reverse_lookup::{ReverseLookup, ReverseLookupNew}, user_role as user_storage, }; -use hyperswitch_domain_models::payments::{ - payment_attempt::PaymentAttemptInterface, payment_intent::PaymentIntentInterface, -}; #[cfg(feature = "payouts")] use hyperswitch_domain_models::payouts::{ payout_attempt::PayoutAttemptInterface, payouts::PayoutsInterface, }; +use hyperswitch_domain_models::{ + payments::{payment_attempt::PaymentAttemptInterface, payment_intent::PaymentIntentInterface}, + refunds, +}; #[cfg(not(feature = "payouts"))] use hyperswitch_domain_models::{PayoutAttemptInterface, PayoutsInterface}; use masking::Secret; @@ -1490,6 +1491,7 @@ impl PaymentAttemptInterface for KafkaStore { payment_method_type: Option>, authentication_type: Option>, merchant_connector_id: Option>, + profile_id_list: Option>, storage_scheme: MerchantStorageScheme, ) -> CustomResult { self.diesel_store @@ -1501,6 +1503,7 @@ impl PaymentAttemptInterface for KafkaStore { payment_method_type, authentication_type, merchant_connector_id, + profile_id_list, storage_scheme, ) .await @@ -1574,6 +1577,7 @@ impl PaymentIntentInterface for KafkaStore { Ok(intent) } + #[cfg(feature = "v1")] async fn find_payment_intent_by_payment_id_merchant_id( &self, state: &KeyManagerState, @@ -1593,7 +1597,20 @@ impl PaymentIntentInterface for KafkaStore { .await } - #[cfg(feature = "olap")] + #[cfg(feature = "v2")] + async fn find_payment_intent_by_id( + &self, + state: &KeyManagerState, + payment_id: &id_type::PaymentId, + key_store: &domain::MerchantKeyStore, + storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + self.diesel_store + .find_payment_intent_by_id(state, payment_id, key_store, storage_scheme) + .await + } + + #[cfg(all(feature = "olap", feature = "v1"))] async fn filter_payment_intent_by_constraints( &self, state: &KeyManagerState, @@ -1613,7 +1630,7 @@ impl PaymentIntentInterface for KafkaStore { .await } - #[cfg(feature = "olap")] + #[cfg(all(feature = "olap", feature = "v1"))] async fn filter_payment_intents_by_time_range_constraints( &self, state: &KeyManagerState, @@ -1633,18 +1650,19 @@ impl PaymentIntentInterface for KafkaStore { .await } - #[cfg(feature = "olap")] + #[cfg(all(feature = "olap", feature = "v1"))] async fn get_intent_status_with_count( &self, merchant_id: &id_type::MerchantId, + profile_id_list: Option>, time_range: &api_models::payments::TimeRange, ) -> error_stack::Result, errors::DataStorageError> { self.diesel_store - .get_intent_status_with_count(merchant_id, time_range) + .get_intent_status_with_count(merchant_id, profile_id_list, time_range) .await } - #[cfg(feature = "olap")] + #[cfg(all(feature = "olap", feature = "v1"))] async fn get_filtered_payment_intents_attempt( &self, state: &KeyManagerState, @@ -1670,7 +1688,7 @@ impl PaymentIntentInterface for KafkaStore { .await } - #[cfg(feature = "olap")] + #[cfg(all(feature = "olap", feature = "v1"))] async fn get_filtered_active_attempt_ids_for_total_count( &self, merchant_id: &id_type::MerchantId, @@ -2070,6 +2088,7 @@ impl PayoutsInterface for KafkaStore { storage::Payouts, storage::PayoutAttempt, Option, + Option, )>, errors::DataStorageError, > { @@ -2361,7 +2380,7 @@ impl RefundInterface for KafkaStore { async fn filter_refund_by_constraints( &self, merchant_id: &id_type::MerchantId, - refund_details: &api_models::refunds::RefundListRequest, + refund_details: &refunds::RefundListConstraints, storage_scheme: MerchantStorageScheme, limit: i64, offset: i64, @@ -2389,11 +2408,23 @@ impl RefundInterface for KafkaStore { .await } + #[cfg(feature = "olap")] + async fn get_refund_status_with_count( + &self, + merchant_id: &id_type::MerchantId, + constraints: &api_models::payments::TimeRange, + storage_scheme: MerchantStorageScheme, + ) -> CustomResult, errors::StorageError> { + self.diesel_store + .get_refund_status_with_count(merchant_id, constraints, storage_scheme) + .await + } + #[cfg(feature = "olap")] async fn get_total_count_of_refunds( &self, merchant_id: &id_type::MerchantId, - refund_details: &api_models::refunds::RefundListRequest, + refund_details: &refunds::RefundListConstraints, storage_scheme: MerchantStorageScheme, ) -> CustomResult { self.diesel_store diff --git a/crates/router/src/db/mandate.rs b/crates/router/src/db/mandate.rs index f59ac2a80b54..24356494471e 100644 --- a/crates/router/src/db/mandate.rs +++ b/crates/router/src/db/mandate.rs @@ -97,9 +97,12 @@ mod storage { .await .map_err(|error| report!(errors::StorageError::from(error))) }; - let storage_scheme = - decide_storage_scheme::<_, diesel_models::Mandate>(self, storage_scheme, Op::Find) - .await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, diesel_models::Mandate>( + self, + storage_scheme, + Op::Find, + )) + .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, MerchantStorageScheme::RedisKv => { @@ -143,9 +146,12 @@ mod storage { .await .map_err(|error| report!(errors::StorageError::from(error))) }; - let storage_scheme = - decide_storage_scheme::<_, diesel_models::Mandate>(self, storage_scheme, Op::Find) - .await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, diesel_models::Mandate>( + self, + storage_scheme, + Op::Find, + )) + .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, MerchantStorageScheme::RedisKv => { @@ -220,11 +226,11 @@ mod storage { mandate_id, }; let field = format!("mandate_{}", mandate_id); - let storage_scheme = decide_storage_scheme::<_, diesel_models::Mandate>( + let storage_scheme = Box::pin(decide_storage_scheme::<_, diesel_models::Mandate>( self, storage_scheme, Op::Update(key.clone(), &field, mandate.updated_by.as_deref()), - ) + )) .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { @@ -312,11 +318,11 @@ mod storage { storage_scheme: MerchantStorageScheme, ) -> CustomResult { let conn = connection::pg_connection_write(self).await?; - let storage_scheme = decide_storage_scheme::<_, diesel_models::Mandate>( + let storage_scheme = Box::pin(decide_storage_scheme::<_, diesel_models::Mandate>( self, storage_scheme, Op::Insert, - ) + )) .await; mandate.update_storage_scheme(storage_scheme); match storage_scheme { diff --git a/crates/router/src/db/payment_method.rs b/crates/router/src/db/payment_method.rs index 695ed68b7969..0541491ab982 100644 --- a/crates/router/src/db/payment_method.rs +++ b/crates/router/src/db/payment_method.rs @@ -173,12 +173,13 @@ mod storage { .await .map_err(|error| report!(errors::StorageError::from(error))) }; - let storage_scheme = decide_storage_scheme::<_, storage_types::PaymentMethod>( - self, - storage_scheme, - Op::Find, - ) - .await; + let storage_scheme = + Box::pin(decide_storage_scheme::<_, storage_types::PaymentMethod>( + self, + storage_scheme, + Op::Find, + )) + .await; let get_pm = || async { match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, @@ -239,12 +240,13 @@ mod storage { .await .map_err(|error| report!(errors::StorageError::from(error))) }; - let storage_scheme = decide_storage_scheme::<_, storage_types::PaymentMethod>( - self, - storage_scheme, - Op::Find, - ) - .await; + let storage_scheme = + Box::pin(decide_storage_scheme::<_, storage_types::PaymentMethod>( + self, + storage_scheme, + Op::Find, + )) + .await; let get_pm = || async { match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, @@ -308,12 +310,13 @@ mod storage { .await .map_err(|error| report!(errors::StorageError::from(error))) }; - let storage_scheme = decide_storage_scheme::<_, storage_types::PaymentMethod>( - self, - storage_scheme, - Op::Find, - ) - .await; + let storage_scheme = + Box::pin(decide_storage_scheme::<_, storage_types::PaymentMethod>( + self, + storage_scheme, + Op::Find, + )) + .await; let get_pm = || async { match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, @@ -390,12 +393,13 @@ mod storage { payment_method: domain::PaymentMethod, storage_scheme: MerchantStorageScheme, ) -> CustomResult { - let storage_scheme = decide_storage_scheme::<_, storage_types::PaymentMethod>( - self, - storage_scheme, - Op::Insert, - ) - .await; + let storage_scheme = + Box::pin(decide_storage_scheme::<_, storage_types::PaymentMethod>( + self, + storage_scheme, + Op::Insert, + )) + .await; let mut payment_method_new = payment_method .construct_new() @@ -507,12 +511,13 @@ mod storage { customer_id: &customer_id, }; let field = format!("payment_method_id_{}", payment_method.get_id()); - let storage_scheme = decide_storage_scheme::<_, storage_types::PaymentMethod>( - self, - storage_scheme, - Op::Update(key.clone(), &field, payment_method.updated_by.as_deref()), - ) - .await; + let storage_scheme = + Box::pin(decide_storage_scheme::<_, storage_types::PaymentMethod>( + self, + storage_scheme, + Op::Update(key.clone(), &field, payment_method.updated_by.as_deref()), + )) + .await; let pm = match storage_scheme { MerchantStorageScheme::PostgresOnly => { let conn = connection::pg_connection_write(self).await?; @@ -593,12 +598,13 @@ mod storage { customer_id: &customer_id, }; let field = format!("payment_method_id_{}", payment_method.get_id()); - let storage_scheme = decide_storage_scheme::<_, storage_types::PaymentMethod>( - self, - storage_scheme, - Op::Update(key.clone(), &field, payment_method.updated_by.as_deref()), - ) - .await; + let storage_scheme = + Box::pin(decide_storage_scheme::<_, storage_types::PaymentMethod>( + self, + storage_scheme, + Op::Update(key.clone(), &field, payment_method.updated_by.as_deref()), + )) + .await; let pm = match storage_scheme { MerchantStorageScheme::PostgresOnly => { let conn = connection::pg_connection_write(self).await?; diff --git a/crates/router/src/db/refund.rs b/crates/router/src/db/refund.rs index e6b46c5af5c4..808f82b00a4d 100644 --- a/crates/router/src/db/refund.rs +++ b/crates/router/src/db/refund.rs @@ -1,9 +1,10 @@ #[cfg(feature = "olap")] -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; #[cfg(feature = "olap")] use common_utils::types::MinorUnit; use diesel_models::{errors::DatabaseError, refund::RefundUpdateInternal}; +use hyperswitch_domain_models::refunds; use super::MockDb; use crate::{ @@ -69,7 +70,7 @@ pub trait RefundInterface { async fn filter_refund_by_constraints( &self, merchant_id: &common_utils::id_type::MerchantId, - refund_details: &api_models::refunds::RefundListRequest, + refund_details: &refunds::RefundListConstraints, storage_scheme: enums::MerchantStorageScheme, limit: i64, offset: i64, @@ -83,11 +84,19 @@ pub trait RefundInterface { storage_scheme: enums::MerchantStorageScheme, ) -> CustomResult; + #[cfg(feature = "olap")] + async fn get_refund_status_with_count( + &self, + merchant_id: &common_utils::id_type::MerchantId, + constraints: &api_models::payments::TimeRange, + storage_scheme: enums::MerchantStorageScheme, + ) -> CustomResult, errors::StorageError>; + #[cfg(feature = "olap")] async fn get_total_count_of_refunds( &self, merchant_id: &common_utils::id_type::MerchantId, - refund_details: &api_models::refunds::RefundListRequest, + refund_details: &refunds::RefundListConstraints, storage_scheme: enums::MerchantStorageScheme, ) -> CustomResult; } @@ -216,7 +225,7 @@ mod storage { async fn filter_refund_by_constraints( &self, merchant_id: &common_utils::id_type::MerchantId, - refund_details: &api_models::refunds::RefundListRequest, + refund_details: &refunds::RefundListConstraints, _storage_scheme: enums::MerchantStorageScheme, limit: i64, offset: i64, @@ -250,12 +259,27 @@ mod storage { .await .map_err(|error|report!(errors::StorageError::from(error))) } + + #[cfg(feature = "olap")] + #[instrument(skip_all)] + async fn get_refund_status_with_count( + &self, + merchant_id: &common_utils::id_type::MerchantId, + time_range: &api_models::payments::TimeRange, + _storage_scheme: enums::MerchantStorageScheme, + ) -> CustomResult, errors::StorageError> { + let conn = connection::pg_connection_read(self).await?; + ::get_refund_status_with_count(&conn, merchant_id, time_range) + .await + .map_err(|error|report!(errors::StorageError::from(error))) + } + #[cfg(feature = "olap")] #[instrument(skip_all)] async fn get_total_count_of_refunds( &self, merchant_id: &common_utils::id_type::MerchantId, - refund_details: &api_models::refunds::RefundListRequest, + refund_details: &refunds::RefundListConstraints, _storage_scheme: enums::MerchantStorageScheme, ) -> CustomResult { let conn = connection::pg_connection_read(self).await?; @@ -274,6 +298,7 @@ mod storage { mod storage { use common_utils::{ext_traits::Encode, fallback_reverse_lookup_not_found}; use error_stack::{report, ResultExt}; + use hyperswitch_domain_models::refunds; use redis_interface::HsetnxReply; use router_env::{instrument, tracing}; use storage_impl::redis::kv_store::{ @@ -308,9 +333,12 @@ mod storage { .await .map_err(|error| report!(errors::StorageError::from(error))) }; - let storage_scheme = - decide_storage_scheme::<_, storage_types::Refund>(self, storage_scheme, Op::Find) - .await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, storage_types::Refund>( + self, + storage_scheme, + Op::Find, + )) + .await; match storage_scheme { enums::MerchantStorageScheme::PostgresOnly => database_call().await, enums::MerchantStorageScheme::RedisKv => { @@ -350,9 +378,12 @@ mod storage { new: storage_types::RefundNew, storage_scheme: enums::MerchantStorageScheme, ) -> CustomResult { - let storage_scheme = - decide_storage_scheme::<_, storage_types::Refund>(self, storage_scheme, Op::Insert) - .await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, storage_types::Refund>( + self, + storage_scheme, + Op::Insert, + )) + .await; match storage_scheme { enums::MerchantStorageScheme::PostgresOnly => { let conn = connection::pg_connection_write(self).await?; @@ -500,9 +531,12 @@ mod storage { .await .map_err(|error| report!(errors::StorageError::from(error))) }; - let storage_scheme = - decide_storage_scheme::<_, storage_types::Refund>(self, storage_scheme, Op::Find) - .await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, storage_types::Refund>( + self, + storage_scheme, + Op::Find, + )) + .await; match storage_scheme { enums::MerchantStorageScheme::PostgresOnly => database_call().await, enums::MerchantStorageScheme::RedisKv => { @@ -553,11 +587,11 @@ mod storage { payment_id: &payment_id, }; let field = format!("pa_{}_ref_{}", &this.attempt_id, &this.refund_id); - let storage_scheme = decide_storage_scheme::<_, storage_types::Refund>( + let storage_scheme = Box::pin(decide_storage_scheme::<_, storage_types::Refund>( self, storage_scheme, Op::Update(key.clone(), &field, Some(&this.updated_by)), - ) + )) .await; match storage_scheme { enums::MerchantStorageScheme::PostgresOnly => { @@ -614,9 +648,12 @@ mod storage { .await .map_err(|error| report!(errors::StorageError::from(error))) }; - let storage_scheme = - decide_storage_scheme::<_, storage_types::Refund>(self, storage_scheme, Op::Find) - .await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, storage_types::Refund>( + self, + storage_scheme, + Op::Find, + )) + .await; match storage_scheme { enums::MerchantStorageScheme::PostgresOnly => database_call().await, enums::MerchantStorageScheme::RedisKv => { @@ -667,9 +704,12 @@ mod storage { .await .map_err(|error| report!(errors::StorageError::from(error))) }; - let storage_scheme = - decide_storage_scheme::<_, storage_types::Refund>(self, storage_scheme, Op::Find) - .await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, storage_types::Refund>( + self, + storage_scheme, + Op::Find, + )) + .await; match storage_scheme { enums::MerchantStorageScheme::PostgresOnly => database_call().await, enums::MerchantStorageScheme::RedisKv => { @@ -720,9 +760,12 @@ mod storage { .await .map_err(|error| report!(errors::StorageError::from(error))) }; - let storage_scheme = - decide_storage_scheme::<_, storage_types::Refund>(self, storage_scheme, Op::Find) - .await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, storage_types::Refund>( + self, + storage_scheme, + Op::Find, + )) + .await; match storage_scheme { enums::MerchantStorageScheme::PostgresOnly => database_call().await, enums::MerchantStorageScheme::RedisKv => { @@ -752,7 +795,7 @@ mod storage { async fn filter_refund_by_constraints( &self, merchant_id: &common_utils::id_type::MerchantId, - refund_details: &api_models::refunds::RefundListRequest, + refund_details: &refunds::RefundListConstraints, _storage_scheme: enums::MerchantStorageScheme, limit: i64, offset: i64, @@ -783,12 +826,26 @@ mod storage { .map_err(|error|report!(errors::StorageError::from(error))) } + #[cfg(feature = "olap")] + #[instrument(skip_all)] + async fn get_refund_status_with_count( + &self, + merchant_id: &common_utils::id_type::MerchantId, + constraints: &api_models::payments::TimeRange, + _storage_scheme: enums::MerchantStorageScheme, + ) -> CustomResult, errors::StorageError> { + let conn = connection::pg_connection_read(self).await?; + ::get_refund_status_with_count(&conn, merchant_id, constraints) + .await + .map_err(|error| report!(errors::StorageError::from(error))) + } + #[cfg(feature = "olap")] #[instrument(skip_all)] async fn get_total_count_of_refunds( &self, merchant_id: &common_utils::id_type::MerchantId, - refund_details: &api_models::refunds::RefundListRequest, + refund_details: &refunds::RefundListConstraints, _storage_scheme: enums::MerchantStorageScheme, ) -> CustomResult { let conn = connection::pg_connection_read(self).await?; @@ -963,7 +1020,7 @@ impl RefundInterface for MockDb { async fn filter_refund_by_constraints( &self, merchant_id: &common_utils::id_type::MerchantId, - refund_details: &api_models::refunds::RefundListRequest, + refund_details: &refunds::RefundListConstraints, _storage_scheme: enums::MerchantStorageScheme, limit: i64, offset: i64, @@ -972,6 +1029,7 @@ impl RefundInterface for MockDb { let mut unique_merchant_connector_ids = HashSet::new(); let mut unique_currencies = HashSet::new(); let mut unique_statuses = HashSet::new(); + let mut unique_profile_ids = HashSet::new(); // Fill the hash sets with data from refund_details if let Some(connectors) = &refund_details.connector { @@ -1000,6 +1058,10 @@ impl RefundInterface for MockDb { }); } + if let Some(profile_id_list) = &refund_details.profile_id { + unique_profile_ids = profile_id_list.iter().collect(); + } + let refunds = self.refunds.lock().await; let filtered_refunds = refunds .iter() @@ -1016,7 +1078,11 @@ impl RefundInterface for MockDb { .clone() .map_or(true, |id| id == refund.refund_id) }) - .filter(|refund| refund_details.profile_id == refund.profile_id) + .filter(|refund| { + refund.profile_id.as_ref().is_some_and(|profile_id| { + unique_profile_ids.is_empty() || unique_profile_ids.contains(profile_id) + }) + }) .filter(|refund| { refund.created_at >= refund_details.time_range.map_or( @@ -1112,17 +1178,53 @@ impl RefundInterface for MockDb { Ok(refund_meta_data) } + #[cfg(feature = "olap")] + async fn get_refund_status_with_count( + &self, + _merchant_id: &common_utils::id_type::MerchantId, + time_range: &api_models::payments::TimeRange, + _storage_scheme: enums::MerchantStorageScheme, + ) -> CustomResult, errors::StorageError> { + let refunds = self.refunds.lock().await; + + let start_time = time_range.start_time; + let end_time = time_range + .end_time + .unwrap_or_else(common_utils::date_time::now); + + let filtered_refunds = refunds + .iter() + .filter(|refund| refund.created_at >= start_time && refund.created_at <= end_time) + .cloned() + .collect::>(); + + let mut refund_status_counts: HashMap = + HashMap::new(); + + for refund in filtered_refunds { + *refund_status_counts + .entry(refund.refund_status) + .or_insert(0) += 1; + } + + let result: Vec<(api_models::enums::RefundStatus, i64)> = + refund_status_counts.into_iter().collect(); + + Ok(result) + } + #[cfg(feature = "olap")] async fn get_total_count_of_refunds( &self, merchant_id: &common_utils::id_type::MerchantId, - refund_details: &api_models::refunds::RefundListRequest, + refund_details: &refunds::RefundListConstraints, _storage_scheme: enums::MerchantStorageScheme, ) -> CustomResult { let mut unique_connectors = HashSet::new(); let mut unique_merchant_connector_ids = HashSet::new(); let mut unique_currencies = HashSet::new(); let mut unique_statuses = HashSet::new(); + let mut unique_profile_ids = HashSet::new(); // Fill the hash sets with data from refund_details if let Some(connectors) = &refund_details.connector { @@ -1151,6 +1253,10 @@ impl RefundInterface for MockDb { }); } + if let Some(profile_id_list) = &refund_details.profile_id { + unique_profile_ids = profile_id_list.iter().collect(); + } + let refunds = self.refunds.lock().await; let filtered_refunds = refunds .iter() @@ -1167,7 +1273,11 @@ impl RefundInterface for MockDb { .clone() .map_or(true, |id| id == refund.refund_id) }) - .filter(|refund| refund_details.profile_id == refund.profile_id) + .filter(|refund| { + refund.profile_id.as_ref().is_some_and(|profile_id| { + unique_profile_ids.is_empty() || unique_profile_ids.contains(profile_id) + }) + }) .filter(|refund| { refund.created_at >= refund_details.time_range.map_or( diff --git a/crates/router/src/db/reverse_lookup.rs b/crates/router/src/db/reverse_lookup.rs index fcf38ca420d0..d51ed5ed385e 100644 --- a/crates/router/src/db/reverse_lookup.rs +++ b/crates/router/src/db/reverse_lookup.rs @@ -93,8 +93,12 @@ mod storage { new: ReverseLookupNew, storage_scheme: enums::MerchantStorageScheme, ) -> CustomResult { - let storage_scheme = - decide_storage_scheme::<_, ReverseLookup>(self, storage_scheme, Op::Insert).await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, ReverseLookup>( + self, + storage_scheme, + Op::Insert, + )) + .await; match storage_scheme { enums::MerchantStorageScheme::PostgresOnly => { let conn = connection::pg_connection_write(self).await?; @@ -154,8 +158,12 @@ mod storage { .await .map_err(|error| report!(errors::StorageError::from(error))) }; - let storage_scheme = - decide_storage_scheme::<_, ReverseLookup>(self, storage_scheme, Op::Find).await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, ReverseLookup>( + self, + storage_scheme, + Op::Find, + )) + .await; match storage_scheme { enums::MerchantStorageScheme::PostgresOnly => database_call().await, enums::MerchantStorageScheme::RedisKv => { diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index fc838645c8b6..ac452233e0f0 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -1,4 +1,4 @@ -#[cfg(feature = "stripe")] +#[cfg(all(feature = "stripe", feature = "v1"))] pub mod compatibility; pub mod configs; pub mod connection; @@ -128,9 +128,14 @@ pub fn mk_app( .service(routes::Customers::server(state.clone())) .service(routes::Configs::server(state.clone())) .service(routes::Forex::server(state.clone())) - .service(routes::Refunds::server(state.clone())) - .service(routes::MerchantConnectorAccount::server(state.clone())) - .service(routes::Mandates::server(state.clone())) + .service(routes::MerchantConnectorAccount::server(state.clone())); + + #[cfg(feature = "v1")] + { + server_app = server_app + .service(routes::Refunds::server(state.clone())) + .service(routes::Mandates::server(state.clone())); + } } #[cfg(all( @@ -152,18 +157,23 @@ pub fn mk_app( .service(routes::Organization::server(state.clone())) .service(routes::MerchantAccount::server(state.clone())) .service(routes::ApiKeys::server(state.clone())) - .service(routes::Files::server(state.clone())) - .service(routes::Disputes::server(state.clone())) .service(routes::Analytics::server(state.clone())) - .service(routes::Routing::server(state.clone())) - .service(routes::Blocklist::server(state.clone())) - .service(routes::Gsm::server(state.clone())) - .service(routes::ApplePayCertificatesMigration::server(state.clone())) - .service(routes::PaymentLink::server(state.clone())) - .service(routes::User::server(state.clone())) - .service(routes::ConnectorOnboarding::server(state.clone())) - .service(routes::Verify::server(state.clone())) - .service(routes::WebhookEvents::server(state.clone())); + .service(routes::Routing::server(state.clone())); + + #[cfg(feature = "v1")] + { + server_app = server_app + .service(routes::Files::server(state.clone())) + .service(routes::Disputes::server(state.clone())) + .service(routes::Blocklist::server(state.clone())) + .service(routes::Gsm::server(state.clone())) + .service(routes::ApplePayCertificatesMigration::server(state.clone())) + .service(routes::PaymentLink::server(state.clone())) + .service(routes::User::server(state.clone())) + .service(routes::ConnectorOnboarding::server(state.clone())) + .service(routes::Verify::server(state.clone())) + .service(routes::WebhookEvents::server(state.clone())); + } } #[cfg(feature = "payouts")] @@ -182,7 +192,7 @@ pub fn mk_app( server_app = server_app.service(routes::StripeApis::server(state.clone())); } - #[cfg(feature = "recon")] + #[cfg(all(feature = "recon", feature = "v1"))] { server_app = server_app.service(routes::Recon::server(state.clone())); } diff --git a/crates/router/src/routes.rs b/crates/router/src/routes.rs index cb8edb24052d..482b9a1b9cc7 100644 --- a/crates/router/src/routes.rs +++ b/crates/router/src/routes.rs @@ -1,8 +1,9 @@ pub mod admin; pub mod api_keys; pub mod app; +#[cfg(feature = "v1")] pub mod apple_pay_certificates_migration; -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v1"))] pub mod blocklist; pub mod cache; pub mod cards_info; @@ -22,9 +23,11 @@ pub mod fraud_check; pub mod gsm; pub mod health; pub mod lock_utils; +#[cfg(feature = "v1")] pub mod locker_migration; pub mod mandates; pub mod metrics; +#[cfg(feature = "v1")] pub mod payment_link; pub mod payment_methods; pub mod payments; @@ -37,6 +40,7 @@ pub mod pm_auth; pub mod poll; #[cfg(feature = "recon")] pub mod recon; +#[cfg(feature = "v1")] pub mod refunds; #[cfg(feature = "olap")] pub mod routing; @@ -48,15 +52,16 @@ pub mod user_role; pub mod verification; #[cfg(feature = "olap")] pub mod verify_connector; -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v1"))] pub mod webhook_events; +#[cfg(feature = "v1")] pub mod webhooks; #[cfg(feature = "dummy_connector")] pub use self::app::DummyConnector; #[cfg(any(feature = "olap", feature = "oltp"))] pub use self::app::Forex; -#[cfg(all(feature = "olap", feature = "recon"))] +#[cfg(all(feature = "olap", feature = "recon", feature = "v1"))] pub use self::app::Recon; pub use self::app::{ ApiKeys, AppState, ApplePayCertificatesMigration, BusinessProfile, BusinessProfileNew, Cache, diff --git a/crates/router/src/routes/admin.rs b/crates/router/src/routes/admin.rs index ae97bfe60447..0dc34aa4245b 100644 --- a/crates/router/src/routes/admin.rs +++ b/crates/router/src/routes/admin.rs @@ -1,4 +1,5 @@ use actix_web::{web, HttpRequest, HttpResponse}; +use common_enums::EntityType; use router_env::{instrument, tracing, Flow}; use super::app::AppState; @@ -118,6 +119,7 @@ pub async fn retrieve_merchant_account( &auth::JWTAuthMerchantFromRoute { merchant_id, required_permission: Permission::MerchantAccountRead, + minimum_entity_level: EntityType::Profile, }, req.headers(), ), @@ -170,6 +172,7 @@ pub async fn update_merchant_account( &auth::JWTAuthMerchantFromRoute { merchant_id: merchant_id.clone(), required_permission: Permission::MerchantAccountWrite, + minimum_entity_level: EntityType::Merchant, }, req.headers(), ), @@ -223,13 +226,20 @@ pub async fn connector_create( &req, payload, |state, auth_data, req, _| { - create_connector(state, req, auth_data.merchant_account, auth_data.key_store) + create_connector( + state, + req, + auth_data.merchant_account, + auth_data.profile_id, + auth_data.key_store, + ) }, auth::auth_type( &auth::AdminApiAuthWithMerchantIdFromRoute(merchant_id.clone()), &auth::JWTAuthMerchantFromRoute { merchant_id: merchant_id.clone(), required_permission: Permission::MerchantConnectorAccountWrite, + minimum_entity_level: EntityType::Profile, }, req.headers(), ), @@ -255,12 +265,19 @@ pub async fn connector_create( &req, payload, |state, auth_data, req, _| { - create_connector(state, req, auth_data.merchant_account, auth_data.key_store) + create_connector( + state, + req, + auth_data.merchant_account, + None, + auth_data.key_store, + ) }, auth::auth_type( &auth::AdminApiAuthWithMerchantIdFromHeader, &auth::JWTAuthMerchantFromHeader { required_permission: Permission::MerchantConnectorAccountWrite, + minimum_entity_level: EntityType::Merchant, }, req.headers(), ), @@ -323,6 +340,7 @@ pub async fn connector_retrieve( &auth::JWTAuthMerchantFromRoute { merchant_id, required_permission: Permission::MerchantConnectorAccountRead, + minimum_entity_level: EntityType::Profile, }, req.headers(), ), @@ -361,6 +379,7 @@ pub async fn connector_retrieve( &auth::AdminApiAuthWithMerchantIdFromHeader, &auth::JWTAuthMerchantFromHeader { required_permission: Permission::MerchantConnectorAccountRead, + minimum_entity_level: EntityType::Merchant, }, req.headers(), ), @@ -406,6 +425,7 @@ pub async fn payment_connector_list( &auth::JWTAuthMerchantFromRoute { merchant_id, required_permission: Permission::MerchantConnectorAccountRead, + minimum_entity_level: EntityType::Merchant, }, req.headers(), ), @@ -457,6 +477,7 @@ pub async fn payment_connector_list_profile( &auth::JWTAuthMerchantFromRoute { merchant_id, required_permission: Permission::MerchantConnectorAccountRead, + minimum_entity_level: EntityType::Profile, }, req.headers(), ), @@ -517,6 +538,7 @@ pub async fn connector_update( &auth::JWTAuthMerchantFromRoute { merchant_id: merchant_id.clone(), required_permission: Permission::MerchantConnectorAccountWrite, + minimum_entity_level: EntityType::Profile, }, req.headers(), ), @@ -568,6 +590,7 @@ pub async fn connector_update( &auth::JWTAuthMerchantFromRoute { merchant_id: merchant_id.clone(), required_permission: Permission::MerchantConnectorAccountWrite, + minimum_entity_level: EntityType::Merchant, }, req.headers(), ), @@ -623,6 +646,7 @@ pub async fn connector_delete( &auth::JWTAuthMerchantFromRoute { merchant_id, required_permission: Permission::MerchantConnectorAccountWrite, + minimum_entity_level: EntityType::Merchant, }, req.headers(), ), @@ -661,6 +685,7 @@ pub async fn connector_delete( &auth::AdminApiAuthWithMerchantIdFromHeader, &auth::JWTAuthMerchantFromHeader { required_permission: Permission::MerchantConnectorAccountWrite, + minimum_entity_level: EntityType::Merchant, }, req.headers(), ), @@ -743,6 +768,7 @@ pub async fn business_profile_create( &auth::JWTAuthMerchantFromRoute { merchant_id, required_permission: Permission::MerchantAccountWrite, + minimum_entity_level: EntityType::Merchant, }, req.headers(), ), @@ -773,6 +799,7 @@ pub async fn business_profile_create( &auth::AdminApiAuthWithMerchantIdFromHeader, &auth::JWTAuthMerchantFromHeader { required_permission: Permission::MerchantAccountWrite, + minimum_entity_level: EntityType::Merchant, }, req.headers(), ), @@ -807,6 +834,7 @@ pub async fn business_profile_retrieve( &auth::JWTAuthMerchantFromRoute { merchant_id: merchant_id.clone(), required_permission: Permission::MerchantAccountRead, + minimum_entity_level: EntityType::Profile, }, req.headers(), ), @@ -837,6 +865,7 @@ pub async fn business_profile_retrieve( &auth::AdminApiAuthWithMerchantIdFromHeader, &auth::JWTAuthMerchantFromHeader { required_permission: Permission::MerchantAccountRead, + minimum_entity_level: EntityType::Merchant, }, req.headers(), ), @@ -873,6 +902,7 @@ pub async fn business_profile_update( merchant_id: merchant_id.clone(), profile_id: profile_id.clone(), required_permission: Permission::MerchantAccountWrite, + minimum_entity_level: EntityType::Profile, }, req.headers(), ), @@ -904,6 +934,7 @@ pub async fn business_profile_update( &auth::AdminApiAuthWithMerchantIdFromHeader, &auth::JWTAuthMerchantFromHeader { required_permission: Permission::MerchantAccountWrite, + minimum_entity_level: EntityType::Merchant, }, req.headers(), ), @@ -955,6 +986,7 @@ pub async fn business_profiles_list( &auth::JWTAuthMerchantFromRoute { merchant_id, required_permission: Permission::MerchantAccountRead, + minimum_entity_level: EntityType::Merchant, }, req.headers(), ), @@ -989,6 +1021,7 @@ pub async fn business_profiles_list_at_profile_level( &auth::JWTAuthMerchantFromRoute { merchant_id, required_permission: Permission::MerchantAccountRead, + minimum_entity_level: EntityType::Profile, }, req.headers(), ), @@ -1018,7 +1051,10 @@ pub async fn toggle_connector_agnostic_mit( |state, _, req, _| connector_agnostic_mit_toggle(state, &merchant_id, &profile_id, req), auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::RoutingWrite), + &auth::JWTAuth { + permission: Permission::RoutingWrite, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), api_locking::LockAction::NotApplicable, diff --git a/crates/router/src/routes/api_keys.rs b/crates/router/src/routes/api_keys.rs index 68e5687e249f..71632cc57496 100644 --- a/crates/router/src/routes/api_keys.rs +++ b/crates/router/src/routes/api_keys.rs @@ -1,4 +1,5 @@ use actix_web::{web, HttpRequest, Responder}; +use common_enums::EntityType; use router_env::{instrument, tracing, Flow}; use super::app::AppState; @@ -37,6 +38,7 @@ pub async fn api_key_create( &auth::JWTAuthMerchantFromRoute { merchant_id: merchant_id.clone(), required_permission: Permission::ApiKeyWrite, + minimum_entity_level: EntityType::Merchant, }, req.headers(), ), @@ -67,6 +69,7 @@ pub async fn api_key_create( &auth::AdminApiAuthWithMerchantIdFromHeader, &auth::JWTAuthMerchantFromHeader { required_permission: Permission::ApiKeyWrite, + minimum_entity_level: EntityType::Merchant, }, req.headers(), ), @@ -104,6 +107,7 @@ pub async fn api_key_retrieve( &auth::AdminApiAuthWithMerchantIdFromHeader, &auth::JWTAuthMerchantFromHeader { required_permission: Permission::ApiKeyRead, + minimum_entity_level: EntityType::Merchant, }, req.headers(), ), @@ -136,6 +140,7 @@ pub async fn api_key_retrieve( &auth::JWTAuthMerchantFromRoute { merchant_id: merchant_id.clone(), required_permission: Permission::ApiKeyRead, + minimum_entity_level: EntityType::Merchant, }, req.headers(), ), @@ -172,6 +177,7 @@ pub async fn api_key_update( &auth::JWTAuthMerchantFromRoute { merchant_id, required_permission: Permission::ApiKeyWrite, + minimum_entity_level: EntityType::Merchant, }, req.headers(), ), @@ -204,6 +210,7 @@ pub async fn api_key_update( &auth::JWTAuthMerchantFromRoute { merchant_id, required_permission: Permission::ApiKeyWrite, + minimum_entity_level: EntityType::Merchant, }, req.headers(), ), @@ -237,6 +244,7 @@ pub async fn api_key_revoke( &auth::JWTAuthMerchantFromRoute { merchant_id: merchant_id.clone(), required_permission: Permission::ApiKeyWrite, + minimum_entity_level: EntityType::Merchant, }, req.headers(), ), @@ -266,6 +274,7 @@ pub async fn api_key_revoke( &auth::JWTAuthMerchantFromRoute { merchant_id: merchant_id.clone(), required_permission: Permission::ApiKeyWrite, + minimum_entity_level: EntityType::Merchant, }, req.headers(), ), @@ -318,6 +327,7 @@ pub async fn api_key_list( &auth::JWTAuthMerchantFromRoute { merchant_id, required_permission: Permission::ApiKeyRead, + minimum_entity_level: EntityType::Merchant, }, req.headers(), ), diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 1e97c7e3f64a..06d2d38f832f 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -20,8 +20,6 @@ use storage_impl::{config::TenantConfig, redis::RedisStore, MockDb}; use tokio::sync::oneshot; use self::settings::Tenant; -#[cfg(feature = "olap")] -use super::blocklist; #[cfg(any(feature = "olap", feature = "oltp"))] use super::currency; #[cfg(feature = "dummy_connector")] @@ -50,16 +48,18 @@ use super::poll::retrieve_poll_status; use super::routing; #[cfg(feature = "olap")] use super::verification::{apple_pay_merchant_registration, retrieve_apple_pay_verified_domains}; -#[cfg(feature = "oltp")] +#[cfg(all(feature = "oltp", feature = "v1"))] use super::webhooks::*; -#[cfg(feature = "olap")] use super::{ - admin::*, api_keys::*, apple_pay_certificates_migration, connector_onboarding::*, disputes::*, - files::*, gsm::*, payment_link::*, user::*, user_role::*, webhook_events::*, + admin, api_keys, cache::*, connector_onboarding, disputes, files, gsm, health::*, user, + user_role, }; -use super::{cache::*, health::*}; +#[cfg(feature = "v1")] +use super::{apple_pay_certificates_migration, blocklist, payment_link, webhook_events}; #[cfg(any(feature = "olap", feature = "oltp"))] -use super::{configs::*, customers::*, mandates::*, payments::*, refunds::*}; +use super::{configs::*, customers::*, payments::*}; +#[cfg(all(any(feature = "olap", feature = "oltp"), feature = "v1"))] +use super::{mandates::*, refunds::*}; #[cfg(feature = "olap")] pub use crate::analytics::opensearch::OpenSearchClient; #[cfg(feature = "olap")] @@ -547,6 +547,10 @@ impl Payments { .service(web::resource("/filter").route(web::post().to(get_filters_for_payments))) .service(web::resource("/v2/filter").route(web::get().to(get_payment_filters))) .service(web::resource("/aggregate").route(web::get().to(get_payments_aggregates))) + .service( + web::resource("/profile/aggregate") + .route(web::get().to(get_payments_aggregates_profile)), + ) .service( web::resource("/v2/profile/filter") .route(web::get().to(get_payment_filters_profile)), @@ -626,6 +630,10 @@ impl Payments { .service( web::resource("/{payment_id}/extended_card_info").route(web::get().to(retrieve_extended_card_info)), ) + .service( + web::resource("{payment_id}/calculate_tax") + .route(web::post().to(payments_dynamic_tax_calculation)), + ); } route } @@ -975,7 +983,7 @@ impl Customers { } pub struct Refunds; -#[cfg(any(feature = "olap", feature = "oltp"))] +#[cfg(all(any(feature = "olap", feature = "oltp"), feature = "v1"))] impl Refunds { pub fn server(state: AppState) -> Scope { let mut route = web::scope("/refunds").app_data(web::Data::new(state)); @@ -987,6 +995,7 @@ impl Refunds { .service(web::resource("/profile/list").route(web::post().to(refunds_list_profile))) .service(web::resource("/filter").route(web::post().to(refunds_filter_list))) .service(web::resource("/v2/filter").route(web::get().to(get_refunds_filters))) + .service(web::resource("/aggregate").route(web::get().to(get_refunds_aggregates))) .service( web::resource("/v2/profile/filter") .route(web::get().to(get_refunds_filters_profile)), @@ -1034,7 +1043,12 @@ impl Payouts { .route(web::post().to(payouts_list_by_filter_profile)), ) .service( - web::resource("/filter").route(web::post().to(payouts_list_available_filters)), + web::resource("/filter") + .route(web::post().to(payouts_list_available_filters_for_merchant)), + ) + .service( + web::resource("/profile/filter") + .route(web::post().to(payouts_list_available_filters_for_profile)), ); } route = route @@ -1112,30 +1126,30 @@ impl PaymentMethods { } } -#[cfg(all(feature = "olap", feature = "recon"))] +#[cfg(all(feature = "olap", feature = "recon", feature = "v1"))] pub struct Recon; -#[cfg(all(feature = "olap", feature = "recon"))] +#[cfg(all(feature = "olap", feature = "recon", feature = "v1"))] impl Recon { pub fn server(state: AppState) -> Scope { web::scope("/recon") .app_data(web::Data::new(state)) .service( - web::resource("/update_merchant") + web::resource("/{merchant_id}/update") .route(web::post().to(recon_routes::update_merchant)), ) .service(web::resource("/token").route(web::get().to(recon_routes::get_recon_token))) .service( web::resource("/request").route(web::post().to(recon_routes::request_for_recon)), ) - .service(web::resource("/verify_token").route(web::get().to(verify_recon_token))) + .service(web::resource("/verify_token").route(web::get().to(user::verify_recon_token))) } } #[cfg(feature = "olap")] pub struct Blocklist; -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v1"))] impl Blocklist { pub fn server(state: AppState) -> Scope { web::scope("/blocklist") @@ -1160,11 +1174,11 @@ impl Organization { pub fn server(state: AppState) -> Scope { web::scope("/organization") .app_data(web::Data::new(state)) - .service(web::resource("").route(web::post().to(organization_create))) + .service(web::resource("").route(web::post().to(admin::organization_create))) .service( web::resource("/{id}") - .route(web::get().to(organization_retrieve)) - .route(web::put().to(organization_update)), + .route(web::get().to(admin::organization_retrieve)) + .route(web::put().to(admin::organization_update)), ) } } @@ -1174,11 +1188,11 @@ impl Organization { pub fn server(state: AppState) -> Scope { web::scope("/v2/organization") .app_data(web::Data::new(state)) - .service(web::resource("").route(web::post().to(organization_create))) + .service(web::resource("").route(web::post().to(admin::organization_create))) .service( web::resource("/{id}") - .route(web::get().to(organization_retrieve)) - .route(web::put().to(organization_update)), + .route(web::get().to(admin::organization_retrieve)) + .route(web::put().to(admin::organization_update)), ) } } @@ -1190,11 +1204,11 @@ impl MerchantAccount { pub fn server(state: AppState) -> Scope { web::scope("/v2/accounts") .app_data(web::Data::new(state)) - .service(web::resource("").route(web::post().to(merchant_account_create))) + .service(web::resource("").route(web::post().to(admin::merchant_account_create))) .service( web::resource("/{id}") - .route(web::get().to(retrieve_merchant_account)) - .route(web::put().to(update_merchant_account)), + .route(web::get().to(admin::retrieve_merchant_account)) + .route(web::put().to(admin::update_merchant_account)), ) } } @@ -1204,22 +1218,25 @@ impl MerchantAccount { pub fn server(state: AppState) -> Scope { web::scope("/accounts") .app_data(web::Data::new(state)) - .service(web::resource("").route(web::post().to(merchant_account_create))) - .service(web::resource("/list").route(web::get().to(merchant_account_list))) + .service(web::resource("").route(web::post().to(admin::merchant_account_create))) + .service(web::resource("/list").route(web::get().to(admin::merchant_account_list))) .service( web::resource("/{id}/kv") - .route(web::post().to(merchant_account_toggle_kv)) - .route(web::get().to(merchant_account_kv_status)), + .route(web::post().to(admin::merchant_account_toggle_kv)) + .route(web::get().to(admin::merchant_account_kv_status)), + ) + .service( + web::resource("/transfer") + .route(web::post().to(admin::merchant_account_transfer_keys)), ) .service( - web::resource("/transfer").route(web::post().to(merchant_account_transfer_keys)), + web::resource("/kv").route(web::post().to(admin::merchant_account_toggle_all_kv)), ) - .service(web::resource("/kv").route(web::post().to(merchant_account_toggle_all_kv))) .service( web::resource("/{id}") - .route(web::get().to(retrieve_merchant_account)) - .route(web::post().to(update_merchant_account)) - .route(web::delete().to(delete_merchant_account)), + .route(web::get().to(admin::retrieve_merchant_account)) + .route(web::post().to(admin::update_merchant_account)) + .route(web::delete().to(admin::delete_merchant_account)), ) } } @@ -1267,10 +1284,6 @@ impl MerchantConnectorAccount { .route(web::post().to(connector_create)) .route(web::get().to(payment_connector_list)), ) - .service( - web::resource("/{merchant_id}/profile/connectors") - .route(web::get().to(payment_connector_list_profile)), - ) .service( web::resource("/{merchant_id}/connectors/{merchant_connector_id}") .route(web::get().to(connector_retrieve)) @@ -1306,7 +1319,7 @@ impl EphemeralKey { pub struct Mandates; -#[cfg(any(feature = "olap", feature = "oltp"))] +#[cfg(all(any(feature = "olap", feature = "oltp"), feature = "v1"))] impl Mandates { pub fn server(state: AppState) -> Scope { let mut route = web::scope("/mandates").app_data(web::Data::new(state)); @@ -1328,7 +1341,7 @@ impl Mandates { pub struct Webhooks; -#[cfg(feature = "oltp")] +#[cfg(all(feature = "oltp", feature = "v1"))] impl Webhooks { pub fn server(config: AppState) -> Scope { use api_models::webhooks as webhook_type; @@ -1378,7 +1391,7 @@ impl Configs { pub struct ApplePayCertificatesMigration; -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v1"))] impl ApplePayCertificatesMigration { pub fn server(state: AppState) -> Scope { web::scope("/apple_pay_certificates_migration") @@ -1402,18 +1415,18 @@ impl Poll { pub struct ApiKeys; -#[cfg(all(feature = "v2", feature = "olap"))] +#[cfg(all(feature = "olap", feature = "v2"))] impl ApiKeys { pub fn server(state: AppState) -> Scope { web::scope("/v2/api_keys") .app_data(web::Data::new(state)) - .service(web::resource("").route(web::post().to(api_key_create))) - .service(web::resource("/list").route(web::get().to(api_key_list))) + .service(web::resource("").route(web::post().to(api_keys::api_key_create))) + .service(web::resource("/list").route(web::get().to(api_keys::api_key_list))) .service( web::resource("/{key_id}") - .route(web::get().to(api_key_retrieve)) - .route(web::put().to(api_key_update)) - .route(web::delete().to(api_key_revoke)), + .route(web::get().to(api_keys::api_key_retrieve)) + .route(web::put().to(api_keys::api_key_update)) + .route(web::delete().to(api_keys::api_key_revoke)), ) } } @@ -1423,40 +1436,46 @@ impl ApiKeys { pub fn server(state: AppState) -> Scope { web::scope("/api_keys/{merchant_id}") .app_data(web::Data::new(state)) - .service(web::resource("").route(web::post().to(api_key_create))) - .service(web::resource("/list").route(web::get().to(api_key_list))) + .service(web::resource("").route(web::post().to(api_keys::api_key_create))) + .service(web::resource("/list").route(web::get().to(api_keys::api_key_list))) .service( web::resource("/{key_id}") - .route(web::get().to(api_key_retrieve)) - .route(web::post().to(api_key_update)) - .route(web::delete().to(api_key_revoke)), + .route(web::get().to(api_keys::api_key_retrieve)) + .route(web::post().to(api_keys::api_key_update)) + .route(web::delete().to(api_keys::api_key_revoke)), ) } } pub struct Disputes; -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v1"))] impl Disputes { pub fn server(state: AppState) -> Scope { web::scope("/disputes") .app_data(web::Data::new(state)) - .service(web::resource("/list").route(web::get().to(retrieve_disputes_list))) + .service(web::resource("/list").route(web::get().to(disputes::retrieve_disputes_list))) + .service( + web::resource("/profile/list") + .route(web::get().to(disputes::retrieve_disputes_list_profile)), + ) .service( - web::resource("/profile/list").route(web::get().to(retrieve_disputes_list_profile)), + web::resource("/accept/{dispute_id}") + .route(web::post().to(disputes::accept_dispute)), ) - .service(web::resource("/accept/{dispute_id}").route(web::post().to(accept_dispute))) .service( web::resource("/evidence") - .route(web::post().to(submit_dispute_evidence)) - .route(web::put().to(attach_dispute_evidence)) - .route(web::delete().to(delete_dispute_evidence)), + .route(web::post().to(disputes::submit_dispute_evidence)) + .route(web::put().to(disputes::attach_dispute_evidence)) + .route(web::delete().to(disputes::delete_dispute_evidence)), ) .service( web::resource("/evidence/{dispute_id}") - .route(web::get().to(retrieve_dispute_evidence)), + .route(web::get().to(disputes::retrieve_dispute_evidence)), + ) + .service( + web::resource("/{dispute_id}").route(web::get().to(disputes::retrieve_dispute)), ) - .service(web::resource("/{dispute_id}").route(web::get().to(retrieve_dispute))) } } @@ -1472,16 +1491,16 @@ impl Cards { pub struct Files; -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v1"))] impl Files { pub fn server(state: AppState) -> Scope { web::scope("/files") .app_data(web::Data::new(state)) - .service(web::resource("").route(web::post().to(files_create))) + .service(web::resource("").route(web::post().to(files::files_create))) .service( web::resource("/{file_id}") - .route(web::delete().to(files_delete)) - .route(web::get().to(files_retrieve)), + .route(web::delete().to(files::files_delete)) + .route(web::get().to(files::files_retrieve)), ) } } @@ -1497,26 +1516,28 @@ impl Cache { } pub struct PaymentLink; -#[cfg(feature = "olap")] + +#[cfg(all(feature = "olap", feature = "v1"))] impl PaymentLink { pub fn server(state: AppState) -> Scope { web::scope("/payment_link") .app_data(web::Data::new(state)) - .service(web::resource("/list").route(web::post().to(payments_link_list))) + .service(web::resource("/list").route(web::post().to(payment_link::payments_link_list))) .service( - web::resource("/{payment_link_id}").route(web::get().to(payment_link_retrieve)), + web::resource("/{payment_link_id}") + .route(web::get().to(payment_link::payment_link_retrieve)), ) .service( web::resource("{merchant_id}/{payment_id}") - .route(web::get().to(initiate_payment_link)), + .route(web::get().to(payment_link::initiate_payment_link)), ) .service( web::resource("s/{merchant_id}/{payment_id}") - .route(web::get().to(initiate_secure_payment_link)), + .route(web::get().to(payment_link::initiate_secure_payment_link)), ) .service( web::resource("status/{merchant_id}/{payment_id}") - .route(web::get().to(payment_link_status)), + .route(web::get().to(payment_link::payment_link_status)), ) } } @@ -1541,13 +1562,13 @@ impl BusinessProfile { pub fn server(state: AppState) -> Scope { web::scope("/v2/profiles") .app_data(web::Data::new(state)) - .service(web::resource("").route(web::post().to(business_profile_create))) + .service(web::resource("").route(web::post().to(super::admin::business_profile_create))) .service( web::scope("/{profile_id}") .service( web::resource("") - .route(web::get().to(business_profile_retrieve)) - .route(web::put().to(business_profile_update)), + .route(web::get().to(super::admin::business_profile_retrieve)) + .route(web::put().to(super::admin::business_profile_update)), ) .service( web::resource("/fallback_routing") @@ -1600,24 +1621,24 @@ impl BusinessProfile { .app_data(web::Data::new(state)) .service( web::resource("") - .route(web::post().to(business_profile_create)) - .route(web::get().to(business_profiles_list)), + .route(web::post().to(admin::business_profile_create)) + .route(web::get().to(admin::business_profiles_list)), ) .service( web::scope("/{profile_id}") .service( web::resource("") - .route(web::get().to(business_profile_retrieve)) - .route(web::post().to(business_profile_update)) - .route(web::delete().to(business_profile_delete)), + .route(web::get().to(admin::business_profile_retrieve)) + .route(web::post().to(admin::business_profile_update)) + .route(web::delete().to(admin::business_profile_delete)), ) .service( web::resource("/toggle_extended_card_info") - .route(web::post().to(toggle_extended_card_info)), + .route(web::post().to(admin::toggle_extended_card_info)), ) .service( web::resource("/toggle_connector_agnostic_mit") - .route(web::post().to(toggle_connector_agnostic_mit)), + .route(web::post().to(admin::toggle_connector_agnostic_mit)), ), ) } @@ -1631,29 +1652,34 @@ impl BusinessProfileNew { web::scope("/account/{account_id}/profile") .app_data(web::Data::new(state)) .service( - web::resource("").route(web::get().to(business_profiles_list_at_profile_level)), + web::resource("") + .route(web::get().to(admin::business_profiles_list_at_profile_level)), + ) + .service( + web::resource("/connectors") + .route(web::get().to(admin::payment_connector_list_profile)), ) } } pub struct Gsm; -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v1"))] impl Gsm { pub fn server(state: AppState) -> Scope { web::scope("/gsm") .app_data(web::Data::new(state)) - .service(web::resource("").route(web::post().to(create_gsm_rule))) - .service(web::resource("/get").route(web::post().to(get_gsm_rule))) - .service(web::resource("/update").route(web::post().to(update_gsm_rule))) - .service(web::resource("/delete").route(web::post().to(delete_gsm_rule))) + .service(web::resource("").route(web::post().to(gsm::create_gsm_rule))) + .service(web::resource("/get").route(web::post().to(gsm::get_gsm_rule))) + .service(web::resource("/update").route(web::post().to(gsm::update_gsm_rule))) + .service(web::resource("/delete").route(web::post().to(gsm::delete_gsm_rule))) } } #[cfg(feature = "olap")] pub struct Verify; -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v1"))] impl Verify { pub fn server(state: AppState) -> Scope { web::scope("/verify") @@ -1671,102 +1697,122 @@ impl Verify { pub struct User; -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v1"))] impl User { pub fn server(state: AppState) -> Scope { let mut route = web::scope("/user").app_data(web::Data::new(state)); route = route - .service(web::resource("").route(web::get().to(get_user_details))) - .service(web::resource("/signin").route(web::post().to(user_signin))) - .service(web::resource("/v2/signin").route(web::post().to(user_signin))) + .service(web::resource("").route(web::get().to(user::get_user_details))) + .service(web::resource("/signin").route(web::post().to(user::user_signin))) + .service(web::resource("/v2/signin").route(web::post().to(user::user_signin))) // signin/signup with sso using openidconnect - .service(web::resource("/oidc").route(web::post().to(sso_sign))) - .service(web::resource("/signout").route(web::post().to(signout))) - .service(web::resource("/rotate_password").route(web::post().to(rotate_password))) - .service(web::resource("/change_password").route(web::post().to(change_password))) - .service(web::resource("/internal_signup").route(web::post().to(internal_user_signup))) - .service(web::resource("/switch_merchant").route(web::post().to(switch_merchant_id))) + .service(web::resource("/oidc").route(web::post().to(user::sso_sign))) + .service(web::resource("/signout").route(web::post().to(user::signout))) + .service(web::resource("/rotate_password").route(web::post().to(user::rotate_password))) + .service(web::resource("/change_password").route(web::post().to(user::change_password))) + .service( + web::resource("/internal_signup").route(web::post().to(user::internal_user_signup)), + ) + .service( + web::resource("/switch_merchant").route(web::post().to(user::switch_merchant_id)), + ) .service( web::resource("/create_merchant") - .route(web::post().to(user_merchant_account_create)), + .route(web::post().to(user::user_merchant_account_create)), ) // TODO: Remove this endpoint once migration to /merchants/list is done - .service(web::resource("/switch/list").route(web::get().to(list_merchants_for_user))) - .service(web::resource("/merchants/list").route(web::get().to(list_merchants_for_user))) + .service( + web::resource("/switch/list").route(web::get().to(user::list_merchants_for_user)), + ) + .service( + web::resource("/merchants/list") + .route(web::get().to(user::list_merchants_for_user)), + ) // The route is utilized to select an invitation from a list of merchants in an intermediate state .service( web::resource("/merchants_select/list") - .route(web::get().to(list_merchants_for_user)), + .route(web::get().to(user::list_merchants_for_user)), + ) + .service( + web::resource("/permission_info") + .route(web::get().to(user_role::get_authorization_info)), + ) + .service( + web::resource("/module/list").route(web::get().to(user_role::get_role_information)), + ) + .service( + web::resource("/update").route(web::post().to(user::update_user_account_details)), ) - .service(web::resource("/permission_info").route(web::get().to(get_authorization_info))) - .service(web::resource("/module/list").route(web::get().to(get_role_information))) - .service(web::resource("/update").route(web::post().to(update_user_account_details))) .service( web::resource("/data") - .route(web::get().to(get_multiple_dashboard_metadata)) - .route(web::post().to(set_dashboard_metadata)), + .route(web::get().to(user::get_multiple_dashboard_metadata)) + .route(web::post().to(user::set_dashboard_metadata)), ); - route = route.service( - web::scope("/key") - .service(web::resource("/transfer").route(web::post().to(transfer_user_key))), - ); + route = route + .service(web::scope("/key").service( + web::resource("/transfer").route(web::post().to(user::transfer_user_key)), + )); route = route.service( web::scope("/list") - .service(web::resource("/org").route(web::get().to(list_orgs_for_user))) + .service(web::resource("/org").route(web::get().to(user::list_orgs_for_user))) .service( - web::resource("/merchant").route(web::get().to(list_merchants_for_user_in_org)), + web::resource("/merchant") + .route(web::get().to(user::list_merchants_for_user_in_org)), ) .service( web::resource("/profile") - .route(web::get().to(list_profiles_for_user_in_org_and_merchant)), + .route(web::get().to(user::list_profiles_for_user_in_org_and_merchant)), ) .service( - web::resource("/invitation").route(web::get().to(list_invitations_for_user)), + web::resource("/invitation") + .route(web::get().to(user_role::list_invitations_for_user)), ), ); route = route.service( web::scope("/switch") - .service(web::resource("/org").route(web::post().to(switch_org_for_user))) + .service(web::resource("/org").route(web::post().to(user::switch_org_for_user))) .service( web::resource("/merchant") - .route(web::post().to(switch_merchant_for_user_in_org)), + .route(web::post().to(user::switch_merchant_for_user_in_org)), ) .service( web::resource("/profile") - .route(web::post().to(switch_profile_for_user_in_org_and_merchant)), + .route(web::post().to(user::switch_profile_for_user_in_org_and_merchant)), ), ); // Two factor auth routes route = route.service( web::scope("/2fa") - .service(web::resource("").route(web::get().to(check_two_factor_auth_status))) + .service(web::resource("").route(web::get().to(user::check_two_factor_auth_status))) .service( web::scope("/totp") - .service(web::resource("/begin").route(web::get().to(totp_begin))) - .service(web::resource("/reset").route(web::get().to(totp_reset))) + .service(web::resource("/begin").route(web::get().to(user::totp_begin))) + .service(web::resource("/reset").route(web::get().to(user::totp_reset))) .service( web::resource("/verify") - .route(web::post().to(totp_verify)) - .route(web::put().to(totp_update)), + .route(web::post().to(user::totp_verify)) + .route(web::put().to(user::totp_update)), ), ) .service( web::scope("/recovery_code") .service( - web::resource("/verify").route(web::post().to(verify_recovery_code)), + web::resource("/verify") + .route(web::post().to(user::verify_recovery_code)), ) .service( web::resource("/generate") - .route(web::get().to(generate_recovery_codes)), + .route(web::get().to(user::generate_recovery_codes)), ), ) .service( - web::resource("/terminate").route(web::get().to(terminate_two_factor_auth)), + web::resource("/terminate") + .route(web::get().to(user::terminate_two_factor_auth)), ), ); @@ -1774,78 +1820,102 @@ impl User { web::scope("/auth") .service( web::resource("") - .route(web::post().to(create_user_authentication_method)) - .route(web::put().to(update_user_authentication_method)), + .route(web::post().to(user::create_user_authentication_method)) + .route(web::put().to(user::update_user_authentication_method)), ) .service( - web::resource("/list").route(web::get().to(list_user_authentication_methods)), + web::resource("/list") + .route(web::get().to(user::list_user_authentication_methods)), ) - .service(web::resource("/url").route(web::get().to(get_sso_auth_url))) - .service(web::resource("/select").route(web::post().to(terminate_auth_select))), + .service(web::resource("/url").route(web::get().to(user::get_sso_auth_url))) + .service( + web::resource("/select").route(web::post().to(user::terminate_auth_select)), + ), ); #[cfg(feature = "email")] { route = route - .service(web::resource("/from_email").route(web::post().to(user_from_email))) + .service(web::resource("/from_email").route(web::post().to(user::user_from_email))) .service( - web::resource("/connect_account").route(web::post().to(user_connect_account)), + web::resource("/connect_account") + .route(web::post().to(user::user_connect_account)), + ) + .service( + web::resource("/forgot_password").route(web::post().to(user::forgot_password)), + ) + .service( + web::resource("/reset_password").route(web::post().to(user::reset_password)), ) - .service(web::resource("/forgot_password").route(web::post().to(forgot_password))) - .service(web::resource("/reset_password").route(web::post().to(reset_password))) .service( web::resource("/signup_with_merchant_id") - .route(web::post().to(user_signup_with_merchant_id)), + .route(web::post().to(user::user_signup_with_merchant_id)), + ) + .service(web::resource("/verify_email").route(web::post().to(user::verify_email))) + .service( + web::resource("/v2/verify_email").route(web::post().to(user::verify_email)), ) - .service(web::resource("/verify_email").route(web::post().to(verify_email))) - .service(web::resource("/v2/verify_email").route(web::post().to(verify_email))) .service( web::resource("/verify_email_request") - .route(web::post().to(verify_email_request)), + .route(web::post().to(user::verify_email_request)), + ) + .service( + web::resource("/user/resend_invite").route(web::post().to(user::resend_invite)), ) - .service(web::resource("/user/resend_invite").route(web::post().to(resend_invite))) .service( web::resource("/accept_invite_from_email") - .route(web::post().to(accept_invite_from_email)), + .route(web::post().to(user::accept_invite_from_email)), ); } #[cfg(not(feature = "email"))] { - route = route.service(web::resource("/signup").route(web::post().to(user_signup))) + route = route.service(web::resource("/signup").route(web::post().to(user::user_signup))) } // User management route = route.service( web::scope("/user") - .service(web::resource("").route(web::get().to(get_user_role_details))) - .service(web::resource("/v2").route(web::post().to(list_user_roles_details))) + .service(web::resource("").route(web::get().to(user::get_user_role_details))) + .service(web::resource("/v2").route(web::post().to(user::list_user_roles_details))) + .service( + web::resource("/list") + .route(web::get().to(user::list_users_for_merchant_account)), + ) .service( - web::resource("/list").route(web::get().to(list_users_for_merchant_account)), + web::resource("/v2/list") + .route(web::get().to(user_role::list_users_in_lineage)), ) - .service(web::resource("/v2/list").route(web::get().to(list_users_in_lineage))) .service( - web::resource("/invite_multiple").route(web::post().to(invite_multiple_user)), + web::resource("/invite_multiple") + .route(web::post().to(user::invite_multiple_user)), ) .service( web::scope("/invite/accept") .service( web::resource("") - .route(web::post().to(merchant_select)) - .route(web::put().to(accept_invitation)), + .route(web::post().to(user_role::merchant_select)) + .route(web::put().to(user_role::accept_invitation)), ) .service( web::scope("/v2") .service( - web::resource("").route(web::post().to(accept_invitations_v2)), + web::resource("") + .route(web::post().to(user_role::accept_invitations_v2)), ) .service( - web::resource("/pre_auth") - .route(web::post().to(accept_invitations_pre_auth)), + web::resource("/pre_auth").route( + web::post().to(user_role::accept_invitations_pre_auth), + ), ), ), ) - .service(web::resource("/update_role").route(web::post().to(update_user_role))) - .service(web::resource("/delete").route(web::delete().to(delete_user_role))), + .service( + web::resource("/update_role") + .route(web::post().to(user_role::update_user_role)), + ) + .service( + web::resource("/delete").route(web::delete().to(user_role::delete_user_role)), + ), ); // Role information @@ -1853,26 +1923,30 @@ impl User { web::scope("/role") .service( web::resource("") - .route(web::get().to(get_role_from_token)) - .route(web::post().to(create_role)), + .route(web::get().to(user_role::get_role_from_token)) + .route(web::post().to(user_role::create_role)), + ) + .service( + web::resource("/v2/list").route(web::get().to(user_role::list_roles_with_info)), ) - .service(web::resource("/v2/list").route(web::get().to(list_roles_with_info))) .service( web::scope("/list") - .service(web::resource("").route(web::get().to(list_all_roles))) + .service(web::resource("").route(web::get().to(user_role::list_all_roles))) .service( - web::resource("/invite") - .route(web::get().to(list_invitable_roles_at_entity_level)), + web::resource("/invite").route( + web::get().to(user_role::list_invitable_roles_at_entity_level), + ), ) .service( - web::resource("/update") - .route(web::get().to(list_updatable_roles_at_entity_level)), + web::resource("/update").route( + web::get().to(user_role::list_updatable_roles_at_entity_level), + ), ), ) .service( web::resource("/{role_id}") - .route(web::get().to(get_role)) - .route(web::put().to(update_role)), + .route(web::get().to(user_role::get_role)) + .route(web::put().to(user_role::update_role)), ), ); @@ -1880,8 +1954,8 @@ impl User { { route = route.service( web::resource("/sample_data") - .route(web::post().to(generate_sample_data)) - .route(web::delete().to(delete_sample_data)), + .route(web::post().to(user::generate_sample_data)) + .route(web::delete().to(user::delete_sample_data)), ) } route @@ -1890,35 +1964,47 @@ impl User { pub struct ConnectorOnboarding; -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v1"))] impl ConnectorOnboarding { pub fn server(state: AppState) -> Scope { web::scope("/connector_onboarding") .app_data(web::Data::new(state)) - .service(web::resource("/action_url").route(web::post().to(get_action_url))) - .service(web::resource("/sync").route(web::post().to(sync_onboarding_status))) - .service(web::resource("/reset_tracking_id").route(web::post().to(reset_tracking_id))) + .service( + web::resource("/action_url") + .route(web::post().to(connector_onboarding::get_action_url)), + ) + .service( + web::resource("/sync") + .route(web::post().to(connector_onboarding::sync_onboarding_status)), + ) + .service( + web::resource("/reset_tracking_id") + .route(web::post().to(connector_onboarding::reset_tracking_id)), + ) } } #[cfg(feature = "olap")] pub struct WebhookEvents; -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v1"))] impl WebhookEvents { pub fn server(config: AppState) -> Scope { web::scope("/events/{merchant_id}") .app_data(web::Data::new(config)) - .service(web::resource("").route(web::get().to(list_initial_webhook_delivery_attempts))) + .service( + web::resource("") + .route(web::get().to(webhook_events::list_initial_webhook_delivery_attempts)), + ) .service( web::scope("/{event_id}") .service( web::resource("attempts") - .route(web::get().to(list_webhook_delivery_attempts)), + .route(web::get().to(webhook_events::list_webhook_delivery_attempts)), ) .service( web::resource("retry") - .route(web::post().to(retry_webhook_delivery_attempt)), + .route(web::post().to(webhook_events::retry_webhook_delivery_attempt)), ), ) } diff --git a/crates/router/src/routes/blocklist.rs b/crates/router/src/routes/blocklist.rs index f2a70a7b9054..4738df5ed2ca 100644 --- a/crates/router/src/routes/blocklist.rs +++ b/crates/router/src/routes/blocklist.rs @@ -1,5 +1,6 @@ use actix_web::{web, HttpRequest, HttpResponse}; use api_models::blocklist as api_blocklist; +use common_enums::EntityType; use router_env::Flow; use crate::{ @@ -36,7 +37,10 @@ pub async fn add_entry_to_blocklist( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::MerchantAccountWrite), + &auth::JWTAuth { + permission: Permission::MerchantAccountWrite, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -72,7 +76,10 @@ pub async fn remove_entry_from_blocklist( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::MerchantAccountWrite), + &auth::JWTAuth { + permission: Permission::MerchantAccountWrite, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -110,7 +117,10 @@ pub async fn list_blocked_payment_methods( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::MerchantAccountRead), + &auth::JWTAuth { + permission: Permission::MerchantAccountRead, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -148,7 +158,10 @@ pub async fn toggle_blocklist_guard( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::MerchantAccountWrite), + &auth::JWTAuth { + permission: Permission::MerchantAccountWrite, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), api_locking::LockAction::NotApplicable, diff --git a/crates/router/src/routes/connector_onboarding.rs b/crates/router/src/routes/connector_onboarding.rs index f5555f5bf9bf..f7494e182c19 100644 --- a/crates/router/src/routes/connector_onboarding.rs +++ b/crates/router/src/routes/connector_onboarding.rs @@ -1,5 +1,6 @@ use actix_web::{web, HttpRequest, HttpResponse}; use api_models::connector_onboarding as api_types; +use common_enums::EntityType; use router_env::Flow; use super::AppState; @@ -21,7 +22,10 @@ pub async fn get_action_url( &http_req, req_payload.clone(), core::get_action_url, - &auth::JWTAuth(Permission::MerchantAccountWrite), + &auth::JWTAuth { + permission: Permission::MerchantAccountWrite, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -40,7 +44,10 @@ pub async fn sync_onboarding_status( &http_req, req_payload.clone(), core::sync_onboarding_status, - &auth::JWTAuth(Permission::MerchantAccountWrite), + &auth::JWTAuth { + permission: Permission::MerchantAccountWrite, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -59,7 +66,10 @@ pub async fn reset_tracking_id( &http_req, req_payload.clone(), core::reset_tracking_id, - &auth::JWTAuth(Permission::MerchantAccountWrite), + &auth::JWTAuth { + permission: Permission::MerchantAccountWrite, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await diff --git a/crates/router/src/routes/customers.rs b/crates/router/src/routes/customers.rs index 2e3ce35a974a..66ff1f8e323d 100644 --- a/crates/router/src/routes/customers.rs +++ b/crates/router/src/routes/customers.rs @@ -1,4 +1,5 @@ use actix_web::{web, HttpRequest, HttpResponse, Responder}; +use common_enums::EntityType; #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] use common_utils::id_type; use router_env::{instrument, tracing, Flow}; @@ -25,7 +26,10 @@ pub async fn customers_create( |state, auth, req, _| create_customer(state, auth.merchant_account, auth.key_store, req), auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::CustomerWrite), + &auth::JWTAuth { + permission: Permission::CustomerWrite, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -48,7 +52,10 @@ pub async fn customers_retrieve( .into_inner(); let auth = if auth::is_jwt_auth(req.headers()) { - Box::new(auth::JWTAuth(Permission::CustomerRead)) + Box::new(auth::JWTAuth { + permission: Permission::CustomerRead, + minimum_entity_level: EntityType::Merchant, + }) } else { match auth::is_ephemeral_auth(req.headers()) { Ok(auth) => auth, @@ -88,7 +95,10 @@ pub async fn customers_retrieve( let payload = web::Json(customers::GlobalId::new(path.into_inner())).into_inner(); let auth = if auth::is_jwt_auth(req.headers()) { - Box::new(auth::JWTAuth(Permission::CustomerRead)) + Box::new(auth::JWTAuth { + permission: Permission::CustomerRead, + minimum_entity_level: EntityType::Merchant, + }) } else { match auth::is_ephemeral_auth(req.headers()) { Ok(auth) => auth, @@ -133,7 +143,10 @@ pub async fn customers_list( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::CustomerRead), + &auth::JWTAuth { + permission: Permission::CustomerRead, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -169,7 +182,10 @@ pub async fn customers_update( }, auth::auth_type( &auth::ApiKeyAuth, - &auth::JWTAuth(Permission::CustomerWrite), + &auth::JWTAuth { + permission: Permission::CustomerWrite, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -204,7 +220,10 @@ pub async fn customers_update( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::CustomerWrite), + &auth::JWTAuth { + permission: Permission::CustomerWrite, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -230,7 +249,10 @@ pub async fn customers_delete( |state, auth, req, _| delete_customer(state, auth.merchant_account, req, auth.key_store), auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::CustomerWrite), + &auth::JWTAuth { + permission: Permission::CustomerWrite, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -259,7 +281,10 @@ pub async fn customers_delete( |state, auth, req, _| delete_customer(state, auth.merchant_account, req, auth.key_store), auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::CustomerWrite), + &auth::JWTAuth { + permission: Permission::CustomerWrite, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -294,7 +319,10 @@ pub async fn get_customer_mandates( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::MandateRead), + &auth::JWTAuth { + permission: Permission::MandateRead, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), api_locking::LockAction::NotApplicable, diff --git a/crates/router/src/routes/disputes.rs b/crates/router/src/routes/disputes.rs index e8edcd704d7b..98ba33a64b49 100644 --- a/crates/router/src/routes/disputes.rs +++ b/crates/router/src/routes/disputes.rs @@ -1,6 +1,7 @@ use actix_multipart::Multipart; use actix_web::{web, HttpRequest, HttpResponse}; use api_models::disputes as dispute_models; +use common_enums::EntityType; use router_env::{instrument, tracing, Flow}; use crate::{core::api_locking, services::authorization::permissions::Permission}; @@ -48,7 +49,10 @@ pub async fn retrieve_dispute( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::DisputeRead), + &auth::JWTAuth { + permission: Permission::DisputeRead, + minimum_entity_level: EntityType::Profile, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -97,7 +101,10 @@ pub async fn retrieve_disputes_list( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::DisputeRead), + &auth::JWTAuth { + permission: Permission::DisputeRead, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -152,7 +159,10 @@ pub async fn retrieve_disputes_list_profile( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::DisputeRead), + &auth::JWTAuth { + permission: Permission::DisputeRead, + minimum_entity_level: EntityType::Profile, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -201,7 +211,10 @@ pub async fn accept_dispute( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::DisputeWrite), + &auth::JWTAuth { + permission: Permission::DisputeWrite, + minimum_entity_level: EntityType::Profile, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -244,7 +257,10 @@ pub async fn submit_dispute_evidence( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::DisputeWrite), + &auth::JWTAuth { + permission: Permission::DisputeWrite, + minimum_entity_level: EntityType::Profile, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -295,7 +311,10 @@ pub async fn attach_dispute_evidence( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::DisputeWrite), + &auth::JWTAuth { + permission: Permission::DisputeWrite, + minimum_entity_level: EntityType::Profile, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -338,7 +357,10 @@ pub async fn retrieve_dispute_evidence( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::DisputeRead), + &auth::JWTAuth { + permission: Permission::DisputeRead, + minimum_entity_level: EntityType::Profile, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -376,7 +398,10 @@ pub async fn delete_dispute_evidence( |state, auth, req, _| disputes::delete_evidence(state, auth.merchant_account, req), auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::DisputeWrite), + &auth::JWTAuth { + permission: Permission::DisputeWrite, + minimum_entity_level: EntityType::Profile, + }, req.headers(), ), api_locking::LockAction::NotApplicable, diff --git a/crates/router/src/routes/fraud_check.rs b/crates/router/src/routes/fraud_check.rs index 20609502c13c..f51becf251cf 100644 --- a/crates/router/src/routes/fraud_check.rs +++ b/crates/router/src/routes/fraud_check.rs @@ -7,6 +7,7 @@ use crate::{ AppState, }; +#[cfg(feature = "v1")] pub async fn frm_fulfillment( state: web::Data, req: HttpRequest, diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index 2b2a1be0878f..0cdfd90e9987 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -133,7 +133,8 @@ impl From for ApiIdentifier { | Flow::PaymentsAuthorize | Flow::GetExtendedCardInfo | Flow::PaymentsCompleteAuthorize - | Flow::PaymentsManualUpdate => Self::Payments, + | Flow::PaymentsManualUpdate + | Flow::SessionUpdateTaxCalculation => Self::Payments, Flow::PayoutsCreate | Flow::PayoutsRetrieve @@ -152,6 +153,7 @@ impl From for ApiIdentifier { | Flow::RefundsUpdate | Flow::RefundsList | Flow::RefundsFilters + | Flow::RefundsAggregate | Flow::RefundsManualUpdate => Self::Refunds, Flow::FrmFulfillment diff --git a/crates/router/src/routes/mandates.rs b/crates/router/src/routes/mandates.rs index d2c5d5e46440..4435c59b423f 100644 --- a/crates/router/src/routes/mandates.rs +++ b/crates/router/src/routes/mandates.rs @@ -1,4 +1,5 @@ use actix_web::{web, HttpRequest, HttpResponse}; +use common_enums::EntityType; use router_env::{instrument, tracing, Flow}; use super::app::AppState; @@ -49,25 +50,9 @@ pub async fn get_mandate( )) .await } -/// Mandates - Revoke Mandate -/// -/// Revokes a mandate created using the Payments/Create API -#[utoipa::path( - post, - path = "/mandates/revoke/{mandate_id}", - params( - ("mandate_id" = String, Path, description = "The identifier for a mandate") - ), - responses( - (status = 200, description = "The mandate was revoked successfully", body = MandateRevokedResponse), - (status = 400, description = "Mandate does not exist in our records") - ), - tag = "Mandates", - operation_id = "Revoke a Mandate", - security(("api_key" = [])) -)] + +#[cfg(feature = "v1")] #[instrument(skip_all, fields(flow = ?Flow::MandatesRevoke))] -// #[post("/revoke/{id}")] pub async fn revoke_mandate( state: web::Data, req: HttpRequest, @@ -131,7 +116,10 @@ pub async fn retrieve_mandates_list( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::MandateRead), + &auth::JWTAuth { + permission: Permission::MandateRead, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), api_locking::LockAction::NotApplicable, diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs index b3c7f5cc59b6..e2cb50b0aa71 100644 --- a/crates/router/src/routes/payment_methods.rs +++ b/crates/router/src/routes/payment_methods.rs @@ -4,6 +4,7 @@ ))] use actix_multipart::form::MultipartForm; use actix_web::{web, HttpRequest, HttpResponse}; +use common_enums::EntityType; use common_utils::{errors::CustomResult, id_type}; use diesel_models::enums::IntentStatus; use error_stack::ResultExt; @@ -663,11 +664,17 @@ pub async fn list_countries_currencies_for_connector_payment_method( #[cfg(not(feature = "release"))] auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::MerchantConnectorAccountWrite), + &auth::JWTAuth { + permission: Permission::MerchantConnectorAccountWrite, + minimum_entity_level: EntityType::Profile, + }, req.headers(), ), #[cfg(feature = "release")] - &auth::JWTAuth(Permission::MerchantConnectorAccountWrite), + &auth::JWTAuth { + permission: Permission::MerchantConnectorAccountWrite, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index 95b65e5be573..82407976c6e3 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -6,6 +6,7 @@ pub mod helpers; use actix_web::{web, Responder}; use api_models::payments::HeaderPayload; +use common_enums::EntityType; use error_stack::report; use masking::PeekInterface; use router_env::{env, instrument, logger, tracing, types, Flow}; @@ -29,65 +30,7 @@ use crate::{ }, }; -/// Payments - Create -/// -/// To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture -#[utoipa::path( - post, - path = "/payments", - request_body( - content = PaymentsCreateRequest, - // examples( - // ( - // "Create a payment with minimul fields" = ( - // value = json!(PAYMENTS_CREATE_MINIMUM_FIELDS) - // ) - // ), - // ( - // "Create a manual capture payment" = ( - // value = json!(PAYMENTS_CREATE_WITH_MANUAL_CAPTURE) - // ) - // ), - // ( - // "Create a payment with address" = ( - // value = json!(PAYMENTS_CREATE_WITH_ADDRESS) - // ) - // ), - // ( - // "Create a payment with customer details" = ( - // value = json!(PAYMENTS_CREATE_WITH_CUSTOMER_DATA) - // ) - // ), - // ( - // "Create a 3DS payment" = ( - // value = json!(PAYMENTS_CREATE_WITH_FORCED_3DS) - // ) - // ), - // ( - // "Create a payment" = ( - // value = json!(PAYMENTS_CREATE) - // ) - // ), - // ( - // "Create a payment with order details" = ( - // value = json!(PAYMENTS_CREATE_WITH_ORDER_DETAILS) - // ) - // ), - // ( - // "Create a payment with order category for noon" = ( - // value = json!(PAYMENTS_CREATE_WITH_NOON_ORDER_CATETORY) - // ) - // ), - // ) - ), - responses( - (status = 200, description = "Payment created", body = PaymentsResponse), - (status = 400, description = "Missing Mandatory fields") - ), - tag = "Payments", - operation_id = "Create a Payment", - security(("api_key" = [])), -)] +#[cfg(feature = "v1")] #[instrument(skip_all, fields(flow = ?Flow::PaymentsCreate, payment_id))] pub async fn payments_create( state: web::Data, @@ -149,7 +92,10 @@ pub async fn payments_create( env::Env::Production => &auth::HeaderAuth(auth::ApiKeyAuth), _ => auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::PaymentWrite), + &auth::JWTAuth { + permission: Permission::PaymentWrite, + minimum_entity_level: EntityType::Profile, + }, req.headers(), ), }, @@ -157,24 +103,8 @@ pub async fn payments_create( )) .await } -// /// Payments - Redirect -// /// -// /// For a payment which involves the redirection flow. This redirects the user to the authentication page -// #[utoipa::path( -// get, -// path = "/payments/redirect/{payment_id}/{merchant_id}/{attempt_id}", -// params( -// ("payment_id" = String, Path, description = "The identifier for payment"), -// ("merchant_id" = String, Path, description = "The identifier for merchant"), -// ("attempt_id" = String, Path, description = "The identifier for transaction") -// ), -// responses( -// (status = 200, description = "Redirects to the authentication page"), -// (status = 404, description = "No redirection found") -// ), -// tag = "Payments", -// operation_id = "Start a Redirection Payment" -// )] + +#[cfg(feature = "v1")] #[instrument(skip(state, req), fields(flow = ?Flow::PaymentsStart, payment_id))] pub async fn payments_start( state: web::Data, @@ -208,7 +138,7 @@ pub async fn payments_start( _, _, _, - + payments::PaymentData, >( state, req_state, @@ -228,26 +158,9 @@ pub async fn payments_start( )) .await } -/// Payments - Retrieve -/// -/// To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment -#[utoipa::path( - get, - path = "/payments/{payment_id}", - params( - ("payment_id" = String, Path, description = "The identifier for payment") - ), - request_body=PaymentRetrieveBody, - responses( - (status = 200, description = "Gets the payment with final status", body = PaymentsResponse), - (status = 404, description = "No payment found") - ), - tag = "Payments", - operation_id = "Retrieve a Payment", - security(("api_key" = []), ("publishable_key" = [])) -)] + +#[cfg(feature = "v1")] #[instrument(skip(state, req), fields(flow, payment_id))] -// #[get("/{payment_id}")] pub async fn payments_retrieve( state: web::Data, req: actix_web::HttpRequest, @@ -293,7 +206,14 @@ pub async fn payments_retrieve( &req, payload, |state, auth, req, req_state| { - payments::payments_core::( + payments::payments_core::< + api_types::PSync, + payment_types::PaymentsResponse, + _, + _, + _, + payments::PaymentData, + >( state, req_state, auth.merchant_account, @@ -309,30 +229,19 @@ pub async fn payments_retrieve( }, auth::auth_type( &*auth_type, - &auth::JWTAuth(Permission::PaymentRead), + &auth::JWTAuth { + permission: Permission::PaymentRead, + minimum_entity_level: EntityType::Profile, + }, req.headers(), ), locking_action, )) .await } -/// Payments - Retrieve with gateway credentials -/// -/// To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment -#[utoipa::path( - post, - path = "/sync", - request_body=PaymentRetrieveBodyWithCredentials, - responses( - (status = 200, description = "Gets the payment with final status", body = PaymentsResponse), - (status = 404, description = "No payment found") - ), - tag = "Payments", - operation_id = "Retrieve a Payment", - security(("api_key" = [])) -)] + +#[cfg(feature = "v1")] #[instrument(skip(state, req), fields(flow, payment_id))] -// #[post("/sync")] pub async fn payments_retrieve_with_gateway_creds( state: web::Data, req: actix_web::HttpRequest, @@ -368,7 +277,14 @@ pub async fn payments_retrieve_with_gateway_creds( &req, payload, |state, auth, req, req_state| { - payments::payments_core::( + payments::payments_core::< + api_types::PSync, + payment_types::PaymentsResponse, + _, + _, + _, + payments::PaymentData, + >( state, req_state, auth.merchant_account, @@ -387,26 +303,9 @@ pub async fn payments_retrieve_with_gateway_creds( )) .await } -/// Payments - Update -/// -/// To update the properties of a PaymentIntent object. This may include attaching a payment method, or attaching customer object or metadata fields after the Payment is created -#[utoipa::path( - post, - path = "/payments/{payment_id}", - params( - ("payment_id" = String, Path, description = "The identifier for payment") - ), - request_body=PaymentsRequest, - responses( - (status = 200, description = "Payment updated", body = PaymentsResponse), - (status = 400, description = "Missing mandatory fields") - ), - tag = "Payments", - operation_id = "Update a Payment", - security(("api_key" = []), ("publishable_key" = [])) -)] + +#[cfg(feature = "v1")] #[instrument(skip_all, fields(flow = ?Flow::PaymentsUpdate, payment_id))] -// #[post("/{payment_id}")] pub async fn payments_update( state: web::Data, req: actix_web::HttpRequest, @@ -456,26 +355,9 @@ pub async fn payments_update( )) .await } -/// Payments - Confirm -/// -/// This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments Create API -#[utoipa::path( - post, - path = "/payments/{payment_id}/confirm", - params( - ("payment_id" = String, Path, description = "The identifier for payment") - ), - request_body=PaymentsRequest, - responses( - (status = 200, description = "Payment confirmed", body = PaymentsResponse), - (status = 400, description = "Missing mandatory fields") - ), - tag = "Payments", - operation_id = "Confirm a Payment", - security(("api_key" = []), ("publishable_key" = [])) -)] + +#[cfg(feature = "v1")] #[instrument(skip_all, fields(flow = ?Flow::PaymentsConfirm, payment_id))] -// #[post("/{payment_id}/confirm")] pub async fn payments_confirm( state: web::Data, req: actix_web::HttpRequest, @@ -535,26 +417,9 @@ pub async fn payments_confirm( )) .await } -/// Payments - Capture -/// -/// To capture the funds for an uncaptured payment -#[utoipa::path( - post, - path = "/payments/{payment_id}/capture", - params( - ("payment_id" = String, Path, description = "The identifier for payment") - ), - request_body=PaymentsCaptureRequest, - responses( - (status = 200, description = "Payment captured", body = PaymentsResponse), - (status = 400, description = "Missing mandatory fields") - ), - tag = "Payments", - operation_id = "Capture a Payment", - security(("api_key" = [])) -)] + +#[cfg(feature = "v1")] #[instrument(skip_all, fields(flow = ?Flow::PaymentsCapture, payment_id))] -// #[post("/{payment_id}/capture")] pub async fn payments_capture( state: web::Data, req: actix_web::HttpRequest, @@ -578,7 +443,14 @@ pub async fn payments_capture( &req, payload, |state, auth, payload, req_state| { - payments::payments_core::( + payments::payments_core::< + api_types::Capture, + payment_types::PaymentsResponse, + _, + _, + _, + payments::PaymentData, + >( state, req_state, auth.merchant_account, @@ -597,21 +469,67 @@ pub async fn payments_capture( )) .await } -/// Payments - Session token -/// -/// To create the session object or to get session token for wallets -#[utoipa::path( - post, - path = "/payments/session_tokens", - request_body=PaymentsSessionRequest, - responses( - (status = 200, description = "Payment session object created or session token was retrieved from wallets", body = PaymentsSessionResponse), - (status = 400, description = "Missing mandatory fields") - ), - tag = "Payments", - operation_id = "Create Session tokens for a Payment", - security(("publishable_key" = [])) -)] + +#[cfg(feature = "v1")] +#[instrument(skip_all, fields(flow = ?Flow::SessionUpdateTaxCalculation, payment_id))] +pub async fn payments_dynamic_tax_calculation( + state: web::Data, + req: actix_web::HttpRequest, + json_payload: web::Json, + path: web::Path, +) -> impl Responder { + let flow = Flow::SessionUpdateTaxCalculation; + let payment_id = path.into_inner(); + let payload = payment_types::PaymentsDynamicTaxCalculationRequest { + payment_id, + ..json_payload.into_inner() + }; + let header_payload = match HeaderPayload::foreign_try_from(req.headers()) { + Ok(headers) => headers, + Err(error) => { + logger::error!( + ?error, + "Failed to get headers in payments_connector_session" + ); + HeaderPayload::default() + } + }; + tracing::Span::current().record("payment_id", payload.payment_id.get_string_repr()); + let locking_action = payload.get_locking_input(flow.clone()); + Box::pin(api::server_wrap( + flow, + state, + &req, + payload, + |state, auth, payload, req_state| { + payments::payments_core::< + api_types::SdkSessionUpdate, + payment_types::PaymentsDynamicTaxCalculationResponse, + _, + _, + _, + _, + >( + state, + req_state, + auth.merchant_account, + auth.profile_id, + auth.key_store, + payments::PaymentSessionUpdate, + payload, + api::AuthFlow::Client, + payments::CallConnectorAction::Trigger, + None, + header_payload.clone(), + ) + }, + &auth::PublishableKeyAuth, + locking_action, + )) + .await +} + +#[cfg(feature = "v1")] #[instrument(skip_all, fields(flow = ?Flow::PaymentsSessionToken, payment_id))] pub async fn payments_connector_session( state: web::Data, @@ -648,6 +566,7 @@ pub async fn payments_connector_session( _, _, _, + payments::PaymentData, >( state, req_state, @@ -667,24 +586,8 @@ pub async fn payments_connector_session( )) .await } -// /// Payments - Redirect response -// /// -// /// To get the payment response for redirect flows -// #[utoipa::path( -// post, -// path = "/payments/{payment_id}/{merchant_id}/response/{connector}", -// params( -// ("payment_id" = String, Path, description = "The identifier for payment"), -// ("merchant_id" = String, Path, description = "The identifier for merchant"), -// ("connector" = String, Path, description = "The name of the connector") -// ), -// responses( -// (status = 302, description = "Received payment redirect response"), -// (status = 400, description = "Missing mandatory fields") -// ), -// tag = "Payments", -// operation_id = "Get Redirect Response for a Payment" -// )] + +#[cfg(feature = "v1")] #[instrument(skip_all, fields(flow = ?Flow::PaymentsRedirect, payment_id))] pub async fn payments_redirect_response( state: web::Data, @@ -734,24 +637,7 @@ pub async fn payments_redirect_response( .await } -// /// Payments - Redirect response with creds_identifier -// /// -// /// To get the payment response for redirect flows -// #[utoipa::path( -// post, -// path = "/payments/{payment_id}/{merchant_id}/response/{connector}/{cred_identifier}", -// params( -// ("payment_id" = String, Path, description = "The identifier for payment"), -// ("merchant_id" = String, Path, description = "The identifier for merchant"), -// ("connector" = String, Path, description = "The name of the connector") -// ), -// responses( -// (status = 302, description = "Received payment redirect response"), -// (status = 400, description = "Missing mandatory fields") -// ), -// tag = "Payments", -// operation_id = "Get Redirect Response for a Payment" -// )] +#[cfg(feature = "v1")] #[instrument(skip_all, fields(flow = ?Flow::PaymentsRedirect, payment_id))] pub async fn payments_redirect_response_with_creds_identifier( state: web::Data, @@ -800,6 +686,8 @@ pub async fn payments_redirect_response_with_creds_identifier( ) .await } + +#[cfg(feature = "v1")] #[instrument(skip_all, fields(flow =? Flow::PaymentsRedirect, payment_id))] pub async fn payments_complete_authorize_redirect( state: web::Data, @@ -850,7 +738,7 @@ pub async fn payments_complete_authorize_redirect( .await } -/// Payments - Complete Authorize +#[cfg(feature = "v1")] #[instrument(skip_all, fields(flow =? Flow::PaymentsCompleteAuthorize, payment_id))] pub async fn payments_complete_authorize( state: web::Data, @@ -894,6 +782,7 @@ pub async fn payments_complete_authorize( _, _, _, + payments::PaymentData, >( state.clone(), req_state, @@ -914,24 +803,7 @@ pub async fn payments_complete_authorize( .await } -/// Payments - Cancel -/// -/// A Payment could can be cancelled when it is in one of these statuses: requires_payment_method, requires_capture, requires_confirmation, requires_customer_action -#[utoipa::path( - post, - path = "/payments/{payment_id}/cancel", - request_body=PaymentsCancelRequest, - params( - ("payment_id" = String, Path, description = "The identifier for payment") - ), - responses( - (status = 200, description = "Payment canceled"), - (status = 400, description = "Missing mandatory fields") - ), - tag = "Payments", - operation_id = "Cancel a Payment", - security(("api_key" = [])) -)] +#[cfg(feature = "v1")] #[instrument(skip_all, fields(flow = ?Flow::PaymentsCancel, payment_id))] pub async fn payments_cancel( state: web::Data, @@ -953,7 +825,14 @@ pub async fn payments_cancel( &req, payload, |state, auth, req, req_state| { - payments::payments_core::( + payments::payments_core::< + api_types::Void, + payment_types::PaymentsResponse, + _, + _, + _, + payments::PaymentData, + >( state, req_state, auth.merchant_account, @@ -972,33 +851,9 @@ pub async fn payments_cancel( )) .await } -/// Payments - List -/// -/// To list the payments -#[utoipa::path( - get, - path = "/payments/list", - params( - ("customer_id" = String, Query, description = "The identifier for the customer"), - ("starting_after" = String, Query, description = "A cursor for use in pagination, fetch the next list after some object"), - ("ending_before" = String, Query, description = "A cursor for use in pagination, fetch the previous list before some object"), - ("limit" = i64, Query, description = "Limit on the number of objects to return"), - ("created" = PrimitiveDateTime, Query, description = "The time at which payment is created"), - ("created_lt" = PrimitiveDateTime, Query, description = "Time less than the payment created time"), - ("created_gt" = PrimitiveDateTime, Query, description = "Time greater than the payment created time"), - ("created_lte" = PrimitiveDateTime, Query, description = "Time less than or equals to the payment created time"), - ("created_gte" = PrimitiveDateTime, Query, description = "Time greater than or equals to the payment created time") - ), - responses( - (status = 200, description = "Received payment list"), - (status = 404, description = "No payments found") - ), - tag = "Payments", - operation_id = "List all Payments", - security(("api_key" = [])) -)] + #[instrument(skip_all, fields(flow = ?Flow::PaymentsList))] -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v1"))] pub async fn payments_list( state: web::Data, req: actix_web::HttpRequest, @@ -1016,40 +871,19 @@ pub async fn payments_list( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::PaymentRead), + &auth::JWTAuth { + permission: Permission::PaymentRead, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), api_locking::LockAction::NotApplicable, )) .await } -/// Business Profile level Payments - List -/// -/// To list the payments -#[utoipa::path( - get, - path = "/payments/list", - params( - ("customer_id" = String, Query, description = "The identifier for the customer"), - ("starting_after" = String, Query, description = "A cursor for use in pagination, fetch the next list after some object"), - ("ending_before" = String, Query, description = "A cursor for use in pagination, fetch the previous list before some object"), - ("limit" = i64, Query, description = "Limit on the number of objects to return"), - ("created" = PrimitiveDateTime, Query, description = "The time at which payment is created"), - ("created_lt" = PrimitiveDateTime, Query, description = "Time less than the payment created time"), - ("created_gt" = PrimitiveDateTime, Query, description = "Time greater than the payment created time"), - ("created_lte" = PrimitiveDateTime, Query, description = "Time less than or equals to the payment created time"), - ("created_gte" = PrimitiveDateTime, Query, description = "Time greater than or equals to the payment created time") - ), - responses( - (status = 200, description = "Received payment list"), - (status = 404, description = "No payments found") - ), - tag = "Payments", - operation_id = "List all Payments for the Profile", - security(("api_key" = [])) -)] + #[instrument(skip_all, fields(flow = ?Flow::PaymentsList))] -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v1"))] pub async fn profile_payments_list( state: web::Data, req: actix_web::HttpRequest, @@ -1073,15 +907,19 @@ pub async fn profile_payments_list( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::PaymentRead), + &auth::JWTAuth { + permission: Permission::PaymentRead, + minimum_entity_level: EntityType::Profile, + }, req.headers(), ), api_locking::LockAction::NotApplicable, )) .await } + #[instrument(skip_all, fields(flow = ?Flow::PaymentsList))] -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v1"))] pub async fn payments_list_by_filter( state: web::Data, req: actix_web::HttpRequest, @@ -1103,14 +941,17 @@ pub async fn payments_list_by_filter( req, ) }, - &auth::JWTAuth(Permission::PaymentRead), + &auth::JWTAuth { + permission: Permission::PaymentRead, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await } #[instrument(skip_all, fields(flow = ?Flow::PaymentsList))] -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v1"))] pub async fn profile_payments_list_by_filter( state: web::Data, req: actix_web::HttpRequest, @@ -1132,14 +973,17 @@ pub async fn profile_payments_list_by_filter( req, ) }, - &auth::JWTAuth(Permission::PaymentRead), + &auth::JWTAuth { + permission: Permission::PaymentRead, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await } #[instrument(skip_all, fields(flow = ?Flow::PaymentsList))] -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v1"))] pub async fn get_filters_for_payments( state: web::Data, req: actix_web::HttpRequest, @@ -1155,14 +999,17 @@ pub async fn get_filters_for_payments( |state, auth: auth::AuthenticationData, req, _| { payments::get_filters_for_payments(state, auth.merchant_account, auth.key_store, req) }, - &auth::JWTAuth(Permission::PaymentRead), + &auth::JWTAuth { + permission: Permission::PaymentRead, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await } #[instrument(skip_all, fields(flow = ?Flow::PaymentsFilters))] -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v1"))] pub async fn get_payment_filters( state: web::Data, req: actix_web::HttpRequest, @@ -1176,14 +1023,17 @@ pub async fn get_payment_filters( |state, auth: auth::AuthenticationData, _, _| { payments::get_payment_filters(state, auth.merchant_account, None) }, - &auth::JWTAuth(Permission::PaymentRead), + &auth::JWTAuth { + permission: Permission::PaymentRead, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await } #[instrument(skip_all, fields(flow = ?Flow::PaymentsFilters))] -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v1"))] pub async fn get_payment_filters_profile( state: web::Data, req: actix_web::HttpRequest, @@ -1201,14 +1051,17 @@ pub async fn get_payment_filters_profile( auth.profile_id.map(|profile_id| vec![profile_id]), ) }, - &auth::JWTAuth(Permission::PaymentRead), + &auth::JWTAuth { + permission: Permission::PaymentRead, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await } #[instrument(skip_all, fields(flow = ?Flow::PaymentsAggregate))] -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v1"))] pub async fn get_payments_aggregates( state: web::Data, req: actix_web::HttpRequest, @@ -1222,15 +1075,18 @@ pub async fn get_payments_aggregates( &req, payload, |state, auth: auth::AuthenticationData, req, _| { - payments::get_aggregates_for_payments(state, auth.merchant_account, req) + payments::get_aggregates_for_payments(state, auth.merchant_account, None, req) + }, + &auth::JWTAuth { + permission: Permission::PaymentRead, + minimum_entity_level: EntityType::Merchant, }, - &auth::JWTAuth(Permission::PaymentRead), api_locking::LockAction::NotApplicable, )) .await } -#[cfg(feature = "oltp")] +#[cfg(all(feature = "oltp", feature = "v1"))] #[instrument(skip_all, fields(flow = ?Flow::PaymentsApprove, payment_id))] pub async fn payments_approve( state: web::Data, @@ -1254,7 +1110,14 @@ pub async fn payments_approve( &http_req, payload.clone(), |state, auth, req, req_state| { - payments::payments_core::( + payments::payments_core::< + api_types::Capture, + payment_types::PaymentsResponse, + _, + _, + _, + payments::PaymentData, + >( state, req_state, auth.merchant_account, @@ -1275,7 +1138,10 @@ pub async fn payments_approve( env::Env::Production => &auth::HeaderAuth(auth::ApiKeyAuth), _ => auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::PaymentWrite), + &auth::JWTAuth { + permission: Permission::PaymentWrite, + minimum_entity_level: EntityType::Profile, + }, http_req.headers(), ), }, @@ -1284,9 +1150,8 @@ pub async fn payments_approve( .await } -#[cfg(feature = "oltp")] +#[cfg(all(feature = "oltp", feature = "v1"))] #[instrument(skip_all, fields(flow = ?Flow::PaymentsReject, payment_id))] -// #[post("/{payment_id}/reject")] pub async fn payments_reject( state: web::Data, http_req: actix_web::HttpRequest, @@ -1309,7 +1174,14 @@ pub async fn payments_reject( &http_req, payload.clone(), |state, auth, req, req_state| { - payments::payments_core::( + payments::payments_core::< + api_types::Void, + payment_types::PaymentsResponse, + _, + _, + _, + payments::PaymentData, + >( state, req_state, auth.merchant_account, @@ -1331,7 +1203,10 @@ pub async fn payments_reject( env::Env::Production => &auth::HeaderAuth(auth::ApiKeyAuth), _ => auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::PaymentWrite), + &auth::JWTAuth { + permission: Permission::PaymentWrite, + minimum_entity_level: EntityType::Profile, + }, http_req.headers(), ), }, @@ -1340,6 +1215,7 @@ pub async fn payments_reject( .await } +#[cfg(feature = "v1")] #[allow(clippy::too_many_arguments)] async fn authorize_verify_select( operation: Op, @@ -1356,10 +1232,14 @@ where Op: Sync + Clone + std::fmt::Debug - + payments::operations::Operation + payments::operations::Operation< + api_types::Authorize, + api_models::payments::PaymentsRequest, + Data = payments::PaymentData, + > + payments::operations::Operation< api_types::SetupMandate, api_models::payments::PaymentsRequest, + Data = payments::PaymentData, >, { // TODO: Change for making it possible for the flow to be inferred internally or through validation layer @@ -1372,26 +1252,29 @@ where match req.payment_type.unwrap_or_default() { api_models::enums::PaymentType::Normal | api_models::enums::PaymentType::RecurringMandate - | api_models::enums::PaymentType::NewMandate => payments::payments_core::< - api_types::Authorize, - payment_types::PaymentsResponse, - _, - _, - _, - >( - state, - req_state, - merchant_account, - profile_id, - key_store, - operation, - req, - auth_flow, - payments::CallConnectorAction::Trigger, - eligible_connectors, - header_payload, - ) - .await, + | api_models::enums::PaymentType::NewMandate => { + payments::payments_core::< + api_types::Authorize, + payment_types::PaymentsResponse, + _, + _, + _, + payments::PaymentData, + >( + state, + req_state, + merchant_account, + profile_id, + key_store, + operation, + req, + auth_flow, + payments::CallConnectorAction::Trigger, + eligible_connectors, + header_payload, + ) + .await + } api_models::enums::PaymentType::SetupMandate => { payments::payments_core::< api_types::SetupMandate, @@ -1399,6 +1282,7 @@ where _, _, _, + payments::PaymentData, >( state, req_state, @@ -1417,24 +1301,7 @@ where } } -/// Payments - Incremental Authorization -/// -/// Authorized amount for a payment can be incremented if it is in status: requires_capture -#[utoipa::path( - post, - path = "/payments/{payment_id}/incremental_authorization", - request_body=PaymentsIncrementalAuthorizationRequest, - params( - ("payment_id" = String, Path, description = "The identifier for payment") - ), - responses( - (status = 200, description = "Payment authorized amount incremented", body = PaymentsResponse), - (status = 400, description = "Missing mandatory fields") - ), - tag = "Payments", - operation_id = "Increment authorized amount for a Payment", - security(("api_key" = [])) -)] +#[cfg(feature = "v1")] #[instrument(skip_all, fields(flow = ?Flow::PaymentsIncrementalAuthorization, payment_id))] pub async fn payments_incremental_authorization( state: web::Data, @@ -1462,6 +1329,7 @@ pub async fn payments_incremental_authorization( _, _, _, + payments::PaymentData, >( state, req_state, @@ -1482,24 +1350,7 @@ pub async fn payments_incremental_authorization( .await } -/// Payments - External 3DS Authentication -/// -/// External 3DS Authentication is performed and returns the AuthenticationResponse -#[utoipa::path( - post, - path = "/payments/{payment_id}/3ds/authentication", - request_body=PaymentsExternalAuthenticationRequest, - params( - ("payment_id" = String, Path, description = "The identifier for payment") - ), - responses( - (status = 200, description = "Authentication created"), - (status = 400, description = "Missing mandatory fields") - ), - tag = "Payments", - operation_id = "Initiate external authentication for a Payment", - security(("api_key" = [])) -)] +#[cfg(feature = "v1")] #[instrument(skip_all, fields(flow = ?Flow::PaymentsExternalAuthentication, payment_id))] pub async fn payments_external_authentication( state: web::Data, @@ -1534,21 +1385,7 @@ pub async fn payments_external_authentication( .await } -#[utoipa::path( - post, - path = "/payments/{payment_id}/{merchant_id}/authorize/{connector}", - params( - ("payment_id" = String, Path, description = "The identifier for payment") - ), - request_body=PaymentsRequest, - responses( - (status = 200, description = "Payment Authorized", body = PaymentsResponse), - (status = 400, description = "Missing mandatory fields") - ), - tag = "Payments", - operation_id = "Authorize a Payment", - security(("api_key" = []), ("publishable_key" = [])) -)] +#[cfg(feature = "v1")] #[instrument(skip_all, fields(flow = ?Flow::PaymentsAuthorize, payment_id))] pub async fn post_3ds_payments_authorize( state: web::Data, @@ -1598,7 +1435,7 @@ pub async fn post_3ds_payments_authorize( .await } -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v1"))] pub async fn payments_manual_update( state: web::Data, req: actix_web::HttpRequest, @@ -1627,6 +1464,7 @@ pub async fn payments_manual_update( .await } +#[cfg(feature = "v1")] /// Retrieve endpoint for merchant to fetch the encrypted customer payment method data #[instrument(skip_all, fields(flow = ?Flow::GetExtendedCardInfo, payment_id))] pub async fn retrieve_extended_card_info( @@ -1655,6 +1493,7 @@ pub async fn retrieve_extended_card_info( .await } +#[cfg(feature = "v1")] pub fn get_or_generate_payment_id( payload: &mut payment_types::PaymentsRequest, ) -> errors::RouterResult<()> { @@ -1677,6 +1516,7 @@ pub fn get_or_generate_payment_id( Ok(()) } +#[cfg(feature = "v1")] impl GetLockingInput for payment_types::PaymentsRequest { fn get_locking_input(&self, flow: F) -> api_locking::LockAction where @@ -1698,6 +1538,7 @@ impl GetLockingInput for payment_types::PaymentsRequest { } } +#[cfg(feature = "v1")] impl GetLockingInput for payment_types::PaymentsStartRequest { fn get_locking_input(&self, flow: F) -> api_locking::LockAction where @@ -1714,6 +1555,7 @@ impl GetLockingInput for payment_types::PaymentsStartRequest { } } +#[cfg(feature = "v1")] impl GetLockingInput for payment_types::PaymentsRetrieveRequest { fn get_locking_input(&self, flow: F) -> api_locking::LockAction where @@ -1735,6 +1577,7 @@ impl GetLockingInput for payment_types::PaymentsRetrieveRequest { } } +#[cfg(feature = "v1")] impl GetLockingInput for payment_types::PaymentsSessionRequest { fn get_locking_input(&self, flow: F) -> api_locking::LockAction where @@ -1751,6 +1594,24 @@ impl GetLockingInput for payment_types::PaymentsSessionRequest { } } +#[cfg(feature = "v1")] +impl GetLockingInput for payment_types::PaymentsDynamicTaxCalculationRequest { + fn get_locking_input(&self, flow: F) -> api_locking::LockAction + where + F: types::FlowMetric, + lock_utils::ApiIdentifier: From, + { + api_locking::LockAction::Hold { + input: api_locking::LockingInput { + unique_locking_key: self.payment_id.get_string_repr().to_owned(), + api_identifier: lock_utils::ApiIdentifier::from(flow), + override_lock_retries: None, + }, + } + } +} + +#[cfg(feature = "v1")] impl GetLockingInput for payments::PaymentsRedirectResponseData { fn get_locking_input(&self, flow: F) -> api_locking::LockAction where @@ -1772,6 +1633,7 @@ impl GetLockingInput for payments::PaymentsRedirectResponseData { } } +#[cfg(feature = "v1")] impl GetLockingInput for payment_types::PaymentsCompleteAuthorizeRequest { fn get_locking_input(&self, flow: F) -> api_locking::LockAction where @@ -1788,6 +1650,7 @@ impl GetLockingInput for payment_types::PaymentsCompleteAuthorizeRequest { } } +#[cfg(feature = "v1")] impl GetLockingInput for payment_types::PaymentsCancelRequest { fn get_locking_input(&self, flow: F) -> api_locking::LockAction where @@ -1804,6 +1667,7 @@ impl GetLockingInput for payment_types::PaymentsCancelRequest { } } +#[cfg(feature = "v1")] impl GetLockingInput for payment_types::PaymentsCaptureRequest { fn get_locking_input(&self, flow: F) -> api_locking::LockAction where @@ -1860,6 +1724,7 @@ impl<'a> GetLockingInput for FPaymentsRejectRequest<'a> { } } +#[cfg(feature = "v1")] impl GetLockingInput for payment_types::PaymentsIncrementalAuthorizationRequest { fn get_locking_input(&self, flow: F) -> api_locking::LockAction where @@ -1876,6 +1741,7 @@ impl GetLockingInput for payment_types::PaymentsIncrementalAuthorizationRequest } } +#[cfg(feature = "v1")] impl GetLockingInput for payment_types::PaymentsExternalAuthenticationRequest { fn get_locking_input(&self, flow: F) -> api_locking::LockAction where @@ -1892,6 +1758,7 @@ impl GetLockingInput for payment_types::PaymentsExternalAuthenticationRequest { } } +#[cfg(feature = "v1")] impl GetLockingInput for payment_types::PaymentsManualUpdateRequest { fn get_locking_input(&self, flow: F) -> api_locking::LockAction where @@ -1907,3 +1774,34 @@ impl GetLockingInput for payment_types::PaymentsManualUpdateRequest { } } } + +#[instrument(skip_all, fields(flow = ?Flow::PaymentsAggregate))] +#[cfg(all(feature = "olap", feature = "v1"))] +pub async fn get_payments_aggregates_profile( + state: web::Data, + req: actix_web::HttpRequest, + payload: web::Query, +) -> impl Responder { + let flow = Flow::PaymentsAggregate; + let payload = payload.into_inner(); + Box::pin(api::server_wrap( + flow, + state, + &req, + payload, + |state, auth: auth::AuthenticationData, req, _| { + payments::get_aggregates_for_payments( + state, + auth.merchant_account, + auth.profile_id.map(|profile_id| vec![profile_id]), + req, + ) + }, + &auth::JWTAuth { + permission: Permission::PaymentRead, + minimum_entity_level: EntityType::Profile, + }, + api_locking::LockAction::NotApplicable, + )) + .await +} diff --git a/crates/router/src/routes/payouts.rs b/crates/router/src/routes/payouts.rs index bc14a6197454..50c18d71f2a4 100644 --- a/crates/router/src/routes/payouts.rs +++ b/crates/router/src/routes/payouts.rs @@ -2,6 +2,7 @@ use actix_web::{ body::{BoxBody, MessageBody}, web, HttpRequest, HttpResponse, Responder, }; +use common_enums::EntityType; use common_utils::consts; use router_env::{instrument, tracing, Flow}; @@ -75,7 +76,10 @@ pub async fn payouts_retrieve( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::PayoutRead), + &auth::JWTAuth { + permission: Permission::PayoutRead, + minimum_entity_level: EntityType::Profile, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -213,7 +217,10 @@ pub async fn payouts_list( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::PayoutRead), + &auth::JWTAuth { + permission: Permission::PayoutRead, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -248,7 +255,10 @@ pub async fn payouts_list_profile( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::PayoutRead), + &auth::JWTAuth { + permission: Permission::PayoutRead, + minimum_entity_level: EntityType::Profile, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -277,7 +287,10 @@ pub async fn payouts_list_by_filter( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::PayoutRead), + &auth::JWTAuth { + permission: Permission::PayoutRead, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -312,7 +325,10 @@ pub async fn payouts_list_by_filter_profile( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::PayoutRead), + &auth::JWTAuth { + permission: Permission::PayoutRead, + minimum_entity_level: EntityType::Profile, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -320,10 +336,10 @@ pub async fn payouts_list_by_filter_profile( .await } -/// Payouts - Available filters +/// Payouts - Available filters for Merchant #[cfg(feature = "olap")] #[instrument(skip_all, fields(flow = ?Flow::PayoutsFilter))] -pub async fn payouts_list_available_filters( +pub async fn payouts_list_available_filters_for_merchant( state: web::Data, req: HttpRequest, json_payload: web::Json, @@ -337,11 +353,51 @@ pub async fn payouts_list_available_filters( &req, payload, |state, auth, req, _| { - payouts_list_available_filters_core(state, auth.merchant_account, req) + payouts_list_available_filters_core(state, auth.merchant_account, None, req) }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::PayoutRead), + &auth::JWTAuth { + permission: Permission::PayoutRead, + minimum_entity_level: EntityType::Merchant, + }, + req.headers(), + ), + api_locking::LockAction::NotApplicable, + )) + .await +} + +/// Payouts - Available filters for Profile +#[cfg(feature = "olap")] +#[instrument(skip_all, fields(flow = ?Flow::PayoutsFilter))] +pub async fn payouts_list_available_filters_for_profile( + state: web::Data, + req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::PayoutsFilter; + let payload = json_payload.into_inner(); + + Box::pin(api::server_wrap( + flow, + state, + &req, + payload, + |state, auth, req, _| { + payouts_list_available_filters_core( + state, + auth.merchant_account, + auth.profile_id.map(|profile_id| vec![profile_id]), + req, + ) + }, + auth::auth_type( + &auth::HeaderAuth(auth::ApiKeyAuth), + &auth::JWTAuth { + permission: Permission::PayoutRead, + minimum_entity_level: EntityType::Profile, + }, req.headers(), ), api_locking::LockAction::NotApplicable, diff --git a/crates/router/src/routes/recon.rs b/crates/router/src/routes/recon.rs index 96a935aef549..1ec571ff7c90 100644 --- a/crates/router/src/routes/recon.rs +++ b/crates/router/src/routes/recon.rs @@ -1,43 +1,29 @@ use actix_web::{web, HttpRequest, HttpResponse}; -use api_models::recon as recon_api; -use diesel_models::enums::UserRoleVersion; -use error_stack::ResultExt; -use masking::{ExposeInterface, PeekInterface, Secret}; +use api_models::{enums::EntityType, recon as recon_api}; use router_env::Flow; -use super::{AppState, SessionState}; +use super::AppState; use crate::{ - core::{ - api_locking, - errors::{self, RouterResponse, RouterResult, StorageErrorExt, UserErrors}, - }, - services::{ - api as service_api, api, - authentication::{self as auth, ReconUser, UserFromToken}, - email::types as email_types, - recon::ReconToken, - }, - types::{ - api::{self as api_types, enums}, - domain::{UserEmail, UserFromStorage, UserName}, - storage, - transformers::ForeignTryFrom, - }, + core::{api_locking, recon}, + services::{api, authentication, authorization::permissions::Permission}, }; pub async fn update_merchant( state: web::Data, req: HttpRequest, + path: web::Path, json_payload: web::Json, ) -> HttpResponse { let flow = Flow::ReconMerchantUpdate; + let merchant_id = path.into_inner(); + Box::pin(api::server_wrap( flow, state, &req, json_payload.into_inner(), - |state, _user, req, _| recon_merchant_account_update(state, req), - &auth::ReconAdmin, + |state, auth, req, _| recon::recon_merchant_account_update(state, auth, req), + &authentication::AdminApiAuthWithMerchantIdFromRoute(merchant_id), api_locking::LockAction::NotApplicable, )) .await @@ -50,8 +36,11 @@ pub async fn request_for_recon(state: web::Data, http_req: HttpRequest state, &http_req, (), - |state, user: UserFromToken, _req, _| send_recon_request(state, user), - &auth::DashboardNoPermissionAuth, + |state, user, _, _| recon::send_recon_request(state, user), + &authentication::JWTAuth { + permission: Permission::ReconAdmin, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -64,203 +53,12 @@ pub async fn get_recon_token(state: web::Data, req: HttpRequest) -> Ht state, &req, (), - |state, user: ReconUser, _, _| generate_recon_token(state, user), - &auth::ReconJWT, + |state, user, _, _| recon::generate_recon_token(state, user), + &authentication::JWTAuth { + permission: Permission::ReconAdmin, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await } - -pub async fn send_recon_request( - state: SessionState, - user: UserFromToken, -) -> RouterResponse { - let global_db = &*state.global_store; - let db = &*state.store; - let user_from_db = global_db - .find_user_by_id(&user.user_id) - .await - .change_context(errors::ApiErrorResponse::InternalServerError)?; - let merchant_id = db - .find_user_role_by_user_id(&user.user_id, UserRoleVersion::V1) - .await - .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)? - .merchant_id - .ok_or(errors::ApiErrorResponse::InternalServerError)?; - let key_manager_state = &(&state).into(); - let key_store = db - .get_merchant_key_store_by_merchant_id( - key_manager_state, - &merchant_id, - &db.get_master_key().to_vec().into(), - ) - .await - .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; - let merchant_account = db - .find_merchant_account_by_merchant_id(key_manager_state, &merchant_id, &key_store) - .await - .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; - - let email_contents = email_types::ProFeatureRequest { - feature_name: "RECONCILIATION & SETTLEMENT".to_string(), - merchant_id: merchant_id.clone(), - user_name: UserName::new(user_from_db.name) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to form username")?, - recipient_email: UserEmail::new(Secret::new("biz@hyperswitch.io".to_string())) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to convert recipient's email to UserEmail")?, - settings: state.conf.clone(), - subject: format!( - "Dashboard Pro Feature Request by {}", - user_from_db.email.expose().peek() - ), - }; - - let is_email_sent = state - .email_client - .compose_and_send_email( - Box::new(email_contents), - state.conf.proxy.https_url.as_ref(), - ) - .await - .change_context(UserErrors::InternalServerError) - .attach_printable("Failed to compose and send email for ProFeatureRequest") - .is_ok(); - - if is_email_sent { - let updated_merchant_account = storage::MerchantAccountUpdate::ReconUpdate { - recon_status: enums::ReconStatus::Requested, - }; - - let response = db - .update_merchant( - key_manager_state, - merchant_account, - updated_merchant_account, - &key_store, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable_lazy(|| { - format!("Failed while updating merchant's recon status: {merchant_id:?}") - })?; - - Ok(service_api::ApplicationResponse::Json( - recon_api::ReconStatusResponse { - recon_status: response.recon_status, - }, - )) - } else { - Ok(service_api::ApplicationResponse::Json( - recon_api::ReconStatusResponse { - recon_status: enums::ReconStatus::NotRequested, - }, - )) - } -} - -pub async fn recon_merchant_account_update( - state: SessionState, - req: recon_api::ReconUpdateMerchantRequest, -) -> RouterResponse { - let merchant_id = &req.merchant_id.clone(); - let user_email = &req.user_email.clone(); - - let db = &*state.store; - - let key_store = db - .get_merchant_key_store_by_merchant_id( - &(&state).into(), - &req.merchant_id, - &db.get_master_key().to_vec().into(), - ) - .await - .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; - - let merchant_account = db - .find_merchant_account_by_merchant_id(&(&state).into(), merchant_id, &key_store) - .await - .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; - - let updated_merchant_account = storage::MerchantAccountUpdate::ReconUpdate { - recon_status: req.recon_status, - }; - - let response = db - .update_merchant( - &(&state).into(), - merchant_account, - updated_merchant_account, - &key_store, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable_lazy(|| { - format!("Failed while updating merchant's recon status: {merchant_id:?}") - })?; - - let email_contents = email_types::ReconActivation { - recipient_email: UserEmail::from_pii_email(user_email.clone()) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to convert recipient's email to UserEmail from pii::Email")?, - user_name: UserName::new(Secret::new("HyperSwitch User".to_string())) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to form username")?, - settings: state.conf.clone(), - subject: "Approval of Recon Request - Access Granted to Recon Dashboard", - }; - - if req.recon_status == enums::ReconStatus::Active { - let _is_email_sent = state - .email_client - .compose_and_send_email( - Box::new(email_contents), - state.conf.proxy.https_url.as_ref(), - ) - .await - .change_context(UserErrors::InternalServerError) - .attach_printable("Failed to compose and send email for ReconActivation") - .is_ok(); - } - - Ok(service_api::ApplicationResponse::Json( - api_types::MerchantAccountResponse::foreign_try_from(response).change_context( - errors::ApiErrorResponse::InvalidDataValue { - field_name: "merchant_account", - }, - )?, - )) -} - -pub async fn generate_recon_token( - state: SessionState, - req: ReconUser, -) -> RouterResponse { - let db = &*state.global_store; - let user = db - .find_user_by_id(&req.user_id) - .await - .map_err(|e| { - if e.current_context().is_db_not_found() { - e.change_context(errors::ApiErrorResponse::InvalidJwtToken) - } else { - e.change_context(errors::ApiErrorResponse::InternalServerError) - } - })? - .into(); - - let token = Box::pin(get_recon_auth_token(user, state)) - .await - .change_context(errors::ApiErrorResponse::InternalServerError)?; - Ok(service_api::ApplicationResponse::Json( - recon_api::ReconTokenResponse { token }, - )) -} - -pub async fn get_recon_auth_token( - user: UserFromStorage, - state: SessionState, -) -> RouterResult> { - ReconToken::new_token(user.0.user_id.clone(), &state.conf).await -} diff --git a/crates/router/src/routes/refunds.rs b/crates/router/src/routes/refunds.rs index bdef96794f48..2f5cb3328376 100644 --- a/crates/router/src/routes/refunds.rs +++ b/crates/router/src/routes/refunds.rs @@ -1,4 +1,5 @@ use actix_web::{web, HttpRequest, HttpResponse}; +use common_enums::EntityType; use router_env::{instrument, tracing, Flow}; use super::app::AppState; @@ -47,7 +48,10 @@ pub async fn refunds_create( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::RefundWrite), + &auth::JWTAuth { + permission: Permission::RefundWrite, + minimum_entity_level: EntityType::Profile, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -108,7 +112,10 @@ pub async fn refunds_retrieve( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::RefundRead), + &auth::JWTAuth { + permission: Permission::RefundRead, + minimum_entity_level: EntityType::Profile, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -233,7 +240,10 @@ pub async fn refunds_list( |state, auth, req, _| refund_list(state, auth.merchant_account, None, req), auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::RefundRead), + &auth::JWTAuth { + permission: Permission::RefundRead, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -278,7 +288,10 @@ pub async fn refunds_list_profile( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::RefundRead), + &auth::JWTAuth { + permission: Permission::RefundRead, + minimum_entity_level: EntityType::Profile, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -316,7 +329,10 @@ pub async fn refunds_filter_list( |state, auth, req, _| refund_filter_list(state, auth.merchant_account, req), auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::RefundRead), + &auth::JWTAuth { + permission: Permission::RefundRead, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -349,7 +365,10 @@ pub async fn get_refunds_filters(state: web::Data, req: HttpRequest) - |state, auth, _, _| get_filters_for_refunds(state, auth.merchant_account, None), auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::RefundRead), + &auth::JWTAuth { + permission: Permission::RefundRead, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -391,7 +410,40 @@ pub async fn get_refunds_filters_profile( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::RefundRead), + &auth::JWTAuth { + permission: Permission::RefundRead, + minimum_entity_level: EntityType::Profile, + }, + req.headers(), + ), + api_locking::LockAction::NotApplicable, + )) + .await +} + +#[instrument(skip_all, fields(flow = ?Flow::RefundsAggregate))] +#[cfg(feature = "olap")] +pub async fn get_refunds_aggregates( + state: web::Data, + req: HttpRequest, + query_params: web::Query, +) -> HttpResponse { + let flow = Flow::RefundsAggregate; + let query_params = query_params.into_inner(); + Box::pin(api::server_wrap( + flow, + state, + &req, + query_params, + |state, auth: auth::AuthenticationData, req, _| { + get_aggregates_for_refunds(state, auth.merchant_account, req) + }, + auth::auth_type( + &auth::HeaderAuth(auth::ApiKeyAuth), + &auth::JWTAuth { + permission: Permission::RefundRead, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), api_locking::LockAction::NotApplicable, diff --git a/crates/router/src/routes/routing.rs b/crates/router/src/routes/routing.rs index 09bf77c62ef1..ee0a10a8228d 100644 --- a/crates/router/src/routes/routing.rs +++ b/crates/router/src/routes/routing.rs @@ -5,6 +5,7 @@ use actix_web::{web, HttpRequest, Responder}; use api_models::{enums, routing as routing_types, routing::RoutingRetrieveQuery}; +use common_enums::EntityType; use router_env::{ tracing::{self, instrument}, Flow, @@ -42,11 +43,17 @@ pub async fn routing_create_config( #[cfg(not(feature = "release"))] auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::RoutingWrite), + &auth::JWTAuth { + permission: Permission::RoutingWrite, + minimum_entity_level: EntityType::Profile, + }, req.headers(), ), #[cfg(feature = "release")] - &auth::JWTAuth(Permission::RoutingWrite), + &auth::JWTAuth { + permission: Permission::RoutingWrite, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -79,11 +86,17 @@ pub async fn routing_link_config( #[cfg(not(feature = "release"))] auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::RoutingWrite), + &auth::JWTAuth { + permission: Permission::RoutingWrite, + minimum_entity_level: EntityType::Profile, + }, req.headers(), ), #[cfg(feature = "release")] - &auth::JWTAuth(Permission::RoutingWrite), + &auth::JWTAuth { + permission: Permission::RoutingWrite, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -123,8 +136,9 @@ pub async fn routing_link_config( auth::auth_type( &auth::ApiKeyAuth, &auth::JWTAuthProfileFromRoute { - profile_id: routing_payload_wrapper.profile_id, + profile_id: wrapper.profile_id, required_permission: Permission::RoutingWrite, + minimum_entity_level: EntityType::Merchant, }, req.headers(), ), @@ -132,6 +146,7 @@ pub async fn routing_link_config( &auth::JWTAuthProfileFromRoute { profile_id: wrapper.profile_id, required_permission: Permission::RoutingWrite, + minimum_entity_level: EntityType::Merchant, }, api_locking::LockAction::NotApplicable, )) @@ -164,11 +179,17 @@ pub async fn routing_retrieve_config( #[cfg(not(feature = "release"))] auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::RoutingRead), + &auth::JWTAuth { + permission: Permission::RoutingRead, + minimum_entity_level: EntityType::Profile, + }, req.headers(), ), #[cfg(feature = "release")] - &auth::JWTAuth(Permission::RoutingRead), + &auth::JWTAuth { + permission: Permission::RoutingRead, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -200,11 +221,17 @@ pub async fn list_routing_configs( #[cfg(not(feature = "release"))] auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::RoutingRead), + &auth::JWTAuth { + permission: Permission::RoutingRead, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), #[cfg(feature = "release")] - &auth::JWTAuth(Permission::RoutingRead), + &auth::JWTAuth { + permission: Permission::RoutingRead, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -236,11 +263,17 @@ pub async fn list_routing_configs_for_profile( #[cfg(not(feature = "release"))] auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::RoutingRead), + &auth::JWTAuth { + permission: Permission::RoutingRead, + minimum_entity_level: EntityType::Profile, + }, req.headers(), ), #[cfg(feature = "release")] - &auth::JWTAuth(Permission::RoutingRead), + &auth::JWTAuth { + permission: Permission::RoutingRead, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -276,6 +309,7 @@ pub async fn routing_unlink_config( &auth::JWTAuthProfileFromRoute { profile_id: path, required_permission: Permission::RoutingWrite, + minimum_entity_level: EntityType::Merchant, }, req.headers(), ), @@ -283,6 +317,7 @@ pub async fn routing_unlink_config( &auth::JWTAuthProfileFromRoute { profile_id: path, required_permission: Permission::RoutingWrite, + minimum_entity_level: EntityType::Merchant, }, api_locking::LockAction::NotApplicable, )) @@ -316,11 +351,17 @@ pub async fn routing_unlink_config( #[cfg(not(feature = "release"))] auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::RoutingWrite), + &auth::JWTAuth { + permission: Permission::RoutingWrite, + minimum_entity_level: EntityType::Profile, + }, req.headers(), ), #[cfg(feature = "release")] - &auth::JWTAuth(Permission::RoutingWrite), + &auth::JWTAuth { + permission: Permission::RoutingWrite, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -355,11 +396,17 @@ pub async fn routing_update_default_config( #[cfg(not(feature = "release"))] auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::RoutingWrite), + &auth::JWTAuth { + permission: Permission::RoutingWrite, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), #[cfg(feature = "release")] - &auth::JWTAuth(Permission::RoutingWrite), + &auth::JWTAuth { + permission: Permission::RoutingWrite, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -389,11 +436,17 @@ pub async fn routing_update_default_config( #[cfg(not(feature = "release"))] auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::RoutingWrite), + &auth::JWTAuth { + permission: Permission::RoutingWrite, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), #[cfg(feature = "release")] - &auth::JWTAuth(Permission::RoutingWrite), + &auth::JWTAuth { + permission: Permission::RoutingWrite, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -426,6 +479,7 @@ pub async fn routing_retrieve_default_config( &auth::JWTAuthProfileFromRoute { profile_id: path, required_permission: Permission::RoutingRead, + minimum_entity_level: EntityType::Merchant, }, req.headers(), ), @@ -433,6 +487,7 @@ pub async fn routing_retrieve_default_config( &auth::JWTAuthProfileFromRoute { profile_id: path, required_permission: Permission::RoutingRead, + minimum_entity_level: EntityType::Merchant, }, api_locking::LockAction::NotApplicable, )) @@ -457,11 +512,17 @@ pub async fn routing_retrieve_default_config( #[cfg(not(feature = "release"))] auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::RoutingRead), + &auth::JWTAuth { + permission: Permission::RoutingRead, + minimum_entity_level: EntityType::Profile, + }, req.headers(), ), #[cfg(feature = "release")] - &auth::JWTAuth(Permission::RoutingRead), + &auth::JWTAuth { + permission: Permission::RoutingRead, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -491,11 +552,17 @@ pub async fn upsert_surcharge_decision_manager_config( #[cfg(not(feature = "release"))] auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::SurchargeDecisionManagerWrite), + &auth::JWTAuth { + permission: Permission::SurchargeDecisionManagerWrite, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), #[cfg(feature = "release")] - &auth::JWTAuth(Permission::SurchargeDecisionManagerWrite), + &auth::JWTAuth { + permission: Permission::SurchargeDecisionManagerWrite, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -522,11 +589,17 @@ pub async fn delete_surcharge_decision_manager_config( #[cfg(not(feature = "release"))] auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::SurchargeDecisionManagerWrite), + &auth::JWTAuth { + permission: Permission::SurchargeDecisionManagerWrite, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), #[cfg(feature = "release")] - &auth::JWTAuth(Permission::SurchargeDecisionManagerWrite), + &auth::JWTAuth { + permission: Permission::SurchargeDecisionManagerWrite, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -553,11 +626,17 @@ pub async fn retrieve_surcharge_decision_manager_config( #[cfg(not(feature = "release"))] auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::SurchargeDecisionManagerRead), + &auth::JWTAuth { + permission: Permission::SurchargeDecisionManagerRead, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), #[cfg(feature = "release")] - &auth::JWTAuth(Permission::SurchargeDecisionManagerRead), + &auth::JWTAuth { + permission: Permission::SurchargeDecisionManagerRead, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, ) .await @@ -587,11 +666,17 @@ pub async fn upsert_decision_manager_config( #[cfg(not(feature = "release"))] auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::SurchargeDecisionManagerRead), + &auth::JWTAuth { + permission: Permission::SurchargeDecisionManagerRead, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), #[cfg(feature = "release")] - &auth::JWTAuth(Permission::SurchargeDecisionManagerRead), + &auth::JWTAuth { + permission: Permission::SurchargeDecisionManagerRead, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -619,11 +704,17 @@ pub async fn delete_decision_manager_config( #[cfg(not(feature = "release"))] auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::SurchargeDecisionManagerWrite), + &auth::JWTAuth { + permission: Permission::SurchargeDecisionManagerWrite, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), #[cfg(feature = "release")] - &auth::JWTAuth(Permission::SurchargeDecisionManagerWrite), + &auth::JWTAuth { + permission: Permission::SurchargeDecisionManagerWrite, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -647,11 +738,17 @@ pub async fn retrieve_decision_manager_config( #[cfg(not(feature = "release"))] auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::SurchargeDecisionManagerRead), + &auth::JWTAuth { + permission: Permission::SurchargeDecisionManagerRead, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), #[cfg(feature = "release")] - &auth::JWTAuth(Permission::SurchargeDecisionManagerRead), + &auth::JWTAuth { + permission: Permission::SurchargeDecisionManagerRead, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, ) .await @@ -690,6 +787,7 @@ pub async fn routing_retrieve_linked_config( &auth::JWTAuthProfileFromRoute { profile_id, required_permission: Permission::RoutingRead, + minimum_entity_level: EntityType::Profile, }, req.headers(), ), @@ -697,6 +795,7 @@ pub async fn routing_retrieve_linked_config( &auth::JWTAuthProfileFromRoute { profile_id, required_permission: Permission::RoutingRead, + minimum_entity_level: EntityType::Profile, }, api_locking::LockAction::NotApplicable, )) @@ -720,11 +819,17 @@ pub async fn routing_retrieve_linked_config( #[cfg(not(feature = "release"))] auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::RoutingRead), + &auth::JWTAuth { + permission: Permission::RoutingRead, + minimum_entity_level: EntityType::Profile, + }, req.headers(), ), #[cfg(feature = "release")] - &auth::JWTAuth(Permission::RoutingRead), + &auth::JWTAuth { + permission: Permission::RoutingRead, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -767,6 +872,7 @@ pub async fn routing_retrieve_linked_config( &auth::JWTAuthProfileFromRoute { profile_id: wrapper.profile_id, required_permission: Permission::RoutingRead, + minimum_entity_level: EntityType::Profile, }, req.headers(), ), @@ -774,6 +880,7 @@ pub async fn routing_retrieve_linked_config( &auth::JWTAuthProfileFromRoute { profile_id: wrapper.profile_id, required_permission: Permission::RoutingRead, + minimum_entity_level: EntityType::Profile, }, api_locking::LockAction::NotApplicable, )) @@ -803,13 +910,19 @@ pub async fn routing_retrieve_default_config_for_profiles( #[cfg(not(feature = "release"))] auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::RoutingRead), + &auth::JWTAuth { + permission: Permission::RoutingRead, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), #[cfg(feature = "release")] auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::RoutingRead), + &auth::JWTAuth { + permission: Permission::RoutingRead, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -851,6 +964,7 @@ pub async fn routing_update_default_config_for_profile( &auth::JWTAuthProfileFromRoute { profile_id: routing_payload_wrapper.profile_id, required_permission: Permission::RoutingWrite, + minimum_entity_level: EntityType::Profile, }, req.headers(), ), @@ -858,6 +972,7 @@ pub async fn routing_update_default_config_for_profile( &auth::JWTAuthProfileFromRoute { profile_id: routing_payload_wrapper.profile_id, required_permission: Permission::RoutingWrite, + minimum_entity_level: EntityType::Profile, }, api_locking::LockAction::NotApplicable, )) diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs index dfc25c68945f..a6c8659617f9 100644 --- a/crates/router/src/routes/user.rs +++ b/crates/router/src/routes/user.rs @@ -5,7 +5,7 @@ use api_models::{ errors::types::ApiErrorResponse, user::{self as user_api}, }; -use common_enums::TokenPurpose; +use common_enums::{EntityType, TokenPurpose}; use common_utils::errors::ReportSwitchExt; use router_env::Flow; @@ -175,7 +175,10 @@ pub async fn set_dashboard_metadata( &req, payload, user_core::dashboard_metadata::set_metadata, - &auth::JWTAuth(Permission::MerchantAccountWrite), + &auth::JWTAuth { + permission: Permission::MerchantAccountWrite, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -257,13 +260,16 @@ pub async fn user_merchant_account_create( |state, auth: auth::UserFromToken, json_payload, _| { user_core::create_merchant_account(state, auth, json_payload) }, - &auth::JWTAuth(Permission::MerchantAccountCreate), + &auth::JWTAuth { + permission: Permission::MerchantAccountCreate, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await } -#[cfg(feature = "dummy_connector")] +#[cfg(all(feature = "dummy_connector", feature = "v1"))] pub async fn generate_sample_data( state: web::Data, http_req: HttpRequest, @@ -278,7 +284,10 @@ pub async fn generate_sample_data( &http_req, payload.into_inner(), sample_data::generate_sample_data_for_user, - &auth::JWTAuth(Permission::PaymentWrite), + &auth::JWTAuth { + permission: Permission::PaymentWrite, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -298,7 +307,10 @@ pub async fn delete_sample_data( &http_req, payload.into_inner(), sample_data::delete_sample_data_for_user, - &auth::JWTAuth(Permission::MerchantAccountWrite), + &auth::JWTAuth { + permission: Permission::MerchantAccountWrite, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -330,7 +342,10 @@ pub async fn get_user_role_details( &req, payload.into_inner(), user_core::get_user_details_in_merchant_account, - &auth::JWTAuth(Permission::UsersRead), + &auth::JWTAuth { + permission: Permission::UsersRead, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -348,7 +363,10 @@ pub async fn list_user_roles_details( &req, payload.into_inner(), user_core::list_user_roles_details, - &auth::JWTAuth(Permission::UsersRead), + &auth::JWTAuth { + permission: Permission::UsersRead, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -365,7 +383,10 @@ pub async fn list_users_for_merchant_account( &req, (), |state, user, _, _| user_core::list_users_for_merchant_account(state, user), - &auth::JWTAuth(Permission::UsersRead), + &auth::JWTAuth { + permission: Permission::UsersRead, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -445,7 +466,10 @@ pub async fn invite_multiple_user( |state, user, payload, req_state| { user_core::invite_multiple_user(state, user, payload, req_state, auth_id.clone()) }, - &auth::JWTAuth(Permission::UsersWrite), + &auth::JWTAuth { + permission: Permission::UsersWrite, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -468,7 +492,10 @@ pub async fn resend_invite( |state, user, req_payload, _| { user_core::resend_invite(state, user, req_payload, auth_id.clone()) }, - &auth::JWTAuth(Permission::UsersWrite), + &auth::JWTAuth { + permission: Permission::UsersWrite, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -548,7 +575,7 @@ pub async fn verify_recon_token(state: web::Data, http_req: HttpReques &http_req, (), |state, user, _req, _| user_core::verify_token(state, user), - &auth::ReconJWT, + &auth::DashboardNoPermissionAuth, api_locking::LockAction::NotApplicable, )) .await diff --git a/crates/router/src/routes/user_role.rs b/crates/router/src/routes/user_role.rs index 35c9098edd28..b1260b5d7ad9 100644 --- a/crates/router/src/routes/user_role.rs +++ b/crates/router/src/routes/user_role.rs @@ -1,6 +1,6 @@ use actix_web::{web, HttpRequest, HttpResponse}; use api_models::user_role::{self as user_role_api, role as role_api}; -use common_enums::TokenPurpose; +use common_enums::{EntityType, TokenPurpose}; use router_env::Flow; use super::AppState; @@ -29,7 +29,10 @@ pub async fn get_authorization_info( |state, _: (), _, _| async move { user_role_core::get_authorization_info_with_groups(state).await }, - &auth::JWTAuth(Permission::UsersRead), + &auth::JWTAuth { + permission: Permission::UsersRead, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -64,7 +67,10 @@ pub async fn create_role( &req, json_payload.into_inner(), role_core::create_role, - &auth::JWTAuth(Permission::UsersWrite), + &auth::JWTAuth { + permission: Permission::UsersWrite, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -80,7 +86,10 @@ pub async fn list_all_roles(state: web::Data, req: HttpRequest) -> Htt |state, user, _, _| async move { role_core::list_invitable_roles_with_groups(state, user).await }, - &auth::JWTAuth(Permission::UsersRead), + &auth::JWTAuth { + permission: Permission::UsersRead, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -103,7 +112,10 @@ pub async fn get_role( |state, user, payload, _| async move { role_core::get_role_with_groups(state, user, payload).await }, - &auth::JWTAuth(Permission::UsersRead), + &auth::JWTAuth { + permission: Permission::UsersRead, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -124,7 +136,10 @@ pub async fn update_role( &req, json_payload.into_inner(), |state, user, req, _| role_core::update_role(state, user, req, &role_id), - &auth::JWTAuth(Permission::UsersWrite), + &auth::JWTAuth { + permission: Permission::UsersWrite, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await @@ -143,7 +158,10 @@ pub async fn update_user_role( &req, payload, user_role_core::update_user_role, - &auth::JWTAuth(Permission::UsersWrite), + &auth::JWTAuth { + permission: Permission::UsersWrite, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -241,7 +259,10 @@ pub async fn delete_user_role( &req, payload.into_inner(), user_role_core::delete_user_role, - &auth::JWTAuth(Permission::UsersWrite), + &auth::JWTAuth { + permission: Permission::UsersWrite, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -261,7 +282,10 @@ pub async fn get_role_information( |_, _: (), _, _| async move { user_role_core::get_authorization_info_with_group_tag().await }, - &auth::JWTAuth(Permission::UsersRead), + &auth::JWTAuth { + permission: Permission::UsersRead, + minimum_entity_level: EntityType::Profile + }, api_locking::LockAction::NotApplicable, )) .await @@ -293,7 +317,10 @@ pub async fn list_roles_with_info(state: web::Data, req: HttpRequest) &req, (), |state, user_from_token, _, _| role_core::list_roles_with_info(state, user_from_token), - &auth::JWTAuth(Permission::UsersRead), + &auth::JWTAuth { + permission: Permission::UsersRead, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -319,7 +346,10 @@ pub async fn list_invitable_roles_at_entity_level( role_api::RoleCheckType::Invite, ) }, - &auth::JWTAuth(Permission::UsersRead), + &auth::JWTAuth { + permission: Permission::UsersRead, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await @@ -345,7 +375,10 @@ pub async fn list_updatable_roles_at_entity_level( role_api::RoleCheckType::Update, ) }, - &auth::JWTAuth(Permission::UsersRead), + &auth::JWTAuth { + permission: Permission::UsersRead, + minimum_entity_level: EntityType::Profile, + }, api_locking::LockAction::NotApplicable, )) .await diff --git a/crates/router/src/routes/verification.rs b/crates/router/src/routes/verification.rs index b818fcfc1d43..f2996009f82d 100644 --- a/crates/router/src/routes/verification.rs +++ b/crates/router/src/routes/verification.rs @@ -1,5 +1,6 @@ use actix_web::{web, HttpRequest, Responder}; use api_models::verifications; +use common_enums::EntityType; use router_env::{instrument, tracing, Flow}; use super::app::AppState; @@ -32,7 +33,10 @@ pub async fn apple_pay_merchant_registration( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::MerchantAccountWrite), + &auth::JWTAuth { + permission: Permission::MerchantAccountWrite, + minimum_entity_level: EntityType::Profile, + }, req.headers(), ), api_locking::LockAction::NotApplicable, @@ -64,7 +68,10 @@ pub async fn retrieve_apple_pay_verified_domains( }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), - &auth::JWTAuth(Permission::MerchantAccountRead), + &auth::JWTAuth { + permission: Permission::MerchantAccountRead, + minimum_entity_level: EntityType::Merchant, + }, req.headers(), ), api_locking::LockAction::NotApplicable, diff --git a/crates/router/src/routes/verify_connector.rs b/crates/router/src/routes/verify_connector.rs index b510ba29637f..29f8c154bc0d 100644 --- a/crates/router/src/routes/verify_connector.rs +++ b/crates/router/src/routes/verify_connector.rs @@ -1,5 +1,6 @@ use actix_web::{web, HttpRequest, HttpResponse}; use api_models::verify_connector::VerifyConnectorRequest; +use common_enums::EntityType; use router_env::{instrument, tracing, Flow}; use super::AppState; @@ -23,7 +24,10 @@ pub async fn payment_connector_verify( |state, auth: auth::AuthenticationData, req, _| { verify_connector::verify_connector_credentials(state, req, auth.profile_id) }, - &auth::JWTAuth(Permission::MerchantConnectorAccountWrite), + &auth::JWTAuth { + permission: Permission::MerchantConnectorAccountWrite, + minimum_entity_level: EntityType::Merchant, + }, api_locking::LockAction::NotApplicable, )) .await diff --git a/crates/router/src/routes/webhook_events.rs b/crates/router/src/routes/webhook_events.rs index d5150ad1fefa..8b94fb61f56b 100644 --- a/crates/router/src/routes/webhook_events.rs +++ b/crates/router/src/routes/webhook_events.rs @@ -1,4 +1,5 @@ use actix_web::{web, HttpRequest, Responder}; +use common_enums::EntityType; use router_env::{instrument, tracing, Flow}; use crate::{ @@ -44,6 +45,7 @@ pub async fn list_initial_webhook_delivery_attempts( &auth::JWTAuthMerchantFromRoute { merchant_id, required_permission: Permission::WebhookEventRead, + minimum_entity_level: EntityType::Merchant, }, req.headers(), ), @@ -83,6 +85,7 @@ pub async fn list_webhook_delivery_attempts( &auth::JWTAuthMerchantFromRoute { merchant_id, required_permission: Permission::WebhookEventRead, + minimum_entity_level: EntityType::Merchant, }, req.headers(), ), @@ -122,6 +125,7 @@ pub async fn retry_webhook_delivery_attempt( &auth::JWTAuthMerchantFromRoute { merchant_id, required_permission: Permission::WebhookEventWrite, + minimum_entity_level: EntityType::Merchant, }, req.headers(), ), diff --git a/crates/router/src/services.rs b/crates/router/src/services.rs index 8792f0c8d80b..18bc1eb41c41 100644 --- a/crates/router/src/services.rs +++ b/crates/router/src/services.rs @@ -11,8 +11,6 @@ pub mod jwt; pub mod kafka; pub mod logger; pub mod pm_auth; -#[cfg(feature = "recon")] -pub mod recon; #[cfg(feature = "olap")] pub mod openidconnect; diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index 38d9b7770352..7444fea0c3bc 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -383,11 +383,11 @@ pub async fn call_connector_api( let status_code = resp.status().as_u16(); let elapsed_time = current_time.elapsed(); logger::info!( - headers=?headers, - url=?url, - status_code=?status_code, + ?headers, + url, + status_code, flow=?flow_name, - elapsed_time=?elapsed_time + ?elapsed_time ); } Err(err) => { @@ -1242,6 +1242,11 @@ impl Authenticate for api_models::payments::PaymentsSessionRequest { Some(&self.client_secret) } } +impl Authenticate for api_models::payments::PaymentsDynamicTaxCalculationRequest { + fn get_client_secret(&self) -> Option<&String> { + Some(self.client_secret.peek()) + } +} impl Authenticate for api_models::payments::PaymentsRetrieveRequest {} impl Authenticate for api_models::payments::PaymentsCancelRequest {} @@ -1315,8 +1320,22 @@ pub fn build_redirection_form( input type="hidden" name=(field) value=(value); } } - - (PreEscaped(format!(""))) + (PreEscaped(format!(r#" + + "#))) } } diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index 4f9a222db5ed..5f9fc798d89c 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -10,7 +10,7 @@ use api_models::payment_methods::PaymentMethodIntentConfirm; use api_models::payouts; use api_models::{payment_methods::PaymentMethodListRequest, payments}; use async_trait::async_trait; -use common_enums::TokenPurpose; +use common_enums::{EntityType, TokenPurpose}; use common_utils::{date_time, id_type}; use error_stack::{report, ResultExt}; use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; @@ -24,8 +24,6 @@ use self::detached::{ExtractedPayload, GetAuthType}; use super::authorization::{self, permissions::Permission}; #[cfg(feature = "olap")] use super::jwt; -#[cfg(feature = "recon")] -use super::recon::ReconToken; #[cfg(feature = "olap")] use crate::configs::Settings; #[cfg(feature = "olap")] @@ -34,8 +32,6 @@ use crate::consts; use crate::core::errors::UserResult; #[cfg(feature = "partial-auth")] use crate::core::metrics; -#[cfg(feature = "recon")] -use crate::routes::SessionState; use crate::{ core::{ api_keys, @@ -44,7 +40,7 @@ use crate::{ headers, routes::app::SessionStateInfo, services::api, - types::domain, + types::{domain, storage}, utils::OptionExt, }; @@ -69,6 +65,14 @@ pub struct AuthenticationDataWithMultipleProfiles { pub profile_id_list: Option>, } +#[derive(Clone, Debug)] +pub struct AuthenticationDataWithUser { + pub merchant_account: domain::MerchantAccount, + pub key_store: domain::MerchantKeyStore, + pub user: storage::User, + pub profile_id: Option, +} + #[derive(Clone, Debug, Eq, PartialEq, Serialize)] #[serde( tag = "api_auth_type", @@ -1004,12 +1008,9 @@ where } #[derive(Debug)] -pub(crate) struct JWTAuth(pub Permission); - -#[derive(serde::Deserialize)] -struct JwtAuthPayloadFetchUnit { - #[serde(rename(deserialize = "exp"))] - _exp: u64, +pub(crate) struct JWTAuth { + pub permission: Permission, + pub minimum_entity_level: EntityType, } #[async_trait] @@ -1027,8 +1028,9 @@ where return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } - let permissions = authorization::get_permissions(state, &payload).await?; - authorization::check_authorization(&self.0, &permissions)?; + let role_info = authorization::get_role_info(state, &payload).await?; + authorization::check_permission(&self.permission, &role_info)?; + authorization::check_entity(self.minimum_entity_level, &role_info)?; Ok(( (), @@ -1056,8 +1058,9 @@ where return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } - let permissions = authorization::get_permissions(state, &payload).await?; - authorization::check_authorization(&self.0, &permissions)?; + let role_info = authorization::get_role_info(state, &payload).await?; + authorization::check_permission(&self.permission, &role_info)?; + authorization::check_entity(self.minimum_entity_level, &role_info)?; Ok(( UserFromToken { @@ -1091,8 +1094,10 @@ where return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } - let permissions = authorization::get_permissions(state, &payload).await?; - authorization::check_authorization(&self.0, &permissions)?; + let role_info = authorization::get_role_info(state, &payload).await?; + authorization::check_permission(&self.permission, &role_info)?; + authorization::check_entity(self.minimum_entity_level, &role_info)?; + let key_manager_state = &(&state.session_state()).into(); let key_store = state .store() @@ -1132,10 +1137,12 @@ where pub struct JWTAuthMerchantFromRoute { pub merchant_id: id_type::MerchantId, pub required_permission: Permission, + pub minimum_entity_level: EntityType, } pub struct JWTAuthMerchantFromHeader { pub required_permission: Permission, + pub minimum_entity_level: EntityType, } #[async_trait] @@ -1153,8 +1160,9 @@ where return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } - let permissions = authorization::get_permissions(state, &payload).await?; - authorization::check_authorization(&self.required_permission, &permissions)?; + let role_info = authorization::get_role_info(state, &payload).await?; + authorization::check_permission(&self.required_permission, &role_info)?; + authorization::check_entity(self.minimum_entity_level, &role_info)?; let merchant_id_from_header = HeaderMapStruct::new(request_headers).get_merchant_id_from_header()?; @@ -1188,8 +1196,9 @@ where return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } - let permissions = authorization::get_permissions(state, &payload).await?; - authorization::check_authorization(&self.required_permission, &permissions)?; + let role_info = authorization::get_role_info(state, &payload).await?; + authorization::check_permission(&self.required_permission, &role_info)?; + authorization::check_entity(self.minimum_entity_level, &role_info)?; let merchant_id_from_header = HeaderMapStruct::new(request_headers).get_merchant_id_from_header()?; @@ -1254,8 +1263,9 @@ where return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } - let permissions = authorization::get_permissions(state, &payload).await?; - authorization::check_authorization(&self.required_permission, &permissions)?; + let role_info = authorization::get_role_info(state, &payload).await?; + authorization::check_permission(&self.required_permission, &role_info)?; + authorization::check_entity(self.minimum_entity_level, &role_info)?; // Check if token has access to MerchantId that has been requested through query param if payload.merchant_id != self.merchant_id { @@ -1290,8 +1300,10 @@ where return Err(report!(errors::ApiErrorResponse::InvalidJwtToken)); } - let permissions = authorization::get_permissions(state, &payload).await?; - authorization::check_authorization(&self.required_permission, &permissions)?; + let role_info = authorization::get_role_info(state, &payload).await?; + authorization::check_permission(&self.required_permission, &role_info)?; + authorization::check_entity(self.minimum_entity_level, &role_info)?; + let key_manager_state = &(&state.session_state()).into(); let key_store = state .store() @@ -1333,6 +1345,7 @@ pub struct JWTAuthMerchantAndProfileFromRoute { pub merchant_id: id_type::MerchantId, pub profile_id: id_type::ProfileId, pub required_permission: Permission, + pub minimum_entity_level: EntityType, } #[async_trait] @@ -1362,8 +1375,10 @@ where return Err(report!(errors::ApiErrorResponse::InvalidJwtToken)); } - let permissions = authorization::get_permissions(state, &payload).await?; - authorization::check_authorization(&self.required_permission, &permissions)?; + let role_info = authorization::get_role_info(state, &payload).await?; + authorization::check_permission(&self.required_permission, &role_info)?; + authorization::check_entity(self.minimum_entity_level, &role_info)?; + let key_manager_state = &(&state.session_state()).into(); let key_store = state .store() @@ -1406,6 +1421,7 @@ where pub struct JWTAuthProfileFromRoute { pub profile_id: id_type::ProfileId, pub required_permission: Permission, + pub minimum_entity_level: EntityType, } #[async_trait] @@ -1423,8 +1439,10 @@ where return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } - let permissions = authorization::get_permissions(state, &payload).await?; - authorization::check_authorization(&self.required_permission, &permissions)?; + let role_info = authorization::get_role_info(state, &payload).await?; + authorization::check_permission(&self.required_permission, &role_info)?; + authorization::check_entity(self.minimum_entity_level, &role_info)?; + let key_manager_state = &(&state.session_state()).into(); let key_store = state .store() @@ -1517,8 +1535,10 @@ where return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } - let permissions = authorization::get_permissions(state, &payload).await?; - authorization::check_authorization(&self.0, &permissions)?; + let role_info = authorization::get_role_info(state, &payload).await?; + authorization::check_permission(&self.permission, &role_info)?; + authorization::check_entity(self.minimum_entity_level, &role_info)?; + let key_manager_state = &(&state.session_state()).into(); let key_store = state .store() @@ -1574,8 +1594,10 @@ where return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } - let permissions = authorization::get_permissions(state, &payload).await?; - authorization::check_authorization(&self.0, &permissions)?; + let role_info = authorization::get_role_info(state, &payload).await?; + authorization::check_permission(&self.permission, &role_info)?; + authorization::check_entity(self.minimum_entity_level, &role_info)?; + let key_manager_state = &(&state.session_state()).into(); let key_store = state .store() @@ -1962,12 +1984,13 @@ where default_auth } +#[derive(Clone)] #[cfg(feature = "recon")] -pub struct ReconAdmin; +pub struct UserFromTokenWithAuthData(pub UserFromToken, pub AuthenticationDataWithUser); -#[async_trait] #[cfg(feature = "recon")] -impl AuthenticateAndFetch<(), A> for ReconAdmin +#[async_trait] +impl AuthenticateAndFetch for JWTAuth where A: SessionStateInfo + Sync, { @@ -1975,49 +1998,68 @@ where &self, request_headers: &HeaderMap, state: &A, - ) -> RouterResult<((), AuthenticationType)> { - let request_admin_api_key = - get_api_key(request_headers).change_context(errors::ApiErrorResponse::Unauthorized)?; - let conf = state.conf(); + ) -> RouterResult<(UserFromTokenWithAuthData, AuthenticationType)> { + let payload = parse_jwt_payload::(request_headers, state).await?; + if payload.check_in_blacklist(state).await? { + return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); + } + let role_info = authorization::get_role_info(state, &payload).await?; + authorization::check_permission(&self.permission, &role_info)?; + authorization::check_entity(self.minimum_entity_level, &role_info)?; - let admin_api_key = conf.secrets.get_inner().recon_admin_api_key.peek(); + let key_manager_state = &(&state.session_state()).into(); + let key_store = state + .store() + .get_merchant_key_store_by_merchant_id( + key_manager_state, + &payload.merchant_id, + &state.store().get_master_key().to_vec().into(), + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken) + .attach_printable("Failed to fetch merchant key store for the merchant id")?; - if request_admin_api_key != admin_api_key { - Err(report!(errors::ApiErrorResponse::Unauthorized) - .attach_printable("Recon Admin Authentication Failure"))?; - } + let merchant = state + .store() + .find_merchant_account_by_merchant_id( + key_manager_state, + &payload.merchant_id, + &key_store, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken) + .attach_printable("Failed to fetch merchant account for the merchant id")?; - Ok(((), AuthenticationType::NoAuth)) - } -} + let user_id = payload.user_id; -#[cfg(feature = "recon")] -pub struct ReconJWT; -#[cfg(feature = "recon")] -pub struct ReconUser { - pub user_id: String, -} -#[cfg(feature = "recon")] -impl AuthInfo for ReconUser { - fn get_merchant_id(&self) -> Option<&id_type::MerchantId> { - None - } -} -#[cfg(all(feature = "olap", feature = "recon"))] -#[async_trait] -impl AuthenticateAndFetch for ReconJWT { - async fn authenticate_and_fetch( - &self, - request_headers: &HeaderMap, - state: &SessionState, - ) -> RouterResult<(ReconUser, AuthenticationType)> { - let payload = parse_jwt_payload::(request_headers, state).await?; + let user = state + .session_state() + .global_store + .find_user_by_id(&user_id) + .await + .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken) + .attach_printable("Failed to fetch user for the user id")?; - Ok(( - ReconUser { - user_id: payload.user_id, - }, - AuthenticationType::NoAuth, - )) + let auth = AuthenticationDataWithUser { + merchant_account: merchant, + key_store, + profile_id: payload.profile_id.clone(), + user, + }; + + let auth_type = AuthenticationType::MerchantJwt { + merchant_id: auth.merchant_account.get_id().clone(), + user_id: Some(user_id.clone()), + }; + + let user = UserFromToken { + user_id, + merchant_id: payload.merchant_id.clone(), + org_id: payload.org_id, + role_id: payload.role_id, + profile_id: payload.profile_id, + }; + + Ok((UserFromTokenWithAuthData(user, auth), auth_type)) } } diff --git a/crates/router/src/services/authorization.rs b/crates/router/src/services/authorization.rs index ea1264bde069..78af4c00884e 100644 --- a/crates/router/src/services/authorization.rs +++ b/crates/router/src/services/authorization.rs @@ -1,6 +1,5 @@ use std::sync::Arc; -use common_enums::PermissionGroup; use common_utils::id_type; use error_stack::ResultExt; use redis_interface::RedisConnectionPool; @@ -19,69 +18,57 @@ pub mod permission_groups; pub mod permissions; pub mod roles; -pub async fn get_permissions( - state: &A, - token: &AuthToken, -) -> RouterResult> +pub async fn get_role_info(state: &A, token: &AuthToken) -> RouterResult where A: SessionStateInfo + Sync, { - if let Some(permissions) = get_permissions_from_predefined_roles(&token.role_id) { - return Ok(permissions); + if let Some(role_info) = roles::predefined_roles::PREDEFINED_ROLES.get(token.role_id.as_str()) { + return Ok(role_info.clone()); } - if let Ok(permissions) = get_permissions_from_cache(state, &token.role_id) + if let Ok(role_info) = get_role_info_from_cache(state, &token.role_id) .await .map_err(|e| logger::error!("Failed to get permissions from cache {e:?}")) { - return Ok(permissions); + return Ok(role_info.clone()); } - let permissions = - get_permissions_from_db(state, &token.role_id, &token.merchant_id, &token.org_id).await?; + let role_info = + get_role_info_from_db(state, &token.role_id, &token.merchant_id, &token.org_id).await?; let token_expiry = i64::try_from(token.exp).change_context(ApiErrorResponse::InternalServerError)?; let cache_ttl = token_expiry - common_utils::date_time::now_unix_timestamp(); - set_permissions_in_cache(state, &token.role_id, &permissions, cache_ttl) + set_role_info_in_cache(state, &token.role_id, &role_info, cache_ttl) .await - .map_err(|e| logger::error!("Failed to set permissions in cache {e:?}")) + .map_err(|e| logger::error!("Failed to set role info in cache {e:?}")) .ok(); - Ok(permissions) + Ok(role_info) } -async fn get_permissions_from_cache( - state: &A, - role_id: &str, -) -> RouterResult> +async fn get_role_info_from_cache(state: &A, role_id: &str) -> RouterResult where A: SessionStateInfo + Sync, { let redis_conn = get_redis_connection(state)?; redis_conn - .get_and_deserialize_key(&get_cache_key_from_role_id(role_id), "Vec") + .get_and_deserialize_key(&get_cache_key_from_role_id(role_id), "RoleInfo") .await .change_context(ApiErrorResponse::InternalServerError) } pub fn get_cache_key_from_role_id(role_id: &str) -> String { - format!("{}{}", consts::ROLE_CACHE_PREFIX, role_id) + format!("{}{}", consts::ROLE_INFO_CACHE_PREFIX, role_id) } -fn get_permissions_from_predefined_roles(role_id: &str) -> Option> { - roles::predefined_roles::PREDEFINED_ROLES - .get(role_id) - .map(|role_info| get_permissions_from_groups(role_info.get_permission_groups())) -} - -async fn get_permissions_from_db( +async fn get_role_info_from_db( state: &A, role_id: &str, merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, -) -> RouterResult> +) -> RouterResult where A: SessionStateInfo + Sync, { @@ -89,14 +76,14 @@ where .store() .find_role_by_role_id_in_merchant_scope(role_id, merchant_id, org_id) .await - .map(|role| get_permissions_from_groups(&role.groups)) + .map(roles::RoleInfo::from) .to_not_found_response(ApiErrorResponse::InvalidJwtToken) } -pub async fn set_permissions_in_cache( +pub async fn set_role_info_in_cache( state: &A, role_id: &str, - permissions: &Vec, + role_info: &roles::RoleInfo, expiry: i64, ) -> RouterResult<()> where @@ -105,32 +92,17 @@ where let redis_conn = get_redis_connection(state)?; redis_conn - .serialize_and_set_key_with_expiry( - &get_cache_key_from_role_id(role_id), - permissions, - expiry, - ) + .serialize_and_set_key_with_expiry(&get_cache_key_from_role_id(role_id), role_info, expiry) .await .change_context(ApiErrorResponse::InternalServerError) } -pub fn get_permissions_from_groups(groups: &[PermissionGroup]) -> Vec { - groups - .iter() - .flat_map(|group| { - permission_groups::get_permissions_vec(group) - .iter() - .cloned() - }) - .collect() -} - -pub fn check_authorization( +pub fn check_permission( required_permission: &permissions::Permission, - permissions: &[permissions::Permission], + role_info: &roles::RoleInfo, ) -> RouterResult<()> { - permissions - .contains(required_permission) + role_info + .check_permission_exists(required_permission) .then_some(()) .ok_or( ApiErrorResponse::AccessForbidden { @@ -140,6 +112,18 @@ pub fn check_authorization( ) } +pub fn check_entity( + required_minimum_entity: common_enums::EntityType, + role_info: &roles::RoleInfo, +) -> RouterResult<()> { + if required_minimum_entity > role_info.get_entity_type() { + Err(ApiErrorResponse::AccessForbidden { + resource: required_minimum_entity.to_string(), + })?; + } + Ok(()) +} + fn get_redis_connection(state: &A) -> RouterResult> { state .store() diff --git a/crates/router/src/services/authorization/info.rs b/crates/router/src/services/authorization/info.rs index ca5a66893950..822035c7925d 100644 --- a/crates/router/src/services/authorization/info.rs +++ b/crates/router/src/services/authorization/info.rs @@ -42,6 +42,7 @@ pub enum PermissionModule { SurchargeDecisionManager, AccountCreate, Payouts, + Recon, } impl PermissionModule { @@ -59,7 +60,8 @@ impl PermissionModule { Self::ThreeDsDecisionManager => "View and configure 3DS decision rules configured for a merchant", Self::SurchargeDecisionManager =>"View and configure surcharge decision rules configured for a merchant", Self::AccountCreate => "Create new account within your organization", - Self::Payouts => "Everything related to payouts - like creating and viewing payout related information are within this module" + Self::Payouts => "Everything related to payouts - like creating and viewing payout related information are within this module", + Self::Recon => "Everything related to recon - raise requests for activating recon and generate recon auth tokens", } } } @@ -178,6 +180,11 @@ impl ModuleInfo { Permission::PayoutWrite, ]), }, + PermissionModule::Recon => Self { + module: module_name, + description, + permissions: get_permission_info_from_permissions(&[Permission::ReconAdmin]), + }, } } } @@ -215,6 +222,7 @@ fn get_group_description(group: PermissionGroup) -> &'static str { PermissionGroup::MerchantDetailsView => "View Merchant Details", PermissionGroup::MerchantDetailsManage => "Create, modify and delete Merchant Details like api keys, webhooks, etc", PermissionGroup::OrganizationManage => "Manage organization level tasks like create new Merchant accounts, Organization level roles, etc", + PermissionGroup::ReconOps => "View and manage reconciliation reports", } } @@ -233,6 +241,7 @@ pub fn get_parent_name(group: PermissionGroup) -> ParentGroup { ParentGroup::Merchant } PermissionGroup::OrganizationManage => ParentGroup::Organization, + PermissionGroup::ReconOps => ParentGroup::Recon, } } @@ -245,5 +254,6 @@ pub fn get_parent_group_description(group: ParentGroup) -> &'static str { ParentGroup::Users => "Manage and invite Users to the Team", ParentGroup::Merchant => "Create, modify and delete Merchant Details like api keys, webhooks, etc", ParentGroup::Organization =>"Manage organization level tasks like create new Merchant accounts, Organization level roles, etc", + ParentGroup::Recon => "View and manage reconciliation reports", } } diff --git a/crates/router/src/services/authorization/permission_groups.rs b/crates/router/src/services/authorization/permission_groups.rs index c31e3d32b1f2..aafc9cee9430 100644 --- a/crates/router/src/services/authorization/permission_groups.rs +++ b/crates/router/src/services/authorization/permission_groups.rs @@ -16,6 +16,7 @@ pub fn get_permissions_vec(permission_group: &PermissionGroup) -> &[Permission] PermissionGroup::MerchantDetailsView => &MERCHANT_DETAILS_VIEW, PermissionGroup::MerchantDetailsManage => &MERCHANT_DETAILS_MANAGE, PermissionGroup::OrganizationManage => &ORGANIZATION_MANAGE, + PermissionGroup::ReconOps => &RECON, } } @@ -92,3 +93,5 @@ pub static ORGANIZATION_MANAGE: [Permission; 2] = [ Permission::MerchantAccountCreate, Permission::MerchantAccountRead, ]; + +pub static RECON: [Permission; 1] = [Permission::ReconAdmin]; diff --git a/crates/router/src/services/authorization/permissions.rs b/crates/router/src/services/authorization/permissions.rs index 36ed89f4a44a..2f0617557caf 100644 --- a/crates/router/src/services/authorization/permissions.rs +++ b/crates/router/src/services/authorization/permissions.rs @@ -35,6 +35,7 @@ pub enum Permission { PayoutRead, PayoutWrite, GenerateReport, + ReconAdmin, } impl Permission { @@ -77,6 +78,7 @@ impl Permission { Self::PayoutRead => "View all payouts", Self::PayoutWrite => "Create payout, download payout data", Self::GenerateReport => "Generate reports for payments, refunds and disputes", + Self::ReconAdmin => "View and manage reconciliation reports", } } } diff --git a/crates/router/src/services/authorization/roles.rs b/crates/router/src/services/authorization/roles.rs index 9b506cc5ef7f..c498a33614e1 100644 --- a/crates/router/src/services/authorization/roles.rs +++ b/crates/router/src/services/authorization/roles.rs @@ -8,7 +8,7 @@ use crate::{core::errors, routes::SessionState}; pub mod predefined_roles; -#[derive(Clone)] +#[derive(Clone, serde::Serialize, serde::Deserialize, Debug)] pub struct RoleInfo { role_id: String, role_name: String, diff --git a/crates/router/src/services/authorization/roles/predefined_roles.rs b/crates/router/src/services/authorization/roles/predefined_roles.rs index f5320c41335b..df42d412989a 100644 --- a/crates/router/src/services/authorization/roles/predefined_roles.rs +++ b/crates/router/src/services/authorization/roles/predefined_roles.rs @@ -8,6 +8,8 @@ use crate::consts; pub static PREDEFINED_ROLES: Lazy> = Lazy::new(|| { let mut roles = HashMap::new(); + + // Internal Roles roles.insert( common_utils::consts::ROLE_ID_INTERNAL_ADMIN, RoleInfo { @@ -24,6 +26,7 @@ pub static PREDEFINED_ROLES: Lazy> = Lazy::new(| PermissionGroup::MerchantDetailsView, PermissionGroup::MerchantDetailsManage, PermissionGroup::OrganizationManage, + PermissionGroup::ReconOps, ], role_id: common_utils::consts::ROLE_ID_INTERNAL_ADMIN.to_string(), role_name: "internal_admin".to_string(), @@ -57,6 +60,7 @@ pub static PREDEFINED_ROLES: Lazy> = Lazy::new(| }, ); + // Merchant Roles roles.insert( common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN, RoleInfo { @@ -73,6 +77,7 @@ pub static PREDEFINED_ROLES: Lazy> = Lazy::new(| PermissionGroup::MerchantDetailsView, PermissionGroup::MerchantDetailsManage, PermissionGroup::OrganizationManage, + PermissionGroup::ReconOps, ], role_id: common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN.to_string(), role_name: "organization_admin".to_string(), @@ -101,9 +106,10 @@ pub static PREDEFINED_ROLES: Lazy> = Lazy::new(| PermissionGroup::UsersManage, PermissionGroup::MerchantDetailsView, PermissionGroup::MerchantDetailsManage, + PermissionGroup::ReconOps, ], role_id: consts::user_role::ROLE_ID_MERCHANT_ADMIN.to_string(), - role_name: "admin".to_string(), + role_name: "merchant_admin".to_string(), scope: RoleScope::Organization, entity_type: EntityType::Merchant, is_invitable: true, @@ -124,7 +130,7 @@ pub static PREDEFINED_ROLES: Lazy> = Lazy::new(| PermissionGroup::MerchantDetailsView, ], role_id: consts::user_role::ROLE_ID_MERCHANT_VIEW_ONLY.to_string(), - role_name: "view_only".to_string(), + role_name: "merchant_view_only".to_string(), scope: RoleScope::Organization, entity_type: EntityType::Merchant, is_invitable: true, @@ -144,7 +150,7 @@ pub static PREDEFINED_ROLES: Lazy> = Lazy::new(| PermissionGroup::MerchantDetailsView, ], role_id: consts::user_role::ROLE_ID_MERCHANT_IAM_ADMIN.to_string(), - role_name: "iam".to_string(), + role_name: "merchant_iam".to_string(), scope: RoleScope::Organization, entity_type: EntityType::Merchant, is_invitable: true, @@ -165,7 +171,7 @@ pub static PREDEFINED_ROLES: Lazy> = Lazy::new(| PermissionGroup::MerchantDetailsManage, ], role_id: consts::user_role::ROLE_ID_MERCHANT_DEVELOPER.to_string(), - role_name: "developer".to_string(), + role_name: "merchant_developer".to_string(), scope: RoleScope::Organization, entity_type: EntityType::Merchant, is_invitable: true, @@ -187,7 +193,7 @@ pub static PREDEFINED_ROLES: Lazy> = Lazy::new(| PermissionGroup::MerchantDetailsView, ], role_id: consts::user_role::ROLE_ID_MERCHANT_OPERATOR.to_string(), - role_name: "operator".to_string(), + role_name: "merchant_operator".to_string(), scope: RoleScope::Organization, entity_type: EntityType::Merchant, is_invitable: true, @@ -215,5 +221,136 @@ pub static PREDEFINED_ROLES: Lazy> = Lazy::new(| is_internal: false, }, ); + + // Profile Roles + roles.insert( + consts::user_role::ROLE_ID_PROFILE_ADMIN, + RoleInfo { + groups: vec![ + PermissionGroup::OperationsView, + PermissionGroup::OperationsManage, + PermissionGroup::ConnectorsView, + PermissionGroup::ConnectorsManage, + PermissionGroup::WorkflowsView, + PermissionGroup::WorkflowsManage, + PermissionGroup::AnalyticsView, + PermissionGroup::UsersView, + PermissionGroup::UsersManage, + PermissionGroup::MerchantDetailsView, + PermissionGroup::MerchantDetailsManage, + ], + role_id: consts::user_role::ROLE_ID_PROFILE_ADMIN.to_string(), + role_name: "profile_admin".to_string(), + scope: RoleScope::Organization, + entity_type: EntityType::Profile, + is_invitable: true, + is_deletable: true, + is_updatable: true, + is_internal: false, + }, + ); + roles.insert( + consts::user_role::ROLE_ID_PROFILE_VIEW_ONLY, + RoleInfo { + groups: vec![ + PermissionGroup::OperationsView, + PermissionGroup::ConnectorsView, + PermissionGroup::WorkflowsView, + PermissionGroup::AnalyticsView, + PermissionGroup::UsersView, + PermissionGroup::MerchantDetailsView, + ], + role_id: consts::user_role::ROLE_ID_PROFILE_VIEW_ONLY.to_string(), + role_name: "profile_view_only".to_string(), + scope: RoleScope::Organization, + entity_type: EntityType::Profile, + is_invitable: true, + is_deletable: true, + is_updatable: true, + is_internal: false, + }, + ); + roles.insert( + consts::user_role::ROLE_ID_PROFILE_IAM_ADMIN, + RoleInfo { + groups: vec![ + PermissionGroup::OperationsView, + PermissionGroup::AnalyticsView, + PermissionGroup::UsersView, + PermissionGroup::UsersManage, + PermissionGroup::MerchantDetailsView, + ], + role_id: consts::user_role::ROLE_ID_PROFILE_IAM_ADMIN.to_string(), + role_name: "profile_iam".to_string(), + scope: RoleScope::Organization, + entity_type: EntityType::Profile, + is_invitable: true, + is_deletable: true, + is_updatable: true, + is_internal: false, + }, + ); + roles.insert( + consts::user_role::ROLE_ID_PROFILE_DEVELOPER, + RoleInfo { + groups: vec![ + PermissionGroup::OperationsView, + PermissionGroup::ConnectorsView, + PermissionGroup::AnalyticsView, + PermissionGroup::UsersView, + PermissionGroup::MerchantDetailsView, + PermissionGroup::MerchantDetailsManage, + ], + role_id: consts::user_role::ROLE_ID_PROFILE_DEVELOPER.to_string(), + role_name: "profile_developer".to_string(), + scope: RoleScope::Organization, + entity_type: EntityType::Profile, + is_invitable: true, + is_deletable: true, + is_updatable: true, + is_internal: false, + }, + ); + roles.insert( + consts::user_role::ROLE_ID_PROFILE_OPERATOR, + RoleInfo { + groups: vec![ + PermissionGroup::OperationsView, + PermissionGroup::OperationsManage, + PermissionGroup::ConnectorsView, + PermissionGroup::WorkflowsView, + PermissionGroup::AnalyticsView, + PermissionGroup::UsersView, + PermissionGroup::MerchantDetailsView, + ], + role_id: consts::user_role::ROLE_ID_PROFILE_OPERATOR.to_string(), + role_name: "profile_operator".to_string(), + scope: RoleScope::Organization, + entity_type: EntityType::Profile, + is_invitable: true, + is_deletable: true, + is_updatable: true, + is_internal: false, + }, + ); + roles.insert( + consts::user_role::ROLE_ID_PROFILE_CUSTOMER_SUPPORT, + RoleInfo { + groups: vec![ + PermissionGroup::OperationsView, + PermissionGroup::AnalyticsView, + PermissionGroup::UsersView, + PermissionGroup::MerchantDetailsView, + ], + role_id: consts::user_role::ROLE_ID_PROFILE_CUSTOMER_SUPPORT.to_string(), + role_name: "profile_customer_support".to_string(), + scope: RoleScope::Organization, + entity_type: EntityType::Profile, + is_invitable: true, + is_deletable: true, + is_updatable: true, + is_internal: false, + }, + ); roles }); diff --git a/crates/router/src/services/connector_integration_interface.rs b/crates/router/src/services/connector_integration_interface.rs index 8da81a52f9fd..6b22d5ebf4d9 100644 --- a/crates/router/src/services/connector_integration_interface.rs +++ b/crates/router/src/services/connector_integration_interface.rs @@ -570,7 +570,7 @@ where } ConnectorIntegrationEnum::New(new_integration) => { let new_router_data = ResourceCommonData::from_old_router_data(req)?; - new_integration.build_request_v2(&new_router_data, connectors) + new_integration.build_request_v2(&new_router_data) } } } diff --git a/crates/router/src/services/email/types.rs b/crates/router/src/services/email/types.rs index 6ed03e1ea061..35bc9f06d9af 100644 --- a/crates/router/src/services/email/types.rs +++ b/crates/router/src/services/email/types.rs @@ -6,7 +6,7 @@ use common_utils::{ }; use error_stack::ResultExt; use external_services::email::{EmailContents, EmailData, EmailError}; -use masking::{ExposeInterface, PeekInterface, Secret}; +use masking::{ExposeInterface, Secret}; use crate::{configs, consts, routes::SessionState}; #[cfg(feature = "olap")] @@ -454,6 +454,7 @@ pub struct ProFeatureRequest { pub feature_name: String, pub merchant_id: common_utils::id_type::MerchantId, pub user_name: domain::UserName, + pub user_email: domain::UserEmail, pub settings: std::sync::Arc, pub subject: String, } @@ -467,7 +468,7 @@ impl EmailData for ProFeatureRequest { user_name: self.user_name.clone().get_secret().expose(), feature_name: self.feature_name.clone(), merchant_id: self.merchant_id.clone(), - user_email: recipient.peek().to_string(), + user_email: self.user_email.clone().get_secret().expose(), }); Ok(EmailContents { diff --git a/crates/router/src/services/kafka/payment_intent.rs b/crates/router/src/services/kafka/payment_intent.rs index 6e8e79544280..cb5ec9cf2e3a 100644 --- a/crates/router/src/services/kafka/payment_intent.rs +++ b/crates/router/src/services/kafka/payment_intent.rs @@ -5,6 +5,7 @@ use masking::{PeekInterface, Secret}; use serde_json::Value; use time::OffsetDateTime; +#[cfg(feature = "v1")] #[derive(serde::Serialize, Debug)] pub struct KafkaPaymentIntent<'a> { pub payment_id: &'a id_type::PaymentId, @@ -43,6 +44,42 @@ pub struct KafkaPaymentIntent<'a> { pub organization_id: &'a id_type::OrganizationId, } +#[cfg(feature = "v2")] +#[derive(serde::Serialize, Debug)] +pub struct KafkaPaymentIntent<'a> { + pub id: &'a id_type::PaymentId, + pub merchant_id: &'a id_type::MerchantId, + pub status: storage_enums::IntentStatus, + pub amount: MinorUnit, + pub currency: Option, + pub amount_captured: Option, + pub customer_id: Option<&'a id_type::CustomerId>, + pub description: Option<&'a String>, + pub return_url: Option<&'a String>, + pub metadata: Option, + pub statement_descriptor_name: Option<&'a String>, + #[serde(with = "time::serde::timestamp")] + pub created_at: OffsetDateTime, + #[serde(with = "time::serde::timestamp")] + pub modified_at: OffsetDateTime, + #[serde(default, with = "time::serde::timestamp::option")] + pub last_synced: Option, + pub setup_future_usage: Option, + pub off_session: Option, + pub client_secret: Option<&'a String>, + pub active_attempt_id: String, + pub attempt_count: i16, + pub profile_id: Option<&'a id_type::ProfileId>, + pub payment_confirm_source: Option, + pub billing_details: Option>>, + pub shipping_details: Option>>, + pub customer_email: Option>, + pub feature_metadata: Option<&'a Value>, + pub merchant_order_reference_id: Option<&'a String>, + pub organization_id: &'a id_type::OrganizationId, +} + +#[cfg(feature = "v1")] impl<'a> KafkaPaymentIntent<'a> { pub fn from_storage(intent: &'a PaymentIntent) -> Self { Self { @@ -88,12 +125,66 @@ impl<'a> KafkaPaymentIntent<'a> { } } +#[cfg(feature = "v2")] +impl<'a> KafkaPaymentIntent<'a> { + pub fn from_storage(intent: &'a PaymentIntent) -> Self { + Self { + id: &intent.id, + merchant_id: &intent.merchant_id, + status: intent.status, + amount: intent.amount, + currency: intent.currency, + amount_captured: intent.amount_captured, + customer_id: intent.customer_id.as_ref(), + description: intent.description.as_ref(), + return_url: intent.return_url.as_ref(), + metadata: intent.metadata.as_ref().map(|x| x.to_string()), + statement_descriptor_name: intent.statement_descriptor_name.as_ref(), + created_at: intent.created_at.assume_utc(), + modified_at: intent.modified_at.assume_utc(), + last_synced: intent.last_synced.map(|i| i.assume_utc()), + setup_future_usage: intent.setup_future_usage, + off_session: intent.off_session, + client_secret: intent.client_secret.as_ref(), + active_attempt_id: intent.active_attempt.get_id(), + attempt_count: intent.attempt_count, + profile_id: intent.profile_id.as_ref(), + payment_confirm_source: intent.payment_confirm_source, + // TODO: use typed information here to avoid PII logging + billing_details: None, + shipping_details: None, + customer_email: intent + .customer_details + .as_ref() + .and_then(|value| value.get_inner().peek().as_object()) + .and_then(|obj| obj.get("email")) + .and_then(|email| email.as_str()) + .map(|email| HashedString::from(Secret::new(email.to_string()))), + feature_metadata: intent.feature_metadata.as_ref(), + merchant_order_reference_id: intent.merchant_order_reference_id.as_ref(), + organization_id: &intent.organization_id, + } + } +} + +impl KafkaPaymentIntent<'_> { + #[cfg(feature = "v1")] + fn get_id(&self) -> &id_type::PaymentId { + self.payment_id + } + + #[cfg(feature = "v2")] + fn get_id(&self) -> &id_type::PaymentId { + self.id + } +} + impl<'a> super::KafkaMessage for KafkaPaymentIntent<'a> { fn key(&self) -> String { format!( "{}_{}", self.merchant_id.get_string_repr(), - self.payment_id.get_string_repr(), + self.get_id().get_string_repr(), ) } diff --git a/crates/router/src/services/kafka/payment_intent_event.rs b/crates/router/src/services/kafka/payment_intent_event.rs index 1d450c35cfae..321ce5581034 100644 --- a/crates/router/src/services/kafka/payment_intent_event.rs +++ b/crates/router/src/services/kafka/payment_intent_event.rs @@ -5,6 +5,7 @@ use masking::{PeekInterface, Secret}; use serde_json::Value; use time::OffsetDateTime; +#[cfg(feature = "v1")] #[serde_with::skip_serializing_none] #[derive(serde::Serialize, Debug)] pub struct KafkaPaymentIntentEvent<'a> { @@ -44,6 +45,55 @@ pub struct KafkaPaymentIntentEvent<'a> { pub organization_id: &'a id_type::OrganizationId, } +#[cfg(feature = "v2")] +#[serde_with::skip_serializing_none] +#[derive(serde::Serialize, Debug)] +pub struct KafkaPaymentIntentEvent<'a> { + pub id: &'a id_type::PaymentId, + pub merchant_id: &'a id_type::MerchantId, + pub status: storage_enums::IntentStatus, + pub amount: MinorUnit, + pub currency: Option, + pub amount_captured: Option, + pub customer_id: Option<&'a id_type::CustomerId>, + pub description: Option<&'a String>, + pub return_url: Option<&'a String>, + pub metadata: Option, + pub statement_descriptor_name: Option<&'a String>, + #[serde(with = "time::serde::timestamp::milliseconds")] + pub created_at: OffsetDateTime, + #[serde(with = "time::serde::timestamp::milliseconds")] + pub modified_at: OffsetDateTime, + #[serde(default, with = "time::serde::timestamp::milliseconds::option")] + pub last_synced: Option, + pub setup_future_usage: Option, + pub off_session: Option, + pub client_secret: Option<&'a String>, + pub active_attempt_id: String, + pub attempt_count: i16, + pub profile_id: Option<&'a id_type::ProfileId>, + pub payment_confirm_source: Option, + pub billing_details: Option>>, + pub shipping_details: Option>>, + pub customer_email: Option>, + pub feature_metadata: Option<&'a Value>, + pub merchant_order_reference_id: Option<&'a String>, + pub organization_id: &'a id_type::OrganizationId, +} + +impl KafkaPaymentIntentEvent<'_> { + #[cfg(feature = "v1")] + pub fn get_id(&self) -> &id_type::PaymentId { + self.payment_id + } + + #[cfg(feature = "v2")] + pub fn get_id(&self) -> &id_type::PaymentId { + self.id + } +} + +#[cfg(feature = "v1")] impl<'a> KafkaPaymentIntentEvent<'a> { pub fn from_storage(intent: &'a PaymentIntent) -> Self { Self { @@ -89,12 +139,54 @@ impl<'a> KafkaPaymentIntentEvent<'a> { } } +#[cfg(feature = "v2")] +impl<'a> KafkaPaymentIntentEvent<'a> { + pub fn from_storage(intent: &'a PaymentIntent) -> Self { + Self { + id: &intent.id, + merchant_id: &intent.merchant_id, + status: intent.status, + amount: intent.amount, + currency: intent.currency, + amount_captured: intent.amount_captured, + customer_id: intent.customer_id.as_ref(), + description: intent.description.as_ref(), + return_url: intent.return_url.as_ref(), + metadata: intent.metadata.as_ref().map(|x| x.to_string()), + statement_descriptor_name: intent.statement_descriptor_name.as_ref(), + created_at: intent.created_at.assume_utc(), + modified_at: intent.modified_at.assume_utc(), + last_synced: intent.last_synced.map(|i| i.assume_utc()), + setup_future_usage: intent.setup_future_usage, + off_session: intent.off_session, + client_secret: intent.client_secret.as_ref(), + active_attempt_id: intent.active_attempt.get_id(), + attempt_count: intent.attempt_count, + profile_id: intent.profile_id.as_ref(), + payment_confirm_source: intent.payment_confirm_source, + // TODO: use typed information here to avoid PII logging + billing_details: None, + shipping_details: None, + customer_email: intent + .customer_details + .as_ref() + .and_then(|value| value.get_inner().peek().as_object()) + .and_then(|obj| obj.get("email")) + .and_then(|email| email.as_str()) + .map(|email| HashedString::from(Secret::new(email.to_string()))), + feature_metadata: intent.feature_metadata.as_ref(), + merchant_order_reference_id: intent.merchant_order_reference_id.as_ref(), + organization_id: &intent.organization_id, + } + } +} + impl<'a> super::KafkaMessage for KafkaPaymentIntentEvent<'a> { fn key(&self) -> String { format!( "{}_{}", self.merchant_id.get_string_repr(), - self.payment_id.get_string_repr(), + self.get_id().get_string_repr(), ) } diff --git a/crates/router/src/services/recon.rs b/crates/router/src/services/recon.rs deleted file mode 100644 index cc10fb7c7f5a..000000000000 --- a/crates/router/src/services/recon.rs +++ /dev/null @@ -1,29 +0,0 @@ -use error_stack::ResultExt; -use masking::Secret; - -use super::jwt; -use crate::{ - configs::Settings, - consts, - core::{self, errors::RouterResult}, -}; - -#[derive(serde::Serialize, serde::Deserialize)] -pub struct ReconToken { - pub user_id: String, - pub exp: u64, -} - -impl ReconToken { - pub async fn new_token(user_id: String, settings: &Settings) -> RouterResult> { - let exp_duration = std::time::Duration::from_secs(consts::JWT_TOKEN_TIME_IN_SECS); - let exp = jwt::generate_exp(exp_duration) - .change_context(core::errors::ApiErrorResponse::InternalServerError)? - .as_secs(); - let token_payload = Self { user_id, exp }; - let token = jwt::generate_jwt(&token_payload, settings) - .await - .change_context(core::errors::ApiErrorResponse::InternalServerError)?; - Ok(Secret::new(token)) - } -} diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index d71d0b2f5002..16a8ed1ccc41 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -30,9 +30,9 @@ use hyperswitch_domain_models::router_flow_types::{ files::{Retrieve, Upload}, mandate_revoke::MandateRevoke, payments::{ - Approve, Authorize, AuthorizeSessionToken, Balance, Capture, CompleteAuthorize, - CreateConnectorCustomer, IncrementalAuthorization, InitPayment, PSync, PostProcessing, - PreProcessing, Reject, Session, SetupMandate, Void, + Approve, Authorize, AuthorizeSessionToken, Balance, CalculateTax, Capture, + CompleteAuthorize, CreateConnectorCustomer, IncrementalAuthorization, InitPayment, PSync, + PostProcessing, PreProcessing, Reject, SdkSessionUpdate, Session, SetupMandate, Void, }, refunds::{Execute, RSync}, webhooks::VerifyWebhookSource, @@ -58,15 +58,16 @@ pub use hyperswitch_domain_models::{ PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, PaymentsPreProcessingData, PaymentsRejectData, PaymentsSessionData, PaymentsSyncData, - RefundsData, ResponseId, RetrieveFileRequestData, SetupMandateRequestData, - SubmitEvidenceRequestData, SyncRequestType, UploadFileRequestData, - VerifyWebhookSourceRequestData, + PaymentsTaxCalculationData, RefundsData, ResponseId, RetrieveFileRequestData, + SdkPaymentsSessionUpdateData, SetupMandateRequestData, SubmitEvidenceRequestData, + SyncRequestType, UploadFileRequestData, VerifyWebhookSourceRequestData, }, router_response_types::{ AcceptDisputeResponse, CaptureSyncResponse, DefendDisputeResponse, MandateReference, MandateRevokeResponseData, PaymentsResponseData, PreprocessingResponseId, - RefundsResponseData, RetrieveFileResponse, SubmitEvidenceResponse, UploadFileResponse, - VerifyWebhookSourceResponseData, VerifyWebhookStatus, + RefundsResponseData, RetrieveFileResponse, SubmitEvidenceResponse, + TaxCalculationResponseData, UploadFileResponse, VerifyWebhookSourceResponseData, + VerifyWebhookStatus, }, }; #[cfg(feature = "payouts")] @@ -126,6 +127,12 @@ pub type PaymentsIncrementalAuthorizationRouterData = RouterData< PaymentsIncrementalAuthorizationData, PaymentsResponseData, >; +pub type PaymentsTaxCalculationRouterData = + RouterData; + +pub type SdkSessionUpdateRouterData = + RouterData; + pub type PaymentsCancelRouterData = RouterData; pub type PaymentsRejectRouterData = RouterData; pub type PaymentsApproveRouterData = RouterData; @@ -200,6 +207,14 @@ pub type PayoutsRouterData = RouterData; pub type PayoutsResponseRouterData = ResponseRouterData; +#[cfg(feature = "payouts")] +pub type PayoutActionData = Vec<( + storage::Payouts, + storage::PayoutAttempt, + Option, + Option, +)>; + #[cfg(feature = "payouts")] pub trait PayoutIndividualDetailsExt { type Error; @@ -362,6 +377,8 @@ impl Capturable for CompleteAuthorizeData { } } impl Capturable for SetupMandateRequestData {} +impl Capturable for PaymentsTaxCalculationData {} +impl Capturable for SdkPaymentsSessionUpdateData {} impl Capturable for PaymentsCancelData { fn get_captured_amount(&self, payment_data: &PaymentData) -> Option where diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 33ecacc681a8..34dea537b2dc 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -112,6 +112,7 @@ pub trait Connector: + ConnectorMandateRevokeV2 + ExternalAuthentication + ExternalAuthenticationV2 + + TaxCalculation { } @@ -139,7 +140,8 @@ impl< + ConnectorMandateRevoke + ConnectorMandateRevokeV2 + ExternalAuthentication - + ExternalAuthenticationV2, + + ExternalAuthenticationV2 + + TaxCalculation, > Connector for T { } @@ -357,9 +359,9 @@ impl ConnectorData { enums::Connector::Datatrans => { Ok(ConnectorEnum::Old(Box::new(connector::Datatrans::new()))) } - // enums::Connector::Deutschebank => { - // Ok(ConnectorEnum::Old(Box::new(connector::Deutschebank::new()))) - // } + enums::Connector::Deutschebank => { + Ok(ConnectorEnum::Old(Box::new(connector::Deutschebank::new()))) + } enums::Connector::Dlocal => Ok(ConnectorEnum::Old(Box::new(&connector::Dlocal))), #[cfg(feature = "dummy_connector")] enums::Connector::DummyConnector1 => Ok(ConnectorEnum::Old(Box::new( @@ -420,7 +422,9 @@ impl ConnectorData { enums::Connector::Mollie => Ok(ConnectorEnum::Old(Box::new(&connector::Mollie))), enums::Connector::Nmi => Ok(ConnectorEnum::Old(Box::new(connector::Nmi::new()))), enums::Connector::Noon => Ok(ConnectorEnum::Old(Box::new(connector::Noon::new()))), - // enums::Connector::Novalnet => Ok(ConnectorEnum::Old(Box::new(connector::Novalnet))), + enums::Connector::Novalnet => { + Ok(ConnectorEnum::Old(Box::new(connector::Novalnet::new()))) + } enums::Connector::Nuvei => Ok(ConnectorEnum::Old(Box::new(&connector::Nuvei))), enums::Connector::Opennode => { Ok(ConnectorEnum::Old(Box::new(&connector::Opennode))) @@ -455,7 +459,6 @@ impl ConnectorData { enums::Connector::Stripe => { Ok(ConnectorEnum::Old(Box::new(connector::Stripe::new()))) } - // enums::Connector::Taxjar => Ok(ConnectorEnum::Old(Box::new(connector::Taxjar))), enums::Connector::Wise => Ok(ConnectorEnum::Old(Box::new(&connector::Wise))), enums::Connector::Worldline => { Ok(ConnectorEnum::Old(Box::new(&connector::Worldline))) @@ -481,14 +484,17 @@ impl ConnectorData { enums::Connector::Paypal => { Ok(ConnectorEnum::Old(Box::new(connector::Paypal::new()))) } + // enums::Connector::Thunes => Ok(ConnectorEnum::Old(Box::new(connector::Thunes))), enums::Connector::Trustpay => { Ok(ConnectorEnum::Old(Box::new(connector::Trustpay::new()))) } enums::Connector::Tsys => Ok(ConnectorEnum::Old(Box::new(&connector::Tsys))), + enums::Connector::Volt => Ok(ConnectorEnum::Old(Box::new(connector::Volt::new()))), enums::Connector::Wellsfargo => { Ok(ConnectorEnum::Old(Box::new(&connector::Wellsfargo))) } + // enums::Connector::Wellsfargopayout => { // Ok(Box::new(connector::Wellsfargopayout::new())) // } @@ -500,7 +506,8 @@ impl ConnectorData { enums::Connector::Signifyd | enums::Connector::Riskified | enums::Connector::Gpayments - | enums::Connector::Threedsecureio => { + | enums::Connector::Threedsecureio + | enums::Connector::Taxjar => { Err(report!(errors::ConnectorError::InvalidConnectorName) .attach_printable(format!("invalid connector name: {connector_name}"))) .change_context(errors::ApiErrorResponse::InternalServerError) @@ -590,3 +597,32 @@ mod test { assert!(result.is_err()); } } + +#[derive(Clone)] +pub struct TaxCalculateConnectorData { + pub connector: ConnectorEnum, + pub connector_name: enums::TaxConnectors, +} + +impl TaxCalculateConnectorData { + pub fn get_connector_by_name(name: &str) -> CustomResult { + let connector_name = enums::TaxConnectors::from_str(name) + .change_context(errors::ApiErrorResponse::IncorrectConnectorNameGiven) + .attach_printable_lazy(|| format!("unable to parse connector: {name}"))?; + let connector = Self::convert_connector(connector_name)?; + Ok(Self { + connector, + connector_name, + }) + } + + fn convert_connector( + connector_name: enums::TaxConnectors, + ) -> CustomResult { + match connector_name { + enums::TaxConnectors::Taxjar => { + Ok(ConnectorEnum::Old(Box::new(connector::Taxjar::new()))) + } + } + } +} diff --git a/crates/router/src/types/api/payments.rs b/crates/router/src/types/api/payments.rs index 3b342c5c7de7..449d1c725c51 100644 --- a/crates/router/src/types/api/payments.rs +++ b/crates/router/src/types/api/payments.rs @@ -7,7 +7,8 @@ pub use api_models::payments::{ PaymentListResponseV2, PaymentMethodData, PaymentMethodDataRequest, PaymentMethodDataResponse, PaymentOp, PaymentRetrieveBody, PaymentRetrieveBodyWithCredentials, PaymentsAggregateResponse, PaymentsApproveRequest, PaymentsCancelRequest, PaymentsCaptureRequest, - PaymentsCompleteAuthorizeRequest, PaymentsExternalAuthenticationRequest, + PaymentsCompleteAuthorizeRequest, PaymentsDynamicTaxCalculationRequest, + PaymentsDynamicTaxCalculationResponse, PaymentsExternalAuthenticationRequest, PaymentsIncrementalAuthorizationRequest, PaymentsManualUpdateRequest, PaymentsRedirectRequest, PaymentsRedirectionResponse, PaymentsRejectRequest, PaymentsRequest, PaymentsResponse, PaymentsResponseForm, PaymentsRetrieveRequest, PaymentsSessionRequest, PaymentsSessionResponse, @@ -16,22 +17,23 @@ pub use api_models::payments::{ }; use error_stack::ResultExt; pub use hyperswitch_domain_models::router_flow_types::payments::{ - Approve, Authorize, AuthorizeSessionToken, Balance, Capture, CompleteAuthorize, + Approve, Authorize, AuthorizeSessionToken, Balance, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, IncrementalAuthorization, InitPayment, PSync, PaymentMethodToken, - PostProcessing, PreProcessing, Reject, Session, SetupMandate, Void, + PostProcessing, PreProcessing, Reject, SdkSessionUpdate, Session, SetupMandate, Void, }; pub use hyperswitch_interfaces::api::payments::{ ConnectorCustomer, MandateSetup, Payment, PaymentApprove, PaymentAuthorize, PaymentAuthorizeSessionToken, PaymentCapture, PaymentIncrementalAuthorization, PaymentReject, - PaymentSession, PaymentSync, PaymentToken, PaymentVoid, PaymentsCompleteAuthorize, - PaymentsPostProcessing, PaymentsPreProcessing, + PaymentSession, PaymentSessionUpdate, PaymentSync, PaymentToken, PaymentVoid, + PaymentsCompleteAuthorize, PaymentsPostProcessing, PaymentsPreProcessing, TaxCalculation, }; pub use super::payments_v2::{ ConnectorCustomerV2, MandateSetupV2, PaymentApproveV2, PaymentAuthorizeSessionTokenV2, PaymentAuthorizeV2, PaymentCaptureV2, PaymentIncrementalAuthorizationV2, PaymentRejectV2, - PaymentSessionV2, PaymentSyncV2, PaymentTokenV2, PaymentV2, PaymentVoidV2, - PaymentsCompleteAuthorizeV2, PaymentsPostProcessingV2, PaymentsPreProcessingV2, + PaymentSessionUpdateV2, PaymentSessionV2, PaymentSyncV2, PaymentTokenV2, PaymentV2, + PaymentVoidV2, PaymentsCompleteAuthorizeV2, PaymentsPostProcessingV2, PaymentsPreProcessingV2, + TaxCalculationV2, }; use crate::core::errors; diff --git a/crates/router/src/types/api/payments_v2.rs b/crates/router/src/types/api/payments_v2.rs index 8d8e3498eff4..59f132c0154f 100644 --- a/crates/router/src/types/api/payments_v2.rs +++ b/crates/router/src/types/api/payments_v2.rs @@ -1,6 +1,7 @@ pub use hyperswitch_interfaces::api::payments_v2::{ ConnectorCustomerV2, MandateSetupV2, PaymentApproveV2, PaymentAuthorizeSessionTokenV2, PaymentAuthorizeV2, PaymentCaptureV2, PaymentIncrementalAuthorizationV2, PaymentRejectV2, - PaymentSessionV2, PaymentSyncV2, PaymentTokenV2, PaymentV2, PaymentVoidV2, - PaymentsCompleteAuthorizeV2, PaymentsPostProcessingV2, PaymentsPreProcessingV2, + PaymentSessionUpdateV2, PaymentSessionV2, PaymentSyncV2, PaymentTokenV2, PaymentV2, + PaymentVoidV2, PaymentsCompleteAuthorizeV2, PaymentsPostProcessingV2, PaymentsPreProcessingV2, + TaxCalculationV2, }; diff --git a/crates/router/src/types/api/payouts.rs b/crates/router/src/types/api/payouts.rs index 84b7d93d68a3..984fba1c8dbd 100644 --- a/crates/router/src/types/api/payouts.rs +++ b/crates/router/src/types/api/payouts.rs @@ -1,9 +1,10 @@ pub use api_models::payouts::{ - AchBankTransfer, BacsBankTransfer, Bank as BankPayout, CardPayout, PayoutActionRequest, - PayoutAttemptResponse, PayoutCreateRequest, PayoutCreateResponse, PayoutLinkResponse, - PayoutListConstraints, PayoutListFilterConstraints, PayoutListFilters, PayoutListResponse, - PayoutMethodData, PayoutRequest, PayoutRetrieveBody, PayoutRetrieveRequest, PixBankTransfer, - SepaBankTransfer, Wallet as WalletPayout, + AchBankTransfer, BacsBankTransfer, Bank as BankPayout, CardPayout, PaymentMethodTypeInfo, + PayoutActionRequest, PayoutAttemptResponse, PayoutCreateRequest, PayoutCreateResponse, + PayoutEnabledPaymentMethodsInfo, PayoutLinkResponse, PayoutListConstraints, + PayoutListFilterConstraints, PayoutListFilters, PayoutListResponse, PayoutMethodData, + PayoutRequest, PayoutRetrieveBody, PayoutRetrieveRequest, PixBankTransfer, + RequiredFieldsOverrideRequest, SepaBankTransfer, Wallet as WalletPayout, }; pub use hyperswitch_domain_models::router_flow_types::payouts::{ PoCancel, PoCreate, PoEligibility, PoFulfill, PoQuote, PoRecipient, PoRecipientAccount, PoSync, diff --git a/crates/router/src/types/domain/payments.rs b/crates/router/src/types/domain/payments.rs index 1cf36bc44281..bfd7b54f5563 100644 --- a/crates/router/src/types/domain/payments.rs +++ b/crates/router/src/types/domain/payments.rs @@ -6,8 +6,9 @@ pub use hyperswitch_domain_models::payment_method_data::{ GooglePayThirdPartySdkData, GooglePayWalletData, GpayTokenizationData, IndomaretVoucherData, KakaoPayRedirection, MbWayRedirection, MifinityData, OpenBankingData, PayLaterData, PaymentMethodData, RealTimePaymentData, SamsungPayWalletData, SepaAndBacsBillingDetails, - SwishQrData, TokenizedBankRedirectValue1, TokenizedBankRedirectValue2, - TokenizedBankTransferValue1, TokenizedBankTransferValue2, TokenizedCardValue1, - TokenizedCardValue2, TokenizedWalletValue1, TokenizedWalletValue2, TouchNGoRedirection, - UpiCollectData, UpiData, UpiIntentData, VoucherData, WalletData, WeChatPayQr, + SwishQrData, TokenizedBankDebitValue1, TokenizedBankDebitValue2, TokenizedBankRedirectValue1, + TokenizedBankRedirectValue2, TokenizedBankTransferValue1, TokenizedBankTransferValue2, + TokenizedCardValue1, TokenizedCardValue2, TokenizedWalletValue1, TokenizedWalletValue2, + TouchNGoRedirection, UpiCollectData, UpiData, UpiIntentData, VoucherData, WalletData, + WeChatPayQr, }; diff --git a/crates/router/src/types/domain/types.rs b/crates/router/src/types/domain/types.rs index 10657adc1228..082e8df4bfc9 100644 --- a/crates/router/src/types/domain/types.rs +++ b/crates/router/src/types/domain/types.rs @@ -10,6 +10,8 @@ impl From<&crate::SessionState> for KeyManagerState { enabled: conf.enabled, url: conf.url.clone(), client_idle_timeout: state.conf.proxy.idle_pool_connection_timeout, + #[cfg(feature = "km_forward_x_request_id")] + request_id: state.request_id, #[cfg(feature = "keymanager_mtls")] cert: conf.cert.clone(), #[cfg(feature = "keymanager_mtls")] diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs index 36af30cd9fa4..8d6fb1a8ffb8 100644 --- a/crates/router/src/types/domain/user.rs +++ b/crates/router/src/types/domain/user.rs @@ -5,7 +5,7 @@ use api_models::{ }; use common_enums::EntityType; use common_utils::{ - crypto::Encryptable, errors::CustomResult, id_type, new_type::MerchantName, pii, type_name, + crypto::Encryptable, id_type, new_type::MerchantName, pii, type_name, types::keymanager::Identifier, }; use diesel_models::{ @@ -28,7 +28,7 @@ use crate::{ consts, core::{ admin, - errors::{self, UserErrors, UserResult}, + errors::{UserErrors, UserResult}, }, db::{user_role::InsertUserRolePayload, GlobalStorageInterface}, routes::SessionState, @@ -867,22 +867,6 @@ impl UserFromStorage { self.0.email.clone() } - pub async fn get_role_from_db(&self, state: SessionState) -> UserResult { - state - .store - .find_user_role_by_user_id(&self.0.user_id, UserRoleVersion::V1) - .await - .change_context(UserErrors::InternalServerError) - } - - pub async fn get_roles_from_db(&self, state: &SessionState) -> UserResult> { - state - .store - .list_user_roles_by_user_id_and_version(&self.0.user_id, UserRoleVersion::V1) - .await - .change_context(UserErrors::InternalServerError) - } - #[cfg(feature = "email")] pub fn get_verification_days_left(&self, state: &SessionState) -> UserResult> { if self.0.is_verified { @@ -930,21 +914,6 @@ impl UserFromStorage { Ok(days_left_for_password_rotate.whole_days() < 0) } - pub async fn get_role_from_db_by_merchant_id( - &self, - state: &SessionState, - merchant_id: &id_type::MerchantId, - ) -> CustomResult { - state - .store - .find_user_role_by_user_id_merchant_id( - self.get_user_id(), - merchant_id, - UserRoleVersion::V1, - ) - .await - } - pub async fn get_or_create_key_store(&self, state: &SessionState) -> UserResult { let master_key = state.store.get_master_key(); let key_manager_state = &state.into(); @@ -1076,6 +1045,7 @@ impl From for user_role_api::PermissionModule { info::PermissionModule::SurchargeDecisionManager => Self::SurchargeDecisionManager, info::PermissionModule::AccountCreate => Self::AccountCreate, info::PermissionModule::Payouts => Self::Payouts, + info::PermissionModule::Recon => Self::Recon, } } } @@ -1253,7 +1223,7 @@ where } } - async fn insert_v1_and_v2_in_db_and_get_v1( + async fn insert_v1_and_v2_in_db_and_get_v2( state: &SessionState, v1_role: UserRoleNew, v2_role: UserRoleNew, @@ -1264,10 +1234,9 @@ where .await .change_context(UserErrors::InternalServerError)?; - // Returning v1 role so other code which was not migrated doesn't break inserted_roles .into_iter() - .find(|role| role.version == UserRoleVersion::V1) + .find(|role| role.version == UserRoleVersion::V2) .ok_or(report!(UserErrors::InternalServerError)) } } @@ -1323,7 +1292,7 @@ impl NewUserRole { entity_type: EntityType::Organization, }); - Self::insert_v1_and_v2_in_db_and_get_v1(state, new_v1_role, new_v2_role).await + Self::insert_v1_and_v2_in_db_and_get_v2(state, new_v1_role, new_v2_role).await } } @@ -1343,7 +1312,7 @@ impl NewUserRole { entity_type: EntityType::Merchant, }); - Self::insert_v1_and_v2_in_db_and_get_v1(state, new_v1_role, new_v2_role).await + Self::insert_v1_and_v2_in_db_and_get_v2(state, new_v1_role, new_v2_role).await } } @@ -1366,7 +1335,7 @@ impl NewUserRole { entity_type: EntityType::Internal, }); - Self::insert_v1_and_v2_in_db_and_get_v1(state, new_v1_role, new_v2_role).await + Self::insert_v1_and_v2_in_db_and_get_v2(state, new_v1_role, new_v2_role).await } } diff --git a/crates/router/src/types/domain/user/decision_manager.rs b/crates/router/src/types/domain/user/decision_manager.rs index af9918af6738..3fc05285ea89 100644 --- a/crates/router/src/types/domain/user/decision_manager.rs +++ b/crates/router/src/types/domain/user/decision_manager.rs @@ -1,8 +1,5 @@ use common_enums::TokenPurpose; -use diesel_models::{ - enums::{UserRoleVersion, UserStatus}, - user_role::UserRole, -}; +use diesel_models::{enums::UserStatus, user_role::UserRole}; use error_stack::{report, ResultExt}; use masking::Secret; @@ -67,10 +64,21 @@ impl SPTFlow { Self::ForceSetPassword => user .is_password_rotate_required(state) .map(|rotate_required| rotate_required && !path.contains(&TokenPurpose::SSO)), - Self::MerchantSelect => user - .get_roles_from_db(state) + Self::MerchantSelect => Ok(state + .store + .list_user_roles_by_user_id(ListUserRolesByUserIdPayload { + user_id: user.get_user_id(), + org_id: None, + merchant_id: None, + profile_id: None, + entity_id: None, + version: None, + status: Some(UserStatus::Active), + limit: Some(1), + }) .await - .map(|roles| !roles.iter().any(|role| role.status == UserStatus::Active)), + .change_context(UserErrors::InternalServerError)? + .is_empty()), } } @@ -105,15 +113,17 @@ impl JWTFlow { Ok(true) } - pub async fn generate_jwt_without_profile( + pub async fn generate_jwt( self, state: &SessionState, next_flow: &NextFlow, user_role: &UserRole, ) -> UserResult> { + let (merchant_id, profile_id) = + utils::user_role::get_single_merchant_id_and_profile_id(state, user_role).await?; auth::AuthToken::new_token( next_flow.user.get_user_id().to_string(), - utils::user_role::get_single_merchant_id(state, user_role).await?, + merchant_id, user_role.role_id.clone(), &state.conf, user_role @@ -121,7 +131,7 @@ impl JWTFlow { .clone() .ok_or(report!(UserErrors::InternalServerError)) .attach_printable("org_id not found")?, - None, + Some(profile_id), ) .await .map(|token| token.into()) @@ -296,7 +306,7 @@ impl NextFlow { merchant_id: None, profile_id: None, entity_id: None, - version: Some(UserRoleVersion::V1), + version: None, status: Some(UserStatus::Active), limit: Some(1), }) @@ -307,9 +317,7 @@ impl NextFlow { utils::user_role::set_role_permissions_in_cache_by_user_role(state, &user_role) .await; - jwt_flow - .generate_jwt_without_profile(state, self, &user_role) - .await + jwt_flow.generate_jwt(state, self, &user_role).await } } } @@ -329,9 +337,7 @@ impl NextFlow { utils::user_role::set_role_permissions_in_cache_by_user_role(state, user_role) .await; - jwt_flow - .generate_jwt_without_profile(state, self, user_role) - .await + jwt_flow.generate_jwt(state, self, user_role).await } } } diff --git a/crates/router/src/types/storage/payment_attempt.rs b/crates/router/src/types/storage/payment_attempt.rs index 950a666dd61b..782d5ae76a0d 100644 --- a/crates/router/src/types/storage/payment_attempt.rs +++ b/crates/router/src/types/storage/payment_attempt.rs @@ -184,6 +184,8 @@ mod tests { customer_acceptance: Default::default(), profile_id: common_utils::generate_profile_id_of_default_length(), organization_id: Default::default(), + shipping_cost: Default::default(), + order_tax_amount: Default::default(), }; let store = state @@ -270,6 +272,8 @@ mod tests { customer_acceptance: Default::default(), profile_id: common_utils::generate_profile_id_of_default_length(), organization_id: Default::default(), + shipping_cost: Default::default(), + order_tax_amount: Default::default(), }; let store = state .stores @@ -369,6 +373,8 @@ mod tests { customer_acceptance: Default::default(), profile_id: common_utils::generate_profile_id_of_default_length(), organization_id: Default::default(), + shipping_cost: Default::default(), + order_tax_amount: Default::default(), }; let store = state .stores diff --git a/crates/router/src/types/storage/refund.rs b/crates/router/src/types/storage/refund.rs index 1860771d026f..8e0040abfae2 100644 --- a/crates/router/src/types/storage/refund.rs +++ b/crates/router/src/types/storage/refund.rs @@ -12,6 +12,7 @@ use diesel_models::{ schema::refund::dsl, }; use error_stack::ResultExt; +use hyperswitch_domain_models::refunds; use crate::{connection::PgPooledConn, logger}; @@ -20,7 +21,7 @@ pub trait RefundDbExt: Sized { async fn filter_by_constraints( conn: &PgPooledConn, merchant_id: &common_utils::id_type::MerchantId, - refund_list_details: &api_models::refunds::RefundListRequest, + refund_list_details: &refunds::RefundListConstraints, limit: i64, offset: i64, ) -> CustomResult, errors::DatabaseError>; @@ -34,8 +35,14 @@ pub trait RefundDbExt: Sized { async fn get_refunds_count( conn: &PgPooledConn, merchant_id: &common_utils::id_type::MerchantId, - refund_list_details: &api_models::refunds::RefundListRequest, + refund_list_details: &refunds::RefundListConstraints, ) -> CustomResult; + + async fn get_refund_status_with_count( + conn: &PgPooledConn, + merchant_id: &common_utils::id_type::MerchantId, + time_range: &api_models::payments::TimeRange, + ) -> CustomResult, errors::DatabaseError>; } #[async_trait::async_trait] @@ -43,7 +50,7 @@ impl RefundDbExt for Refund { async fn filter_by_constraints( conn: &PgPooledConn, merchant_id: &common_utils::id_type::MerchantId, - refund_list_details: &api_models::refunds::RefundListRequest, + refund_list_details: &refunds::RefundListConstraints, limit: i64, offset: i64, ) -> CustomResult, errors::DatabaseError> { @@ -88,7 +95,7 @@ impl RefundDbExt for Refund { match &refund_list_details.profile_id { Some(profile_id) => { filter = filter - .filter(dsl::profile_id.eq(profile_id.to_owned())) + .filter(dsl::profile_id.eq_any(profile_id.to_owned())) .limit(limit) .offset(offset); } @@ -206,7 +213,7 @@ impl RefundDbExt for Refund { async fn get_refunds_count( conn: &PgPooledConn, merchant_id: &common_utils::id_type::MerchantId, - refund_list_details: &api_models::refunds::RefundListRequest, + refund_list_details: &refunds::RefundListConstraints, ) -> CustomResult { let mut filter = ::table() .count() @@ -237,7 +244,7 @@ impl RefundDbExt for Refund { } } if let Some(profile_id) = &refund_list_details.profile_id { - filter = filter.filter(dsl::profile_id.eq(profile_id.to_owned())); + filter = filter.filter(dsl::profile_id.eq_any(profile_id.to_owned())); } if let Some(time_range) = refund_list_details.time_range { @@ -288,4 +295,33 @@ impl RefundDbExt for Refund { .change_context(errors::DatabaseError::NotFound) .attach_printable_lazy(|| "Error filtering count of refunds") } + + async fn get_refund_status_with_count( + conn: &PgPooledConn, + merchant_id: &common_utils::id_type::MerchantId, + time_range: &api_models::payments::TimeRange, + ) -> CustomResult, errors::DatabaseError> { + let mut query = ::table() + .group_by(dsl::refund_status) + .select((dsl::refund_status, diesel::dsl::count_star())) + .filter(dsl::merchant_id.eq(merchant_id.to_owned())) + .into_boxed(); + + query = query.filter(dsl::created_at.ge(time_range.start_time)); + + query = match time_range.end_time { + Some(ending_at) => query.filter(dsl::created_at.le(ending_at)), + None => query, + }; + + logger::debug!(filter = %diesel::debug_query::(&query).to_string()); + + db_metrics::track_database_call::<::Table, _, _>( + query.get_results_async::<(RefundStatus, i64)>(conn), + db_metrics::DatabaseOperation::Count, + ) + .await + .change_context(errors::DatabaseError::NotFound) + .attach_printable_lazy(|| "Error filtering status count of refunds") + } } diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index ba2f01e93ed0..46210d4dd09e 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -7,7 +7,7 @@ use api_models::{ use common_utils::{ consts::X_HS_LATENCY, crypto::Encryptable, - ext_traits::{StringExt, ValueExt}, + ext_traits::{Encode, StringExt, ValueExt}, fp_utils::when, pii, types::MinorUnit, @@ -15,7 +15,7 @@ use common_utils::{ use diesel_models::enums as storage_enums; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::payments::payment_intent::CustomerData; -use masking::{ExposeInterface, PeekInterface}; +use masking::{ExposeInterface, PeekInterface, Secret}; use super::domain; use crate::{ @@ -274,7 +274,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { api_enums::Connector::Cryptopay => Self::Cryptopay, api_enums::Connector::Cybersource => Self::Cybersource, api_enums::Connector::Datatrans => Self::Datatrans, - // api_enums::Connector::Deutschebank => Self::Deutschebank, + api_enums::Connector::Deutschebank => Self::Deutschebank, api_enums::Connector::Dlocal => Self::Dlocal, api_enums::Connector::Ebanx => Self::Ebanx, api_enums::Connector::Fiserv => Self::Fiserv, @@ -305,7 +305,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { // api_enums::Connector::Nexixpay => Self::Nexixpay, api_enums::Connector::Nmi => Self::Nmi, api_enums::Connector::Noon => Self::Noon, - // api_enums::Connector::Novalnet => Self::Novalnet, + api_enums::Connector::Novalnet => Self::Novalnet, api_enums::Connector::Nuvei => Self::Nuvei, api_enums::Connector::Opennode => Self::Opennode, api_enums::Connector::Paybox => Self::Paybox, @@ -334,6 +334,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { api_enums::Connector::Stax => Self::Stax, api_enums::Connector::Stripe => Self::Stripe, // api_enums::Connector::Taxjar => Self::Taxjar, + // api_enums::Connector::Thunes => Self::Thunes, api_enums::Connector::Trustpay => Self::Trustpay, api_enums::Connector::Tsys => Self::Tsys, api_enums::Connector::Volt => Self::Volt, @@ -363,6 +364,11 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { message: "threedsecureio is not a routable connector".to_string(), })? } + api_enums::Connector::Taxjar => { + Err(common_utils::errors::ValidationError::InvalidValue { + message: "Taxjar is not a routable connector".to_string(), + })? + } }) } } @@ -783,6 +789,52 @@ impl<'a> From<&'a domain::Address> for api_types::Address { } } +impl ForeignFrom for api_types::Address { + fn foreign_from(address: domain::Address) -> Self { + // If all the fields of address are none, then pass the address as None + let address_details = if address.city.is_none() + && address.line1.is_none() + && address.line2.is_none() + && address.line3.is_none() + && address.state.is_none() + && address.country.is_none() + && address.zip.is_none() + && address.first_name.is_none() + && address.last_name.is_none() + { + None + } else { + Some(api_types::AddressDetails { + city: address.city.clone(), + country: address.country, + line1: address.line1.clone().map(Encryptable::into_inner), + line2: address.line2.clone().map(Encryptable::into_inner), + line3: address.line3.clone().map(Encryptable::into_inner), + state: address.state.clone().map(Encryptable::into_inner), + zip: address.zip.clone().map(Encryptable::into_inner), + first_name: address.first_name.clone().map(Encryptable::into_inner), + last_name: address.last_name.clone().map(Encryptable::into_inner), + }) + }; + + // If all the fields of phone are none, then pass the phone as None + let phone_details = if address.phone_number.is_none() && address.country_code.is_none() { + None + } else { + Some(api_types::PhoneDetails { + number: address.phone_number.clone().map(Encryptable::into_inner), + country_code: address.country_code.clone(), + }) + }; + + Self { + address: address_details, + phone: phone_details, + email: address.email.clone().map(pii::Email::from), + } + } +} + impl ForeignFrom<( diesel_models::api_keys::ApiKey, @@ -1097,13 +1149,29 @@ impl ForeignTryFrom } None => None, }; + // parse the connector_account_details into ConnectorAuthType + let connector_account_details: hyperswitch_domain_models::router_data::ConnectorAuthType = + item.connector_account_details + .clone() + .into_inner() + .parse_value("ConnectorAuthType") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while parsing value for ConnectorAuthType")?; + // get the masked keys from the ConnectorAuthType and encode it to secret value + let masked_connector_account_details = Secret::new( + connector_account_details + .get_masked_keys() + .encode_to_value() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to encode ConnectorAuthType")?, + ); #[cfg(feature = "v2")] let response = Self { id: item.get_id(), connector_type: item.connector_type, connector_name: item.connector_name, connector_label: item.connector_label, - connector_account_details: item.connector_account_details.into_inner(), + connector_account_details: masked_connector_account_details, disabled: item.disabled, payment_methods_enabled, metadata: item.metadata, @@ -1143,7 +1211,7 @@ impl ForeignTryFrom connector_name: item.connector_name, connector_label: item.connector_label, merchant_connector_id: item.merchant_connector_id, - connector_account_details: item.connector_account_details.into_inner(), + connector_account_details: masked_connector_account_details, test_mode: item.test_mode, disabled: item.disabled, payment_methods_enabled, @@ -1793,6 +1861,7 @@ impl ForeignFrom fn foreign_from(item: api_models::admin::BusinessPayoutLinkConfig) -> Self { Self { config: item.config.foreign_into(), + form_layout: item.form_layout, payout_test_mode: item.payout_test_mode, } } @@ -1804,6 +1873,7 @@ impl ForeignFrom fn foreign_from(item: diesel_models::business_profile::BusinessPayoutLinkConfig) -> Self { Self { config: item.config.foreign_into(), + form_layout: item.form_layout, payout_test_mode: item.payout_test_mode, } } diff --git a/crates/router/src/utils.rs b/crates/router/src/utils.rs index ea687af796f7..12a26cf7c8b3 100644 --- a/crates/router/src/utils.rs +++ b/crates/router/src/utils.rs @@ -20,7 +20,6 @@ use api_models::{ payments::{self}, webhooks, }; -use base64::Engine; use common_utils::types::keymanager::KeyManagerState; pub use common_utils::{ crypto, @@ -35,12 +34,11 @@ use common_utils::{ types::keymanager::{Identifier, ToEncryptable}, }; use error_stack::ResultExt; +pub use hyperswitch_connectors::utils::QrImage; use hyperswitch_domain_models::payments::PaymentIntent; #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] use hyperswitch_domain_models::type_encryption::{crypto_operation, CryptoOperation}; -use image::Luma; use nanoid::nanoid; -use qrcode; use router_env::metrics::add_attributes; use serde::de::DeserializeOwned; use serde_json::Value; @@ -48,6 +46,8 @@ use tracing_futures::Instrument; use uuid::Uuid; pub use self::ext_traits::{OptionExt, ValidateCall}; +#[cfg(feature = "v1")] +use crate::core::webhooks as webhooks_core; #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] use crate::types::storage; use crate::{ @@ -55,7 +55,7 @@ use crate::{ core::{ authentication::types::ExternalThreeDSConnectorMetadata, errors::{self, CustomResult, RouterResult, StorageErrorExt}, - webhooks as webhooks_core, + payments as payments_core, }, logger, routes::{metrics, SessionState}, @@ -166,38 +166,8 @@ impl ConnectorResponseExt pub fn get_payout_attempt_id(payout_id: impl std::fmt::Display, attempt_count: i16) -> String { format!("{payout_id}_{attempt_count}") } -#[derive(Debug)] -pub struct QrImage { - pub data: String, -} - -impl QrImage { - pub fn new_from_data( - data: String, - ) -> Result> { - let qr_code = qrcode::QrCode::new(data.as_bytes()) - .change_context(common_utils::errors::QrCodeError::FailedToCreateQrCode)?; - - // Renders the QR code into an image. - let qrcode_image_buffer = qr_code.render::>().build(); - let qrcode_dynamic_image = image::DynamicImage::ImageLuma8(qrcode_image_buffer); - - let mut image_bytes = std::io::BufWriter::new(std::io::Cursor::new(Vec::new())); - - // Encodes qrcode_dynamic_image and write it to image_bytes - let _ = qrcode_dynamic_image.write_to(&mut image_bytes, image::ImageFormat::Png); - - let image_data_source = format!( - "{},{}", - consts::QR_IMAGE_DATA_SOURCE_STRING, - consts::BASE64_ENGINE.encode(image_bytes.buffer()) - ); - Ok(Self { - data: image_data_source, - }) - } -} +#[cfg(feature = "v1")] pub async fn find_payment_intent_from_payment_id_type( state: &SessionState, payment_id_type: payments::PaymentIdType, @@ -261,6 +231,7 @@ pub async fn find_payment_intent_from_payment_id_type( } } +#[cfg(feature = "v1")] pub async fn find_payment_intent_from_refund_id_type( state: &SessionState, refund_id_type: webhooks::RefundIdType, @@ -307,6 +278,7 @@ pub async fn find_payment_intent_from_refund_id_type( .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound) } +#[cfg(feature = "v1")] pub async fn find_payment_intent_from_mandate_id_type( state: &SessionState, mandate_id_type: webhooks::MandateIdType, @@ -346,6 +318,7 @@ pub async fn find_payment_intent_from_mandate_id_type( .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound) } +#[cfg(feature = "v1")] pub async fn find_mca_from_authentication_id_type( state: &SessionState, authentication_id_type: webhooks::AuthenticationIdType, @@ -561,6 +534,7 @@ pub async fn get_mca_from_payout_attempt( } } +#[cfg(feature = "v1")] pub async fn get_mca_from_object_reference_id( state: &SessionState, object_reference_id: webhooks::ObjectReferenceId, @@ -1084,12 +1058,13 @@ pub fn check_if_pull_mechanism_for_external_3ds_enabled_from_connector_metadata( .unwrap_or(true) } +#[cfg(feature = "v2")] #[allow(clippy::too_many_arguments)] -pub async fn trigger_payments_webhook( +pub async fn trigger_payments_webhook( merchant_account: domain::MerchantAccount, business_profile: domain::BusinessProfile, key_store: &domain::MerchantKeyStore, - payment_data: crate::core::payments::PaymentData, + payment_data: D, customer: Option, state: &SessionState, operation: Op, @@ -1097,12 +1072,32 @@ pub async fn trigger_payments_webhook( where F: Send + Clone + Sync, Op: Debug, + D: payments_core::OperationSessionGetters, { - let status = payment_data.payment_intent.status; - let payment_id = payment_data.payment_intent.payment_id.clone(); + todo!() +} + +#[cfg(feature = "v1")] +#[allow(clippy::too_many_arguments)] +pub async fn trigger_payments_webhook( + merchant_account: domain::MerchantAccount, + business_profile: domain::BusinessProfile, + key_store: &domain::MerchantKeyStore, + payment_data: D, + customer: Option, + state: &SessionState, + operation: Op, +) -> RouterResult<()> +where + F: Send + Clone + Sync, + Op: Debug, + D: payments_core::OperationSessionGetters, +{ + let status = payment_data.get_payment_intent().status; + let payment_id = payment_data.get_payment_intent().get_id().to_owned(); + let captures = payment_data - .multiple_capture_data - .clone() + .get_multiple_capture_data() .map(|multiple_capture_data| { multiple_capture_data .get_all_captures() @@ -1186,13 +1181,3 @@ pub async fn flatten_join_error(handle: Handle) -> RouterResult { .attach_printable("Join Error"), } } - -#[cfg(test)] -mod tests { - use crate::utils; - #[test] - fn test_image_data_source_url() { - let qr_image_data_source_url = utils::QrImage::new_from_data("Hyperswitch".to_string()); - assert!(qr_image_data_source_url.is_ok()); - } -} diff --git a/crates/router/src/utils/user.rs b/crates/router/src/utils/user.rs index a1bd6972dab5..3da24f2be7d1 100644 --- a/crates/router/src/utils/user.rs +++ b/crates/router/src/utils/user.rs @@ -108,13 +108,25 @@ pub async fn generate_jwt_auth_token_without_profile( Ok(Secret::new(token)) } +pub async fn generate_jwt_auth_token_with_attributes_without_profile( + state: &SessionState, + user_id: String, + merchant_id: id_type::MerchantId, + org_id: id_type::OrganizationId, + role_id: String, +) -> UserResult> { + let token = + AuthToken::new_token(user_id, merchant_id, role_id, &state.conf, org_id, None).await?; + Ok(Secret::new(token)) +} + pub async fn generate_jwt_auth_token_with_attributes( state: &SessionState, user_id: String, merchant_id: id_type::MerchantId, org_id: id_type::OrganizationId, role_id: String, - profile_id: Option, + profile_id: id_type::ProfileId, ) -> UserResult> { let token = AuthToken::new_token( user_id, @@ -122,7 +134,7 @@ pub async fn generate_jwt_auth_token_with_attributes( role_id, &state.conf, org_id, - profile_id, + Some(profile_id), ) .await?; Ok(Secret::new(token)) diff --git a/crates/router/src/utils/user/sample_data.rs b/crates/router/src/utils/user/sample_data.rs index a877ad731561..7147c5071c73 100644 --- a/crates/router/src/utils/user/sample_data.rs +++ b/crates/router/src/utils/user/sample_data.rs @@ -15,6 +15,7 @@ use crate::{ SessionState, }; +#[cfg(feature = "v1")] #[allow(clippy::type_complexity)] pub async fn generate_sample_data( state: &SessionState, @@ -248,6 +249,9 @@ pub async fn generate_sample_data( shipping_details: None, is_payment_processor_token_flow: None, organization_id: org_id.clone(), + shipping_cost: None, + tax_details: None, + skip_external_tax_calculation: None, }; let payment_attempt = PaymentAttemptBatchNew { attempt_id: attempt_id.clone(), @@ -327,6 +331,8 @@ pub async fn generate_sample_data( customer_acceptance: None, profile_id: profile_id.clone(), organization_id: org_id.clone(), + shipping_cost: None, + order_tax_amount: None, }; let refund = if refunds_count < number_of_refunds && !is_failed_payment { diff --git a/crates/router/src/utils/user_role.rs b/crates/router/src/utils/user_role.rs index aeb866d8d03e..68bee0f022d6 100644 --- a/crates/router/src/utils/user_role.rs +++ b/crates/router/src/utils/user_role.rs @@ -54,6 +54,7 @@ impl From for user_role_api::Permission { Permission::PayoutRead => Self::PayoutRead, Permission::PayoutWrite => Self::PayoutWrite, Permission::GenerateReport => Self::GenerateReport, + Permission::ReconAdmin => Self::ReconAdmin, } } } @@ -156,10 +157,10 @@ pub async fn set_role_permissions_in_cache_if_required( .change_context(UserErrors::InternalServerError) .attach_printable("Error getting role_info from role_id")?; - authz::set_permissions_in_cache( + authz::set_role_info_in_cache( state, role_id, - &role_info.get_permissions_set().into_iter().collect(), + &role_info, i64::try_from(consts::JWT_TOKEN_TIME_IN_SECS) .change_context(UserErrors::InternalServerError)?, ) @@ -358,3 +359,42 @@ pub async fn get_lineage_for_user_id_and_entity_for_accepting_invite( } } } + +pub async fn get_single_merchant_id_and_profile_id( + state: &SessionState, + user_role: &UserRole, +) -> UserResult<(id_type::MerchantId, id_type::ProfileId)> { + let merchant_id = get_single_merchant_id(state, user_role).await?; + let (_, entity_type) = user_role + .get_entity_id_and_type() + .ok_or(UserErrors::InternalServerError)?; + let profile_id = match entity_type { + EntityType::Organization | EntityType::Merchant | EntityType::Internal => { + let key_store = state + .store + .get_merchant_key_store_by_merchant_id( + &state.into(), + &merchant_id, + &state.store.get_master_key().to_vec().into(), + ) + .await + .change_context(UserErrors::InternalServerError)?; + + state + .store + .list_business_profile_by_merchant_id(&state.into(), &key_store, &merchant_id) + .await + .change_context(UserErrors::InternalServerError)? + .pop() + .ok_or(UserErrors::InternalServerError)? + .get_id() + .to_owned() + } + EntityType::Profile => user_role + .profile_id + .clone() + .ok_or(UserErrors::InternalServerError)?, + }; + + Ok((merchant_id, profile_id)) +} diff --git a/crates/router/src/workflows.rs b/crates/router/src/workflows.rs index 2f858c59809a..e86d49faf6cb 100644 --- a/crates/router/src/workflows.rs +++ b/crates/router/src/workflows.rs @@ -2,8 +2,12 @@ pub mod api_key_expiry; #[cfg(feature = "payouts")] pub mod attach_payout_account_workflow; +#[cfg(feature = "v1")] pub mod outgoing_webhook_retry; +#[cfg(feature = "v1")] pub mod payment_method_status_update; pub mod payment_sync; +#[cfg(feature = "v1")] pub mod refund_router; +#[cfg(feature = "v1")] pub mod tokenized_data; diff --git a/crates/router/src/workflows/outgoing_webhook_retry.rs b/crates/router/src/workflows/outgoing_webhook_retry.rs index 108316e73392..138b1477d6a3 100644 --- a/crates/router/src/workflows/outgoing_webhook_retry.rs +++ b/crates/router/src/workflows/outgoing_webhook_retry.rs @@ -19,7 +19,10 @@ use scheduler::{ #[cfg(feature = "payouts")] use crate::core::payouts; use crate::{ - core::webhooks::{self as webhooks_core, types::OutgoingWebhookTrackingData}, + core::{ + payments, + webhooks::{self as webhooks_core, types::OutgoingWebhookTrackingData}, + }, db::StorageInterface, errors, logger, routes::{app::ReqState, SessionState}, @@ -375,38 +378,44 @@ async fn get_outgoing_webhook_content_and_event_type( ..Default::default() }; - let payments_response = - match Box::pin(payments_core::( - state, - req_state, - merchant_account, - None, - key_store, - PaymentStatus, - request, - AuthFlow::Client, - CallConnectorAction::Avoid, - None, - HeaderPayload::default(), - )) - .await? - { - ApplicationResponse::Json(payments_response) - | ApplicationResponse::JsonWithHeaders((payments_response, _)) => { - Ok(payments_response) - } - ApplicationResponse::StatusOk - | ApplicationResponse::TextPlain(_) - | ApplicationResponse::JsonForRedirection(_) - | ApplicationResponse::Form(_) - | ApplicationResponse::GenericLinkForm(_) - | ApplicationResponse::PaymentLinkForm(_) - | ApplicationResponse::FileData(_) => { - Err(errors::ProcessTrackerError::ResourceFetchingFailed { - resource_name: tracking_data.primary_object_id.clone(), - }) - } - }?; + let payments_response = match Box::pin(payments_core::< + PSync, + PaymentsResponse, + _, + _, + _, + payments::PaymentData, + >( + state, + req_state, + merchant_account, + None, + key_store, + PaymentStatus, + request, + AuthFlow::Client, + CallConnectorAction::Avoid, + None, + HeaderPayload::default(), + )) + .await? + { + ApplicationResponse::Json(payments_response) + | ApplicationResponse::JsonWithHeaders((payments_response, _)) => { + Ok(payments_response) + } + ApplicationResponse::StatusOk + | ApplicationResponse::TextPlain(_) + | ApplicationResponse::JsonForRedirection(_) + | ApplicationResponse::Form(_) + | ApplicationResponse::GenericLinkForm(_) + | ApplicationResponse::PaymentLinkForm(_) + | ApplicationResponse::FileData(_) => { + Err(errors::ProcessTrackerError::ResourceFetchingFailed { + resource_name: tracking_data.primary_object_id.clone(), + }) + } + }?; let event_type = Option::::foreign_from(payments_response.status); logger::debug!(current_resource_status=%payments_response.status); diff --git a/crates/router/src/workflows/payment_sync.rs b/crates/router/src/workflows/payment_sync.rs index 207af2ea4bf8..3701418c8f17 100644 --- a/crates/router/src/workflows/payment_sync.rs +++ b/crates/router/src/workflows/payment_sync.rs @@ -28,6 +28,16 @@ pub struct PaymentsSyncWorkflow; #[async_trait::async_trait] impl ProcessTrackerWorkflow for PaymentsSyncWorkflow { + #[cfg(feature = "v2")] + async fn execute_workflow<'a>( + &'a self, + state: &'a SessionState, + process: storage::ProcessTracker, + ) -> Result<(), sch_errors::ProcessTrackerError> { + todo!() + } + + #[cfg(feature = "v1")] async fn execute_workflow<'a>( &'a self, state: &'a SessionState, @@ -62,8 +72,14 @@ impl ProcessTrackerWorkflow for PaymentsSyncWorkflow { .await?; // TODO: Add support for ReqState in PT flows - let (mut payment_data, _, customer, _, _) = Box::pin( - payment_flows::payments_operation_core::( + let (mut payment_data, _, customer, _, _) = + Box::pin(payment_flows::payments_operation_core::< + api::PSync, + _, + _, + _, + payment_flows::PaymentData, + >( state, state.get_req_state(), merchant_account.clone(), @@ -75,9 +91,8 @@ impl ProcessTrackerWorkflow for PaymentsSyncWorkflow { services::AuthFlow::Client, None, api::HeaderPayload::default(), - ), - ) - .await?; + )) + .await?; let terminal_status = [ enums::AttemptStatus::RouterDeclined, diff --git a/crates/router/tests/connectors/payme.rs b/crates/router/tests/connectors/payme.rs index 605f63d0f64c..e85c57df622f 100644 --- a/crates/router/tests/connectors/payme.rs +++ b/crates/router/tests/connectors/payme.rs @@ -88,6 +88,7 @@ fn payment_method_details() -> Option { sub_category: None, brand: None, product_type: None, + product_tax_code: None, }]), router_return_url: Some("https://hyperswitch.io".to_string()), webhook_url: Some("https://hyperswitch.io".to_string()), @@ -388,6 +389,7 @@ async fn should_fail_payment_for_incorrect_cvc() { sub_category: None, brand: None, product_type: None, + product_tax_code: None, }]), router_return_url: Some("https://hyperswitch.io".to_string()), webhook_url: Some("https://hyperswitch.io".to_string()), @@ -427,6 +429,7 @@ async fn should_fail_payment_for_invalid_exp_month() { sub_category: None, brand: None, product_type: None, + product_tax_code: None, }]), router_return_url: Some("https://hyperswitch.io".to_string()), webhook_url: Some("https://hyperswitch.io".to_string()), @@ -466,6 +469,7 @@ async fn should_fail_payment_for_incorrect_expiry_year() { sub_category: None, brand: None, product_type: None, + product_tax_code: None, }]), router_return_url: Some("https://hyperswitch.io".to_string()), webhook_url: Some("https://hyperswitch.io".to_string()), diff --git a/crates/router/tests/connectors/sample_auth.toml b/crates/router/tests/connectors/sample_auth.toml index fe5820d34e5b..f61770a5bde5 100644 --- a/crates/router/tests/connectors/sample_auth.toml +++ b/crates/router/tests/connectors/sample_auth.toml @@ -276,4 +276,9 @@ api_key = "API Key" api_key="API Key" [deutschebank] -api_key="API Key" \ No newline at end of file +api_key = "Client ID" +key1 = "Merchant ID" +api_secret = "Client Key" + +[thunes] +api_key="API Key" diff --git a/crates/router/tests/connectors/thunes.rs b/crates/router/tests/connectors/thunes.rs new file mode 100644 index 000000000000..379e102a53c1 --- /dev/null +++ b/crates/router/tests/connectors/thunes.rs @@ -0,0 +1,402 @@ +use masking::Secret; +// use router::{ +// types::{self, api, storage::enums, +// }}; + +use crate::utils::{self, ConnectorActions}; +use test_utils::connector_auth; + +#[derive(Clone, Copy)] +struct ThunesTest; +impl ConnectorActions for ThunesTest {} +impl utils::Connector for ThunesTest { + fn get_data(&self) -> api::ConnectorData { + use router::connector::Thunes; + api::ConnectorData { + connector: Box::new(Thunes::new()), + connector_name: types::Connector::Thunes, + get_token: types::api::GetToken::Connector, + merchant_connector_id: None, + } + } + + fn get_auth_token(&self) -> types::ConnectorAuthType { + utils::to_connector_auth_type( + connector_auth::ConnectorAuthentication::new() + .thunes + .expect("Missing connector authentication configuration").into(), + ) + } + + fn get_name(&self) -> String { + "thunes".to_string() + } +} + +static CONNECTOR: ThunesTest = ThunesTest {}; + +fn get_default_payment_info() -> Option { + None +} + +fn payment_method_details() -> Option { + None +} + +// Cards Positive Tests +// Creates a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_only_authorize_payment() { + let response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized); +} + +// Captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info()) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Partially captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment( + payment_method_details(), + Some(types::PaymentsCaptureData { + amount_to_capture: 50, + ..utils::PaymentCaptureType::default().0 + }), + get_default_payment_info(), + ) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_authorized_payment() { + let authorize_response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("PSync response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized,); +} + +// Voids a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_void_authorized_payment() { + let response = CONNECTOR + .authorize_and_void_payment( + payment_method_details(), + Some(types::PaymentsCancelData { + connector_transaction_id: String::from(""), + cancellation_reason: Some("requested_by_customer".to_string()), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("Void payment response"); + assert_eq!(response.status, enums::AttemptStatus::Voided); +} + +// Refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund(payment_method_details(), None, None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Synchronizes a refund using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_manually_captured_refund() { + let refund_response = CONNECTOR + .capture_payment_and_refund(payment_method_details(), None, None, get_default_payment_info()) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_make_payment() { + let authorize_response = CONNECTOR.make_payment(payment_method_details(), get_default_payment_info()).await.unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_auto_captured_payment() { + let authorize_response = CONNECTOR.make_payment(payment_method_details(), get_default_payment_info()).await.unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Charged, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + capture_method: Some(enums::CaptureMethod::Automatic), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!(response.status, enums::AttemptStatus::Charged,); +} + +// Refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_auto_captured_payment() { + let response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_succeeded_payment() { + let refund_response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + refund_response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_succeeded_payment_multiple_times() { + CONNECTOR + .make_payment_and_multiple_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await; +} + +// Synchronizes a refund using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_refund() { + let refund_response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Cards Negative scenarios +// Creates a payment with incorrect CVC. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_cvc() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: types::api::PaymentMethodData::Card(api::Card { + card_cvc: Secret::new("12345".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's security code is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry month. +#[actix_web::test] +async fn should_fail_payment_for_invalid_exp_month() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: api::PaymentMethodData::Card(api::Card { + card_exp_month: Secret::new("20".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration month is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry year. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_expiry_year() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: api::PaymentMethodData::Card(api::Card { + card_exp_year: Secret::new("2000".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration year is invalid.".to_string(), + ); +} + +// Voids a payment using automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_fail_void_payment_for_auto_capture() { + let authorize_response = CONNECTOR.make_payment(payment_method_details(), get_default_payment_info()).await.unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let void_response = CONNECTOR + .void_payment(txn_id.unwrap(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + void_response.response.unwrap_err().message, + "You cannot cancel this PaymentIntent because it has a status of succeeded." + ); +} + +// Captures a payment using invalid connector payment id. +#[actix_web::test] +async fn should_fail_capture_for_invalid_payment() { + let capture_response = CONNECTOR + .capture_payment("123456789".to_string(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + capture_response.response.unwrap_err().message, + String::from("No such payment_intent: '123456789'") + ); +} + +// Refunds a payment with refund amount higher than payment amount. +#[actix_web::test] +async fn should_fail_for_refund_amount_higher_than_payment_amount() { + let response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 150, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Refund amount (₹1.50) is greater than charge amount (₹1.00)", + ); +} + +// Connector dependent test cases goes here + +// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index b9be08c43545..ea12d463fbdc 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -565,6 +565,7 @@ pub trait ConnectorActions: Connector { Ok(types::PaymentsResponseData::MultipleCaptureResponse { .. }) => None, Ok(types::PaymentsResponseData::IncrementalAuthorizationResponse { .. }) => None, Ok(types::PaymentsResponseData::PostProcessingResponse { .. }) => None, + // Ok(types::PaymentsResponseData::SessionUpdateResponse { .. }) => None, Err(_) => None, } } @@ -1075,6 +1076,7 @@ pub fn get_connector_transaction_id( Ok(types::PaymentsResponseData::MultipleCaptureResponse { .. }) => None, Ok(types::PaymentsResponseData::IncrementalAuthorizationResponse { .. }) => None, Ok(types::PaymentsResponseData::PostProcessingResponse { .. }) => None, + // Ok(types::PaymentsResponseData::SessionUpdateResponse { .. }) => None, Err(_) => None, } } diff --git a/crates/router/tests/connectors/zen.rs b/crates/router/tests/connectors/zen.rs index 2b9ea8f8211c..5c9cc2694c4d 100644 --- a/crates/router/tests/connectors/zen.rs +++ b/crates/router/tests/connectors/zen.rs @@ -331,6 +331,7 @@ async fn should_fail_payment_for_incorrect_card_number() { sub_category: None, brand: None, product_type: None, + product_tax_code: None, }]), email: Some(Email::from_str("test@gmail.com").unwrap()), webhook_url: Some("https://1635-116-74-253-164.ngrok-free.app".to_string()), @@ -373,6 +374,7 @@ async fn should_fail_payment_for_incorrect_cvc() { sub_category: None, brand: None, product_type: None, + product_tax_code: None, }]), email: Some(Email::from_str("test@gmail.com").unwrap()), webhook_url: Some("https://1635-116-74-253-164.ngrok-free.app".to_string()), @@ -415,6 +417,7 @@ async fn should_fail_payment_for_invalid_exp_month() { sub_category: None, brand: None, product_type: None, + product_tax_code: None, }]), email: Some(Email::from_str("test@gmail.com").unwrap()), webhook_url: Some("https://1635-116-74-253-164.ngrok-free.app".to_string()), @@ -457,6 +460,7 @@ async fn should_fail_payment_for_incorrect_expiry_year() { sub_category: None, brand: None, product_type: None, + product_tax_code: None, }]), email: Some(Email::from_str("test@gmail.com").unwrap()), webhook_url: Some("https://1635-116-74-253-164.ngrok-free.app".to_string()), diff --git a/crates/router/tests/macros.rs b/crates/router/tests/macros.rs new file mode 100644 index 000000000000..3a5301efa5fe --- /dev/null +++ b/crates/router/tests/macros.rs @@ -0,0 +1,42 @@ +#[cfg(test)] +mod flat_struct_test { + #![allow(clippy::unwrap_used)] + use std::collections::HashMap; + + use router_derive::FlatStruct; + use serde::Serialize; + + #[test] + fn test_flat_struct() { + #[derive(FlatStruct, Serialize)] + struct User { + address: Address, + } + + #[derive(Serialize)] + struct Address { + line1: String, + zip: String, + city: String, + } + + let line1 = "1397".to_string(); + let zip = "Some street".to_string(); + let city = "941222".to_string(); + + let address = Address { + line1: line1.clone(), + zip: zip.clone(), + city: city.clone(), + }; + let user = User { address }; + let flat_user_map = user.flat_struct(); + + let mut required_map = HashMap::new(); + required_map.insert("address.line1".to_string(), line1); + required_map.insert("address.zip".to_string(), zip); + required_map.insert("address.city".to_string(), city); + + assert_eq!(flat_user_map, required_map); + } +} diff --git a/crates/router/tests/payments.rs b/crates/router/tests/payments.rs index 19a4489c44c9..11d7b644c07e 100644 --- a/crates/router/tests/payments.rs +++ b/crates/router/tests/payments.rs @@ -277,6 +277,7 @@ fn connector_list() { assert_eq!(true, true); } +#[cfg(feature = "v1")] #[actix_rt::test] #[ignore] // AWS async fn payments_create_core() { @@ -443,6 +444,7 @@ async fn payments_create_core() { charges: None, frm_metadata: None, merchant_order_reference_id: None, + order_tax_amount: None, }; let expected_response = services::ApplicationResponse::JsonWithHeaders((expected_response, vec![])); @@ -452,6 +454,7 @@ async fn payments_create_core() { _, _, _, + payments::PaymentData, >( state.clone(), state.get_req_state(), @@ -532,6 +535,7 @@ async fn payments_create_core() { // assert_eq!(expected_response, actual_response); // } +#[cfg(feature = "v1")] #[actix_rt::test] #[ignore] async fn payments_create_core_adyen_no_redirect() { @@ -697,6 +701,7 @@ async fn payments_create_core_adyen_no_redirect() { charges: None, frm_metadata: None, merchant_order_reference_id: None, + order_tax_amount: None, }, vec![], )); @@ -706,6 +711,7 @@ async fn payments_create_core_adyen_no_redirect() { _, _, _, + payments::PaymentData, >( state.clone(), state.get_req_state(), diff --git a/crates/router/tests/payments2.rs b/crates/router/tests/payments2.rs index 430ea0c14b01..28759f7111c2 100644 --- a/crates/router/tests/payments2.rs +++ b/crates/router/tests/payments2.rs @@ -2,7 +2,8 @@ clippy::expect_used, clippy::unwrap_in_result, clippy::unwrap_used, - clippy::print_stdout + clippy::print_stdout, + unused_imports )] mod utils; @@ -36,6 +37,7 @@ fn connector_list() { assert_eq!(true, true); } +#[cfg(feature = "v1")] // FIXME: broken test? #[ignore] #[actix_rt::test] @@ -203,6 +205,7 @@ async fn payments_create_core() { charges: None, frm_metadata: None, merchant_order_reference_id: None, + order_tax_amount: None, }; let expected_response = services::ApplicationResponse::JsonWithHeaders((expected_response, vec![])); @@ -212,6 +215,7 @@ async fn payments_create_core() { _, _, _, + payments::PaymentData, >( state.clone(), state.get_req_state(), @@ -297,6 +301,7 @@ async fn payments_create_core() { // assert_eq!(expected_response, actual_response); // } +#[cfg(feature = "v1")] // FIXME: broken test? #[ignore] #[actix_rt::test] @@ -465,6 +470,7 @@ async fn payments_create_core_adyen_no_redirect() { charges: None, frm_metadata: None, merchant_order_reference_id: None, + order_tax_amount: None, }, vec![], )); @@ -474,6 +480,7 @@ async fn payments_create_core_adyen_no_redirect() { _, _, _, + payments::PaymentData, >( state.clone(), state.get_req_state(), diff --git a/crates/router_derive/Cargo.toml b/crates/router_derive/Cargo.toml index 5a17c32cd013..72cf9605f9b6 100644 --- a/crates/router_derive/Cargo.toml +++ b/crates/router_derive/Cargo.toml @@ -15,6 +15,7 @@ doctest = false indexmap = "2.2.6" proc-macro2 = "1.0.79" quote = "1.0.35" +serde_json = "1.0.115" strum = { version = "0.26.2", features = ["derive"] } syn = { version = "2.0.57", features = ["full", "extra-traits"] } # the full feature does not seem to encompass all the features diff --git a/crates/router_derive/src/lib.rs b/crates/router_derive/src/lib.rs index 2931d9bdca26..02179934e384 100644 --- a/crates/router_derive/src/lib.rs +++ b/crates/router_derive/src/lib.rs @@ -1,10 +1,8 @@ //! Utility macros for the `router` crate. #![warn(missing_docs)] - use syn::parse_macro_input; use crate::macros::diesel::DieselEnumMeta; - mod macros; /// Uses the [`Debug`][Debug] implementation of a type to derive its [`Display`][Display] @@ -615,3 +613,84 @@ pub fn try_get_enum_variant(input: proc_macro::TokenStream) -> proc_macro::Token .unwrap_or_else(|error| error.into_compile_error()) .into() } + +/// Uses the [`Serialize`] implementation of a type to derive a function implementation +/// for converting nested keys structure into a HashMap of key, value where key is in +/// the flattened form. +/// +/// Example +/// +/// #[derive(Default, Serialize, FlatStruct)] +/// pub struct User { +/// name: String, +/// address: Address, +/// email: String, +/// } +/// +/// #[derive(Default, Serialize)] +/// pub struct Address { +/// line1: String, +/// line2: String, +/// zip: String, +/// } +/// +/// let user = User::default(); +/// let flat_struct_map = user.flat_struct(); +/// +/// [ +/// ("name", "Test"), +/// ("address.line1", "1397"), +/// ("address.line2", "Some street"), +/// ("address.zip", "941222"), +/// ("email", "test@example.com"), +/// ] +#[proc_macro_derive(FlatStruct)] +pub fn flat_struct_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as syn::DeriveInput); + let name = &input.ident; + + let expanded = quote::quote! { + impl #name { + pub fn flat_struct(&self) -> std::collections::HashMap { + use serde_json::Value; + use std::collections::HashMap; + + fn flatten_value( + value: &Value, + prefix: &str, + result: &mut HashMap + ) { + match value { + Value::Object(map) => { + for (key, val) in map { + let new_key = if prefix.is_empty() { + key.to_string() + } else { + format!("{}.{}", prefix, key) + }; + flatten_value(val, &new_key, result); + } + } + Value::String(s) => { + result.insert(prefix.to_string(), s.clone()); + } + Value::Number(n) => { + result.insert(prefix.to_string(), n.to_string()); + } + Value::Bool(b) => { + result.insert(prefix.to_string(), b.to_string()); + } + _ => {} + } + } + + let mut result = HashMap::new(); + let value = serde_json::to_value(self).unwrap(); + flatten_value(&value, "", &mut result); + result + } + } + }; + + proc_macro::TokenStream::from(expanded) +} diff --git a/crates/router_derive/src/macros/operation.rs b/crates/router_derive/src/macros/operation.rs index c1e8c4e5bbce..638e6a506f25 100644 --- a/crates/router_derive/src/macros/operation.rs +++ b/crates/router_derive/src/macros/operation.rs @@ -29,6 +29,8 @@ pub enum Derives { SessionData, IncrementalAuthorization, IncrementalAuthorizationData, + SdkSessionUpdate, + SdkSessionUpdateData, } impl Derives { @@ -41,6 +43,7 @@ impl Derives { quote! { #[automatically_derived] impl Operation for #struct_name { + type Data = PaymentData; #(#fns)* } } @@ -55,6 +58,7 @@ impl Derives { quote! { #[automatically_derived] impl Operation for &#struct_name { + type Data = PaymentData; #(#ref_fns)* } } @@ -103,6 +107,12 @@ impl Conversion { Derives::IncrementalAuthorizationData => { syn::Ident::new("PaymentsIncrementalAuthorizationData", Span::call_site()) } + Derives::SdkSessionUpdate => { + syn::Ident::new("PaymentsDynamicTaxCalculationRequest", Span::call_site()) + } + Derives::SdkSessionUpdateData => { + syn::Ident::new("SdkPaymentsSessionUpdateData", Span::call_site()) + } } } @@ -110,27 +120,27 @@ impl Conversion { let req_type = Self::get_req_type(ident); match self { Self::ValidateRequest => quote! { - fn to_validate_request(&self) -> RouterResult<&(dyn ValidateRequest + Send + Sync)> { + fn to_validate_request(&self) -> RouterResult<&(dyn ValidateRequest + Send + Sync)> { Ok(self) } }, Self::GetTracker => quote! { - fn to_get_tracker(&self) -> RouterResult<&(dyn GetTracker,#req_type> + Send + Sync)> { + fn to_get_tracker(&self) -> RouterResult<&(dyn GetTracker + Send + Sync)> { Ok(self) } }, Self::Domain => quote! { - fn to_domain(&self) -> RouterResult<&dyn Domain> { + fn to_domain(&self) -> RouterResult<&dyn Domain> { Ok(self) } }, Self::UpdateTracker => quote! { - fn to_update_tracker(&self) -> RouterResult<&(dyn UpdateTracker,#req_type> + Send + Sync)> { + fn to_update_tracker(&self) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> { Ok(self) } }, Self::PostUpdateTracker => quote! { - fn to_post_update_tracker(&self) -> RouterResult<&(dyn PostUpdateTracker, #req_type> + Send + Sync)> { + fn to_post_update_tracker(&self) -> RouterResult<&(dyn PostUpdateTracker + Send + Sync)> { Ok(self) } }, @@ -158,27 +168,27 @@ impl Conversion { let req_type = Self::get_req_type(ident); match self { Self::ValidateRequest => quote! { - fn to_validate_request(&self) -> RouterResult<&(dyn ValidateRequest + Send + Sync)> { + fn to_validate_request(&self) -> RouterResult<&(dyn ValidateRequest + Send + Sync)> { Ok(*self) } }, Self::GetTracker => quote! { - fn to_get_tracker(&self) -> RouterResult<&(dyn GetTracker,#req_type> + Send + Sync)> { + fn to_get_tracker(&self) -> RouterResult<&(dyn GetTracker + Send + Sync)> { Ok(*self) } }, Self::Domain => quote! { - fn to_domain(&self) -> RouterResult<&(dyn Domain)> { + fn to_domain(&self) -> RouterResult<&(dyn Domain)> { Ok(*self) } }, Self::UpdateTracker => quote! { - fn to_update_tracker(&self) -> RouterResult<&(dyn UpdateTracker,#req_type> + Send + Sync)> { + fn to_update_tracker(&self) -> RouterResult<&(dyn UpdateTracker + Send + Sync)> { Ok(*self) } }, Self::PostUpdateTracker => quote! { - fn to_post_update_tracker(&self) -> RouterResult<&(dyn PostUpdateTracker, #req_type> + Send + Sync)> { + fn to_post_update_tracker(&self) -> RouterResult<&(dyn PostUpdateTracker + Send + Sync)> { Ok(*self) } }, @@ -423,6 +433,7 @@ pub fn operation_derive_inner(input: DeriveInput) -> syn::Result syn::Result Result<(), Box> { #[cfg(feature = "vergen")] #[tokio::test] -async fn env_macro() -> Result<(), Box> { +async fn env_macro() { println!("version : {:?}", env::version!()); println!("build : {:?}", env::build!()); println!("commit : {:?}", env::commit!()); @@ -35,6 +35,4 @@ async fn env_macro() -> Result<(), Box> { assert!(!env::build!().is_empty()); assert!(!env::commit!().is_empty()); // assert!(env::platform!().len() > 0); - - Ok(()) } diff --git a/crates/storage_impl/src/lib.rs b/crates/storage_impl/src/lib.rs index c00d2b445f81..6a7963867772 100644 --- a/crates/storage_impl/src/lib.rs +++ b/crates/storage_impl/src/lib.rs @@ -329,6 +329,21 @@ impl UniqueConstraints for diesel_models::Address { } } +#[cfg(all(feature = "v2", feature = "payment_v2"))] +impl UniqueConstraints for diesel_models::PaymentIntent { + fn unique_constraints(&self) -> Vec { + vec![format!( + "pi_{}_{}", + self.merchant_id.get_string_repr(), + self.merchant_reference_id + )] + } + fn table_name(&self) -> &str { + "PaymentIntent" + } +} + +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] impl UniqueConstraints for diesel_models::PaymentIntent { fn unique_constraints(&self) -> Vec { vec![format!( diff --git a/crates/storage_impl/src/lookup.rs b/crates/storage_impl/src/lookup.rs index 67b8635aba4a..943ef1f36f73 100644 --- a/crates/storage_impl/src/lookup.rs +++ b/crates/storage_impl/src/lookup.rs @@ -71,8 +71,12 @@ impl ReverseLookupInterface for KVRouterStore { new: DieselReverseLookupNew, storage_scheme: storage_enums::MerchantStorageScheme, ) -> CustomResult { - let storage_scheme = - decide_storage_scheme::<_, DieselReverseLookup>(self, storage_scheme, Op::Insert).await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, DieselReverseLookup>( + self, + storage_scheme, + Op::Insert, + )) + .await; match storage_scheme { storage_enums::MerchantStorageScheme::PostgresOnly => { self.router_store @@ -126,8 +130,12 @@ impl ReverseLookupInterface for KVRouterStore { .get_lookup_by_lookup_id(id, storage_scheme) .await }; - let storage_scheme = - decide_storage_scheme::<_, DieselReverseLookup>(self, storage_scheme, Op::Find).await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, DieselReverseLookup>( + self, + storage_scheme, + Op::Find, + )) + .await; match storage_scheme { storage_enums::MerchantStorageScheme::PostgresOnly => database_call().await, storage_enums::MerchantStorageScheme::RedisKv => { diff --git a/crates/storage_impl/src/mock_db/payment_attempt.rs b/crates/storage_impl/src/mock_db/payment_attempt.rs index 9cdc1e87ce84..6558c14ace34 100644 --- a/crates/storage_impl/src/mock_db/payment_attempt.rs +++ b/crates/storage_impl/src/mock_db/payment_attempt.rs @@ -45,6 +45,7 @@ impl PaymentAttemptInterface for MockDb { _payment_method_type: Option>, _authentication_type: Option>, _merchanat_connector_id: Option>, + _profile_id_list: Option>, _storage_scheme: storage_enums::MerchantStorageScheme, ) -> CustomResult { Err(StorageError::MockDbError)? @@ -160,6 +161,8 @@ impl PaymentAttemptInterface for MockDb { customer_acceptance: payment_attempt.customer_acceptance, organization_id: payment_attempt.organization_id, profile_id: payment_attempt.profile_id, + shipping_cost: payment_attempt.shipping_cost, + order_tax_amount: payment_attempt.order_tax_amount, }; payment_attempts.push(payment_attempt.clone()); Ok(payment_attempt) diff --git a/crates/storage_impl/src/mock_db/payment_intent.rs b/crates/storage_impl/src/mock_db/payment_intent.rs index f5db60da3fa6..4cf8272650a4 100644 --- a/crates/storage_impl/src/mock_db/payment_intent.rs +++ b/crates/storage_impl/src/mock_db/payment_intent.rs @@ -16,7 +16,11 @@ use super::MockDb; #[async_trait::async_trait] impl PaymentIntentInterface for MockDb { - #[cfg(feature = "olap")] + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_v2"), + feature = "olap" + ))] async fn filter_payment_intent_by_constraints( &self, _state: &KeyManagerState, @@ -28,7 +32,11 @@ impl PaymentIntentInterface for MockDb { // [#172]: Implement function for `MockDb` Err(StorageError::MockDbError)? } - #[cfg(feature = "olap")] + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_v2"), + feature = "olap" + ))] async fn filter_payment_intents_by_time_range_constraints( &self, _state: &KeyManagerState, @@ -40,16 +48,25 @@ impl PaymentIntentInterface for MockDb { // [#172]: Implement function for `MockDb` Err(StorageError::MockDbError)? } - #[cfg(feature = "olap")] + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_v2"), + feature = "olap" + ))] async fn get_intent_status_with_count( &self, _merchant_id: &common_utils::id_type::MerchantId, + _profile_id_list: Option>, _time_range: &api_models::payments::TimeRange, ) -> CustomResult, StorageError> { // [#172]: Implement function for `MockDb` Err(StorageError::MockDbError)? } - #[cfg(feature = "olap")] + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_v2"), + feature = "olap" + ))] async fn get_filtered_active_attempt_ids_for_total_count( &self, _merchant_id: &common_utils::id_type::MerchantId, @@ -59,7 +76,11 @@ impl PaymentIntentInterface for MockDb { // [#172]: Implement function for `MockDb` Err(StorageError::MockDbError)? } - #[cfg(feature = "olap")] + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_v2"), + feature = "olap" + ))] async fn get_filtered_payment_intents_attempt( &self, _state: &KeyManagerState, @@ -98,7 +119,7 @@ impl PaymentIntentInterface for MockDb { let mut payment_intents = self.payment_intents.lock().await; let payment_intent = payment_intents .iter_mut() - .find(|item| item.payment_id == this.payment_id && item.merchant_id == this.merchant_id) + .find(|item| item.get_id() == this.get_id() && item.merchant_id == this.merchant_id) .unwrap(); let diesel_payment_intent_update = diesel_models::PaymentIntentUpdate::from(update); @@ -120,6 +141,7 @@ impl PaymentIntentInterface for MockDb { Ok(payment_intent.clone()) } + #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] // safety: only used for testing #[allow(clippy::unwrap_used)] async fn find_payment_intent_by_payment_id_merchant_id( @@ -135,13 +157,31 @@ impl PaymentIntentInterface for MockDb { Ok(payment_intents .iter() .find(|payment_intent| { - payment_intent.payment_id == *payment_id - && payment_intent.merchant_id.eq(merchant_id) + payment_intent.get_id() == payment_id && payment_intent.merchant_id.eq(merchant_id) }) .cloned() .unwrap()) } + #[cfg(all(feature = "v2", feature = "payment_v2"))] + async fn find_payment_intent_by_id( + &self, + _state: &KeyManagerState, + id: &common_utils::id_type::PaymentId, + _merchant_key_store: &MerchantKeyStore, + _storage_scheme: storage_enums::MerchantStorageScheme, + ) -> error_stack::Result { + let payment_intents = self.payment_intents.lock().await; + let payment_intent = payment_intents + .iter() + .find(|payment_intent| payment_intent.get_id() == id) + .ok_or(StorageError::ValueNotFound( + "PaymentIntent not found".to_string(), + ))?; + + Ok(payment_intent.clone()) + } + async fn get_active_payment_attempt( &self, payment: &mut PaymentIntent, diff --git a/crates/storage_impl/src/mock_db/payouts.rs b/crates/storage_impl/src/mock_db/payouts.rs index c151e8acb880..5f2cc8824e5e 100644 --- a/crates/storage_impl/src/mock_db/payouts.rs +++ b/crates/storage_impl/src/mock_db/payouts.rs @@ -69,8 +69,15 @@ impl PayoutsInterface for MockDb { _merchant_id: &common_utils::id_type::MerchantId, _filters: &hyperswitch_domain_models::payouts::PayoutFetchConstraints, _storage_scheme: storage_enums::MerchantStorageScheme, - ) -> CustomResult)>, StorageError> - { + ) -> CustomResult< + Vec<( + Payouts, + PayoutAttempt, + Option, + Option, + )>, + StorageError, + > { // TODO: Implement function for `MockDb` Err(StorageError::MockDbError)? } diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 7edf5fd5a773..85fe2b22d3e7 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -296,6 +296,7 @@ impl PaymentAttemptInterface for RouterStore { payment_method_type: Option>, authentication_type: Option>, merchant_connector_id: Option>, + profile_id_list: Option>, _storage_scheme: MerchantStorageScheme, ) -> CustomResult { let conn = self @@ -318,6 +319,7 @@ impl PaymentAttemptInterface for RouterStore { payment_method, payment_method_type, authentication_type, + profile_id_list, merchant_connector_id, ) .await @@ -336,9 +338,12 @@ impl PaymentAttemptInterface for KVRouterStore { payment_attempt: PaymentAttemptNew, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { - let storage_scheme = - decide_storage_scheme::<_, DieselPaymentAttempt>(self, storage_scheme, Op::Insert) - .await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, DieselPaymentAttempt>( + self, + storage_scheme, + Op::Insert, + )) + .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { self.router_store @@ -421,6 +426,8 @@ impl PaymentAttemptInterface for KVRouterStore { customer_acceptance: payment_attempt.customer_acceptance.clone(), organization_id: payment_attempt.organization_id.clone(), profile_id: payment_attempt.profile_id.clone(), + shipping_cost: payment_attempt.shipping_cost, + order_tax_amount: payment_attempt.order_tax_amount, }; let field = format!("pa_{}", created_attempt.attempt_id); @@ -485,11 +492,11 @@ impl PaymentAttemptInterface for KVRouterStore { payment_id: &this.payment_id, }; let field = format!("pa_{}", this.attempt_id); - let storage_scheme = decide_storage_scheme::<_, DieselPaymentAttempt>( + let storage_scheme = Box::pin(decide_storage_scheme::<_, DieselPaymentAttempt>( self, storage_scheme, Op::Update(key.clone(), &field, Some(&this.updated_by)), - ) + )) .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { @@ -604,8 +611,12 @@ impl PaymentAttemptInterface for KVRouterStore { merchant_id: &common_utils::id_type::MerchantId, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { - let storage_scheme = - decide_storage_scheme::<_, DieselPaymentAttempt>(self, storage_scheme, Op::Find).await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, DieselPaymentAttempt>( + self, + storage_scheme, + Op::Find, + )) + .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { self.router_store @@ -666,8 +677,12 @@ impl PaymentAttemptInterface for KVRouterStore { storage_scheme, ) }; - let storage_scheme = - decide_storage_scheme::<_, DieselPaymentAttempt>(self, storage_scheme, Op::Find).await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, DieselPaymentAttempt>( + self, + storage_scheme, + Op::Find, + )) + .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, MerchantStorageScheme::RedisKv => { @@ -720,8 +735,12 @@ impl PaymentAttemptInterface for KVRouterStore { storage_scheme, ) }; - let storage_scheme = - decide_storage_scheme::<_, DieselPaymentAttempt>(self, storage_scheme, Op::Find).await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, DieselPaymentAttempt>( + self, + storage_scheme, + Op::Find, + )) + .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, MerchantStorageScheme::RedisKv => { @@ -769,8 +788,12 @@ impl PaymentAttemptInterface for KVRouterStore { connector_txn_id: &str, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { - let storage_scheme = - decide_storage_scheme::<_, DieselPaymentAttempt>(self, storage_scheme, Op::Find).await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, DieselPaymentAttempt>( + self, + storage_scheme, + Op::Find, + )) + .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { self.router_store @@ -834,8 +857,12 @@ impl PaymentAttemptInterface for KVRouterStore { attempt_id: &str, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { - let storage_scheme = - decide_storage_scheme::<_, DieselPaymentAttempt>(self, storage_scheme, Op::Find).await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, DieselPaymentAttempt>( + self, + storage_scheme, + Op::Find, + )) + .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { self.router_store @@ -882,8 +909,12 @@ impl PaymentAttemptInterface for KVRouterStore { merchant_id: &common_utils::id_type::MerchantId, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { - let storage_scheme = - decide_storage_scheme::<_, DieselPaymentAttempt>(self, storage_scheme, Op::Find).await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, DieselPaymentAttempt>( + self, + storage_scheme, + Op::Find, + )) + .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { self.router_store @@ -943,8 +974,12 @@ impl PaymentAttemptInterface for KVRouterStore { merchant_id: &common_utils::id_type::MerchantId, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { - let storage_scheme = - decide_storage_scheme::<_, DieselPaymentAttempt>(self, storage_scheme, Op::Find).await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, DieselPaymentAttempt>( + self, + storage_scheme, + Op::Find, + )) + .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { self.router_store @@ -1007,8 +1042,12 @@ impl PaymentAttemptInterface for KVRouterStore { payment_id: &common_utils::id_type::PaymentId, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result, errors::StorageError> { - let storage_scheme = - decide_storage_scheme::<_, DieselPaymentAttempt>(self, storage_scheme, Op::Find).await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, DieselPaymentAttempt>( + self, + storage_scheme, + Op::Find, + )) + .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { self.router_store @@ -1067,6 +1106,7 @@ impl PaymentAttemptInterface for KVRouterStore { payment_method_type: Option>, authentication_type: Option>, merchant_connector_id: Option>, + profile_id_list: Option>, storage_scheme: MerchantStorageScheme, ) -> CustomResult { self.router_store @@ -1078,6 +1118,7 @@ impl PaymentAttemptInterface for KVRouterStore { payment_method_type, authentication_type, merchant_connector_id, + profile_id_list, storage_scheme, ) .await @@ -1228,6 +1269,8 @@ impl DataModelExt for PaymentAttempt { customer_acceptance: self.customer_acceptance, organization_id: self.organization_id, profile_id: self.profile_id, + shipping_cost: self.shipping_cost, + order_tax_amount: self.order_tax_amount, } } @@ -1297,6 +1340,8 @@ impl DataModelExt for PaymentAttempt { customer_acceptance: storage_model.customer_acceptance, organization_id: storage_model.organization_id, profile_id: storage_model.profile_id, + shipping_cost: storage_model.shipping_cost, + order_tax_amount: storage_model.order_tax_amount, } } } @@ -1382,6 +1427,8 @@ impl DataModelExt for PaymentAttempt { customer_acceptance: self.customer_acceptance, organization_id: self.organization_id, profile_id: self.profile_id, + shipping_cost: self.shipping_cost, + order_tax_amount: self.order_tax_amount, } } @@ -1451,6 +1498,8 @@ impl DataModelExt for PaymentAttempt { customer_acceptance: storage_model.customer_acceptance, organization_id: storage_model.organization_id, profile_id: storage_model.profile_id, + shipping_cost: storage_model.shipping_cost, + order_tax_amount: storage_model.order_tax_amount, } } } @@ -1536,6 +1585,8 @@ impl DataModelExt for PaymentAttemptNew { customer_acceptance: self.customer_acceptance, organization_id: self.organization_id, profile_id: self.profile_id, + shipping_cost: self.shipping_cost, + order_tax_amount: self.order_tax_amount, } } @@ -1604,6 +1655,8 @@ impl DataModelExt for PaymentAttemptNew { customer_acceptance: storage_model.customer_acceptance, organization_id: storage_model.organization_id, profile_id: storage_model.profile_id, + shipping_cost: storage_model.shipping_cost, + order_tax_amount: storage_model.order_tax_amount, } } } @@ -1729,6 +1782,8 @@ impl DataModelExt for PaymentAttemptUpdate { client_source, client_version, customer_acceptance, + shipping_cost, + order_tax_amount, } => DieselPaymentAttemptUpdate::ConfirmUpdate { amount: amount.get_amount_as_i64(), currency, @@ -1762,6 +1817,8 @@ impl DataModelExt for PaymentAttemptUpdate { client_source, client_version, customer_acceptance, + shipping_cost, + order_tax_amount, }, Self::VoidUpdate { status, @@ -2068,6 +2125,8 @@ impl DataModelExt for PaymentAttemptUpdate { client_source, client_version, customer_acceptance, + shipping_cost, + order_tax_amount, } => Self::ConfirmUpdate { amount: MinorUnit::new(amount), currency, @@ -2099,6 +2158,8 @@ impl DataModelExt for PaymentAttemptUpdate { client_source, client_version, customer_acceptance, + shipping_cost, + order_tax_amount, }, DieselPaymentAttemptUpdate::VoidUpdate { status, diff --git a/crates/storage_impl/src/payments/payment_intent.rs b/crates/storage_impl/src/payments/payment_intent.rs index 4624f33eec30..cb58eac4b61e 100644 --- a/crates/storage_impl/src/payments/payment_intent.rs +++ b/crates/storage_impl/src/payments/payment_intent.rs @@ -73,14 +73,18 @@ impl PaymentIntentInterface for KVRouterStore { storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { let merchant_id = payment_intent.merchant_id.clone(); - let payment_id = payment_intent.payment_id.clone(); - let field = payment_intent.payment_id.get_hash_key_for_kv_store(); + let payment_id = payment_intent.get_id().to_owned(); + let field = payment_intent.get_id().get_hash_key_for_kv_store(); let key = PartitionKey::MerchantIdPaymentId { merchant_id: &merchant_id, payment_id: &payment_id, }; - let storage_scheme = - decide_storage_scheme::<_, DieselPaymentIntent>(self, storage_scheme, Op::Insert).await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, DieselPaymentIntent>( + self, + storage_scheme, + Op::Insert, + )) + .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { self.router_store @@ -148,17 +152,17 @@ impl PaymentIntentInterface for KVRouterStore { storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { let merchant_id = this.merchant_id.clone(); - let payment_id = this.payment_id.clone(); + let payment_id = this.get_id().to_owned(); let key = PartitionKey::MerchantIdPaymentId { merchant_id: &merchant_id, payment_id: &payment_id, }; - let field = format!("pi_{}", this.payment_id.get_string_repr()); - let storage_scheme = decide_storage_scheme::<_, DieselPaymentIntent>( + let field = format!("pi_{}", this.get_id().get_string_repr()); + let storage_scheme = Box::pin(decide_storage_scheme::<_, DieselPaymentIntent>( self, storage_scheme, Op::Update(key.clone(), &field, Some(&this.updated_by)), - ) + )) .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { @@ -225,6 +229,7 @@ impl PaymentIntentInterface for KVRouterStore { } } + #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] #[instrument(skip_all)] async fn find_payment_intent_by_payment_id_merchant_id( &self, @@ -243,8 +248,12 @@ impl PaymentIntentInterface for KVRouterStore { er.change_context(new_err) }) }; - let storage_scheme = - decide_storage_scheme::<_, DieselPaymentIntent>(self, storage_scheme, Op::Find).await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, DieselPaymentIntent>( + self, + storage_scheme, + Op::Find, + )) + .await; let diesel_payment_intent = match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, @@ -280,6 +289,35 @@ impl PaymentIntentInterface for KVRouterStore { .change_context(StorageError::DecryptionError) } + #[cfg(all(feature = "v2", feature = "payment_v2"))] + #[instrument(skip_all)] + async fn find_payment_intent_by_id( + &self, + state: &KeyManagerState, + id: &common_utils::id_type::PaymentId, + merchant_key_store: &MerchantKeyStore, + _storage_scheme: MerchantStorageScheme, + ) -> error_stack::Result { + let conn = pg_connection_read(self).await?; + let diesel_payment_intent = DieselPaymentIntent::find_by_global_id(&conn, id) + .await + .map_err(|er| { + let new_err = diesel_error_to_data_error(er.current_context()); + er.change_context(new_err) + })?; + + let merchant_id = diesel_payment_intent.merchant_id.clone(); + + PaymentIntent::convert_back( + state, + diesel_payment_intent, + merchant_key_store.key.get_inner(), + merchant_id.to_owned().into(), + ) + .await + .change_context(StorageError::DecryptionError) + } + async fn get_active_payment_attempt( &self, payment: &mut PaymentIntent, @@ -307,7 +345,11 @@ impl PaymentIntentInterface for KVRouterStore { } } - #[cfg(feature = "olap")] + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_v2"), + feature = "olap" + ))] async fn filter_payment_intent_by_constraints( &self, state: &KeyManagerState, @@ -327,7 +369,11 @@ impl PaymentIntentInterface for KVRouterStore { .await } - #[cfg(feature = "olap")] + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_v2"), + feature = "olap" + ))] async fn filter_payment_intents_by_time_range_constraints( &self, state: &KeyManagerState, @@ -346,18 +392,28 @@ impl PaymentIntentInterface for KVRouterStore { ) .await } - #[cfg(feature = "olap")] + + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_v2"), + feature = "olap" + ))] async fn get_intent_status_with_count( &self, merchant_id: &common_utils::id_type::MerchantId, + profile_id_list: Option>, time_range: &api_models::payments::TimeRange, ) -> error_stack::Result, StorageError> { self.router_store - .get_intent_status_with_count(merchant_id, time_range) + .get_intent_status_with_count(merchant_id, profile_id_list, time_range) .await } - #[cfg(feature = "olap")] + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_v2"), + feature = "olap" + ))] async fn get_filtered_payment_intents_attempt( &self, state: &KeyManagerState, @@ -377,7 +433,11 @@ impl PaymentIntentInterface for KVRouterStore { .await } - #[cfg(feature = "olap")] + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_v2"), + feature = "olap" + ))] async fn get_filtered_active_attempt_ids_for_total_count( &self, merchant_id: &common_utils::id_type::MerchantId, @@ -459,6 +519,7 @@ impl PaymentIntentInterface for crate::RouterStore { .change_context(StorageError::DecryptionError) } + #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "payment_v2")))] #[instrument(skip_all)] async fn find_payment_intent_by_payment_id_merchant_id( &self, @@ -489,6 +550,35 @@ impl PaymentIntentInterface for crate::RouterStore { .await } + #[cfg(all(feature = "v2", feature = "payment_v2"))] + #[instrument(skip_all)] + async fn find_payment_intent_by_id( + &self, + state: &KeyManagerState, + id: &common_utils::id_type::PaymentId, + merchant_key_store: &MerchantKeyStore, + _storage_scheme: MerchantStorageScheme, + ) -> error_stack::Result { + let conn = pg_connection_read(self).await?; + let diesel_payment_intent = DieselPaymentIntent::find_by_global_id(&conn, id) + .await + .map_err(|er| { + let new_err = diesel_error_to_data_error(er.current_context()); + er.change_context(new_err) + })?; + + let merchant_id = diesel_payment_intent.merchant_id.clone(); + + PaymentIntent::convert_back( + state, + diesel_payment_intent, + merchant_key_store.key.get_inner(), + merchant_id.to_owned().into(), + ) + .await + .change_context(StorageError::DecryptionError) + } + #[instrument(skip_all)] async fn get_active_payment_attempt( &self, @@ -517,7 +607,11 @@ impl PaymentIntentInterface for crate::RouterStore { } } - #[cfg(feature = "olap")] + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_v2"), + feature = "olap" + ))] #[instrument(skip_all)] async fn filter_payment_intent_by_constraints( &self, @@ -553,7 +647,7 @@ impl PaymentIntentInterface for crate::RouterStore { query = query.filter(pi_dsl::customer_id.eq(customer_id.clone())); } if let Some(profile_id) = ¶ms.profile_id { - query = query.filter(pi_dsl::profile_id.eq(profile_id.clone())); + query = query.filter(pi_dsl::profile_id.eq_any(profile_id.clone())); } query = match (params.starting_at, ¶ms.starting_after_id) { @@ -643,7 +737,11 @@ impl PaymentIntentInterface for crate::RouterStore { .await } - #[cfg(feature = "olap")] + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_v2"), + feature = "olap" + ))] #[instrument(skip_all)] async fn filter_payment_intents_by_time_range_constraints( &self, @@ -665,11 +763,16 @@ impl PaymentIntentInterface for crate::RouterStore { .await } - #[cfg(feature = "olap")] + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_v2"), + feature = "olap" + ))] #[instrument(skip_all)] async fn get_intent_status_with_count( &self, merchant_id: &common_utils::id_type::MerchantId, + profile_id_list: Option>, time_range: &api_models::payments::TimeRange, ) -> error_stack::Result, StorageError> { let conn = connection::pg_connection_read(self).await.switch()?; @@ -681,6 +784,10 @@ impl PaymentIntentInterface for crate::RouterStore { .filter(pi_dsl::merchant_id.eq(merchant_id.to_owned())) .into_boxed(); + if let Some(profile_id) = profile_id_list { + query = query.filter(pi_dsl::profile_id.eq_any(profile_id)); + } + query = query.filter(pi_dsl::created_at.ge(time_range.start_time)); query = match time_range.end_time { @@ -704,7 +811,11 @@ impl PaymentIntentInterface for crate::RouterStore { }) } - #[cfg(feature = "olap")] + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_v2"), + feature = "olap" + ))] #[instrument(skip_all)] async fn get_filtered_payment_intents_attempt( &self, @@ -758,7 +869,7 @@ impl PaymentIntentInterface for crate::RouterStore { } if let Some(profile_id) = ¶ms.profile_id { - query = query.filter(pi_dsl::profile_id.eq(profile_id.clone())); + query = query.filter(pi_dsl::profile_id.eq_any(profile_id.clone())); } query = match (params.starting_at, ¶ms.starting_after_id) { @@ -897,7 +1008,11 @@ impl PaymentIntentInterface for crate::RouterStore { .await } - #[cfg(feature = "olap")] + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_v2"), + feature = "olap" + ))] #[instrument(skip_all)] async fn get_filtered_active_attempt_ids_for_total_count( &self, @@ -922,7 +1037,7 @@ impl PaymentIntentInterface for crate::RouterStore { query = query.filter(pi_dsl::customer_id.eq(customer_id.clone())); } if let Some(profile_id) = ¶ms.profile_id { - query = query.filter(pi_dsl::profile_id.eq(profile_id.clone())); + query = query.filter(pi_dsl::profile_id.eq_any(profile_id.clone())); } query = match params.starting_at { diff --git a/crates/storage_impl/src/payouts/payout_attempt.rs b/crates/storage_impl/src/payouts/payout_attempt.rs index 08843a060a79..697b5d91c93c 100644 --- a/crates/storage_impl/src/payouts/payout_attempt.rs +++ b/crates/storage_impl/src/payouts/payout_attempt.rs @@ -44,8 +44,12 @@ impl PayoutAttemptInterface for KVRouterStore { payouts: &Payouts, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { - let storage_scheme = - decide_storage_scheme::<_, DieselPayoutAttempt>(self, storage_scheme, Op::Insert).await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, DieselPayoutAttempt>( + self, + storage_scheme, + Op::Insert, + )) + .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { self.router_store @@ -144,11 +148,11 @@ impl PayoutAttemptInterface for KVRouterStore { payout_attempt_id: &this.payout_id, }; let field = format!("poa_{}", this.payout_attempt_id); - let storage_scheme = decide_storage_scheme::<_, DieselPayoutAttempt>( + let storage_scheme = Box::pin(decide_storage_scheme::<_, DieselPayoutAttempt>( self, storage_scheme, Op::Update(key.clone(), &field, None), - ) + )) .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { @@ -240,8 +244,12 @@ impl PayoutAttemptInterface for KVRouterStore { payout_attempt_id: &str, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { - let storage_scheme = - decide_storage_scheme::<_, DieselPayoutAttempt>(self, storage_scheme, Op::Find).await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, DieselPayoutAttempt>( + self, + storage_scheme, + Op::Find, + )) + .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { self.router_store diff --git a/crates/storage_impl/src/payouts/payouts.rs b/crates/storage_impl/src/payouts/payouts.rs index 3a5cc2854eeb..b6e1c1ac35fb 100644 --- a/crates/storage_impl/src/payouts/payouts.rs +++ b/crates/storage_impl/src/payouts/payouts.rs @@ -19,12 +19,18 @@ use diesel::{associations::HasTable, ExpressionMethods, NullableExpressionMethod not(feature = "customer_v2") ))] use diesel_models::payout_attempt::PayoutAttempt as DieselPayoutAttempt; +#[cfg(all( + feature = "olap", + any(feature = "v1", feature = "v2"), + not(feature = "customer_v2") +))] +use diesel_models::schema::{ + address::dsl as add_dsl, customers::dsl as cust_dsl, payout_attempt::dsl as poa_dsl, +}; #[cfg(feature = "olap")] use diesel_models::{ - customers::Customer as DieselCustomer, - enums as storage_enums, - query::generics::db_metrics, - schema::{customers::dsl as cust_dsl, payout_attempt::dsl as poa_dsl, payouts::dsl as po_dsl}, + address::Address as DieselAddress, customers::Customer as DieselCustomer, + enums as storage_enums, query::generics::db_metrics, schema::payouts::dsl as po_dsl, }; use diesel_models::{ enums::MerchantStorageScheme, @@ -57,8 +63,8 @@ use crate::connection; not(feature = "customer_v2") ))] use crate::store::schema::{ - customers::all_columns as cust_all_columns, payout_attempt::all_columns as poa_all_columns, - payouts::all_columns as po_all_columns, + address::all_columns as addr_all_columns, customers::all_columns as cust_all_columns, + payout_attempt::all_columns as poa_all_columns, payouts::all_columns as po_all_columns, }; use crate::{ diesel_error_to_data_error, @@ -76,8 +82,12 @@ impl PayoutsInterface for KVRouterStore { new: PayoutsNew, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { - let storage_scheme = - decide_storage_scheme::<_, DieselPayouts>(self, storage_scheme, Op::Insert).await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, DieselPayouts>( + self, + storage_scheme, + Op::Insert, + )) + .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { self.router_store.insert_payout(new, storage_scheme).await @@ -162,11 +172,11 @@ impl PayoutsInterface for KVRouterStore { payout_id: &this.payout_id, }; let field = format!("po_{}", this.payout_id); - let storage_scheme = decide_storage_scheme::<_, DieselPayouts>( + let storage_scheme = Box::pin(decide_storage_scheme::<_, DieselPayouts>( self, storage_scheme, Op::Update(key.clone(), &field, None), - ) + )) .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { @@ -229,8 +239,12 @@ impl PayoutsInterface for KVRouterStore { er.change_context(new_err) }) }; - let storage_scheme = - decide_storage_scheme::<_, DieselPayouts>(self, storage_scheme, Op::Find).await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, DieselPayouts>( + self, + storage_scheme, + Op::Find, + )) + .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, MerchantStorageScheme::RedisKv => { @@ -273,8 +287,12 @@ impl PayoutsInterface for KVRouterStore { er.change_context(new_err) }) }; - let storage_scheme = - decide_storage_scheme::<_, DieselPayouts>(self, storage_scheme, Op::Find).await; + let storage_scheme = Box::pin(decide_storage_scheme::<_, DieselPayouts>( + self, + storage_scheme, + Op::Find, + )) + .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { let maybe_payouts = database_call().await?; @@ -331,8 +349,15 @@ impl PayoutsInterface for KVRouterStore { merchant_id: &common_utils::id_type::MerchantId, filters: &PayoutFetchConstraints, storage_scheme: MerchantStorageScheme, - ) -> error_stack::Result)>, StorageError> - { + ) -> error_stack::Result< + Vec<( + Payouts, + PayoutAttempt, + Option, + Option, + )>, + StorageError, + > { self.router_store .filter_payouts_and_attempts(merchant_id, filters, storage_scheme) .await @@ -571,8 +596,17 @@ impl PayoutsInterface for crate::RouterStore { merchant_id: &common_utils::id_type::MerchantId, filters: &PayoutFetchConstraints, storage_scheme: MerchantStorageScheme, - ) -> error_stack::Result)>, StorageError> - { + ) -> error_stack::Result< + Vec<( + Payouts, + PayoutAttempt, + Option, + Option, + )>, + StorageError, + > { + use common_utils::errors::ReportSwitchExt; + let conn = connection::pg_connection_read(self).await.switch()?; let conn = async_bb8_diesel::Connection::as_async_conn(&conn); let mut query = DieselPayouts::table() @@ -585,6 +619,10 @@ impl PayoutsInterface for crate::RouterStore { .on(cust_dsl::customer_id.nullable().eq(po_dsl::customer_id)), ) .filter(cust_dsl::merchant_id.eq(merchant_id.to_owned())) + .left_outer_join( + diesel_models::schema::address::table + .on(add_dsl::address_id.nullable().eq(po_dsl::address_id)), + ) .filter(po_dsl::merchant_id.eq(merchant_id.to_owned())) .order(po_dsl::created_at.desc()) .into_boxed(); @@ -675,17 +713,28 @@ impl PayoutsInterface for crate::RouterStore { logger::debug!(filter = %diesel::debug_query::(&query).to_string()); query - .select((po_all_columns, poa_all_columns, cust_all_columns.nullable())) - .get_results_async::<(DieselPayouts, DieselPayoutAttempt, Option)>(conn) + .select(( + po_all_columns, + poa_all_columns, + cust_all_columns.nullable(), + addr_all_columns.nullable(), + )) + .get_results_async::<( + DieselPayouts, + DieselPayoutAttempt, + Option, + Option, + )>(conn) .await .map(|results| { results .into_iter() - .map(|(pi, pa, c)| { + .map(|(pi, pa, c, add)| { ( Payouts::from_storage_model(pi), PayoutAttempt::from_storage_model(pa), c, + add, ) }) .collect() @@ -706,8 +755,15 @@ impl PayoutsInterface for crate::RouterStore { _merchant_id: &common_utils::id_type::MerchantId, _filters: &PayoutFetchConstraints, _storage_scheme: MerchantStorageScheme, - ) -> error_stack::Result)>, StorageError> - { + ) -> error_stack::Result< + Vec<( + Payouts, + PayoutAttempt, + Option, + Option, + )>, + StorageError, + > { todo!() } diff --git a/crates/storage_impl/src/redis/kv_store.rs b/crates/storage_impl/src/redis/kv_store.rs index 8511702baa23..74b1526fe8e8 100644 --- a/crates/storage_impl/src/redis/kv_store.rs +++ b/crates/storage_impl/src/redis/kv_store.rs @@ -259,12 +259,11 @@ where result .await - .map(|result| { + .inspect(|_| { logger::debug!(kv_operation= %operation, status="success"); let keyvalue = router_env::opentelemetry::KeyValue::new("operation", operation.clone()); metrics::KV_OPERATION_SUCCESSFUL.add(&metrics::CONTEXT, 1, &[keyvalue]); - result }) .inspect_err(|err| { logger::error!(kv_operation = %operation, status="error", error = ?err); diff --git a/crates/storage_impl/src/redis/pub_sub.rs b/crates/storage_impl/src/redis/pub_sub.rs index 8f24b62fcaf2..7d7557a7f5f3 100644 --- a/crates/storage_impl/src/redis/pub_sub.rs +++ b/crates/storage_impl/src/redis/pub_sub.rs @@ -30,7 +30,7 @@ impl PubSubInterface for std::sync::Arc { self.subscriber.manage_subscriptions(); self.subscriber - .subscribe(channel) + .subscribe::<(), &str>(channel) .await .change_context(redis_errors::RedisError::SubscribeError)?; diff --git a/crates/test_utils/src/connector_auth.rs b/crates/test_utils/src/connector_auth.rs index a1261e6edb1c..a0fed3c003ac 100644 --- a/crates/test_utils/src/connector_auth.rs +++ b/crates/test_utils/src/connector_auth.rs @@ -31,7 +31,7 @@ pub struct ConnectorAuthentication { pub cryptopay: Option, pub cybersource: Option, pub datatrans: Option, - pub deutschebank: Option, + pub deutschebank: Option, pub dlocal: Option, #[cfg(feature = "dummy_connector")] pub dummyconnector: Option, @@ -77,6 +77,7 @@ pub struct ConnectorAuthentication { pub stripe: Option, pub taxjar: Option, pub threedsecureio: Option, + pub thunes: Option, pub stripe_au: Option, pub stripe_uk: Option, pub trustpay: Option, diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Commons.js b/cypress-tests/cypress/e2e/PaymentUtils/Commons.js index cfb136acd1dc..eff36cf502c4 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Commons.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Commons.js @@ -17,7 +17,7 @@ function normalise(input) { paybox: "Paybox", paypal: "Paypal", wellsfargo: "Wellsfargo", - fiuu: "Fiuu" + fiuu: "Fiuu", // Add more known exceptions here }; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Novalnet.js b/cypress-tests/cypress/e2e/PaymentUtils/Novalnet.js new file mode 100644 index 000000000000..6fe7dc639228 --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentUtils/Novalnet.js @@ -0,0 +1,218 @@ +const successfulNo3DSCardDetails = { + card_number: "4200000000000000", + card_exp_month: "12", + card_exp_year: "25", + card_holder_name: "Max Mustermann", + card_cvc: "123", +}; + +const successfulThreeDSTestCardDetails = { + card_number: "4000000000001091", + card_exp_month: "12", + card_exp_year: "25", + card_holder_name: "Max Mustermann", + card_cvc: "123", +}; + +export const connectorDetails = { + card_pm: { + PaymentIntent: { + Request: { + currency: "EUR", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + }, + }, + }, + "3DSManualCapture": { + Request: { + payment_method: "card", + billing: { + address: { + line1: "1467", + line2: "CA", + line3: "CA", + city: "Musterhausen", + state: "California", + zip: "12345", + country: "DE", + first_name: "Max", + last_name: "Mustermann" + }, + email: "test@novalnet.de", + phone: { + number: "9123456789", + country_code: "+91" + } + }, + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + "3DSAutoCapture": { + Request: { + payment_method: "card", + billing: { + address: { + line1: "1467", + line2: "CA", + line3: "CA", + city: "Musterhausen", + state: "California", + zip: "12345", + country: "DE", + first_name: "Max", + last_name: "Mustermann" + }, + email: "test@novalnet.de", + phone: { + number: "9123456789", + country_code: "+91" + } + }, + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + //TODO: Add No3DSManualCapture, No3DSAutoCapture + // No3DSManualCapture: { + // Request: { + // payment_method: "card", + // payment_method_data: { + // card: successfulNo3DSCardDetails, + // }, + // customer_acceptance: null, + // setup_future_usage: "on_session", + // }, + // Response: { + // status: 200, + // body: { + // status: "requires_capture", + // }, + // }, + // }, + // No3DSAutoCapture: { + // Request: { + // payment_method: "card", + // payment_method_data: { + // card: successfulNo3DSCardDetails, + // }, + // customer_acceptance: null, + // setup_future_usage: "on_session", + // }, + // Response: { + // status: 200, + // body: { + // status: "succeeded", + // }, + // }, + // }, + Capture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + amount: 6500, + amount_capturable: 0, + amount_received: 6500, + }, + }, + }, + PartialCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "partially_captured", + amount: 6500, + amount_capturable: 0, + amount_received: 100, + }, + }, + }, + Void: { + Request: {}, + Response: { + status: 200, + body: { + status: "cancelled", + }, + }, + }, + Refund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + PartialRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SyncRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + }, +}; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Utils.js b/cypress-tests/cypress/e2e/PaymentUtils/Utils.js index 8c643f9cd87f..03fc7fe9724e 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Utils.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Utils.js @@ -11,6 +11,7 @@ import { connectorDetails as fiservemeaConnectorDetails } from "./Fiservemea.js" import { connectorDetails as iatapayConnectorDetails } from "./Iatapay.js"; import { connectorDetails as itaubankConnectorDetails } from "./ItauBank.js"; import { connectorDetails as nmiConnectorDetails } from "./Nmi.js"; +import { connectorDetails as novalnetConnectorDetails } from "./Novalnet.js"; import { connectorDetails as payboxConnectorDetails } from "./Paybox.js"; import { connectorDetails as paypalConnectorDetails } from "./Paypal.js"; import { connectorDetails as stripeConnectorDetails } from "./Stripe.js"; @@ -28,6 +29,7 @@ const connectorDetails = { iatapay: iatapayConnectorDetails, itaubank: itaubankConnectorDetails, nmi: nmiConnectorDetails, + novalnet: novalnetConnectorDetails, paybox: payboxConnectorDetails, paypal: paypalConnectorDetails, stripe: stripeConnectorDetails, diff --git a/cypress-tests/cypress/support/redirectionHandler.js b/cypress-tests/cypress/support/redirectionHandler.js index 6540632c8936..b493d11fee83 100644 --- a/cypress-tests/cypress/support/redirectionHandler.js +++ b/cypress-tests/cypress/support/redirectionHandler.js @@ -320,6 +320,12 @@ function threeDsRedirection(redirection_url, expected_url, connectorId) { }); }); }); + } else if (connectorId === "novalnet") { + cy.get("form", { timeout: WAIT_TIME }) + .should("exist") + .then((form) => { + cy.get('input[id="submit"]').click(); + }); } else if (connectorId === "stripe") { cy.get("iframe", { timeout: TIMEOUT }) .its("0.contentDocument.body") diff --git a/docker/wasm-build.Dockerfile b/docker/wasm-build.Dockerfile index bddcacdfc64d..015a055e4fd7 100644 --- a/docker/wasm-build.Dockerfile +++ b/docker/wasm-build.Dockerfile @@ -2,6 +2,7 @@ FROM rust:latest as builder ARG RUN_ENV=sandbox ARG EXTRA_FEATURES="" +ARG VERSION_FEATURE_SET="v1" RUN apt-get update \ && apt-get install -y clang libssl-dev pkg-config @@ -18,7 +19,7 @@ ENV env=$env COPY . . RUN echo env RUN cargo install wasm-pack -RUN wasm-pack build --target web --out-dir /tmp/wasm --out-name euclid crates/euclid_wasm -- --features ${RUN_ENV},${EXTRA_FEATURES} +RUN wasm-pack build --target web --out-dir /tmp/wasm --out-name euclid crates/euclid_wasm -- --features ${VERSION_FEATURE_SET},${RUN_ENV},${EXTRA_FEATURES} FROM scratch diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index 6dd25f8fbd2b..ed15e8893bd5 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -96,7 +96,7 @@ coinbase.base_url = "https://api.commerce.coinbase.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" -deutschebank.base_url = "https://sandbox.directpos.de/rest-api/services/v2.1" +deutschebank.base_url = "https://testmerch.directpos.de/rest-api" dlocal.base_url = "https://sandbox.dlocal.com/" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" @@ -104,6 +104,7 @@ fiserv.base_url = "https://cert.api.fiservapps.com/" fiservemea.base_url = "https://prod.emea.api.fiservapps.com/sandbox" fiuu.base_url = "https://sandbox.merchant.razer.com/" fiuu.secondary_base_url="https://sandbox.merchant.razer.com/" +fiuu.third_base_url="https://api.merchant.razer.com/" forte.base_url = "https://sandbox.forte.net/api/v3" globalpay.base_url = "https://apis.sandbox.globalpay.com/ucp/" globepay.base_url = "https://pay.globepay.co/" @@ -148,6 +149,7 @@ stax.base_url = "https://apiprod.fattlabs.com/" stripe.base_url = "https://api.stripe.com/" taxjar.base_url = "https://api.sandbox.taxjar.com/v2/" threedsecureio.base_url = "https://service.sandbox.3dsecure.io" +thunes.base_url = "https://api.limonetikqualif.com/" stripe.base_url_file_upload = "https://files.stripe.com/" trustpay.base_url = "https://test-tpgw.trustpay.eu/" trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/" @@ -230,6 +232,7 @@ cards = [ "stripe", "taxjar", "threedsecureio", + "thunes", "trustpay", "tsys", "volt", @@ -364,3 +367,6 @@ global_tenant = { schema = "public", redis_key_prefix = "" } [multitenancy.tenants] public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"} + +[recipient_emails] +recon = "recon@example.com" diff --git a/migrations/2024-08-05-171030_add_shipping_cost_in_payment_intent/down.sql b/migrations/2024-08-05-171030_add_shipping_cost_in_payment_intent/down.sql new file mode 100644 index 000000000000..21e11e8f48ec --- /dev/null +++ b/migrations/2024-08-05-171030_add_shipping_cost_in_payment_intent/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE payment_intent +DROP COLUMN IF EXISTS shipping_cost; \ No newline at end of file diff --git a/migrations/2024-08-05-171030_add_shipping_cost_in_payment_intent/up.sql b/migrations/2024-08-05-171030_add_shipping_cost_in_payment_intent/up.sql new file mode 100644 index 000000000000..5f3045d16070 --- /dev/null +++ b/migrations/2024-08-05-171030_add_shipping_cost_in_payment_intent/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE payment_intent ADD COLUMN IF NOT EXISTS shipping_cost BIGINT; \ No newline at end of file diff --git a/migrations/2024-08-21-085916_add_tax_details_in_payment_intent_to_store_tax_amount/down.sql b/migrations/2024-08-21-085916_add_tax_details_in_payment_intent_to_store_tax_amount/down.sql new file mode 100644 index 000000000000..235f027efd2f --- /dev/null +++ b/migrations/2024-08-21-085916_add_tax_details_in_payment_intent_to_store_tax_amount/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE payment_intent DROP COLUMN IF EXISTS tax_details; diff --git a/migrations/2024-08-21-085916_add_tax_details_in_payment_intent_to_store_tax_amount/up.sql b/migrations/2024-08-21-085916_add_tax_details_in_payment_intent_to_store_tax_amount/up.sql new file mode 100644 index 000000000000..547b6ff28609 --- /dev/null +++ b/migrations/2024-08-21-085916_add_tax_details_in_payment_intent_to_store_tax_amount/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE payment_intent ADD COLUMN IF NOT EXISTS tax_details JSONB; diff --git a/migrations/2024-08-27-190822_add_tax_processor_in_connector_type/down.sql b/migrations/2024-08-27-190822_add_tax_processor_in_connector_type/down.sql new file mode 100644 index 000000000000..da13010c0122 --- /dev/null +++ b/migrations/2024-08-27-190822_add_tax_processor_in_connector_type/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +select 1; \ No newline at end of file diff --git a/migrations/2024-08-27-190822_add_tax_processor_in_connector_type/up.sql b/migrations/2024-08-27-190822_add_tax_processor_in_connector_type/up.sql new file mode 100644 index 000000000000..3026bf047825 --- /dev/null +++ b/migrations/2024-08-27-190822_add_tax_processor_in_connector_type/up.sql @@ -0,0 +1,3 @@ +-- Your SQL goes here +ALTER TYPE "ConnectorType" +ADD VALUE IF NOT EXISTS 'tax_processor'; \ No newline at end of file diff --git a/migrations/2024-08-28-044317_add_skip_external_tax_calcualtion_in_payment_intent_table/down.sql b/migrations/2024-08-28-044317_add_skip_external_tax_calcualtion_in_payment_intent_table/down.sql new file mode 100644 index 000000000000..4ca933a4e369 --- /dev/null +++ b/migrations/2024-08-28-044317_add_skip_external_tax_calcualtion_in_payment_intent_table/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE payment_intent DROP COLUMN if EXISTS skip_external_tax_calculation; \ No newline at end of file diff --git a/migrations/2024-08-28-044317_add_skip_external_tax_calcualtion_in_payment_intent_table/up.sql b/migrations/2024-08-28-044317_add_skip_external_tax_calcualtion_in_payment_intent_table/up.sql new file mode 100644 index 000000000000..cf185dcdb7a2 --- /dev/null +++ b/migrations/2024-08-28-044317_add_skip_external_tax_calcualtion_in_payment_intent_table/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE payment_intent ADD COLUMN IF NOT EXISTS skip_external_tax_calculation BOOLEAN; \ No newline at end of file diff --git a/migrations/2024-09-10-080050_add_shipping_cost_and_order_tax_amount_to_payment_attempt/down.sql b/migrations/2024-09-10-080050_add_shipping_cost_and_order_tax_amount_to_payment_attempt/down.sql new file mode 100644 index 000000000000..8ad0af96f00c --- /dev/null +++ b/migrations/2024-09-10-080050_add_shipping_cost_and_order_tax_amount_to_payment_attempt/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE payment_attempt DROP COLUMN IF EXISTS shipping_cost; +ALTER TABLE payment_attempt DROP COLUMN IF EXISTS order_tax_amount; \ No newline at end of file diff --git a/migrations/2024-09-10-080050_add_shipping_cost_and_order_tax_amount_to_payment_attempt/up.sql b/migrations/2024-09-10-080050_add_shipping_cost_and_order_tax_amount_to_payment_attempt/up.sql new file mode 100644 index 000000000000..2122db364c9d --- /dev/null +++ b/migrations/2024-09-10-080050_add_shipping_cost_and_order_tax_amount_to_payment_attempt/up.sql @@ -0,0 +1,4 @@ +-- Your SQL goes here +ALTER TABLE payment_attempt ADD COLUMN IF NOT EXISTS shipping_cost BIGINT; +ALTER TABLE payment_attempt +ADD COLUMN IF NOT EXISTS order_tax_amount BIGINT; diff --git a/package-lock.json b/package-lock.json index d7c69792ce44..19a2d2fb02ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,855 +1,662 @@ { "name": "hyperswitch", "version": "0.0.0", - "lockfileVersion": 3, + "lockfileVersion": 1, "requires": true, - "packages": { - "": { - "name": "hyperswitch", - "version": "0.0.0", - "devDependencies": { - "newman": "git+ssh://git@github.com:knutties/newman.git#7106e194c15d49d066fa09d9a2f18b2238f3dba8" - } - }, - "node_modules/@colors/colors": { + "dependencies": { + "@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true, - "optional": true, - "engines": { - "node": ">=0.1.90" - } + "optional": true }, - "node_modules/@faker-js/faker": { + "@faker-js/faker": { "version": "5.5.3", "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-5.5.3.tgz", "integrity": "sha512-R11tGE6yIFwqpaIqcfkcg7AICXzFg14+5h5v0TfF/9+RMDL6jhzCy/pxHVOfbALGdtVYdt6JdR21tuxEgl34dw==", "dev": true }, - "node_modules/@postman/form-data": { + "@postman/form-data": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@postman/form-data/-/form-data-3.1.1.tgz", "integrity": "sha512-vjh8Q2a8S6UCm/KKs31XFJqEEgmbjBmpPNVV2eVav6905wyFAwaUOBGA1NPBI4ERH9MMZc6w0umFgM6WbEPMdg==", "dev": true, - "dependencies": { + "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" } }, - "node_modules/@postman/tough-cookie": { + "@postman/tough-cookie": { "version": "4.1.3-postman.1", "resolved": "https://registry.npmjs.org/@postman/tough-cookie/-/tough-cookie-4.1.3-postman.1.tgz", "integrity": "sha512-txpgUqZOnWYnUHZpHjkfb0IwVH4qJmyq77pPnJLlfhMtdCLMFTEeQHlzQiK906aaNCe4NEB5fGJHo9uzGbFMeA==", "dev": true, - "dependencies": { + "requires": { "psl": "^1.1.33", "punycode": "^2.1.1", "universalify": "^0.2.0", "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" } }, - "node_modules/@postman/tunnel-agent": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@postman/tunnel-agent/-/tunnel-agent-0.6.3.tgz", - "integrity": "sha512-k57fzmAZ2PJGxfOA4SGR05ejorHbVAa/84Hxh/2nAztjNXc4ZjOm9NUIk6/Z6LCrBvJZqjRZbN8e/nROVUPVdg==", + "@postman/tunnel-agent": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@postman/tunnel-agent/-/tunnel-agent-0.6.4.tgz", + "integrity": "sha512-CJJlq8V7rNKhAw4sBfjixKpJW00SHqebqNUQKxMoepgeWZIbdPcD+rguRcivGhS4N12PymDcKgUgSD4rVC+RjQ==", "dev": true, - "dependencies": { + "requires": { "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" } }, - "node_modules/ajv": { + "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "dependencies": { + "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-regex": { + "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } + "dev": true }, - "node_modules/ansi-styles": { + "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, - "dependencies": { + "requires": { "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" } }, - "node_modules/array-back": { + "array-back": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", - "dev": true, - "engines": { - "node": ">=6" - } + "dev": true }, - "node_modules/asn1": { + "asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", "dev": true, - "dependencies": { + "requires": { "safer-buffer": "~2.1.0" } }, - "node_modules/assert-plus": { + "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true, - "engines": { - "node": ">=0.8" - } + "dev": true }, - "node_modules/async": { + "async": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", "dev": true }, - "node_modules/asynckit": { + "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, - "node_modules/aws-sign2": { + "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true, - "engines": { - "node": "*" - } + "dev": true }, - "node_modules/aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", + "aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", "dev": true }, - "node_modules/base64-js": { + "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "dev": true }, - "node_modules/bcrypt-pbkdf": { + "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", "dev": true, - "dependencies": { + "requires": { "tweetnacl": "^0.14.3" } }, - "node_modules/bluebird": { + "bluebird": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", "integrity": "sha512-UfFSr22dmHPQqPP9XWHRhq+gWnHCYguQGkXQlbyPtW5qTnhFWA8/iXg765tH0cAjy7l/zPJ1aBTO0g5XgA7kvQ==", "dev": true }, - "node_modules/brotli": { + "brotli": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", "dev": true, - "dependencies": { + "requires": { "base64-js": "^1.1.2" } }, - "node_modules/caseless": { + "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", "dev": true }, - "node_modules/chalk": { + "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, - "dependencies": { + "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" } }, - "node_modules/chardet": { + "chardet": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-1.6.0.tgz", "integrity": "sha512-+QOTw3otC4+FxdjK9RopGpNOglADbr4WPFi0SonkO99JbpkTPbMxmdm4NenhF5Zs+4gPXLI1+y2uazws5TMe8w==", "dev": true }, - "node_modules/charset": { + "charset": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/charset/-/charset-1.0.1.tgz", "integrity": "sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } + "dev": true }, - "node_modules/cli-progress": { + "cli-progress": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", "dev": true, - "dependencies": { + "requires": { "string-width": "^4.2.3" - }, - "engines": { - "node": ">=4" } }, - "node_modules/cli-table3": { + "cli-table3": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", "dev": true, - "dependencies": { + "requires": { + "@colors/colors": "1.5.0", "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" } }, - "node_modules/color-convert": { + "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, - "dependencies": { + "requires": { "color-name": "1.1.3" } }, - "node_modules/color-name": { + "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, - "node_modules/colors": { + "colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, - "engines": { - "node": ">=0.1.90" - } + "dev": true }, - "node_modules/combined-stream": { + "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, - "dependencies": { + "requires": { "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" } }, - "node_modules/command-line-args": { + "command-line-args": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", "dev": true, - "dependencies": { + "requires": { "array-back": "^3.1.0", "find-replace": "^3.0.0", "lodash.camelcase": "^4.3.0", "typical": "^4.0.0" - }, - "engines": { - "node": ">=4.0.0" } }, - "node_modules/command-line-usage": { + "command-line-usage": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.3.tgz", "integrity": "sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw==", "dev": true, - "dependencies": { + "requires": { "array-back": "^4.0.2", "chalk": "^2.4.2", "table-layout": "^1.0.2", "typical": "^5.2.0" }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/command-line-usage/node_modules/array-back": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", - "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/command-line-usage/node_modules/typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "dev": true, - "engines": { - "node": ">=8" + "dependencies": { + "array-back": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", + "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", + "dev": true + }, + "typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "dev": true + } } }, - "node_modules/commander": { + "commander": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz", "integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==", - "dev": true, - "engines": { - "node": ">=16" - } + "dev": true }, - "node_modules/core-util-is": { + "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", "dev": true }, - "node_modules/csv-parse": { + "csv-parse": { "version": "4.16.3", "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.16.3.tgz", "integrity": "sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==", "dev": true }, - "node_modules/dashdash": { + "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", "dev": true, - "dependencies": { + "requires": { "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" } }, - "node_modules/deep-extend": { + "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } + "dev": true }, - "node_modules/delayed-stream": { + "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } + "dev": true }, - "node_modules/des.js": { + "des.js": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", "dev": true, - "dependencies": { + "requires": { "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0" } }, - "node_modules/directory-tree": { + "directory-tree": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/directory-tree/-/directory-tree-3.5.1.tgz", "integrity": "sha512-HqjZ49fDzUnKYUhHxVw9eKBqbQ+lL0v4kSBInlDlaktmLtGoV9tC54a6A0ZfYeIrkMHWTE6MwwmUXP477+UEKQ==", "dev": true, - "dependencies": { + "requires": { "command-line-args": "^5.2.0", "command-line-usage": "^6.1.1" - }, - "bin": { - "directory-tree": "bin/index.js" - }, - "engines": { - "node": ">=10.0" } }, - "node_modules/ecc-jsbn": { + "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", "dev": true, - "dependencies": { + "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" } }, - "node_modules/emoji-regex": { + "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "node_modules/escape-string-regexp": { + "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } + "dev": true }, - "node_modules/eventemitter3": { + "eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "dev": true }, - "node_modules/extend": { + "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, - "node_modules/extsprintf": { + "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true, - "engines": [ - "node >=0.6.0" - ] + "dev": true }, - "node_modules/fast-deep-equal": { + "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, - "node_modules/fast-json-stable-stringify": { + "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, - "node_modules/file-type": { + "file-type": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } + "dev": true }, - "node_modules/filesize": { + "filesize": { "version": "10.0.12", "resolved": "https://registry.npmjs.org/filesize/-/filesize-10.0.12.tgz", "integrity": "sha512-6RS9gDchbn+qWmtV2uSjo5vmKizgfCQeb5jKmqx8HyzA3MoLqqyQxN+QcjkGBJt7FjJ9qFce67Auyya5rRRbpw==", - "dev": true, - "engines": { - "node": ">= 10.4.0" - } + "dev": true }, - "node_modules/find-replace": { + "find-replace": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", "dev": true, - "dependencies": { + "requires": { "array-back": "^3.0.1" - }, - "engines": { - "node": ">=4.0.0" } }, - "node_modules/flatted": { + "flatted": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", "dev": true }, - "node_modules/forever-agent": { + "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true, - "engines": { - "node": "*" - } + "dev": true }, - "node_modules/getpass": { + "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", "dev": true, - "dependencies": { + "requires": { "assert-plus": "^1.0.0" } }, - "node_modules/handlebars": { + "handlebars": { "version": "4.7.8", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, - "dependencies": { + "requires": { "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", + "uglify-js": "^3.1.4", "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" } }, - "node_modules/har-schema": { + "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", - "dev": true, - "engines": { - "node": ">=4" - } + "dev": true }, - "node_modules/har-validator": { + "har-validator": { "version": "5.1.5", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "deprecated": "this library is no longer supported", "dev": true, - "dependencies": { + "requires": { "ajv": "^6.12.3", "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" } }, - "node_modules/has-flag": { + "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } + "dev": true }, - "node_modules/http-reasons": { + "http-reasons": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/http-reasons/-/http-reasons-0.1.0.tgz", "integrity": "sha512-P6kYh0lKZ+y29T2Gqz+RlC9WBLhKe8kDmcJ+A+611jFfxdPsbMRQ5aNmFRM3lENqFkK+HTTL+tlQviAiv0AbLQ==", "dev": true }, - "node_modules/http-signature": { + "http-signature": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", "dev": true, - "dependencies": { + "requires": { "assert-plus": "^1.0.0", "jsprim": "^2.0.2", "sshpk": "^1.14.1" - }, - "engines": { - "node": ">=0.10" } }, - "node_modules/httpntlm": { + "httpntlm": { "version": "1.8.13", "resolved": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.8.13.tgz", "integrity": "sha512-2F2FDPiWT4rewPzNMg3uPhNkP3NExENlUGADRUDPQvuftuUTGW98nLZtGemCIW3G40VhWZYgkIDcQFAwZ3mf2Q==", "dev": true, - "funding": [ - { - "type": "paypal", - "url": "https://www.paypal.com/donate/?hosted_button_id=2CKNJLZJBW8ZC" - }, - { - "type": "buymeacoffee", - "url": "https://www.buymeacoffee.com/samdecrock" - } - ], - "dependencies": { + "requires": { "des.js": "^1.0.1", "httpreq": ">=0.4.22", "js-md4": "^0.3.2", "underscore": "~1.12.1" - }, - "engines": { - "node": ">=10.4.0" } }, - "node_modules/httpreq": { + "httpreq": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/httpreq/-/httpreq-1.1.1.tgz", "integrity": "sha512-uhSZLPPD2VXXOSN8Cni3kIsoFHaU2pT/nySEU/fHr/ePbqHYr0jeiQRmUKLEirC09SFPsdMoA7LU7UXMd/w0Kw==", - "dev": true, - "engines": { - "node": ">= 6.15.1" - } + "dev": true }, - "node_modules/iconv-lite": { + "iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, - "dependencies": { + "requires": { "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" } }, - "node_modules/inherits": { + "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "node_modules/is-fullwidth-code-point": { + "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } + "dev": true }, - "node_modules/is-typedarray": { + "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", "dev": true }, - "node_modules/isstream": { + "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", "dev": true }, - "node_modules/jose": { + "jose": { "version": "4.14.4", "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/panva" - } + "dev": true }, - "node_modules/js-md4": { + "js-md4": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==", "dev": true }, - "node_modules/js-sha512": { + "js-sha512": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha512/-/js-sha512-0.8.0.tgz", "integrity": "sha512-PWsmefG6Jkodqt+ePTvBZCSMFgN7Clckjd0O7su3I0+BW2QWUTJNzjktHsztGLhncP2h8mcF9V9Y2Ha59pAViQ==", "dev": true }, - "node_modules/jsbn": { + "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", "dev": true }, - "node_modules/json-schema": { + "json-schema": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", "dev": true }, - "node_modules/json-schema-traverse": { + "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, - "node_modules/json-stringify-safe": { + "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "dev": true }, - "node_modules/jsprim": { + "jsprim": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", "dev": true, - "engines": [ - "node >=0.6.0" - ], - "dependencies": { + "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", "json-schema": "0.4.0", "verror": "1.10.0" } }, - "node_modules/liquid-json": { + "liquid-json": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/liquid-json/-/liquid-json-0.3.1.tgz", "integrity": "sha512-wUayTU8MS827Dam6MxgD72Ui+KOSF+u/eIqpatOtjnvgJ0+mnDq33uC2M7J0tPK+upe/DpUAuK4JUU89iBoNKQ==", - "dev": true, - "engines": { - "node": ">=4" - } + "dev": true }, - "node_modules/lodash": { + "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "node_modules/lodash.camelcase": { + "lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", "dev": true }, - "node_modules/lru-cache": { + "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, - "dependencies": { + "requires": { "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" } }, - "node_modules/mime-db": { + "mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } + "dev": true }, - "node_modules/mime-format": { + "mime-format": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mime-format/-/mime-format-2.0.1.tgz", "integrity": "sha512-XxU3ngPbEnrYnNbIX+lYSaYg0M01v6p2ntd2YaFksTu0vayaw5OJvbdRyWs07EYRlLED5qadUZ+xo+XhOvFhwg==", "dev": true, - "dependencies": { + "requires": { "charset": "^1.0.0" } }, - "node_modules/mime-types": { + "mime-types": { "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, - "dependencies": { + "requires": { "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" } }, - "node_modules/minimalistic-assert": { + "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", "dev": true }, - "node_modules/minimist": { + "minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "dev": true }, - "node_modules/mkdirp": { + "mkdirp": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "dev": true, - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "dev": true }, - "node_modules/neo-async": { + "neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, - "node_modules/newman": { - "version": "6.0.0", - "resolved": "git+ssh://git@github.com/knutties/newman.git#7106e194c15d49d066fa09d9a2f18b2238f3dba8", - "integrity": "sha512-peCwAcRcwBCerxnYiEXAGHkO2ZxsmJyW7BH2/PigzO1qbWtD9FXOnDuwXmp6/Hnc9uxgKqokd2YKPjkHXU6qCw==", + "newman": { + "version": "git+ssh://git@github.com/knutties/newman.git#7106e194c15d49d066fa09d9a2f18b2238f3dba8", + "from": "git+ssh://git@github.com/knutties/newman.git#7106e194c15d49d066fa09d9a2f18b2238f3dba8", "dev": true, - "license": "Apache-2.0", - "dependencies": { + "requires": { "@postman/tough-cookie": "4.1.3-postman.1", "async": "3.2.4", "chardet": "1.6.0", @@ -873,59 +680,44 @@ "serialised-error": "1.1.3", "word-wrap": "1.2.5", "xmlbuilder": "15.1.1" - }, - "bin": { - "newman": "bin/newman.js" - }, - "engines": { - "node": ">=16" } }, - "node_modules/node-oauth1": { + "node-oauth1": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/node-oauth1/-/node-oauth1-1.3.0.tgz", "integrity": "sha512-0yggixNfrA1KcBwvh/Hy2xAS1Wfs9dcg6TdFf2zN7gilcAigMdrtZ4ybrBSXBgLvGDw9V1p2MRnGBMq7XjTWLg==", "dev": true }, - "node_modules/oauth-sign": { + "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true, - "engines": { - "node": "*" - } + "dev": true }, - "node_modules/object-hash": { + "object-hash": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz", "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==", - "dev": true, - "engines": { - "node": ">= 0.10.0" - } + "dev": true }, - "node_modules/parse-ms": { + "parse-ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==", - "dev": true, - "engines": { - "node": ">=6" - } + "dev": true }, - "node_modules/performance-now": { + "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", "dev": true }, - "node_modules/postman-collection": { + "postman-collection": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-4.2.1.tgz", "integrity": "sha512-DFLt3/yu8+ldtOTIzmBUctoupKJBOVK4NZO0t68K2lIir9smQg7OdQTBjOXYy+PDh7u0pSDvD66tm93eBHEPHA==", "dev": true, - "dependencies": { + "requires": { "@faker-js/faker": "5.5.3", "file-type": "3.9.0", "http-reasons": "0.1.0", @@ -937,45 +729,35 @@ "postman-url-encoder": "3.0.5", "semver": "7.5.4", "uuid": "8.3.2" - }, - "engines": { - "node": ">=10" } }, - "node_modules/postman-collection-transformer": { + "postman-collection-transformer": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/postman-collection-transformer/-/postman-collection-transformer-4.1.7.tgz", "integrity": "sha512-SxJkm/LnlFZs2splUBnS4jQFicgBptghpm4voHtNnaum3Ad64E2MHLV4fJhv58dVUmFwdSwdQUN3m2q0iLecnQ==", "dev": true, - "dependencies": { + "requires": { "commander": "8.3.0", "inherits": "2.0.4", "lodash": "4.17.21", "semver": "7.5.4", "strip-json-comments": "3.1.1" }, - "bin": { - "postman-collection-transformer": "bin/transform-collection.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/postman-collection-transformer/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "dev": true, - "engines": { - "node": ">= 12" + "dependencies": { + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true + } } }, - "node_modules/postman-request": { + "postman-request": { "version": "2.88.1-postman.33", "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.33.tgz", "integrity": "sha512-uL9sCML4gPH6Z4hreDWbeinKU0p0Ke261nU7OvII95NU22HN6Dk7T/SaVPaj6T4TsQqGKIFw6/woLZnH7ugFNA==", "dev": true, - "dependencies": { + "requires": { "@postman/form-data": "~3.1.1", "@postman/tough-cookie": "~4.1.3-postman.1", "@postman/tunnel-agent": "^0.6.3", @@ -998,17 +780,14 @@ "safe-buffer": "^5.1.2", "stream-length": "^1.0.2", "uuid": "^8.3.2" - }, - "engines": { - "node": ">= 6" } }, - "node_modules/postman-runtime": { + "postman-runtime": { "version": "7.33.0", "resolved": "https://registry.npmjs.org/postman-runtime/-/postman-runtime-7.33.0.tgz", "integrity": "sha512-cYCb+5Y12FwZU/T3gOj2SKiOz38pisVLc0tdppb+ZlG7iqn5aLgxghJwhjG62pZCV6uixKiQX1hNdLSk9a9Xtw==", "dev": true, - "dependencies": { + "requires": { "@postman/tough-cookie": "4.1.3-postman.1", "async": "3.2.4", "aws4": "1.12.0", @@ -1028,218 +807,173 @@ "strip-json-comments": "3.1.1", "uuid": "8.3.2" }, - "engines": { - "node": ">=12" - } - }, - "node_modules/postman-runtime/node_modules/postman-collection": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-4.2.0.tgz", - "integrity": "sha512-tvOLgN1h6Kab6dt43PmBoV5kYO/YUta3x0C2QqfmbzmHZe47VTpZ/+gIkGlbNhjKNPUUub5X6ehxYKoaTYdy1w==", - "dev": true, "dependencies": { - "@faker-js/faker": "5.5.3", - "file-type": "3.9.0", - "http-reasons": "0.1.0", - "iconv-lite": "0.6.3", - "liquid-json": "0.3.1", - "lodash": "4.17.21", - "mime-format": "2.0.1", - "mime-types": "2.1.35", - "postman-url-encoder": "3.0.5", - "semver": "7.5.4", - "uuid": "8.3.2" - }, - "engines": { - "node": ">=10" + "aws4": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", + "dev": true + }, + "postman-collection": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-4.2.0.tgz", + "integrity": "sha512-tvOLgN1h6Kab6dt43PmBoV5kYO/YUta3x0C2QqfmbzmHZe47VTpZ/+gIkGlbNhjKNPUUub5X6ehxYKoaTYdy1w==", + "dev": true, + "requires": { + "@faker-js/faker": "5.5.3", + "file-type": "3.9.0", + "http-reasons": "0.1.0", + "iconv-lite": "0.6.3", + "liquid-json": "0.3.1", + "lodash": "4.17.21", + "mime-format": "2.0.1", + "mime-types": "2.1.35", + "postman-url-encoder": "3.0.5", + "semver": "7.5.4", + "uuid": "8.3.2" + } + } } }, - "node_modules/postman-sandbox": { + "postman-sandbox": { "version": "4.2.7", "resolved": "https://registry.npmjs.org/postman-sandbox/-/postman-sandbox-4.2.7.tgz", "integrity": "sha512-/EcCrKnb/o+9iLS4u+H76E0kBomJFjPptVjoDiq1uZ7Es/4aTv0MAX+0aoDxdDO+0h9sl8vy65uKQwyjN7AOaw==", "dev": true, - "dependencies": { + "requires": { "lodash": "4.17.21", "postman-collection": "4.2.0", "teleport-javascript": "1.0.0", "uvm": "2.1.1" }, - "engines": { - "node": ">=10" - } - }, - "node_modules/postman-sandbox/node_modules/postman-collection": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-4.2.0.tgz", - "integrity": "sha512-tvOLgN1h6Kab6dt43PmBoV5kYO/YUta3x0C2QqfmbzmHZe47VTpZ/+gIkGlbNhjKNPUUub5X6ehxYKoaTYdy1w==", - "dev": true, "dependencies": { - "@faker-js/faker": "5.5.3", - "file-type": "3.9.0", - "http-reasons": "0.1.0", - "iconv-lite": "0.6.3", - "liquid-json": "0.3.1", - "lodash": "4.17.21", - "mime-format": "2.0.1", - "mime-types": "2.1.35", - "postman-url-encoder": "3.0.5", - "semver": "7.5.4", - "uuid": "8.3.2" - }, - "engines": { - "node": ">=10" + "postman-collection": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-4.2.0.tgz", + "integrity": "sha512-tvOLgN1h6Kab6dt43PmBoV5kYO/YUta3x0C2QqfmbzmHZe47VTpZ/+gIkGlbNhjKNPUUub5X6ehxYKoaTYdy1w==", + "dev": true, + "requires": { + "@faker-js/faker": "5.5.3", + "file-type": "3.9.0", + "http-reasons": "0.1.0", + "iconv-lite": "0.6.3", + "liquid-json": "0.3.1", + "lodash": "4.17.21", + "mime-format": "2.0.1", + "mime-types": "2.1.35", + "postman-url-encoder": "3.0.5", + "semver": "7.5.4", + "uuid": "8.3.2" + } + } } }, - "node_modules/postman-url-encoder": { + "postman-url-encoder": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/postman-url-encoder/-/postman-url-encoder-3.0.5.tgz", "integrity": "sha512-jOrdVvzUXBC7C+9gkIkpDJ3HIxOHTIqjpQ4C1EMt1ZGeMvSEpbFCKq23DEfgsj46vMnDgyQf+1ZLp2Wm+bKSsA==", "dev": true, - "dependencies": { + "requires": { "punycode": "^2.1.1" - }, - "engines": { - "node": ">=10" } }, - "node_modules/pretty-ms": { + "pretty-ms": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", "dev": true, - "dependencies": { + "requires": { "parse-ms": "^2.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/psl": { + "psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", "dev": true }, - "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true, - "engines": { - "node": ">=6" - } + "punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true }, - "node_modules/qs": { + "qs": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "dev": true, - "engines": { - "node": ">=0.6" - } + "dev": true }, - "node_modules/querystringify": { + "querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "dev": true }, - "node_modules/reduce-flatten": { + "reduce-flatten": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==", - "dev": true, - "engines": { - "node": ">=6" - } + "dev": true }, - "node_modules/requires-port": { + "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "dev": true }, - "node_modules/safe-buffer": { + "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "dev": true }, - "node_modules/safer-buffer": { + "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, - "node_modules/semver": { + "semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, - "dependencies": { + "requires": { "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" } }, - "node_modules/serialised-error": { + "serialised-error": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/serialised-error/-/serialised-error-1.1.3.tgz", "integrity": "sha512-vybp3GItaR1ZtO2nxZZo8eOo7fnVaNtP3XE2vJKgzkKR2bagCkdJ1EpYYhEMd3qu/80DwQk9KjsNSxE3fXWq0g==", "dev": true, - "dependencies": { + "requires": { "object-hash": "^1.1.2", "stack-trace": "0.0.9", "uuid": "^3.0.0" + }, + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + } } }, - "node_modules/serialised-error/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "dev": true, - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/source-map": { + "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } + "dev": true }, - "node_modules/sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", "dev": true, - "dependencies": { + "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", "bcrypt-pbkdf": "^1.0.0", @@ -1249,267 +983,203 @@ "jsbn": "~0.1.0", "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" } }, - "node_modules/stack-trace": { + "stack-trace": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", "integrity": "sha512-vjUc6sfgtgY0dxCdnc40mK6Oftjo9+2K8H/NG81TMhgL392FtiPA9tn9RLyTxXmTLPJPjF3VyzFp6bsWFLisMQ==", - "dev": true, - "engines": { - "node": "*" - } + "dev": true }, - "node_modules/stream-length": { + "stream-length": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stream-length/-/stream-length-1.0.2.tgz", "integrity": "sha512-aI+qKFiwoDV4rsXiS7WRoCt+v2RX1nUj17+KJC5r2gfh5xoSJIfP6Y3Do/HtvesFcTSWthIuJ3l1cvKQY/+nZg==", "dev": true, - "dependencies": { + "requires": { "bluebird": "^2.6.2" } }, - "node_modules/string-width": { + "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "dependencies": { + "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" } }, - "node_modules/strip-ansi": { + "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "dependencies": { + "requires": { "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" } }, - "node_modules/strip-json-comments": { + "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "dev": true }, - "node_modules/supports-color": { + "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, - "dependencies": { + "requires": { "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" } }, - "node_modules/table-layout": { + "table-layout": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.2.tgz", "integrity": "sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==", "dev": true, - "dependencies": { + "requires": { "array-back": "^4.0.1", "deep-extend": "~0.6.0", "typical": "^5.2.0", "wordwrapjs": "^4.0.0" }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/table-layout/node_modules/array-back": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", - "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/table-layout/node_modules/typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "dev": true, - "engines": { - "node": ">=8" + "dependencies": { + "array-back": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", + "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", + "dev": true + }, + "typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "dev": true + } } }, - "node_modules/teleport-javascript": { + "teleport-javascript": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/teleport-javascript/-/teleport-javascript-1.0.0.tgz", "integrity": "sha512-j1llvWVFyEn/6XIFDfX5LAU43DXe0GCt3NfXDwJ8XpRRMkS+i50SAkonAONBy+vxwPFBd50MFU8a2uj8R/ccLg==", "dev": true }, - "node_modules/tweetnacl": { + "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", "dev": true }, - "node_modules/typical": { + "typical": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", - "dev": true, - "engines": { - "node": ">=8" - } + "dev": true }, - "node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", "dev": true, - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } + "optional": true }, - "node_modules/underscore": { + "underscore": { "version": "1.12.1", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==", "dev": true }, - "node_modules/universalify": { + "universalify": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } + "dev": true }, - "node_modules/uri-js": { + "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "dependencies": { + "requires": { "punycode": "^2.1.0" } }, - "node_modules/url-parse": { + "url-parse": { "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "dev": true, - "dependencies": { + "requires": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" } }, - "node_modules/uuid": { + "uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } + "dev": true }, - "node_modules/uvm": { + "uvm": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/uvm/-/uvm-2.1.1.tgz", "integrity": "sha512-BZ5w8adTpNNr+zczOBRpaX/hH8UPKAf7fmCnidrcsqt3bn8KT9bDIfuS7hgRU9RXgiN01su2pwysBONY6w8W5w==", "dev": true, - "dependencies": { + "requires": { "flatted": "3.2.6" - }, - "engines": { - "node": ">=10" } }, - "node_modules/verror": { + "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", "dev": true, - "engines": [ - "node >=0.6.0" - ], - "dependencies": { + "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, - "node_modules/word-wrap": { + "word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } + "dev": true }, - "node_modules/wordwrap": { + "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true }, - "node_modules/wordwrapjs": { + "wordwrapjs": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.1.tgz", "integrity": "sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA==", "dev": true, - "dependencies": { + "requires": { "reduce-flatten": "^2.0.0", "typical": "^5.2.0" }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/wordwrapjs/node_modules/typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "dev": true, - "engines": { - "node": ">=8" + "dependencies": { + "typical": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", + "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", + "dev": true + } } }, - "node_modules/xmlbuilder": { + "xmlbuilder": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", - "dev": true, - "engines": { - "node": ">=8.0" - } + "dev": true }, - "node_modules/yallist": { + "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", diff --git a/scripts/add_connector.sh b/scripts/add_connector.sh index 0dd3b6434c85..fb7252833d5f 100755 --- a/scripts/add_connector.sh +++ b/scripts/add_connector.sh @@ -6,7 +6,7 @@ function find_prev_connector() { git checkout $self cp $self $self.tmp # Add new connector to existing list and sort it - connectors=(aci adyen adyenplatform airwallex applepay authorizedotnet bambora bamboraapac bankofamerica billwerk bitpay bluesnap boku braintree cashtocode checkout coinbase cryptopay cybersource datatrans deutschebank dlocal dummyconnector ebanx fiserv fiservemea fiuu forte globalpay globepay gocardless gpayments helcim iatapay itaubank klarna mifinity mollie multisafepay netcetera nexinets nexixpay noon novalnet nuvei opayo opennode paybox payeezy payme payone paypal payu placetopay plaid powertranz prophetpay rapyd razorpay shift4 square stax stripe taxjar threedsecureio trustpay tsys volt wellsfargo wellsfargopayout wise worldline worldpay zsl "$1") + connectors=(aci adyen adyenplatform airwallex applepay authorizedotnet bambora bamboraapac bankofamerica billwerk bitpay bluesnap boku braintree cashtocode checkout coinbase cryptopay cybersource datatrans deutschebank dlocal dummyconnector ebanx fiserv fiservemea fiuu forte globalpay globepay gocardless gpayments helcim iatapay itaubank klarna mifinity mollie multisafepay netcetera nexinets nexixpay noon novalnet nuvei opayo opennode paybox payeezy payme payone paypal payu placetopay plaid powertranz prophetpay rapyd razorpay shift4 square stax stripe taxjar threedsecureio thunes trustpay tsys volt wellsfargo wellsfargopayout wise worldline worldpay zsl "$1") IFS=$'\n' sorted=($(sort <<<"${connectors[*]}")); unset IFS res=`echo ${sorted[@]}` sed -i'' -e "s/^ connectors=.*/ connectors=($res \"\$1\")/" $self.tmp diff --git a/v2_migrations/2024-08-28-081721_add_v2_columns/down.sql b/v2_migrations/2024-08-28-081721_add_v2_columns/down.sql index cf261b322b87..427dbcb4d5fa 100644 --- a/v2_migrations/2024-08-28-081721_add_v2_columns/down.sql +++ b/v2_migrations/2024-08-28-081721_add_v2_columns/down.sql @@ -13,3 +13,15 @@ ALTER TABLE business_profile DROP COLUMN routing_algorithm_id, DROP COLUMN default_fallback_routing; DROP TYPE "OrderFulfillmentTimeOrigin"; + +-- Revert renaming of field, +ALTER TABLE payment_intent DROP COLUMN merchant_reference_id, + DROP COLUMN billing_address, + DROP COLUMN shipping_address, + DROP COLUMN capture_method, + DROP COLUMN authentication_type, + DROP COLUMN amount_to_capture, + DROP COLUMN prerouting_algorithm, + DROP COLUMN surcharge_amount, + DROP COLUMN tax_on_surcharge, + DROP COLUMN frm_merchant_decision; diff --git a/v2_migrations/2024-08-28-081721_add_v2_columns/up.sql b/v2_migrations/2024-08-28-081721_add_v2_columns/up.sql index 194d347f4ca5..719a8afa30e5 100644 --- a/v2_migrations/2024-08-28-081721_add_v2_columns/up.sql +++ b/v2_migrations/2024-08-28-081721_add_v2_columns/up.sql @@ -14,3 +14,16 @@ ADD COLUMN routing_algorithm_id VARCHAR(64) DEFAULT NULL, ADD COLUMN frm_routing_algorithm_id VARCHAR(64) DEFAULT NULL, ADD COLUMN payout_routing_algorithm_id VARCHAR(64) DEFAULT NULL, ADD COLUMN default_fallback_routing JSONB DEFAULT NULL; + +ALTER TABLE payment_intent +ADD COLUMN merchant_reference_id VARCHAR(64) NOT NULL, + ADD COLUMN billing_address BYTEA DEFAULT NULL, + ADD COLUMN shipping_address BYTEA DEFAULT NULL, + ADD COLUMN capture_method "CaptureMethod", + ADD COLUMN authentication_type "AuthenticationType", + ADD COLUMN amount_to_capture bigint, + ADD COLUMN prerouting_algorithm JSONB, -- straight_through_algorithm from payment_attempt + ADD COLUMN surcharge_amount bigint, + ADD COLUMN tax_on_surcharge bigint, -- tax_amount from payment_attempt + ADD COLUMN frm_merchant_decision VARCHAR(64); + diff --git a/v2_migrations/2024-08-28-081747_recreate_ids_for_v2/down.sql b/v2_migrations/2024-08-28-081747_recreate_ids_for_v2/down.sql index 1aa2f1f60e71..6315fe7f799a 100644 --- a/v2_migrations/2024-08-28-081747_recreate_ids_for_v2/down.sql +++ b/v2_migrations/2024-08-28-081747_recreate_ids_for_v2/down.sql @@ -15,8 +15,12 @@ ALTER TABLE customers DROP COLUMN IF EXISTS id; ALTER TABLE customers ADD COLUMN IF NOT EXISTS id SERIAL; +ALTER TABLE payment_intent DROP COLUMN IF EXISTS id; + ALTER TABLE payment_intent ADD id SERIAL; +ALTER TABLE payment_attempt DROP COLUMN IF EXISTS id; + ALTER TABLE payment_attempt ADD id SERIAL; diff --git a/v2_migrations/2024-08-28-081747_recreate_ids_for_v2/up.sql b/v2_migrations/2024-08-28-081747_recreate_ids_for_v2/up.sql index 0e98d9792b38..99b944173937 100644 --- a/v2_migrations/2024-08-28-081747_recreate_ids_for_v2/up.sql +++ b/v2_migrations/2024-08-28-081747_recreate_ids_for_v2/up.sql @@ -1,6 +1,7 @@ -- This file contains queries to re-create the `id` column as a `VARCHAR` column instead of `SERIAL` column for tables that already have it. -- It must be ensured that the deployed version of the application does not include the `id` column in any of its queries. -- Drop the id column as this will be used later as the primary key with a different type +------------------------ Merchant Account ----------------------- ALTER TABLE merchant_account DROP COLUMN IF EXISTS id; -- Adding a new column called `id` which will be the new primary key for v2 @@ -8,17 +9,24 @@ ALTER TABLE merchant_account DROP COLUMN IF EXISTS id; ALTER TABLE merchant_account ADD COLUMN id VARCHAR(64); +------------------------ Merchant Connector Account ----------------------- -- This migration is to modify the id column in merchant_connector_account table to be a VARCHAR(64) and to set the id column as primary key ALTER TABLE merchant_connector_account DROP COLUMN IF EXISTS id; ALTER TABLE merchant_connector_account ADD COLUMN IF NOT EXISTS id VARCHAR(64); +------------------------ Customers ----------------------- ALTER TABLE customers DROP COLUMN IF EXISTS id; ALTER TABLE customers ADD COLUMN IF NOT EXISTS id VARCHAR(64); +------------------------ Payment Intent ----------------------- ALTER TABLE payment_intent DROP COLUMN id; +ALTER TABLE payment_intent +ADD COLUMN IF NOT EXISTS id VARCHAR(64); + +------------------------ Payment Attempt ----------------------- ALTER TABLE payment_attempt DROP COLUMN id; diff --git a/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/down.sql b/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/down.sql index 5d9f96e15f60..99af8b6deda9 100644 --- a/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/down.sql +++ b/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/down.sql @@ -62,3 +62,12 @@ WHERE customer_id IS NULL; ALTER TABLE customers ADD PRIMARY KEY (merchant_id, customer_id); + +ALTER TABLE payment_intent DROP CONSTRAINT payment_intent_pkey; + +UPDATE payment_intent +SET payment_id = id +WHERE payment_id IS NULL; + +ALTER TABLE payment_intent +ADD PRIMARY KEY (payment_id, merchant_id); diff --git a/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/up.sql b/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/up.sql index 19cf516da142..cde27046bcf8 100644 --- a/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/up.sql +++ b/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/up.sql @@ -1,6 +1,6 @@ -- This file contains queries to update the primary key constraints suitable to the v2 application. -- This also has queries to update other constraints and indexes on tables where applicable. --- Backfill for organization table +------------------------ Organization ----------------------- UPDATE ORGANIZATION SET id = org_id WHERE id IS NULL; @@ -16,6 +16,7 @@ ALTER TABLE ORGANIZATION DROP CONSTRAINT organization_pkey; ALTER TABLE ORGANIZATION ADD CONSTRAINT organization_pkey_id PRIMARY KEY (id); +------------------------ Merchant Account ----------------------- -- The new primary key for v2 merchant account will be `id` ALTER TABLE merchant_account DROP CONSTRAINT merchant_account_pkey; @@ -34,6 +35,7 @@ WHERE id IS NULL; ALTER TABLE merchant_account ADD PRIMARY KEY (id); +------------------------ Business Profile ----------------------- -- This migration is to modify the id column in business_profile table to be a VARCHAR(64) and to set the id column as primary key ALTER TABLE business_profile ADD COLUMN id VARCHAR(64); @@ -48,6 +50,7 @@ ALTER TABLE business_profile DROP CONSTRAINT business_profile_pkey; ALTER TABLE business_profile ADD PRIMARY KEY (id); +------------------------ Merchant Connector Account ----------------------- -- Backfill the id column with the merchant_connector_id to prevent null values UPDATE merchant_connector_account SET id = merchant_connector_id @@ -63,6 +66,7 @@ ALTER TABLE merchant_connector_account ALTER COLUMN profile_id SET NOT NULL; +------------------------ Customers ----------------------- -- Run this query only when V1 is deprecated ALTER TABLE customers DROP CONSTRAINT IF EXISTS customers_pkey; @@ -75,3 +79,9 @@ WHERE id IS NULL; ALTER TABLE customers ADD PRIMARY KEY (id); + +------------------------ Payment Intent ----------------------- +ALTER TABLE payment_intent DROP CONSTRAINT payment_intent_pkey; + +ALTER TABLE payment_intent +ADD PRIMARY KEY (id); diff --git a/v2_migrations/2024-08-28-081847_drop_v1_columns/down.sql b/v2_migrations/2024-08-28-081847_drop_v1_columns/down.sql index 7ca0e260b4fc..83b369beac70 100644 --- a/v2_migrations/2024-08-28-081847_drop_v1_columns/down.sql +++ b/v2_migrations/2024-08-28-081847_drop_v1_columns/down.sql @@ -46,3 +46,17 @@ ADD COLUMN IF NOT EXISTS business_country "CountryAlpha2", ALTER TABLE customers ADD COLUMN customer_id VARCHAR(64), ADD COLUMN address_id VARCHAR(64); + +ALTER TABLE payment_intent +ADD COLUMN IF NOT EXISTS payment_id VARCHAR(64) NOT NULL, + ADD COLUMN connector_id VARCHAR(64), + ADD COLUMN shipping_address_id VARCHAR(64), + ADD COLUMN billing_address_id VARCHAR(64), + ADD COLUMN shipping_details VARCHAR(64), + ADD COLUMN billing_details VARCHAR(64), + ADD COLUMN statement_descriptor_suffix VARCHAR(255), + ADD COLUMN business_country "CountryAlpha2", + ADD COLUMN business_label VARCHAR(64), + ADD COLUMN incremental_authorization_allowed BOOLEAN, + ADD COLUMN fingerprint_id VARCHAR(64), + ADD COLUMN merchant_decision VARCHAR(64); diff --git a/v2_migrations/2024-08-28-081847_drop_v1_columns/up.sql b/v2_migrations/2024-08-28-081847_drop_v1_columns/up.sql index 72344b8e7de2..0879d667f99f 100644 --- a/v2_migrations/2024-08-28-081847_drop_v1_columns/up.sql +++ b/v2_migrations/2024-08-28-081847_drop_v1_columns/up.sql @@ -44,3 +44,17 @@ ALTER TABLE merchant_connector_account DROP COLUMN IF EXISTS business_country, -- Run this query only when V1 is deprecated ALTER TABLE customers DROP COLUMN customer_id, DROP COLUMN address_id; + +-- Run below queries only when V1 is deprecated +ALTER TABLE payment_intent DROP COLUMN payment_id, + DROP COLUMN connector_id, + DROP COLUMN shipping_address_id, + DROP COLUMN billing_address_id, + DROP COLUMN shipping_details, + DROP COLUMN billing_details, + DROP COLUMN statement_descriptor_suffix, + DROP COLUMN business_country, + DROP COLUMN business_label, + DROP COLUMN incremental_authorization_allowed, + DROP COLUMN fingerprint_id, + DROP COLUMN merchant_decision;