diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 933fe96526c0..0d693808db73 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -20,6 +20,7 @@ crates/router/src/routes/ @juspay/hyperswitch-framework migrations/ @juspay/hyperswitch-framework openapi/ @juspay/hyperswitch-framework postman/ @juspay/hyperswitch-framework +cypress-tests/ @juspay/hyperswitch-framework @juspay/hyperswitch-qa Cargo.toml @juspay/hyperswitch-framework Cargo.lock @juspay/hyperswitch-framework diff --git a/.github/workflows/release-stable-version.yml b/.github/workflows/release-stable-version.yml index 64609aed863e..54a4d2b1a4ef 100644 --- a/.github/workflows/release-stable-version.yml +++ b/.github/workflows/release-stable-version.yml @@ -118,7 +118,7 @@ jobs: echo "PREVIOUS_TAG=${PREVIOUS_TAG}" >> $GITHUB_ENV echo "NEXT_TAG=${NEXT_TAG}" >> $GITHUB_ENV - # We make use of GitHub API calls to create the tag to have signed tags + # We make use of GitHub API calls to create and update tags - name: Create SemVer tag shell: bash env: @@ -133,6 +133,18 @@ jobs: --raw-field "ref=refs/tags/${NEXT_TAG}" \ --raw-field 'sha=${{ github.sha }}' + - name: Update `latest` tag to point to newly created SemVer tag + shell: bash + env: + GH_TOKEN: ${{ steps.generate_app_token.outputs.token }} + run: | + gh api \ + --method PATCH \ + --header 'Accept: application/vnd.github+json' \ + --header 'X-GitHub-Api-Version: 2022-11-28' \ + '/repos/{owner}/{repo}/git/refs/tags/latest' \ + --raw-field 'sha=${{ github.sha }}' + - name: Generate changelog shell: bash run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d5e4bc4fc60..0e59a94df3cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,248 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.05.27.0 + +### Refactors + +- **core:** Inclusion of constraint graph for merchant Payment Method list ([#4626](https://github.com/juspay/hyperswitch/pull/4626)) ([`2cabb0b`](https://github.com/juspay/hyperswitch/commit/2cabb0bedcdf0d1adf568f2533b6ab9ce8d9fc57)) + +### Miscellaneous Tasks + +- Add missing migrations for recently added currencies ([#4760](https://github.com/juspay/hyperswitch/pull/4760)) ([`1026f47`](https://github.com/juspay/hyperswitch/commit/1026f4783000a13b43f22e4db0b36c217d39e541)) + +**Full Changelog:** [`2024.05.24.1...2024.05.27.0`](https://github.com/juspay/hyperswitch/compare/2024.05.24.1...2024.05.27.0) + +- - - + +## 2024.05.24.1 + +### Features + +- **payment_charges:** Add support for collecting and refunding charges on payments ([#4628](https://github.com/juspay/hyperswitch/pull/4628)) ([`55ccce6`](https://github.com/juspay/hyperswitch/commit/55ccce61898083992afeab03ba1690954b1b45ef)) + +### Bug Fixes + +- **payment_methods:** + - Log and ignore the apple pay metadata parsing error while fetching apple pay retry connectors ([#4747](https://github.com/juspay/hyperswitch/pull/4747)) ([`a7fc4c6`](https://github.com/juspay/hyperswitch/commit/a7fc4c6fcd2f031b92e36f40a14be641673b7422)) + - Revert the filter for getting the mcas which are disabled ([#4756](https://github.com/juspay/hyperswitch/pull/4756)) ([`9fb2a83`](https://github.com/juspay/hyperswitch/commit/9fb2a8301453b47e2d1c17e215f740bea8eaa91a)) + +**Full Changelog:** [`2024.05.24.0...2024.05.24.1`](https://github.com/juspay/hyperswitch/compare/2024.05.24.0...2024.05.24.1) + +- - - + +## 2024.05.24.0 + +### Features + +- **analytics:** Added client columns in payments analytics ([#4658](https://github.com/juspay/hyperswitch/pull/4658)) ([`0b415dc`](https://github.com/juspay/hyperswitch/commit/0b415dcca67f2994727627990a9cc9db19885b34)) +- **router:** Send message_version and directory_server_id in next_action block of three_ds_data for external 3ds flow ([#4715](https://github.com/juspay/hyperswitch/pull/4715)) ([`13f6efc`](https://github.com/juspay/hyperswitch/commit/13f6efc7e8c01b4a377f627b9cfe2319b518204d)) +- **users:** + - Create terminate 2fa API ([#4731](https://github.com/juspay/hyperswitch/pull/4731)) ([`42e5ef1`](https://github.com/juspay/hyperswitch/commit/42e5ef155128f4df717e8fb101da6e6929659a0a)) + - Add support to verify 2FA using recovery code ([#4737](https://github.com/juspay/hyperswitch/pull/4737)) ([`f04c6ac`](https://github.com/juspay/hyperswitch/commit/f04c6ac030485cb28ab09e85a0f2f3c13beb6df3)) +- Authentication analytics ([#4684](https://github.com/juspay/hyperswitch/pull/4684)) ([`5e5eb5f`](https://github.com/juspay/hyperswitch/commit/5e5eb5fbae7de2e296899e0372c82906603526d6)) + +### Bug Fixes + +- **kafka:** Fix kafka timestamps sent from application ([#4709](https://github.com/juspay/hyperswitch/pull/4709)) ([`c778af2`](https://github.com/juspay/hyperswitch/commit/c778af26ddb46ff98072e8934a9509ff6e00ddc5)) +- **payment_methods:** Mask the email address being logged in the `payment_method_list` response logs ([#4749](https://github.com/juspay/hyperswitch/pull/4749)) ([`23c7395`](https://github.com/juspay/hyperswitch/commit/23c73951bbdd5e049b75ca6d8e3bcccfb629e6eb)) + +### Refactors + +- **bank-redirect:** Dynamic field changes for bankredirect payment method ([#4650](https://github.com/juspay/hyperswitch/pull/4650)) ([`da2dc10`](https://github.com/juspay/hyperswitch/commit/da2dc10f3d7233a0a9eae7d23cb07f7e8fafad78)) +- **payment_methods:** Use recurring enabled flag to decide which payment method supports MIT ([#4732](https://github.com/juspay/hyperswitch/pull/4732)) ([`ba624d0`](https://github.com/juspay/hyperswitch/commit/ba624d049840f65fc21a5e578f8d4ba8543e1420)) + +### Miscellaneous Tasks + +- Move RouterData Request types to hyperswitch_domain_models crate ([#4723](https://github.com/juspay/hyperswitch/pull/4723)) ([`ae77373`](https://github.com/juspay/hyperswitch/commit/ae77373b4cac63979673fdac37c55986d954358e)) + +**Full Changelog:** [`2024.05.23.0...2024.05.24.0`](https://github.com/juspay/hyperswitch/compare/2024.05.23.0...2024.05.24.0) + +- - - + +## 2024.05.23.0 + +### Features + +- **connector:** + - Accept connector_transaction_id in 4xx error_response of connector ([#4720](https://github.com/juspay/hyperswitch/pull/4720)) ([`2ad7fc0`](https://github.com/juspay/hyperswitch/commit/2ad7fc0cd6c102bea4d671c98f7fe50fd709d4ec)) + - [AUTHORIZEDOTNET] Implement zero mandates ([#4704](https://github.com/juspay/hyperswitch/pull/4704)) ([`8afeda5`](https://github.com/juspay/hyperswitch/commit/8afeda54fc5e3f3d510c48c81c222387e9cacc0e)) +- **payment_methods:** Enable auto-retries for apple pay ([#4721](https://github.com/juspay/hyperswitch/pull/4721)) ([`d942a31`](https://github.com/juspay/hyperswitch/commit/d942a31d60595d366977746be7215620da0ababd)) +- **routing:** Use Moka cache for routing with cache invalidation ([#3216](https://github.com/juspay/hyperswitch/pull/3216)) ([`431560b`](https://github.com/juspay/hyperswitch/commit/431560b7fb4401d000c11dbb9c7eb70663591307)) +- **users:** Create generate recovery codes API ([#4708](https://github.com/juspay/hyperswitch/pull/4708)) ([`8fa2cd5`](https://github.com/juspay/hyperswitch/commit/8fa2cd556bf898621a1a8722a0af99d174447485)) +- **webhook:** Add frm webhook support ([#4662](https://github.com/juspay/hyperswitch/pull/4662)) ([`ae601e8`](https://github.com/juspay/hyperswitch/commit/ae601e8e1be9215488daaae7cb39ad5a030e98d9)) + +### Bug Fixes + +- **core:** Fix failing token based MIT payments ([#4735](https://github.com/juspay/hyperswitch/pull/4735)) ([`1bd4061`](https://github.com/juspay/hyperswitch/commit/1bd406197b5baf1c041f0dffa5bc02dce10f1529)) +- Added hget lookup for all updated_by existing cases ([#4716](https://github.com/juspay/hyperswitch/pull/4716)) ([`fabf80c`](https://github.com/juspay/hyperswitch/commit/fabf80c2b18ca690b7fb709c8c12d1ef7f24e5b6)) + +### Miscellaneous Tasks + +- **postman:** Update Postman collection files ([`ec50843`](https://github.com/juspay/hyperswitch/commit/ec508435a19c2942a5d66757a74dd06bed5b1a76)) + +**Full Changelog:** [`2024.05.22.0...2024.05.23.0`](https://github.com/juspay/hyperswitch/compare/2024.05.22.0...2024.05.23.0) + +- - - + +## 2024.05.22.0 + +### Features + +- **core:** Add support for connectors having separate version call for pre authentication ([#4603](https://github.com/juspay/hyperswitch/pull/4603)) ([`528d692`](https://github.com/juspay/hyperswitch/commit/528d692a89f5cf9a82d1e5c28e4b3a1ef4bf6c6a)) + +### Refactors + +- **graph:** Refactor the Knowledge Graph to include configs check, while eligibility analysis ([#4687](https://github.com/juspay/hyperswitch/pull/4687)) ([`a917776`](https://github.com/juspay/hyperswitch/commit/a917776bb8cd294f77c569e81ea4d665b6611c6d)) + +### Miscellaneous Tasks + +- Move tracing to workspace deps and remove router_env as a dependency of redis_interface ([#4717](https://github.com/juspay/hyperswitch/pull/4717)) ([`fea2ea6`](https://github.com/juspay/hyperswitch/commit/fea2ea6d2cf4f3f68e4779e53b82120806748d7b)) + +**Full Changelog:** [`2024.05.21.1...2024.05.22.0`](https://github.com/juspay/hyperswitch/compare/2024.05.21.1...2024.05.22.0) + +- - - + +## 2024.05.21.1 + +### Features + +- **Cypress:** Add response handler for Connector Testing ([#4624](https://github.com/juspay/hyperswitch/pull/4624)) ([`2e79ee0`](https://github.com/juspay/hyperswitch/commit/2e79ee0615292182111586fda7655dd9a796ef4f)) +- **constraint_graph:** Add visualization functionality to the constraint graph ([#4701](https://github.com/juspay/hyperswitch/pull/4701)) ([`0f53f74`](https://github.com/juspay/hyperswitch/commit/0f53f74d26e829602519998c41a460dc9a4809af)) + +### Refactors + +- **core:** Add support to enable pm_data and pm_id in payments response ([#4711](https://github.com/juspay/hyperswitch/pull/4711)) ([`2cd360e`](https://github.com/juspay/hyperswitch/commit/2cd360e6a9d6bbe4b91f7b501b6013db1f31d898)) +- **router:** Added a new type minor unit to amount ([#4629](https://github.com/juspay/hyperswitch/pull/4629)) ([`443b7e6`](https://github.com/juspay/hyperswitch/commit/443b7e6ea2cf63f35a28a1cd24860399d96b15ba)) + +**Full Changelog:** [`2024.05.21.0...2024.05.21.1`](https://github.com/juspay/hyperswitch/compare/2024.05.21.0...2024.05.21.1) + +- - - + +## 2024.05.21.0 + +### Features + +- **core:** Add a new endpoint for Complete Authorize flow ([#4686](https://github.com/juspay/hyperswitch/pull/4686)) ([`226c337`](https://github.com/juspay/hyperswitch/commit/226c337399a2e4c1fa50c4f3d0d4b237b5543426)) + +### Bug Fixes + +- **router:** Handle connector authentication technical failures and skip confirm in authorize flow only when authentication_type is not challenge ([#4667](https://github.com/juspay/hyperswitch/pull/4667)) ([`842728e`](https://github.com/juspay/hyperswitch/commit/842728ef93241643d12170695ddf56cee4da45bd)) + +### Refactors + +- **cache:** Remove `deref` impl on `Cache` type ([#4671](https://github.com/juspay/hyperswitch/pull/4671)) ([`36409bd`](https://github.com/juspay/hyperswitch/commit/36409bdc9185d4241971a30c55e1e331568abd2f)) + +### Documentation + +- Update Docker Compose setup guide to checkout `latest` tag ([#4695](https://github.com/juspay/hyperswitch/pull/4695)) ([`40f6776`](https://github.com/juspay/hyperswitch/commit/40f6776c46abc4b9c89fb2aa195f4ce64b312cf6)) + +### Miscellaneous Tasks + +- **docker-compose:** Specify `pull_policy` for hyperswitch services ([#4688](https://github.com/juspay/hyperswitch/pull/4688)) ([`909e75c`](https://github.com/juspay/hyperswitch/commit/909e75c71a6e3418b5d15396569d986eff852c06)) + +**Full Changelog:** [`2024.05.20.2...2024.05.21.0`](https://github.com/juspay/hyperswitch/compare/2024.05.20.2...2024.05.21.0) + +- - - + +## 2024.05.20.2 + +### Features + +- Add an api for toggle KV for all merchants ([#4600](https://github.com/juspay/hyperswitch/pull/4600)) ([`7f53461`](https://github.com/juspay/hyperswitch/commit/7f5346169edc4266b7b08578aac7aef1ede630f3)) + +**Full Changelog:** [`2024.05.20.1...2024.05.20.2`](https://github.com/juspay/hyperswitch/compare/2024.05.20.1...2024.05.20.2) + +- - - + +## 2024.05.20.1 + +### Features + +- Soft kill kv ([#4582](https://github.com/juspay/hyperswitch/pull/4582)) ([`3fa59d4`](https://github.com/juspay/hyperswitch/commit/3fa59d4bac01de8fa25e28340a57e578d9980032)) + +**Full Changelog:** [`2024.05.20.0...2024.05.20.1`](https://github.com/juspay/hyperswitch/compare/2024.05.20.0...2024.05.20.1) + +- - - + +## 2024.05.20.0 + +### Features + +- Added client_source, client_version in payment_attempt from payments confirm request headers ([#4657](https://github.com/juspay/hyperswitch/pull/4657)) ([`7e44bbc`](https://github.com/juspay/hyperswitch/commit/7e44bbca63c1818c0fabdf2734d9b0ae5d639fe1)) + +### Bug Fixes + +- **docker:** Fix stack overflow for docker images ([#4660](https://github.com/juspay/hyperswitch/pull/4660)) ([`a62f69d`](https://github.com/juspay/hyperswitch/commit/a62f69d447245273c73611309055d2341a47b783)) +- Address non-digit character cases in card number validation ([#4649](https://github.com/juspay/hyperswitch/pull/4649)) ([`8c0d72e`](https://github.com/juspay/hyperswitch/commit/8c0d72e225c56b7bece733d9565fc8774deaa490)) + +### Refactors + +- **FRM:** Refactor frm configs ([#4581](https://github.com/juspay/hyperswitch/pull/4581)) ([`853f3b4`](https://github.com/juspay/hyperswitch/commit/853f3b4854ff9ec1e169b7633f1e9bf8259e9ceb)) + +**Full Changelog:** [`2024.05.17.0...2024.05.20.0`](https://github.com/juspay/hyperswitch/compare/2024.05.17.0...2024.05.20.0) + +- - - + +## 2024.05.17.0 + +### Bug Fixes + +- **core:** Use `realip_remote_addr` function to extract ip address ([#4653](https://github.com/juspay/hyperswitch/pull/4653)) ([`8427b60`](https://github.com/juspay/hyperswitch/commit/8427b60a1851f2d9d2f141f28eb122d42f680736)) +- **recon:** Make recon status optional in merchant account ([#4654](https://github.com/juspay/hyperswitch/pull/4654)) ([`84cb2bc`](https://github.com/juspay/hyperswitch/commit/84cb2bcb6bbb82f54315c82c7421a222d2e37bc6)) + +### Refactors + +- **access_token:** Handle network delays with expiry of access token ([#4617](https://github.com/juspay/hyperswitch/pull/4617)) ([`0d45f85`](https://github.com/juspay/hyperswitch/commit/0d45f854a2cc18cc421a3d449a6dc2c830ef9dd5)) +- **cards,router:** Remove duplicated card number interface ([#4404](https://github.com/juspay/hyperswitch/pull/4404)) ([`27ae437`](https://github.com/juspay/hyperswitch/commit/27ae437a88492bf5b17ad2fbf4a083891602c07a)) + +### Miscellaneous Tasks + +- Add deprecated flag to soon to be deprecated fields in payment request and response ([#4261](https://github.com/juspay/hyperswitch/pull/4261)) ([`9ac5d70`](https://github.com/juspay/hyperswitch/commit/9ac5d70e2ed0a036b5f2bfe7488f218b83fce7c3)) + +**Full Changelog:** [`2024.05.16.1...2024.05.17.0`](https://github.com/juspay/hyperswitch/compare/2024.05.16.1...2024.05.17.0) + +- - - + +## 2024.05.16.1 + +### Features + +- **middleware:** Log content_length for 4xx ([#4655](https://github.com/juspay/hyperswitch/pull/4655)) ([`4b5b558`](https://github.com/juspay/hyperswitch/commit/4b5b558dae8d2fefb66b8b16c486f07e3e800758)) + +### Refactors + +- **session_flow:** Remove the shipping and billing parameter fields if null for apple pay and google pay ([#4661](https://github.com/juspay/hyperswitch/pull/4661)) ([`0dee53e`](https://github.com/juspay/hyperswitch/commit/0dee53ecb2d5203285a819bc8e71111d2c133f03)) + +**Full Changelog:** [`2024.05.16.0...2024.05.16.1`](https://github.com/juspay/hyperswitch/compare/2024.05.16.0...2024.05.16.1) + +- - - + +## 2024.05.16.0 + +### Features + +- **core:** Move RouterData to crate hyperswitch_domain_models ([#4524](https://github.com/juspay/hyperswitch/pull/4524)) ([`ff1c2dd`](https://github.com/juspay/hyperswitch/commit/ff1c2ddf8b9d8f35deee1ab41c2286cc5b349271)) + +### Bug Fixes + +- **connector:** Accept state abbreviation in 2 letter ([#4646](https://github.com/juspay/hyperswitch/pull/4646)) ([`3cf840e`](https://github.com/juspay/hyperswitch/commit/3cf840e48678e56a443bc891c48589d4b53bc07a)) +- **router:** Add `max_amount` validation in payment flows ([#4645](https://github.com/juspay/hyperswitch/pull/4645)) ([`df865d7`](https://github.com/juspay/hyperswitch/commit/df865d76be1c867b9ee4d9cbb92a98dca4ecf229)) + +### Refactors + +- **bank-redirect:** Remove billing from bankredirect payment data ([#4362](https://github.com/juspay/hyperswitch/pull/4362)) ([`0958d94`](https://github.com/juspay/hyperswitch/commit/0958d948f98bc41df64d8ea18cb1a8d3a0eb80fe)) +- **db:** Add TenantID field to KafkaEvent struct ([#4598](https://github.com/juspay/hyperswitch/pull/4598)) ([`24214bc`](https://github.com/juspay/hyperswitch/commit/24214bcfcd0a34acd39dba88f6c015ac6b1edbc4)) +- **router:** Remove default case handling in bambora connector ([#4473](https://github.com/juspay/hyperswitch/pull/4473)) ([`1a27ba5`](https://github.com/juspay/hyperswitch/commit/1a27ba576427126cc6a3fe2be86489abc9af63d8)) + +### Miscellaneous Tasks + +- **postman:** Update Postman collection files ([`f2ff7a2`](https://github.com/juspay/hyperswitch/commit/f2ff7a211b9f7ca16352061768e9b7c0a38a3845)) + +**Full Changelog:** [`2024.05.15.0...2024.05.16.0`](https://github.com/juspay/hyperswitch/compare/2024.05.15.0...2024.05.16.0) + +- - - + ## 2024.05.15.0 ### Features diff --git a/Cargo.lock b/Cargo.lock index b823f9e5c965..9411c009aef1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1728,7 +1728,6 @@ version = "0.1.0" dependencies = [ "common_utils", "error-stack", - "luhn", "masking", "router_env", "serde", @@ -1978,6 +1977,7 @@ dependencies = [ "thiserror", "time", "tokio 1.37.0", + "utoipa", "uuid", ] @@ -2598,12 +2598,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "digits_iterator" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af83450b771231745d43edf36dc9b7813ab83be5e8cbea344ccced1a09dfebcd" - [[package]] name = "displaydoc" version = "0.2.4" @@ -2624,6 +2618,21 @@ dependencies = [ "const-random", ] +[[package]] +name = "dot-generator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aaac7ada45f71873ebce336491d1c1bc4a7c8042c7cea978168ad59e805b871" +dependencies = [ + "dot-structures", +] + +[[package]] +name = "dot-structures" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "675e35c02a51bb4d4618cb4885b3839ce6d1787c97b664474d9208d074742e20" + [[package]] name = "dotenvy" version = "0.15.7" @@ -3259,6 +3268,22 @@ dependencies = [ "walkdir", ] +[[package]] +name = "graphviz-rust" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27dafd1ac303e0dfb347a3861d9ac440859bab26ec2f534bbceb262ea492a1e0" +dependencies = [ + "dot-generator", + "dot-structures", + "into-attr", + "into-attr-derive", + "pest", + "pest_derive", + "rand", + "tempfile", +] + [[package]] name = "h2" version = "0.3.25" @@ -3630,6 +3655,7 @@ name = "hyperswitch_constraint_graph" version = "0.1.0" dependencies = [ "erased-serde 0.3.31", + "graphviz-rust", "rustc-hash", "serde", "serde_json", @@ -3641,17 +3667,25 @@ dependencies = [ name = "hyperswitch_domain_models" version = "0.1.0" dependencies = [ + "actix-web", "api_models", "async-trait", + "cards", "common_enums", "common_utils", "diesel_models", "error-stack", + "http 0.2.12", "masking", + "mime", + "router_derive", "serde", "serde_json", + "serde_with", "thiserror", "time", + "url", + "utoipa", ] [[package]] @@ -3779,6 +3813,28 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "into-attr" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18b48c537e49a709e678caec3753a7dba6854661a1eaa27675024283b3f8b376" +dependencies = [ + "dot-structures", +] + +[[package]] +name = "into-attr-derive" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecac7c1ae6cd2c6a3a64d1061a8bdc7f52ff62c26a831a2301e54c1b5d70d5b1" +dependencies = [ + "dot-generator", + "dot-structures", + "into-attr", + "quote", + "syn 1.0.109", +] + [[package]] name = "iovec" version = "0.1.4" @@ -3950,6 +4006,7 @@ dependencies = [ "masking", "serde", "serde_json", + "strum 0.26.2", "thiserror", ] @@ -4088,15 +4145,6 @@ dependencies = [ "linked-hash-map", ] -[[package]] -name = "luhn" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d10b831402a3b10e018c8bc7f0ec3344a67d0725919cbaf393accb9baf8700b" -dependencies = [ - "digits_iterator", -] - [[package]] name = "masking" version = "0.1.0" @@ -4526,6 +4574,7 @@ name = "openapi" version = "0.1.0" dependencies = [ "api_models", + "common_utils", "serde_json", "utoipa", ] @@ -5378,11 +5427,11 @@ dependencies = [ "error-stack", "fred", "futures 0.3.30", - "router_env", "serde", "thiserror", "tokio 1.37.0", "tokio-stream", + "tracing", ] [[package]] @@ -6009,6 +6058,7 @@ dependencies = [ "error-stack", "external_services", "futures 0.3.30", + "hyperswitch_domain_models", "masking", "num_cpus", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index 82687a32ba6b..fc2095275d3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,9 @@ package.edition = "2021" package.rust-version = "1.70" package.license = "Apache-2.0" +[workspace.dependencies] +tracing = { version = "0.1.40" } + [profile.release] strip = true lto = true diff --git a/Dockerfile b/Dockerfile index e9591e5e9f27..a86971777163 100644 --- a/Dockerfile +++ b/Dockerfile @@ -61,7 +61,8 @@ ENV TZ=Etc/UTC \ RUN_ENV=${RUN_ENV} \ CONFIG_DIR=${CONFIG_DIR} \ SCHEDULER_FLOW=${SCHEDULER_FLOW} \ - BINARY=${BINARY} + BINARY=${BINARY} \ + RUST_MIN_STACK=4194304 RUN mkdir -p ${BIN_DIR} diff --git a/README.md b/README.md index 2be649b1db6e..5bfcfdfd62b1 100644 --- a/README.md +++ b/README.md @@ -75,10 +75,12 @@ The fastest and easiest way to try Hyperswitch is via our CDK scripts You can run Hyperswitch on your system using Docker Compose after cloning this repository: ```shell +git clone --depth 1 --branch latest https://github.com/juspay/hyperswitch +cd hyperswitch docker compose up -d ``` -This will start the payments router, the primary component within Hyperswitch. +This will start the app server, web client and control center. Check out the [local setup guide][local-setup-guide] for a more comprehensive setup, which includes the [scheduler and monitoring services][docker-compose-scheduler-monitoring]. diff --git a/config/config.example.toml b/config/config.example.toml index 6835cfdb4a1a..d5bdfb5a6a7c 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -351,7 +351,8 @@ email_role_arn = "" # The amazon resource name ( arn ) of the role which sts_role_session_name = "" # An identifier for the assumed role session, used to uniquely identify a session. [user] -password_validity_in_days = 90 # Number of days after which password should be updated +password_validity_in_days = 90 # Number of days after which password should be updated +two_factor_auth_expiry_in_secs = 300 # Number of seconds after which 2FA should be done again if doing update/change from inside #tokenization configuration which describe token lifetime and payment method for specific connector [tokenization] @@ -623,4 +624,4 @@ refunds = "hyperswitch-refund-events" disputes = "hyperswitch-dispute-events" [saved_payment_methods] -sdk_eligible_payment_methods = "card" \ No newline at end of file +sdk_eligible_payment_methods = "card" diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index c9b35a1f359c..9551cd2a80d8 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -113,6 +113,7 @@ slack_invite_url = "https://join.slack.com/t/hyperswitch-io/shared_invite/zt-2aw [user] password_validity_in_days = 90 +two_factor_auth_expiry_in_secs = 300 [frm] enabled = true @@ -327,4 +328,4 @@ connectors_with_webhook_source_verification_call = "paypal" # List of co keys = "user-agent" [saved_payment_methods] -sdk_eligible_payment_methods = "card" \ No newline at end of file +sdk_eligible_payment_methods = "card" diff --git a/config/deployments/production.toml b/config/deployments/production.toml index 938c034c9d16..5f9408674383 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -120,6 +120,7 @@ slack_invite_url = "https://join.slack.com/t/hyperswitch-io/shared_invite/zt-2aw [user] password_validity_in_days = 90 +two_factor_auth_expiry_in_secs = 300 [frm] enabled = false @@ -338,4 +339,4 @@ connectors_with_webhook_source_verification_call = "paypal" # List of connec keys = "user-agent" [saved_payment_methods] -sdk_eligible_payment_methods = "card" \ No newline at end of file +sdk_eligible_payment_methods = "card" diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 7388d5bb8a76..d5f63524e6bd 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -120,6 +120,7 @@ slack_invite_url = "https://join.slack.com/t/hyperswitch-io/shared_invite/zt-2aw [user] password_validity_in_days = 90 +two_factor_auth_expiry_in_secs = 300 [frm] enabled = true @@ -342,4 +343,4 @@ connectors_with_webhook_source_verification_call = "paypal" # List of con keys = "user-agent" [saved_payment_methods] -sdk_eligible_payment_methods = "card" \ No newline at end of file +sdk_eligible_payment_methods = "card" diff --git a/config/development.toml b/config/development.toml index 80f4d2b8eaa5..a9f7f4d7b4d8 100644 --- a/config/development.toml +++ b/config/development.toml @@ -269,6 +269,7 @@ sts_role_session_name = "" [user] password_validity_in_days = 90 +two_factor_auth_expiry_in_secs = 300 [bank_config.eps] stripe = { banks = "arzte_und_apotheker_bank,austrian_anadi_bank_ag,bank_austria,bankhaus_carl_spangler,bankhaus_schelhammer_und_schattera_ag,bawag_psk_ag,bks_bank_ag,brull_kallmus_bank_ag,btv_vier_lander_bank,capital_bank_grawe_gruppe_ag,dolomitenbank,easybank_ag,erste_bank_und_sparkassen,hypo_alpeadriabank_international_ag,hypo_noe_lb_fur_niederosterreich_u_wien,hypo_oberosterreich_salzburg_steiermark,hypo_tirol_bank_ag,hypo_vorarlberg_bank_ag,hypo_bank_burgenland_aktiengesellschaft,marchfelder_bank,oberbank_ag,raiffeisen_bankengruppe_osterreich,schoellerbank_ag,sparda_bank_wien,volksbank_gruppe,volkskreditbank_ag,vr_bank_braunau" } @@ -560,6 +561,7 @@ delay_between_retries_in_milliseconds = 500 [kv_config] ttl = 900 # 15 * 60 seconds +soft_kill = false [frm] enabled = true diff --git a/config/docker_compose.toml b/config/docker_compose.toml index b9b25e4666fd..6661d164032d 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -53,6 +53,7 @@ recon_admin_api_key = "recon_test_admin" [user] password_validity_in_days = 90 +two_factor_auth_expiry_in_secs = 300 [locker] host = "" @@ -450,6 +451,7 @@ queue_strategy = "Fifo" [kv_config] ttl = 900 # 15 * 60 seconds +soft_kill = false [frm] enabled = true @@ -485,4 +487,4 @@ refunds = "hyperswitch-refund-events" disputes = "hyperswitch-dispute-events" [saved_payment_methods] -sdk_eligible_payment_methods = "card" \ No newline at end of file +sdk_eligible_payment_methods = "card" diff --git a/connector-template/mod.rs b/connector-template/mod.rs index 760e1528cfae..cecf5ef1c60a 100644 --- a/connector-template/mod.rs +++ b/connector-template/mod.rs @@ -7,7 +7,7 @@ use masking::ExposeInterface; use crate::{ events::connector_api_logs::ConnectorEvent, configs::settings, - utils::{self, BytesExt}, + utils::BytesExt, core::{ errors::{self, CustomResult}, }, diff --git a/connector-template/transformers.rs b/connector-template/transformers.rs index 60b13693054d..fb08c1026f25 100644 --- a/connector-template/transformers.rs +++ b/connector-template/transformers.rs @@ -1,10 +1,10 @@ use serde::{Deserialize, Serialize}; use masking::Secret; -use crate::{connector::utils::{PaymentsAuthorizeRequestData},core::errors,types::{self,api, storage::enums}}; +use crate::{connector::utils::{PaymentsAuthorizeRequestData},core::errors,types::{self, domain, api, storage::enums}}; //TODO: Fill the struct with respective fields pub struct {{project-name | downcase | pascal_case}}RouterData { - pub amount: i64, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub amount: MinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. pub router_data: T, } @@ -53,7 +53,7 @@ impl TryFrom<&{{project-name | downcase | pascal_case}}RouterData<&types::Paymen type Error = error_stack::Report; fn try_from(item: &{{project-name | downcase | pascal_case}}RouterData<&types::PaymentsAuthorizeRouterData>) -> Result { match item.router_data.request.payment_method_data.clone() { - api::PaymentMethodData::Card(req_card) => { + domain::PaymentMethodData::Card(req_card) => { let card = {{project-name | downcase | pascal_case}}Card { number: req_card.card_number, expiry_month: req_card.card_exp_month, @@ -128,7 +128,8 @@ impl TryFrom TryFrom<&{{project-name | downcase | pascal_case}}RouterData<&types::RefundsRouterData>> for {{project-name | downcase | pascal_case}}RefundRequest { diff --git a/crates/analytics/docs/clickhouse/scripts/payment_attempts.sql b/crates/analytics/docs/clickhouse/scripts/payment_attempts.sql index e5c52ccace82..223d29c98368 100644 --- a/crates/analytics/docs/clickhouse/scripts/payment_attempts.sql +++ b/crates/analytics/docs/clickhouse/scripts/payment_attempts.sql @@ -38,6 +38,8 @@ CREATE TABLE payment_attempt_queue ( `unified_code` Nullable(String), `unified_message` Nullable(String), `mandate_data` Nullable(String), + `client_source` LowCardinality(Nullable(String)), + `client_version` LowCardinality(Nullable(String)), `sign_flag` Int8 ) ENGINE = Kafka SETTINGS kafka_broker_list = 'kafka0:29092', kafka_topic_list = 'hyperswitch-payment-attempt-events', @@ -86,6 +88,8 @@ CREATE TABLE payment_attempts ( `unified_message` Nullable(String), `mandate_data` Nullable(String), `inserted_at` DateTime DEFAULT now() CODEC(T64, LZ4), + `client_source` LowCardinality(Nullable(String)), + `client_version` LowCardinality(Nullable(String)), `sign_flag` Int8, INDEX connectorIndex connector TYPE bloom_filter GRANULARITY 1, INDEX paymentMethodIndex payment_method TYPE bloom_filter GRANULARITY 1, @@ -137,6 +141,8 @@ CREATE MATERIALIZED VIEW payment_attempt_mv TO payment_attempts ( `unified_message` Nullable(String), `mandate_data` Nullable(String), `inserted_at` DateTime64(3), + `client_source` LowCardinality(Nullable(String)), + `client_version` LowCardinality(Nullable(String)), `sign_flag` Int8 ) AS SELECT @@ -180,6 +186,8 @@ SELECT unified_message, mandate_data, now() AS inserted_at, + client_source, + client_version, sign_flag FROM payment_attempt_queue diff --git a/crates/analytics/src/auth_events/accumulator.rs b/crates/analytics/src/auth_events/accumulator.rs index 409e805af8f9..2958030c8da6 100644 --- a/crates/analytics/src/auth_events/accumulator.rs +++ b/crates/analytics/src/auth_events/accumulator.rs @@ -11,6 +11,7 @@ pub struct AuthEventMetricsAccumulator { pub challenge_attempt_count: CountAccumulator, pub challenge_success_count: CountAccumulator, pub frictionless_flow_count: CountAccumulator, + pub frictionless_success_count: CountAccumulator, } #[derive(Debug, Default)] @@ -53,6 +54,7 @@ impl AuthEventMetricsAccumulator { challenge_attempt_count: self.challenge_attempt_count.collect(), challenge_success_count: self.challenge_success_count.collect(), frictionless_flow_count: self.frictionless_flow_count.collect(), + frictionless_success_count: self.frictionless_success_count.collect(), } } } diff --git a/crates/analytics/src/auth_events/core.rs b/crates/analytics/src/auth_events/core.rs index 761a95bb9b3b..5ee34d7a36fd 100644 --- a/crates/analytics/src/auth_events/core.rs +++ b/crates/analytics/src/auth_events/core.rs @@ -78,6 +78,9 @@ pub async fn get_metrics( AuthEventMetrics::FrictionlessFlowCount => metrics_builder .frictionless_flow_count .add_metrics_bucket(&value), + AuthEventMetrics::FrictionlessSuccessCount => metrics_builder + .frictionless_success_count + .add_metrics_bucket(&value), } } } diff --git a/crates/analytics/src/auth_events/metrics.rs b/crates/analytics/src/auth_events/metrics.rs index 683074749273..ae61aefb858b 100644 --- a/crates/analytics/src/auth_events/metrics.rs +++ b/crates/analytics/src/auth_events/metrics.rs @@ -15,6 +15,7 @@ mod challenge_attempt_count; mod challenge_flow_count; mod challenge_success_count; mod frictionless_flow_count; +mod frictionless_success_count; mod three_ds_sdk_count; use authentication_attempt_count::AuthenticationAttemptCount; @@ -23,6 +24,7 @@ use challenge_attempt_count::ChallengeAttemptCount; use challenge_flow_count::ChallengeFlowCount; use challenge_success_count::ChallengeSuccessCount; use frictionless_flow_count::FrictionlessFlowCount; +use frictionless_success_count::FrictionlessSuccessCount; use three_ds_sdk_count::ThreeDsSdkCount; #[derive(Debug, PartialEq, Eq, serde::Deserialize)] @@ -102,6 +104,11 @@ where .load_metrics(merchant_id, publishable_key, granularity, time_range, pool) .await } + Self::FrictionlessSuccessCount => { + FrictionlessSuccessCount + .load_metrics(merchant_id, publishable_key, granularity, time_range, pool) + .await + } } } } diff --git a/crates/analytics/src/auth_events/metrics/challenge_attempt_count.rs b/crates/analytics/src/auth_events/metrics/challenge_attempt_count.rs index dbea5a1dc17a..f2d2ab153f06 100644 --- a/crates/analytics/src/auth_events/metrics/challenge_attempt_count.rs +++ b/crates/analytics/src/auth_events/metrics/challenge_attempt_count.rs @@ -8,7 +8,7 @@ use time::PrimitiveDateTime; use super::AuthEventMetricRow; use crate::{ - query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, ToSql, Window}, + query::{Aggregate, FilterTypes, GroupByClause, QueryBuilder, QueryFilter, ToSql, Window}, types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, }; @@ -34,7 +34,7 @@ where pool: &T, ) -> MetricsResult> { let mut query_builder: QueryBuilder = - QueryBuilder::new(AnalyticsCollection::ConnectorEventsAnalytics); + QueryBuilder::new(AnalyticsCollection::ApiEventsAnalytics); query_builder .add_select_column(Aggregate::Count { @@ -54,7 +54,11 @@ where .switch()?; query_builder - .add_filter_clause("flow", AuthEventFlows::PostAuthentication) + .add_filter_clause("api_flow", AuthEventFlows::IncomingWebhookReceive) + .switch()?; + + query_builder + .add_custom_filter_clause("request", "threeDSServerTransID", FilterTypes::Like) .switch()?; time_range diff --git a/crates/analytics/src/auth_events/metrics/challenge_success_count.rs b/crates/analytics/src/auth_events/metrics/challenge_success_count.rs index cb0932c33c22..b7d55714b604 100644 --- a/crates/analytics/src/auth_events/metrics/challenge_success_count.rs +++ b/crates/analytics/src/auth_events/metrics/challenge_success_count.rs @@ -34,7 +34,7 @@ where pool: &T, ) -> MetricsResult> { let mut query_builder: QueryBuilder = - QueryBuilder::new(AnalyticsCollection::ConnectorEventsAnalytics); + QueryBuilder::new(AnalyticsCollection::ApiEventsAnalytics); query_builder .add_select_column(Aggregate::Count { @@ -54,11 +54,11 @@ where .switch()?; query_builder - .add_filter_clause("flow", AuthEventFlows::PostAuthentication) + .add_filter_clause("api_flow", AuthEventFlows::IncomingWebhookReceive) .switch()?; query_builder - .add_filter_clause("visitParamExtractRaw(response, 'transStatus')", "\"Y\"") + .add_filter_clause("visitParamExtractRaw(request, 'transStatus')", "\"Y\"") .switch()?; time_range diff --git a/crates/analytics/src/auth_events/metrics/frictionless_success_count.rs b/crates/analytics/src/auth_events/metrics/frictionless_success_count.rs new file mode 100644 index 000000000000..2f05a050ce7b --- /dev/null +++ b/crates/analytics/src/auth_events/metrics/frictionless_success_count.rs @@ -0,0 +1,94 @@ +use api_models::analytics::{ + auth_events::{AuthEventFlows, AuthEventMetricsBucketIdentifier}, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::AuthEventMetricRow; +use crate::{ + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(super) struct FrictionlessSuccessCount; + +#[async_trait::async_trait] +impl super::AuthEventMetric for FrictionlessSuccessCount +where + T: AnalyticsDataSource + super::AuthEventMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + merchant_id: &str, + _publishable_key: &str, + granularity: &Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> { + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::ApiEventsAnalytics); + + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + + if let Some(granularity) = granularity.as_ref() { + query_builder + .add_granularity_in_mins(granularity) + .switch()?; + } + + query_builder + .add_filter_clause("merchant_id", merchant_id) + .switch()?; + + query_builder + .add_filter_clause("api_flow", AuthEventFlows::PaymentsExternalAuthentication) + .switch()?; + + query_builder + .add_filter_clause("visitParamExtractRaw(response, 'transStatus')", "\"Y\"") + .switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + if let Some(_granularity) = granularity.as_ref() { + query_builder + .add_group_by_clause("time_bucket") + .attach_printable("Error adding granularity") + .switch()?; + } + + query_builder + .execute_query::(pool) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(|i| { + Ok(( + AuthEventMetricsBucketIdentifier::new(i.time_bucket.clone()), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/clickhouse.rs b/crates/analytics/src/clickhouse.rs index 3009c036b99c..64064c09c880 100644 --- a/crates/analytics/src/clickhouse.rs +++ b/crates/analytics/src/clickhouse.rs @@ -136,7 +136,7 @@ impl AnalyticsDataSource for ClickhouseClient { AnalyticsCollection::SdkEvents | AnalyticsCollection::ApiEvents | AnalyticsCollection::ConnectorEvents - | AnalyticsCollection::ConnectorEventsAnalytics + | AnalyticsCollection::ApiEventsAnalytics | AnalyticsCollection::OutgoingWebhookEvent => TableEngine::BasicTree, } } @@ -374,7 +374,7 @@ impl ToSql for AnalyticsCollection { Self::Refund => Ok("refunds".to_string()), Self::SdkEvents => Ok("sdk_events_audit".to_string()), Self::ApiEvents => Ok("api_events_audit".to_string()), - Self::ConnectorEventsAnalytics => Ok("connector_events".to_string()), + Self::ApiEventsAnalytics => Ok("api_events".to_string()), Self::PaymentIntent => Ok("payment_intents".to_string()), Self::ConnectorEvents => Ok("connector_events_audit".to_string()), Self::OutgoingWebhookEvent => Ok("outgoing_webhook_events_audit".to_string()), diff --git a/crates/analytics/src/payments/core.rs b/crates/analytics/src/payments/core.rs index debc03fc9d58..a3f24b65a17e 100644 --- a/crates/analytics/src/payments/core.rs +++ b/crates/analytics/src/payments/core.rs @@ -291,6 +291,8 @@ pub async fn get_filters( PaymentDimensions::AuthType => fil.authentication_type.map(|i| i.as_ref().to_string()), PaymentDimensions::PaymentMethod => fil.payment_method, PaymentDimensions::PaymentMethodType => fil.payment_method_type, + PaymentDimensions::ClientSource => fil.client_source, + PaymentDimensions::ClientVersion => fil.client_version, }) .collect::>(); res.query_data.push(FilterValue { diff --git a/crates/analytics/src/payments/distribution.rs b/crates/analytics/src/payments/distribution.rs index cf18c26310a7..c238eb79b9b8 100644 --- a/crates/analytics/src/payments/distribution.rs +++ b/crates/analytics/src/payments/distribution.rs @@ -24,6 +24,8 @@ pub struct PaymentDistributionRow { pub authentication_type: Option>, pub payment_method: Option, pub payment_method_type: Option, + pub client_source: Option, + pub client_version: Option, pub total: Option, pub count: Option, pub error_message: Option, diff --git a/crates/analytics/src/payments/distribution/payment_error_message.rs b/crates/analytics/src/payments/distribution/payment_error_message.rs index c70fc09aeac4..7dc42994d295 100644 --- a/crates/analytics/src/payments/distribution/payment_error_message.rs +++ b/crates/analytics/src/payments/distribution/payment_error_message.rs @@ -153,6 +153,8 @@ where i.authentication_type.as_ref().map(|i| i.0), i.payment_method.clone(), i.payment_method_type.clone(), + i.client_source.clone(), + i.client_version.clone(), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/payments/filters.rs b/crates/analytics/src/payments/filters.rs index 6c165f78a8e4..232a3e5b0a39 100644 --- a/crates/analytics/src/payments/filters.rs +++ b/crates/analytics/src/payments/filters.rs @@ -57,4 +57,6 @@ pub struct FilterRow { pub authentication_type: Option>, pub payment_method: Option, pub payment_method_type: Option, + pub client_source: Option, + pub client_version: Option, } diff --git a/crates/analytics/src/payments/metrics.rs b/crates/analytics/src/payments/metrics.rs index 6fe6b6260d48..59a9ca88a847 100644 --- a/crates/analytics/src/payments/metrics.rs +++ b/crates/analytics/src/payments/metrics.rs @@ -35,6 +35,8 @@ pub struct PaymentMetricRow { pub authentication_type: Option>, pub payment_method: Option, pub payment_method_type: Option, + pub client_source: Option, + pub client_version: Option, pub total: Option, pub count: Option, #[serde(with = "common_utils::custom_serde::iso8601::option")] diff --git a/crates/analytics/src/payments/metrics/avg_ticket_size.rs b/crates/analytics/src/payments/metrics/avg_ticket_size.rs index 9475d5288a64..38ab0a9540d8 100644 --- a/crates/analytics/src/payments/metrics/avg_ticket_size.rs +++ b/crates/analytics/src/payments/metrics/avg_ticket_size.rs @@ -113,6 +113,8 @@ where i.authentication_type.as_ref().map(|i| i.0), i.payment_method.clone(), i.payment_method_type.clone(), + i.client_source.clone(), + i.client_version.clone(), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/payments/metrics/connector_success_rate.rs b/crates/analytics/src/payments/metrics/connector_success_rate.rs index 0c4d19b2e0ba..2742327f4ab9 100644 --- a/crates/analytics/src/payments/metrics/connector_success_rate.rs +++ b/crates/analytics/src/payments/metrics/connector_success_rate.rs @@ -107,6 +107,8 @@ where i.authentication_type.as_ref().map(|i| i.0), i.payment_method.clone(), i.payment_method_type.clone(), + i.client_source.clone(), + i.client_version.clone(), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/payments/metrics/payment_count.rs b/crates/analytics/src/payments/metrics/payment_count.rs index 34e71f3da6fb..50ea1791b398 100644 --- a/crates/analytics/src/payments/metrics/payment_count.rs +++ b/crates/analytics/src/payments/metrics/payment_count.rs @@ -99,6 +99,8 @@ where i.authentication_type.as_ref().map(|i| i.0), i.payment_method.clone(), i.payment_method_type.clone(), + i.client_source.clone(), + i.client_version.clone(), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/payments/metrics/payment_processed_amount.rs b/crates/analytics/src/payments/metrics/payment_processed_amount.rs index f2dbf97e0db9..6ea5b15734f4 100644 --- a/crates/analytics/src/payments/metrics/payment_processed_amount.rs +++ b/crates/analytics/src/payments/metrics/payment_processed_amount.rs @@ -107,6 +107,8 @@ where i.authentication_type.as_ref().map(|i| i.0), i.payment_method.clone(), i.payment_method_type.clone(), + i.client_source.clone(), + i.client_version.clone(), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/payments/metrics/payment_success_count.rs b/crates/analytics/src/payments/metrics/payment_success_count.rs index a6fb8ed2239d..4350700618e9 100644 --- a/crates/analytics/src/payments/metrics/payment_success_count.rs +++ b/crates/analytics/src/payments/metrics/payment_success_count.rs @@ -106,6 +106,8 @@ where i.authentication_type.as_ref().map(|i| i.0), i.payment_method.clone(), i.payment_method_type.clone(), + i.client_source.clone(), + i.client_version.clone(), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/payments/metrics/retries_count.rs b/crates/analytics/src/payments/metrics/retries_count.rs index 91952adb569a..87d80c87fb4d 100644 --- a/crates/analytics/src/payments/metrics/retries_count.rs +++ b/crates/analytics/src/payments/metrics/retries_count.rs @@ -99,6 +99,8 @@ where i.authentication_type.as_ref().map(|i| i.0), i.payment_method.clone(), i.payment_method_type.clone(), + i.client_source.clone(), + i.client_version.clone(), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/payments/metrics/success_rate.rs b/crates/analytics/src/payments/metrics/success_rate.rs index 9e688240ddbf..a0c67c1d8070 100644 --- a/crates/analytics/src/payments/metrics/success_rate.rs +++ b/crates/analytics/src/payments/metrics/success_rate.rs @@ -102,6 +102,8 @@ where i.authentication_type.as_ref().map(|i| i.0), i.payment_method.clone(), i.payment_method_type.clone(), + i.client_source.clone(), + i.client_version.clone(), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/payments/types.rs b/crates/analytics/src/payments/types.rs index d5d8eca13e58..b44446c66a94 100644 --- a/crates/analytics/src/payments/types.rs +++ b/crates/analytics/src/payments/types.rs @@ -50,6 +50,16 @@ where ) .attach_printable("Error adding payment method filter")?; } + if !self.client_source.is_empty() { + builder + .add_filter_in_range_clause(PaymentDimensions::ClientSource, &self.client_source) + .attach_printable("Error adding client source filter")?; + } + if !self.client_version.is_empty() { + builder + .add_filter_in_range_clause(PaymentDimensions::ClientVersion, &self.client_version) + .attach_printable("Error adding client version filter")?; + } Ok(()) } } diff --git a/crates/analytics/src/sqlx.rs b/crates/analytics/src/sqlx.rs index df146cc799d0..d3cb24a00c26 100644 --- a/crates/analytics/src/sqlx.rs +++ b/crates/analytics/src/sqlx.rs @@ -260,6 +260,15 @@ impl<'a> FromRow<'a, PgRow> for super::payments::metrics::PaymentMetricRow { ColumnNotFound(_) => Ok(Default::default()), e => Err(e), })?; + let client_source: Option = row.try_get("client_source").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let client_version: Option = + row.try_get("client_version").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; let total: Option = row.try_get("total").or_else(|e| match e { ColumnNotFound(_) => Ok(Default::default()), e => Err(e), @@ -282,6 +291,8 @@ impl<'a> FromRow<'a, PgRow> for super::payments::metrics::PaymentMetricRow { authentication_type, payment_method, payment_method_type, + client_source, + client_version, total, count, start_bucket, @@ -321,6 +332,15 @@ impl<'a> FromRow<'a, PgRow> for super::payments::distribution::PaymentDistributi ColumnNotFound(_) => Ok(Default::default()), e => Err(e), })?; + let client_source: Option = row.try_get("client_source").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let client_version: Option = + row.try_get("client_version").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; let total: Option = row.try_get("total").or_else(|e| match e { ColumnNotFound(_) => Ok(Default::default()), e => Err(e), @@ -347,6 +367,8 @@ impl<'a> FromRow<'a, PgRow> for super::payments::distribution::PaymentDistributi authentication_type, payment_method, payment_method_type, + client_source, + client_version, total, count, error_message, @@ -387,6 +409,15 @@ impl<'a> FromRow<'a, PgRow> for super::payments::filters::FilterRow { ColumnNotFound(_) => Ok(Default::default()), e => Err(e), })?; + let client_source: Option = row.try_get("client_source").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let client_version: Option = + row.try_get("client_version").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; Ok(Self { currency, status, @@ -394,6 +425,8 @@ impl<'a> FromRow<'a, PgRow> for super::payments::filters::FilterRow { authentication_type, payment_method, payment_method_type, + client_source, + client_version, }) } } @@ -515,10 +548,10 @@ impl ToSql for AnalyticsCollection { Self::ApiEvents => Err(error_stack::report!(ParsingError::UnknownError) .attach_printable("ApiEvents table is not implemented for Sqlx"))?, Self::PaymentIntent => Ok("payment_intent".to_string()), - Self::ConnectorEvents | Self::ConnectorEventsAnalytics => { - Err(error_stack::report!(ParsingError::UnknownError) - .attach_printable("ConnectorEvents table is not implemented for Sqlx"))? - } + Self::ConnectorEvents => Err(error_stack::report!(ParsingError::UnknownError) + .attach_printable("ConnectorEvents table is not implemented for Sqlx"))?, + Self::ApiEventsAnalytics => Err(error_stack::report!(ParsingError::UnknownError) + .attach_printable("ApiEvents table is not implemented for Sqlx"))?, Self::OutgoingWebhookEvent => Err(error_stack::report!(ParsingError::UnknownError) .attach_printable("OutgoingWebhookEvents table is not implemented for Sqlx"))?, Self::Dispute => Ok("dispute".to_string()), diff --git a/crates/analytics/src/types.rs b/crates/analytics/src/types.rs index e8196a0968d7..86a9ec86eff2 100644 --- a/crates/analytics/src/types.rs +++ b/crates/analytics/src/types.rs @@ -31,7 +31,7 @@ pub enum AnalyticsCollection { ConnectorEvents, OutgoingWebhookEvent, Dispute, - ConnectorEventsAnalytics, + ApiEventsAnalytics, } #[allow(dead_code)] diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index 93cf7574d988..12d9832ada67 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -729,8 +729,11 @@ pub struct FrmPaymentMethod { ///payment methods(card, wallet, etc) that can be used in the payment #[schema(value_type = PaymentMethod,example = "card")] pub payment_method: Option, - ///payment method types(credit, debit) that can be used in the payment - pub payment_method_types: Vec, + ///payment method types(credit, debit) that can be used in the payment. This field is deprecated. It has not been removed to provide backward compatibility. + pub payment_method_types: Option>, + ///frm flow type to be used, can be pre/post + #[schema(value_type = Option)] + pub flow: Option, } ///Details of FrmPaymentMethodType are mentioned here... it should be passed in payment connector create api call, and stored in merchant_connector_table @@ -743,7 +746,7 @@ pub struct FrmPaymentMethodType { ///card networks(like visa mastercard) types that can be used in the payment #[schema(value_type = CardNetwork)] pub card_networks: Option>, - ///frm flow type to be used...can be pre/post + ///frm flow type to be used, can be pre/post #[schema(value_type = FrmPreferredFlowTypes)] pub flow: api_enums::FrmPreferredFlowTypes, ///action that the frm would take, in case fraud is detected @@ -826,6 +829,22 @@ pub struct ToggleKVRequest { pub kv_enabled: bool, } +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +pub struct ToggleAllKVRequest { + /// Status of KV for the specific merchant + #[schema(example = true)] + pub kv_enabled: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +pub struct ToggleAllKVResponse { + ///Total number of updated merchants + #[schema(example = 20)] + pub total_updated: usize, + /// Status of KV for the specific merchant + #[schema(example = true)] + pub kv_enabled: bool, +} #[derive(Debug, Clone, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)] pub struct MerchantConnectorDetailsWrap { /// Creds Identifier is to uniquely identify the credentials. Do not send any sensitive info in this field. And do not send the string "null". diff --git a/crates/api_models/src/analytics/auth_events.rs b/crates/api_models/src/analytics/auth_events.rs index bb08f9f59342..7791a2f3cd5f 100644 --- a/crates/api_models/src/analytics/auth_events.rs +++ b/crates/api_models/src/analytics/auth_events.rs @@ -25,6 +25,7 @@ pub enum AuthEventMetrics { AuthenticationSuccessCount, ChallengeFlowCount, FrictionlessFlowCount, + FrictionlessSuccessCount, ChallengeAttemptCount, ChallengeSuccessCount, } @@ -42,7 +43,8 @@ pub enum AuthEventMetrics { strum::AsRefStr, )] pub enum AuthEventFlows { - PostAuthentication, + IncomingWebhookReceive, + PaymentsExternalAuthentication, } pub mod metric_behaviour { @@ -51,6 +53,7 @@ pub mod metric_behaviour { pub struct AuthenticationSuccessCount; pub struct ChallengeFlowCount; pub struct FrictionlessFlowCount; + pub struct FrictionlessSuccessCount; pub struct ChallengeAttemptCount; pub struct ChallengeSuccessCount; } @@ -100,6 +103,7 @@ pub struct AuthEventMetricsBucketValue { pub challenge_attempt_count: Option, pub challenge_success_count: Option, pub frictionless_flow_count: Option, + pub frictionless_success_count: Option, } #[derive(Debug, serde::Serialize)] diff --git a/crates/api_models/src/analytics/payments.rs b/crates/api_models/src/analytics/payments.rs index 2d7ae262f489..61b88c8fc545 100644 --- a/crates/api_models/src/analytics/payments.rs +++ b/crates/api_models/src/analytics/payments.rs @@ -22,6 +22,10 @@ pub struct PaymentFilters { pub payment_method: Vec, #[serde(default)] pub payment_method_type: Vec, + #[serde(default)] + pub client_source: Vec, + #[serde(default)] + pub client_version: Vec, } #[derive( @@ -53,6 +57,8 @@ pub enum PaymentDimensions { #[strum(serialize = "status")] #[serde(rename = "status")] PaymentStatus, + ClientSource, + ClientVersion, } #[derive( @@ -141,6 +147,8 @@ pub struct PaymentMetricsBucketIdentifier { pub auth_type: Option, pub payment_method: Option, pub payment_method_type: Option, + pub client_source: Option, + pub client_version: Option, #[serde(rename = "time_range")] pub time_bucket: TimeRange, // Coz FE sucks @@ -150,6 +158,7 @@ pub struct PaymentMetricsBucketIdentifier { } impl PaymentMetricsBucketIdentifier { + #[allow(clippy::too_many_arguments)] pub fn new( currency: Option, status: Option, @@ -157,6 +166,8 @@ impl PaymentMetricsBucketIdentifier { auth_type: Option, payment_method: Option, payment_method_type: Option, + client_source: Option, + client_version: Option, normalized_time_range: TimeRange, ) -> Self { Self { @@ -166,6 +177,8 @@ impl PaymentMetricsBucketIdentifier { auth_type, payment_method, payment_method_type, + client_source, + client_version, time_bucket: normalized_time_range, start_time: normalized_time_range.start_time, } @@ -180,6 +193,8 @@ impl Hash for PaymentMetricsBucketIdentifier { self.auth_type.map(|i| i.to_string()).hash(state); self.payment_method.hash(state); self.payment_method_type.hash(state); + self.client_source.hash(state); + self.client_version.hash(state); self.time_bucket.hash(state); } } diff --git a/crates/api_models/src/currency.rs b/crates/api_models/src/currency.rs index c1d7e422d041..608e4c7a6bcf 100644 --- a/crates/api_models/src/currency.rs +++ b/crates/api_models/src/currency.rs @@ -1,10 +1,10 @@ -use common_utils::events::ApiEventMetric; +use common_utils::{events::ApiEventMetric, types::MinorUnit}; /// QueryParams to be send to convert the amount -> from_currency -> to_currency #[derive(Debug, serde::Deserialize)] #[serde(rename_all = "snake_case")] pub struct CurrencyConversionParams { - pub amount: i64, + pub amount: MinorUnit, pub to_currency: String, pub from_currency: String, } diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index 866db8cc18db..59a85841212e 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -273,6 +273,15 @@ pub enum AuthenticationConnectors { Gpayments, } +impl AuthenticationConnectors { + pub fn is_separate_version_call_required(&self) -> bool { + match self { + Self::Threedsecureio | Self::Netcetera => false, + Self::Gpayments => true, + } + } +} + #[cfg(feature = "payouts")] #[derive( Clone, @@ -590,3 +599,52 @@ pub fn convert_pm_auth_connector(connector_name: &str) -> Option Option { AuthenticationConnectors::from_str(connector_name).ok() } + +#[derive( + Clone, + Debug, + Eq, + PartialEq, + serde::Deserialize, + serde::Serialize, + strum::Display, + strum::EnumString, + ToSchema, + Hash, +)] +pub enum PaymentChargeType { + #[serde(untagged)] + Stripe(StripeChargeType), +} + +impl Default for PaymentChargeType { + fn default() -> Self { + Self::Stripe(StripeChargeType::default()) + } +} + +#[derive( + Clone, + Debug, + Default, + Hash, + Eq, + PartialEq, + ToSchema, + serde::Serialize, + serde::Deserialize, + strum::Display, + strum::EnumString, +)] +#[serde(rename_all = "lowercase")] +#[strum(serialize_all = "lowercase")] +pub enum StripeChargeType { + #[default] + Direct, + Destination, +} + +#[cfg(feature = "frm")] +pub fn convert_frm_connector(connector_name: &str) -> Option { + FrmConnectors::from_str(connector_name).ok() +} diff --git a/crates/api_models/src/events.rs b/crates/api_models/src/events.rs index c9ae775fed87..9c26576e77b5 100644 --- a/crates/api_models/src/events.rs +++ b/crates/api_models/src/events.rs @@ -60,6 +60,8 @@ impl_misc_api_event_type!( RevokeApiKeyResponse, ToggleKVResponse, ToggleKVRequest, + ToggleAllKVRequest, + ToggleAllKVResponse, MerchantAccountDeleteResponse, MerchantAccountUpdate, CardInfoResponse, diff --git a/crates/api_models/src/events/payment.rs b/crates/api_models/src/events/payment.rs index 59e65c0605f5..cd1671b2b8e0 100644 --- a/crates/api_models/src/events/payment.rs +++ b/crates/api_models/src/events/payment.rs @@ -11,10 +11,10 @@ use crate::{ ExtendedCardInfoResponse, PaymentIdType, PaymentListConstraints, PaymentListFilterConstraints, PaymentListFilters, PaymentListFiltersV2, PaymentListResponse, PaymentListResponseV2, PaymentsApproveRequest, PaymentsCancelRequest, - PaymentsCaptureRequest, PaymentsExternalAuthenticationRequest, - PaymentsExternalAuthenticationResponse, PaymentsIncrementalAuthorizationRequest, - PaymentsRejectRequest, PaymentsRequest, PaymentsResponse, PaymentsRetrieveRequest, - PaymentsStartRequest, RedirectionResponse, + PaymentsCaptureRequest, PaymentsCompleteAuthorizeRequest, + PaymentsExternalAuthenticationRequest, PaymentsExternalAuthenticationResponse, + PaymentsIncrementalAuthorizationRequest, PaymentsRejectRequest, PaymentsRequest, + PaymentsResponse, PaymentsRetrieveRequest, PaymentsStartRequest, RedirectionResponse, }, }; impl ApiEventMetric for PaymentsRetrieveRequest { @@ -44,6 +44,14 @@ impl ApiEventMetric for PaymentsCaptureRequest { } } +impl ApiEventMetric for PaymentsCompleteAuthorizeRequest { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Payment { + payment_id: self.payment_id.clone(), + }) + } +} + impl ApiEventMetric for PaymentsCancelRequest { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { diff --git a/crates/api_models/src/events/user.rs b/crates/api_models/src/events/user.rs index e9eb51570952..a472b3a76e68 100644 --- a/crates/api_models/src/events/user.rs +++ b/crates/api_models/src/events/user.rs @@ -14,10 +14,10 @@ use crate::user::{ ConnectAccountRequest, CreateInternalUserRequest, DashboardEntryResponse, ForgotPasswordRequest, GetUserDetailsResponse, GetUserRoleDetailsRequest, GetUserRoleDetailsResponse, InviteUserRequest, ListUsersResponse, ReInviteUserRequest, - ResetPasswordRequest, RotatePasswordRequest, SendVerifyEmailRequest, SignInResponse, - SignUpRequest, SignUpWithMerchantIdRequest, SwitchMerchantIdRequest, TokenOrPayloadResponse, - TokenResponse, UpdateUserAccountDetailsRequest, UserFromEmailRequest, UserMerchantCreate, - VerifyEmailRequest, VerifyTotpRequest, + RecoveryCodes, ResetPasswordRequest, RotatePasswordRequest, SendVerifyEmailRequest, + SignInResponse, SignUpRequest, SignUpWithMerchantIdRequest, SwitchMerchantIdRequest, + TokenOrPayloadResponse, TokenResponse, UpdateUserAccountDetailsRequest, UserFromEmailRequest, + UserMerchantCreate, VerifyEmailRequest, VerifyRecoveryCodeRequest, VerifyTotpRequest, }; impl ApiEventMetric for DashboardEntryResponse { @@ -75,7 +75,9 @@ common_utils::impl_misc_api_event_type!( TokenResponse, UserFromEmailRequest, BeginTotpResponse, - VerifyTotpRequest + VerifyRecoveryCodeRequest, + VerifyTotpRequest, + RecoveryCodes ); #[cfg(feature = "dummy_connector")] diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index e045cf2c42da..d63d2b085b69 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -5,7 +5,7 @@ use common_utils::{ consts::SURCHARGE_PERCENTAGE_PRECISION_LENGTH, crypto::OptionalEncryptableName, pii, - types::{Percentage, Surcharge}, + types::{MinorUnit, Percentage, Surcharge}, }; use serde::de; use utoipa::{schema, ToSchema}; @@ -361,7 +361,7 @@ impl From for payments::AdditionalCardInfo { card_isin: item.card_isin, card_extended_bin: item .card_number - .map(|card_number| card_number.get_card_extended_bin()), + .map(|card_number| card_number.get_extended_card_bin()), card_exp_month: item.expiry_month, card_exp_year: item.expiry_year, card_holder_name: item.card_holder_name, @@ -492,7 +492,7 @@ pub struct SurchargeDetailsResponse { #[serde(rename_all = "snake_case", tag = "type", content = "value")] pub enum SurchargeResponse { /// Fixed Surcharge value - Fixed(i64), + Fixed(MinorUnit), /// Surcharge percentage Rate(SurchargePercentage), } @@ -531,7 +531,8 @@ pub struct RequiredFieldInfo { #[schema(value_type = FieldType)] pub field_type: api_enums::FieldType, - pub value: Option, + #[schema(value_type = Option)] + pub value: Option>, } #[derive(Debug, Clone, serde::Serialize, ToSchema)] @@ -638,7 +639,7 @@ pub struct PaymentMethodListRequest { /// Filter by amount #[schema(example = 60)] - pub amount: Option, + pub amount: Option, /// Indicates whether the payment method is eligible for recurring payments #[schema(example = true)] diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 343763321dae..f0ce3839fceb 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -10,6 +10,7 @@ use common_utils::{ crypto, ext_traits::{ConfigExt, Encode}, pii::{self, Email}, + types::MinorUnit, }; use masking::{PeekInterface, Secret}; use router_derive::Setter; @@ -223,8 +224,8 @@ pub struct PaymentsRequest { pub currency: Option, /// The Amount to be captured / debited from the users payment method. It shall be in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc., If not provided, the default amount_to_capture will be the payment amount. - #[schema(example = 6540)] - pub amount_to_capture: Option, + #[schema(value_type = Option, example = 6540)] + pub amount_to_capture: Option, /// Unique identifier for the payment. This ensures idempotency for multiple payments /// that have been done by a single merchant. This field is auto generated and is returned in the API response. @@ -276,27 +277,32 @@ pub struct PaymentsRequest { /// Passing this object creates a new customer or attaches an existing customer to the payment pub customer: Option, - /// The identifier for the customer object. This field will be deprecated soon, use the customer object instead + /// The identifier for the customer object. #[schema(max_length = 255, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")] pub customer_id: Option, - /// The customer's email address This field will be deprecated soon, use the customer object instead - #[schema(max_length = 255, value_type = Option, example = "johntest@test.com")] + /// The customer's email address. + /// This field will be deprecated soon, use the customer object instead + #[schema(max_length = 255, value_type = Option, example = "johntest@test.com", deprecated)] + #[remove_in(PaymentsUpdateRequest, PaymentsCreateRequest, PaymentsConfirmRequest)] pub email: Option, /// The customer's name. /// This field will be deprecated soon, use the customer object instead. - #[schema(value_type = Option, max_length = 255, example = "John Test")] + #[schema(value_type = Option, max_length = 255, example = "John Test", deprecated)] + #[remove_in(PaymentsUpdateRequest, PaymentsCreateRequest, PaymentsConfirmRequest)] pub name: Option>, /// The customer's phone number /// This field will be deprecated soon, use the customer object instead - #[schema(value_type = Option, max_length = 255, example = "3141592653")] + #[schema(value_type = Option, max_length = 255, example = "3141592653", deprecated)] + #[remove_in(PaymentsUpdateRequest, PaymentsCreateRequest, PaymentsConfirmRequest)] pub phone: Option>, /// The country code for the customer phone number /// This field will be deprecated soon, use the customer object instead - #[schema(max_length = 255, example = "+1")] + #[schema(max_length = 255, example = "+1", deprecated)] + #[remove_in(PaymentsUpdateRequest, PaymentsCreateRequest, PaymentsConfirmRequest)] pub phone_country_code: Option, /// Set to true to indicate that the customer is not in your checkout flow during this payment, and therefore is unable to authenticate. This parameter is intended for scenarios where you collect card details and charge them later. When making a recurring payment by passing a mandate_id, this parameter is mandatory @@ -459,7 +465,8 @@ pub struct PaymentsRequest { pub session_expiry: Option, /// additional data related to some frm connectors - pub frm_metadata: Option, + #[schema(value_type = Option, example = r#"{ "coverage_request" : "fraud", "fulfillment_method" : "delivery" }"#)] + pub frm_metadata: Option, /// Whether to perform external authentication (if applicable) #[schema(example = true)] @@ -467,24 +474,42 @@ pub struct PaymentsRequest { /// Details required for recurring payment pub recurring_details: Option, + + /// Fee information to be charged on the payment being collected + pub charges: Option, +} + +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +#[serde(rename_all = "snake_case")] +pub struct PaymentChargeRequest { + /// Stripe's charge type + #[schema(value_type = PaymentChargeType, example = "direct")] + pub charge_type: api_enums::PaymentChargeType, + + /// Platform fees to be collected on the payment + pub fees: i64, + + /// Identifier for the reseller's account to send the funds to + pub transfer_account_id: String, } impl PaymentsRequest { - pub fn get_total_capturable_amount(&self) -> Option { + pub fn get_total_capturable_amount(&self) -> Option { let surcharge_amount = self .surcharge_details .map(|surcharge_details| surcharge_details.get_total_surcharge_amount()) - .unwrap_or(0); + .unwrap_or_default(); self.amount - .map(|amount| i64::from(amount) + surcharge_amount) + .map(|amount| MinorUnit::from(amount) + surcharge_amount) } } #[derive( Default, Debug, Clone, serde::Serialize, serde::Deserialize, Copy, ToSchema, PartialEq, )] pub struct RequestSurchargeDetails { - pub surcharge_amount: i64, - pub tax_amount: Option, + #[schema(value_type = i64, example = 6540)] + pub surcharge_amount: MinorUnit, + pub tax_amount: Option, } /// Browser information to be used for 3DS 2.0 @@ -527,16 +552,19 @@ pub struct BrowserInformation { impl RequestSurchargeDetails { pub fn is_surcharge_zero(&self) -> bool { - self.surcharge_amount == 0 && self.tax_amount.unwrap_or(0) == 0 + self.surcharge_amount == MinorUnit::new(0) + && self.tax_amount.unwrap_or_default() == MinorUnit::new(0) } - pub fn get_total_surcharge_amount(&self) -> i64 { - self.surcharge_amount + self.tax_amount.unwrap_or(0) + pub fn get_total_surcharge_amount(&self) -> MinorUnit { + self.surcharge_amount + self.tax_amount.unwrap_or_default() } } -#[derive(Default, Debug, Clone, Copy)] +#[derive(Default, Debug, Clone)] pub struct HeaderPayload { pub payment_confirm_source: Option, + pub client_source: Option, + pub client_version: Option, pub x_hs_latency: Option, } @@ -559,7 +587,8 @@ pub struct PaymentAttemptResponse { #[schema(value_type = AttemptStatus, example = "charged")] pub status: enums::AttemptStatus, /// The payment attempt amount. Amount for the payment in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc., - pub amount: i64, + #[schema(value_type = i64, example = 6540)] + pub amount: MinorUnit, /// The currency of the amount of the payment attempt #[schema(value_type = Option, example = "USD")] pub currency: Option, @@ -613,7 +642,8 @@ pub struct CaptureResponse { #[schema(value_type = CaptureStatus, example = "charged")] pub status: enums::CaptureStatus, /// The capture amount. Amount for the payment in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc., - pub amount: i64, + #[schema(value_type = i64, example = 6540)] + pub amount: MinorUnit, /// The currency of the amount of the capture #[schema(value_type = Option, example = "USD")] pub currency: Option, @@ -696,21 +726,23 @@ pub enum Amount { Zero, } -impl From for i64 { +impl From for MinorUnit { fn from(amount: Amount) -> Self { match amount { - Amount::Value(val) => val.get(), - Amount::Zero => 0, + Amount::Value(val) => Self::new(val.get()), + Amount::Zero => Self::new(0), } } } -impl From for Amount { - fn from(val: i64) -> Self { - NonZeroI64::new(val).map_or(Self::Zero, Amount::Value) +impl From for Amount { + fn from(minor_unit: MinorUnit) -> Self { + match minor_unit.get_amount_as_i64() { + 0 => Self::Zero, + val => NonZeroI64::new(val).map_or(Self::Zero, Self::Value), + } } } - #[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)] #[serde(deny_unknown_fields)] pub struct PaymentsRedirectRequest { @@ -798,15 +830,15 @@ pub struct MandateData { #[derive(Clone, Eq, PartialEq, Copy, Debug, Default, serde::Serialize, serde::Deserialize)] pub struct SingleUseMandate { - pub amount: i64, + pub amount: MinorUnit, pub currency: api_enums::Currency, } #[derive(Clone, Eq, PartialEq, Debug, Default, ToSchema, serde::Serialize, serde::Deserialize)] pub struct MandateAmountData { /// The maximum amount to be debited for the mandate transaction - #[schema(example = 6540)] - pub amount: i64, + #[schema(value_type = i64, example = 6540)] + pub amount: MinorUnit, /// The currency for the transaction #[schema(value_type = Currency, example = "USD")] pub currency: api_enums::Currency, @@ -1450,9 +1482,9 @@ impl GetAddressFromPaymentMethodData for PaymentMethodData { Self::CardRedirect(_) => None, Self::Wallet(wallet_data) => wallet_data.get_billing_address(), Self::PayLater(pay_later) => pay_later.get_billing_address(), - Self::BankRedirect(_) => None, + Self::BankRedirect(bank_redirect_data) => bank_redirect_data.get_billing_address(), Self::BankDebit(bank_debit_data) => bank_debit_data.get_billing_address(), - Self::BankTransfer(_) => None, + Self::BankTransfer(bank_transfer_data) => bank_transfer_data.get_billing_address(), Self::Voucher(voucher_data) => voucher_data.get_billing_address(), Self::Crypto(_) | Self::Reward @@ -1814,11 +1846,11 @@ pub enum BankRedirectData { }, Interac { /// The country for bank payment - #[schema(value_type = CountryAlpha2, example = "US")] - country: api_enums::CountryAlpha2, + #[schema(value_type = Option, example = "US")] + country: Option, - #[schema(value_type = String, example = "john.doe@example.com")] - email: Email, + #[schema(value_type = Option, example = "john.doe@example.com")] + email: Option, }, OnlineBankingCzechRepublic { // Issuer banks @@ -1978,7 +2010,7 @@ impl GetAddressFromPaymentMethodData for BankRedirectData { .. } => get_billing_address_inner(billing_details.as_ref(), country.as_ref(), None), Self::Interac { country, email } => { - get_billing_address_inner(None, Some(country), Some(email)) + get_billing_address_inner(None, country.as_ref(), email.as_ref()) } Self::OnlineBankingFinland { email } => { get_billing_address_inner(None, None, email.as_ref()) @@ -2870,7 +2902,8 @@ pub struct PaymentsCaptureRequest { /// The unique identifier for the merchant pub merchant_id: Option, /// The Amount to be captured/ debited from the user's payment method. - pub amount_to_capture: Option, + #[schema(value_type = i64, example = 6540)] + pub amount_to_capture: Option, /// Decider to refund the uncaptured amount pub refund_uncaptured_amount: Option, /// Provides information about a card payment that customers see on their statements. @@ -2947,6 +2980,10 @@ pub struct ThreeDsData { pub three_ds_method_details: ThreeDsMethodData, /// Poll config for a connector pub poll_config: PollConfigResponse, + /// Message Version + pub message_version: Option, + /// Directory Server ID + pub directory_server_id: Option, } #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, ToSchema)] @@ -3123,21 +3160,21 @@ pub struct PaymentsResponse { pub status: api_enums::IntentStatus, /// The payment amount. Amount for the payment in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc., - #[schema(example = 100)] - pub amount: i64, + #[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 - #[schema(example = 110)] - pub net_amount: i64, + #[schema(value_type = i64, example = 6540)] + pub net_amount: MinorUnit, /// The maximum amount that could be captured from the payment - #[schema(minimum = 100, example = 6540)] - pub amount_capturable: Option, + #[schema(value_type = i64, minimum = 100, example = 6540)] + pub amount_capturable: Option, /// The amount which is already captured from the payment - #[schema(minimum = 100, example = 6540)] - pub amount_received: Option, + #[schema(value_type = i64, example = 6540)] + pub amount_received: Option, /// The connector used for the payment #[schema(example = "stripe")] @@ -3157,7 +3194,12 @@ pub struct PaymentsResponse { pub currency: String, /// The identifier for the customer object. If not provided the customer ID will be autogenerated. - #[schema(max_length = 255, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")] + /// This field will be deprecated soon. Please refer to `customer.id` + #[schema( + max_length = 255, + example = "cus_y3oqhf46pyzuxjbcn2giaqnb44", + deprecated + )] pub customer_id: Option, /// Details of customer attached to this payment @@ -3241,15 +3283,18 @@ pub struct PaymentsResponse { pub order_details: Option>, /// description: The customer's email address - #[schema(max_length = 255, value_type = Option, example = "johntest@test.com")] + /// This field will be deprecated soon. Please refer to `customer.email` object + #[schema(max_length = 255, value_type = Option, example = "johntest@test.com", deprecated)] pub email: crypto::OptionalEncryptableEmail, /// description: The customer's name - #[schema(value_type = Option, max_length = 255, example = "John Test")] + /// This field will be deprecated soon. Please refer to `customer.name` object + #[schema(value_type = Option, max_length = 255, example = "John Test", deprecated)] pub name: crypto::OptionalEncryptableName, /// The customer's phone number - #[schema(value_type = Option, max_length = 255, example = "3141592653")] + /// This field will be deprecated soon. Please refer to `customer.phone` object + #[schema(value_type = Option, max_length = 255, example = "3141592653", deprecated)] pub phone: crypto::OptionalEncryptablePhone, /// The URL to redirect after the completion of the operation @@ -3397,6 +3442,29 @@ pub struct PaymentsResponse { #[schema(example = "2022-09-10T10:11:12Z")] #[serde(default, with = "common_utils::custom_serde::iso8601::option")] pub updated: Option, + + /// Fee information to be charged on the payment being collected + pub charges: Option, + + /// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. FRM Metadata is useful for storing additional, structured information on an object related to FRM. + #[schema(value_type = Option, example = r#"{ "fulfillment_method" : "deliver", "coverage_request" : "fraud" }"#)] + pub frm_metadata: Option, +} + +#[derive(Setter, Clone, Default, Debug, PartialEq, serde::Serialize, ToSchema)] +pub struct PaymentChargeResponse { + /// Identifier for charge created for the payment + pub charge_id: Option, + + /// Type of charge (connector specific) + #[schema(value_type = PaymentChargeType, example = "direct")] + pub charge_type: api_enums::PaymentChargeType, + + /// Platform fees collected on the payment + pub application_fees: i64, + + /// Identifier for the reseller's account where the funds were transferred + pub transfer_account_id: String, } #[derive(Setter, Clone, Default, Debug, PartialEq, serde::Serialize, ToSchema)] @@ -3491,7 +3559,8 @@ pub struct IncrementalAuthorizationResponse { /// The unique identifier of authorization pub authorization_id: String, /// Amount the authorization has been made for - pub amount: i64, + #[schema(value_type = i64, example = 6540)] + pub amount: MinorUnit, #[schema(value_type= AuthorizationStatus)] /// The status of the authorization pub status: common_enums::AuthorizationStatus, @@ -3500,7 +3569,7 @@ pub struct IncrementalAuthorizationResponse { /// Error message sent by the connector for authorization pub error_message: Option, /// Previously authorized amount for the payment - pub previously_authorized_amount: i64, + pub previously_authorized_amount: MinorUnit, } #[derive(Clone, Debug, serde::Serialize)] @@ -3736,7 +3805,7 @@ pub struct PgRedirectResponse { pub status: api_enums::IntentStatus, pub gateway_id: String, pub customer_id: Option, - pub amount: Option, + pub amount: Option, } #[derive(Debug, serde::Serialize, PartialEq, Eq, serde::Deserialize)] @@ -3872,6 +3941,7 @@ pub struct GpayAllowedMethodsParameters { /// Is billing address required pub billing_address_required: Option, /// Billing address parameters + #[serde(skip_serializing_if = "Option::is_none")] pub billing_address_parameters: Option, } @@ -4247,7 +4317,9 @@ pub struct ApplePayPaymentRequest { pub supported_networks: Option>, pub merchant_identifier: Option, /// The required billing contact fields for connector + #[serde(skip_serializing_if = "Option::is_none")] pub required_billing_contact_fields: Option, + #[serde(skip_serializing_if = "Option::is_none")] /// The required shipping contacht fields for connector pub required_shipping_contact_fields: Option, } @@ -4320,6 +4392,18 @@ pub struct PaymentRetrieveBodyWithCredentials { pub merchant_connector_details: Option, } +#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +pub struct PaymentsCompleteAuthorizeRequest { + /// The unique identifier for the payment + #[serde(skip_deserializing)] + pub payment_id: String, + /// The shipping address for the payment + pub shipping: Option
, + /// Client Secret + #[schema(value_type = String)] + pub client_secret: Secret, +} + #[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] pub struct PaymentsCancelRequest { /// The identifier for the payment @@ -4339,7 +4423,7 @@ pub struct PaymentsIncrementalAuthorizationRequest { pub payment_id: String, /// The total amount including previously authorized amount and additional amount #[schema(value_type = i64, example = 6540)] - pub amount: i64, + pub amount: MinorUnit, /// Reason for incremental authorization pub reason: Option, } @@ -4542,6 +4626,7 @@ pub mod amount { use super::Amount; struct AmountVisitor; struct OptionalAmountVisitor; + use crate::payments::MinorUnit; // This is defined to provide guarded deserialization of amount // which itself handles zero and non-zero values internally @@ -4574,7 +4659,7 @@ pub mod amount { "invalid value `{v}`, expected a positive integer" ))); } - Ok(Amount::from(v)) + Ok(Amount::from(MinorUnit::new(v))) } } @@ -4646,7 +4731,8 @@ pub struct RetrievePaymentLinkResponse { pub payment_link_id: String, pub merchant_id: String, pub link_to_pay: String, - pub amount: i64, + #[schema(value_type = i64, example = 6540)] + pub amount: MinorUnit, #[serde(with = "common_utils::custom_serde::iso8601")] pub created_at: PrimitiveDateTime, #[serde(with = "common_utils::custom_serde::iso8601::option")] diff --git a/crates/api_models/src/refunds.rs b/crates/api_models/src/refunds.rs index 369aa0a66023..881cc3912ece 100644 --- a/crates/api_models/src/refunds.rs +++ b/crates/api_models/src/refunds.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use common_utils::pii; +pub use common_utils::types::ChargeRefunds; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; use utoipa::ToSchema; @@ -53,6 +54,10 @@ pub struct RefundRequest { /// Merchant connector details used to make payments. #[schema(value_type = Option)] pub merchant_connector_details: Option, + + /// Charge specific fields for controlling the revert of funds from either platform or connected account + #[schema(value_type = Option)] + pub charges: Option, } #[derive(Default, Debug, Clone, Deserialize)] @@ -137,6 +142,9 @@ pub struct RefundResponse { pub profile_id: Option, /// The merchant_connector_id of the processor through which this payment went through pub merchant_connector_id: Option, + /// Charge specific fields for controlling the revert of funds from either platform or connected account + #[schema(value_type = Option)] + pub charges: Option, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, ToSchema)] @@ -168,7 +176,7 @@ pub struct RefundListRequest { pub refund_status: Option>, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, ToSchema)] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, ToSchema)] pub struct RefundListResponse { /// The number of refunds included in the list pub count: usize, diff --git a/crates/api_models/src/surcharge_decision_configs.rs b/crates/api_models/src/surcharge_decision_configs.rs index 0777bde85de0..9c2d1ac26e8d 100644 --- a/crates/api_models/src/surcharge_decision_configs.rs +++ b/crates/api_models/src/surcharge_decision_configs.rs @@ -1,4 +1,8 @@ -use common_utils::{consts::SURCHARGE_PERCENTAGE_PRECISION_LENGTH, events, types::Percentage}; +use common_utils::{ + consts::SURCHARGE_PERCENTAGE_PRECISION_LENGTH, + events, + types::{MinorUnit, Percentage}, +}; use euclid::frontend::{ ast::Program, dir::{DirKeyKind, EuclidDirFilter}, @@ -15,7 +19,7 @@ pub struct SurchargeDetailsOutput { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case", tag = "type", content = "value")] pub enum SurchargeOutput { - Fixed { amount: i64 }, + Fixed { amount: MinorUnit }, Rate(Percentage), } diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs index 7dbf867d1a0b..5423fc830a5c 100644 --- a/crates/api_models/src/user.rs +++ b/crates/api_models/src/user.rs @@ -224,6 +224,11 @@ pub struct TokenOnlyQueryParam { pub token_only: Option, } +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct SkipTwoFactorAuthQueryParam { + pub skip_two_factor_auth: Option, +} + #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct TokenResponse { pub token: Secret, @@ -257,3 +262,13 @@ pub struct TotpSecret { pub struct VerifyTotpRequest { pub totp: Option>, } + +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct VerifyRecoveryCodeRequest { + pub recovery_code: Secret, +} + +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct RecoveryCodes { + pub recovery_codes: Vec>, +} diff --git a/crates/api_models/src/webhooks.rs b/crates/api_models/src/webhooks.rs index 1a3e5e48787d..f3f21a03e6fe 100644 --- a/crates/api_models/src/webhooks.rs +++ b/crates/api_models/src/webhooks.rs @@ -41,6 +41,8 @@ pub enum IncomingWebhookEvent { MandateRevoked, EndpointVerification, ExternalAuthenticationARes, + FrmApproved, + FrmRejected, #[cfg(feature = "payouts")] PayoutSuccess, #[cfg(feature = "payouts")] @@ -62,6 +64,7 @@ pub enum WebhookFlow { BankTransfer, Mandate, ExternalAuthentication, + FraudCheck, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] @@ -136,6 +139,9 @@ impl From for WebhookFlow { IncomingWebhookEvent::SourceChargeable | IncomingWebhookEvent::SourceTransactionCreated => Self::BankTransfer, IncomingWebhookEvent::ExternalAuthenticationARes => Self::ExternalAuthentication, + IncomingWebhookEvent::FrmApproved | IncomingWebhookEvent::FrmRejected => { + Self::FraudCheck + } #[cfg(feature = "payouts")] IncomingWebhookEvent::PayoutSuccess | IncomingWebhookEvent::PayoutFailure diff --git a/crates/cards/Cargo.toml b/crates/cards/Cargo.toml index ac8a417fb993..ed2be966559b 100644 --- a/crates/cards/Cargo.toml +++ b/crates/cards/Cargo.toml @@ -11,7 +11,6 @@ license.workspace = true [dependencies] error-stack = "0.4.1" -luhn = "1.0.1" serde = { version = "1.0.197", features = ["derive"] } thiserror = "1.0.58" time = "0.3.35" diff --git a/crates/cards/src/lib.rs b/crates/cards/src/lib.rs index 5308718826ae..91cb93301a93 100644 --- a/crates/cards/src/lib.rs +++ b/crates/cards/src/lib.rs @@ -7,7 +7,7 @@ use masking::{PeekInterface, StrongSecret}; use serde::{de, Deserialize, Serialize}; use time::{util::days_in_year_month, Date, Duration, PrimitiveDateTime, Time}; -pub use crate::validate::{CCValError, CardNumber, CardNumberStrategy}; +pub use crate::validate::{CardNumber, CardNumberStrategy, CardNumberValidationErr}; #[derive(Serialize)] pub struct CardSecurityCode(StrongSecret); diff --git a/crates/cards/src/validate.rs b/crates/cards/src/validate.rs index 08ad127047c4..1eb07599785f 100644 --- a/crates/cards/src/validate.rs +++ b/crates/cards/src/validate.rs @@ -6,32 +6,36 @@ use router_env::{logger, which as router_env_which, Env}; use serde::{Deserialize, Deserializer, Serialize}; use thiserror::Error; -#[derive(Debug, Deserialize, Serialize, Error)] -#[error("not a valid credit card number")] -pub struct CCValError; +/// +/// Minimum limit of a card number will not be less than 8 by ISO standards +/// +pub const MIN_CARD_NUMBER_LENGTH: usize = 8; -impl From for CCValError { - fn from(_: core::convert::Infallible) -> Self { - Self - } -} +/// +/// Maximum limit of a card number will not exceed 19 by ISO standards +/// +pub const MAX_CARD_NUMBER_LENGTH: usize = 19; + +#[derive(Debug, Deserialize, Serialize, Error)] +#[error("{0}")] +pub struct CardNumberValidationErr(&'static str); /// Card number #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)] pub struct CardNumber(StrongSecret); impl CardNumber { - pub fn get_card_isin(self) -> String { + pub fn get_card_isin(&self) -> String { self.0.peek().chars().take(6).collect::() } - pub fn get_extended_card_bin(self) -> String { + pub fn get_extended_card_bin(&self) -> String { self.0.peek().chars().take(8).collect::() } - pub fn get_card_no(self) -> String { + pub fn get_card_no(&self) -> String { self.0.peek().chars().collect::() } - pub fn get_last4(self) -> String { + pub fn get_last4(&self) -> String { self.0 .peek() .chars() @@ -42,15 +46,12 @@ impl CardNumber { .rev() .collect::() } - pub fn get_card_extended_bin(self) -> String { - self.0.peek().chars().take(8).collect::() - } } impl FromStr for CardNumber { - type Err = CCValError; + type Err = CardNumberValidationErr; - fn from_str(s: &str) -> Result { + fn from_str(card_number: &str) -> Result { // Valid test cards for threedsecureio let valid_test_cards = vec![ "4000100511112003", @@ -63,17 +64,79 @@ impl FromStr for CardNumber { Env::Development | Env::Sandbox => valid_test_cards, Env::Production => vec![], }; - if luhn::valid(s) || valid_test_cards.contains(&s) { - let cc_no_whitespace: String = s.split_whitespace().collect(); - Ok(Self(StrongSecret::from_str(&cc_no_whitespace)?)) + + let card_number = card_number.split_whitespace().collect::(); + + let is_card_valid = sanitize_card_number(&card_number)?; + + if valid_test_cards.contains(&card_number.as_str()) || is_card_valid { + Ok(Self(StrongSecret::new(card_number))) } else { - Err(CCValError) + Err(CardNumberValidationErr("card number invalid")) } } } +pub fn sanitize_card_number(card_number: &str) -> Result { + let is_card_number_valid = Ok(card_number) + .and_then(validate_card_number_chars) + .and_then(validate_card_number_length) + .map(|number| luhn(&number))?; + + Ok(is_card_number_valid) +} + +/// +/// # Panics +/// +/// Never, as a single character will never be greater than 10, or `u8` +/// +pub fn validate_card_number_chars(number: &str) -> Result, CardNumberValidationErr> { + let data = number.chars().try_fold( + Vec::with_capacity(MAX_CARD_NUMBER_LENGTH), + |mut data, character| { + data.push( + #[allow(clippy::expect_used)] + character + .to_digit(10) + .ok_or(CardNumberValidationErr( + "invalid character found in card number", + ))? + .try_into() + .expect("error while converting a single character to u8"), // safety, a single character will never be greater `u8` + ); + Ok::, CardNumberValidationErr>(data) + }, + )?; + + Ok(data) +} + +pub fn validate_card_number_length(number: Vec) -> Result, CardNumberValidationErr> { + if number.len() >= MIN_CARD_NUMBER_LENGTH && number.len() <= MAX_CARD_NUMBER_LENGTH { + Ok(number) + } else { + Err(CardNumberValidationErr("invalid card number length")) + } +} + +#[allow(clippy::as_conversions)] +pub fn luhn(number: &[u8]) -> bool { + number + .iter() + .rev() + .enumerate() + .map(|(idx, element)| { + ((*element * 2) / 10 + (*element * 2) % 10) * ((idx as u8) % 2) + + (*element) * (((idx + 1) as u8) % 2) + }) + .sum::() + % 10 + == 0 +} + impl TryFrom for CardNumber { - type Error = CCValError; + type Error = CardNumberValidationErr; fn try_from(value: String) -> Result { Self::from_str(&value) @@ -135,12 +198,30 @@ mod tests { ); } + #[test] + fn invalid_card_number_length() { + let s = "371446"; + assert_eq!( + CardNumber::from_str(s).unwrap_err().to_string(), + "invalid card number length".to_string() + ); + } + + #[test] + fn card_number_with_non_digit_character() { + let s = "371446431 A"; + assert_eq!( + CardNumber::from_str(s).unwrap_err().to_string(), + "invalid character found in card number".to_string() + ); + } + #[test] fn invalid_card_number() { let s = "371446431"; assert_eq!( CardNumber::from_str(s).unwrap_err().to_string(), - "not a valid credit card number".to_string() + "card number invalid".to_string() ); } @@ -184,6 +265,6 @@ mod tests { fn test_invalid_card_number_deserialization() { let card_number = serde_json::from_str::(r#""1234 5678""#); let error_msg = card_number.unwrap_err().to_string(); - assert_eq!(error_msg, "not a valid credit card number".to_string()); + assert_eq!(error_msg, "card number invalid".to_string()); } } diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index db0c6c4c37bb..1f60eab1f0a4 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -2016,7 +2016,9 @@ pub enum FileUploadProvider { Checkout, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display)] +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] pub enum UsStatesAbbreviation { AL, AK, @@ -2079,7 +2081,9 @@ pub enum UsStatesAbbreviation { WY, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display)] +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] pub enum CanadaStatesAbbreviation { AB, BC, @@ -2267,7 +2271,6 @@ pub enum FrmSuggestion { Clone, Debug, Eq, - Default, Hash, PartialEq, serde::Deserialize, @@ -2281,7 +2284,6 @@ pub enum FrmSuggestion { #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] pub enum ReconStatus { - #[default] NotRequested, Requested, Active, diff --git a/crates/common_utils/Cargo.toml b/crates/common_utils/Cargo.toml index 6f08649fbda5..ff64d6517c7f 100644 --- a/crates/common_utils/Cargo.toml +++ b/crates/common_utils/Cargo.toml @@ -39,6 +39,7 @@ thiserror = "1.0.58" time = { version = "0.3.35", features = ["serde", "serde-well-known", "std"] } tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread"], optional = true } semver = { version = "1.0.22", features = ["serde"] } +utoipa = { version = "4.2.0", features = ["preserve_order", "preserve_path_order"] } uuid = { version = "1.8.0", features = ["v7"] } # First party crates diff --git a/crates/common_utils/src/errors.rs b/crates/common_utils/src/errors.rs index 7f3430657058..967580f0ae59 100644 --- a/crates/common_utils/src/errors.rs +++ b/crates/common_utils/src/errors.rs @@ -1,5 +1,7 @@ //! Errors and error specific types for universal use +use crate::types::MinorUnit; + /// Custom Result /// A custom datatype that wraps the error variant into a report, allowing /// error_stack::Report specific extendability @@ -89,7 +91,7 @@ pub enum PercentageError { /// percentage value percentage: f32, /// amount value - amount: i64, + amount: MinorUnit, }, } diff --git a/crates/common_utils/src/types.rs b/crates/common_utils/src/types.rs index b15a519caad7..d9a7aef3f578 100644 --- a/crates/common_utils/src/types.rs +++ b/crates/common_utils/src/types.rs @@ -1,23 +1,31 @@ //! Types that can be used in other crates -use std::{fmt::Display, str::FromStr}; +use std::{ + fmt::Display, + ops::{Add, Sub}, + primitive::i64, + str::FromStr, +}; use diesel::{ backend::Backend, + deserialize, deserialize::FromSql, serialize::{Output, ToSql}, + sql_types, sql_types::Jsonb, - AsExpression, FromSqlRow, + AsExpression, FromSqlRow, Queryable, }; use error_stack::{report, ResultExt}; use semver::Version; -use serde::{de::Visitor, Deserialize, Deserializer}; +use serde::{de::Visitor, Deserialize, Deserializer, Serialize}; +use utoipa::ToSchema; use crate::{ consts, errors::{CustomResult, ParsingError, PercentageError}, }; /// Represents Percentage Value between 0 and 100 both inclusive -#[derive(Clone, Default, Debug, PartialEq, serde::Serialize)] +#[derive(Clone, Default, Debug, PartialEq, Serialize)] pub struct Percentage { // this value will range from 0 to 100, decimal length defined by precision macro /// Percentage value ranging between 0 and 100 @@ -52,13 +60,17 @@ impl Percentage { /// apply the percentage to amount and ceil the result #[allow(clippy::as_conversions)] - pub fn apply_and_ceil_result(&self, amount: i64) -> CustomResult { + pub fn apply_and_ceil_result( + &self, + amount: MinorUnit, + ) -> CustomResult { let max_amount = i64::MAX / 10000; + let amount = amount.0; if amount > max_amount { // value gets rounded off after i64::MAX/10000 Err(report!(PercentageError::UnableToApplyPercentage { percentage: self.percentage, - amount, + amount: MinorUnit::new(amount), })) .attach_printable(format!( "Cannot calculate percentage for amount greater than {}", @@ -67,9 +79,10 @@ impl Percentage { } else { let percentage_f64 = f64::from(self.percentage); let result = (amount as f64 * (percentage_f64 / 100.0)).ceil() as i64; - Ok(result) + Ok(MinorUnit::new(result)) } } + fn is_valid_string_value(value: &str) -> CustomResult { let float_value = Self::is_valid_float_string(value)?; Ok(Self::is_valid_range(float_value) && Self::is_valid_precision_length(value)) @@ -147,11 +160,11 @@ impl<'de, const PRECISION: u8> Deserialize<'de> for Percentage { } /// represents surcharge type and value -#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, serde::Deserialize)] #[serde(rename_all = "snake_case", tag = "type", content = "value")] pub enum Surcharge { /// Fixed Surcharge value - Fixed(i64), + Fixed(MinorUnit), /// Surcharge percentage Rate(Percentage<{ consts::SURCHARGE_PERCENTAGE_PRECISION_LENGTH }>), } @@ -159,7 +172,7 @@ pub enum Surcharge { /// This struct lets us represent a semantic version type #[derive(Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, Ord, PartialOrd)] #[diesel(sql_type = Jsonb)] -#[derive(serde::Serialize, serde::Deserialize)] +#[derive(Serialize, serde::Deserialize)] pub struct SemanticVersion(#[serde(with = "Version")] Version); impl SemanticVersion { @@ -193,7 +206,7 @@ impl FromSql for SemanticVersion where serde_json::Value: FromSql, { - fn from_sql(bytes: DB::RawValue<'_>) -> diesel::deserialize::Result { + fn from_sql(bytes: DB::RawValue<'_>) -> deserialize::Result { let value = >::from_sql(bytes)?; Ok(serde_json::from_value(value)?) } @@ -212,3 +225,129 @@ where >::to_sql(&value, &mut out.reborrow()) } } + +#[derive( + Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +/// Charge object for refunds +pub struct ChargeRefunds { + /// Identifier for charge created for the payment + pub charge_id: String, + + /// Toggle for reverting the application fee that was collected for the payment. + /// If set to false, the funds are pulled from the destination account. + pub revert_platform_fee: Option, + + /// Toggle for reverting the transfer that was made during the charge. + /// If set to false, the funds are pulled from the main platform's account. + pub revert_transfer: Option, +} + +impl FromSql for ChargeRefunds +where + serde_json::Value: FromSql, +{ + fn from_sql(bytes: DB::RawValue<'_>) -> deserialize::Result { + let value = >::from_sql(bytes)?; + Ok(serde_json::from_value(value)?) + } +} + +impl ToSql for ChargeRefunds +where + serde_json::Value: ToSql, +{ + fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, diesel::pg::Pg>) -> diesel::serialize::Result { + let value = serde_json::to_value(self)?; + + // the function `reborrow` only works in case of `Pg` backend. But, in case of other backends + // please refer to the diesel migration blog: + // https://github.com/Diesel-rs/Diesel/blob/master/guide_drafts/migration_guide.md#changed-tosql-implementations + >::to_sql(&value, &mut out.reborrow()) + } +} + +/// This Unit struct represents MinorUnit in which core amount works +#[derive( + Default, + Debug, + serde::Deserialize, + AsExpression, + serde::Serialize, + Clone, + Copy, + PartialEq, + Eq, + Hash, + ToSchema, + PartialOrd, +)] +#[diesel(sql_type = sql_types::BigInt)] +pub struct MinorUnit(i64); + +impl MinorUnit { + /// gets amount as i64 value + pub fn get_amount_as_i64(&self) -> i64 { + // will be removed in future + self.0 + } + + /// forms a new minor unit from amount + pub fn new(value: i64) -> Self { + Self(value) + } +} + +impl Display for MinorUnit { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl FromSql for MinorUnit +where + DB: Backend, + i64: FromSql, +{ + fn from_sql(value: DB::RawValue<'_>) -> deserialize::Result { + let val = i64::from_sql(value)?; + Ok(Self(val)) + } +} + +impl ToSql for MinorUnit +where + DB: Backend, + i64: ToSql, +{ + fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> diesel::serialize::Result { + self.0.to_sql(out) + } +} + +impl Queryable for MinorUnit +where + DB: Backend, + Self: FromSql, +{ + type Row = Self; + + fn build(row: Self::Row) -> deserialize::Result { + Ok(row) + } +} + +impl Add for MinorUnit { + type Output = Self; + fn add(self, a2: Self) -> Self { + Self(self.0 + a2.0) + } +} + +impl Sub for MinorUnit { + type Output = Self; + fn sub(self, a2: Self) -> Self { + Self(self.0 - a2.0) + } +} diff --git a/crates/diesel_models/src/authentication.rs b/crates/diesel_models/src/authentication.rs index ce2f02d087fe..9b7c4c96ee04 100644 --- a/crates/diesel_models/src/authentication.rs +++ b/crates/diesel_models/src/authentication.rs @@ -42,6 +42,7 @@ pub struct Authentication { pub profile_id: String, pub payment_id: Option, pub merchant_connector_id: String, + pub directory_server_id: Option, } impl Authentication { @@ -86,6 +87,7 @@ pub struct AuthenticationNew { pub profile_id: String, pub payment_id: Option, pub merchant_connector_id: String, + pub directory_server_id: Option, } #[derive(Debug)] @@ -101,6 +103,7 @@ pub enum AuthenticationUpdate { authentication_status: common_enums::AuthenticationStatus, acquirer_bin: Option, acquirer_merchant_id: Option, + directory_server_id: Option, }, AuthenticationUpdate { authentication_value: Option, @@ -159,6 +162,7 @@ pub struct AuthenticationUpdateInternal { pub acs_reference_number: Option, pub acs_trans_id: Option, pub acs_signed_content: Option, + pub directory_server_id: Option, } impl Default for AuthenticationUpdateInternal { @@ -189,6 +193,7 @@ impl Default for AuthenticationUpdateInternal { acs_reference_number: Default::default(), acs_trans_id: Default::default(), acs_signed_content: Default::default(), + directory_server_id: Default::default(), } } } @@ -221,6 +226,7 @@ impl AuthenticationUpdateInternal { acs_reference_number, acs_trans_id, acs_signed_content, + directory_server_id, } = self; Authentication { connector_authentication_id: connector_authentication_id @@ -252,6 +258,7 @@ impl AuthenticationUpdateInternal { acs_reference_number: acs_reference_number.or(source.acs_reference_number), acs_trans_id: acs_trans_id.or(source.acs_trans_id), acs_signed_content: acs_signed_content.or(source.acs_signed_content), + directory_server_id: directory_server_id.or(source.directory_server_id), ..source } } @@ -304,6 +311,7 @@ impl From for AuthenticationUpdateInternal { authentication_status, acquirer_bin, acquirer_merchant_id, + directory_server_id, } => Self { threeds_server_transaction_id: Some(threeds_server_transaction_id), maximum_supported_version: Some(maximum_supported_3ds_version), @@ -315,6 +323,7 @@ impl From for AuthenticationUpdateInternal { authentication_status: Some(authentication_status), acquirer_bin, acquirer_merchant_id, + directory_server_id, ..Default::default() }, AuthenticationUpdate::AuthenticationUpdate { diff --git a/crates/diesel_models/src/authorization.rs b/crates/diesel_models/src/authorization.rs index b6f75bbb9b77..a7f13821d899 100644 --- a/crates/diesel_models/src/authorization.rs +++ b/crates/diesel_models/src/authorization.rs @@ -1,3 +1,4 @@ +use common_utils::types::MinorUnit; use diesel::{AsChangeset, Identifiable, Insertable, Queryable}; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -11,7 +12,7 @@ pub struct Authorization { pub authorization_id: String, pub merchant_id: String, pub payment_id: String, - pub amount: i64, + pub amount: MinorUnit, #[serde(with = "common_utils::custom_serde::iso8601")] pub created_at: PrimitiveDateTime, #[serde(with = "common_utils::custom_serde::iso8601")] @@ -20,7 +21,7 @@ pub struct Authorization { pub error_code: Option, pub error_message: Option, pub connector_authorization_id: Option, - pub previously_authorized_amount: i64, + pub previously_authorized_amount: MinorUnit, } #[derive(Clone, Debug, Insertable, router_derive::DebugAsDisplay, Serialize, Deserialize)] @@ -29,12 +30,12 @@ pub struct AuthorizationNew { pub authorization_id: String, pub merchant_id: String, pub payment_id: String, - pub amount: i64, + pub amount: MinorUnit, pub status: storage_enums::AuthorizationStatus, pub error_code: Option, pub error_message: Option, pub connector_authorization_id: Option, - pub previously_authorized_amount: i64, + pub previously_authorized_amount: MinorUnit, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/crates/diesel_models/src/capture.rs b/crates/diesel_models/src/capture.rs index adc313ca3dde..0a66ae789aa5 100644 --- a/crates/diesel_models/src/capture.rs +++ b/crates/diesel_models/src/capture.rs @@ -1,3 +1,4 @@ +use common_utils::types::MinorUnit; use diesel::{AsChangeset, Identifiable, Insertable, Queryable}; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -12,13 +13,13 @@ pub struct Capture { pub payment_id: String, pub merchant_id: String, pub status: storage_enums::CaptureStatus, - pub amount: i64, + pub amount: MinorUnit, pub currency: Option, pub connector: String, pub error_message: Option, pub error_code: Option, pub error_reason: Option, - pub tax_amount: Option, + pub tax_amount: Option, #[serde(with = "common_utils::custom_serde::iso8601")] pub created_at: PrimitiveDateTime, #[serde(with = "common_utils::custom_serde::iso8601")] @@ -37,13 +38,13 @@ pub struct CaptureNew { pub payment_id: String, pub merchant_id: String, pub status: storage_enums::CaptureStatus, - pub amount: i64, + pub amount: MinorUnit, pub currency: Option, pub connector: String, pub error_message: Option, pub error_code: Option, pub error_reason: Option, - pub tax_amount: Option, + pub tax_amount: Option, #[serde(with = "common_utils::custom_serde::iso8601")] pub created_at: PrimitiveDateTime, #[serde(with = "common_utils::custom_serde::iso8601")] diff --git a/crates/diesel_models/src/customers.rs b/crates/diesel_models/src/customers.rs index fcabc8879b3f..0d1657136e02 100644 --- a/crates/diesel_models/src/customers.rs +++ b/crates/diesel_models/src/customers.rs @@ -1,3 +1,4 @@ +use common_enums::MerchantStorageScheme; use common_utils::pii; use diesel::{AsChangeset, Identifiable, Insertable, Queryable}; use time::PrimitiveDateTime; @@ -21,6 +22,13 @@ pub struct CustomerNew { pub created_at: PrimitiveDateTime, pub modified_at: PrimitiveDateTime, pub address_id: Option, + pub updated_by: Option, +} + +impl CustomerNew { + pub fn update_storage_scheme(&mut self, storage_scheme: MerchantStorageScheme) { + self.updated_by = Some(storage_scheme.to_string()); + } } impl From for Customer { @@ -40,6 +48,7 @@ impl From for Customer { modified_at: customer_new.modified_at, address_id: customer_new.address_id, default_payment_method_id: None, + updated_by: customer_new.updated_by, } } } @@ -61,6 +70,7 @@ pub struct Customer { pub modified_at: PrimitiveDateTime, pub address_id: Option, pub default_payment_method_id: Option, + pub updated_by: Option, } #[derive( @@ -84,6 +94,7 @@ pub struct CustomerUpdateInternal { pub connector_customer: Option, pub address_id: Option, pub default_payment_method_id: Option>, + pub updated_by: Option, } impl CustomerUpdateInternal { diff --git a/crates/diesel_models/src/mandate.rs b/crates/diesel_models/src/mandate.rs index 39c43a4a178b..65576b08f237 100644 --- a/crates/diesel_models/src/mandate.rs +++ b/crates/diesel_models/src/mandate.rs @@ -1,3 +1,4 @@ +use common_enums::MerchantStorageScheme; use common_utils::pii; use diesel::{AsChangeset, Identifiable, Insertable, Queryable}; use masking::Secret; @@ -32,6 +33,7 @@ pub struct Mandate { pub connector_mandate_ids: Option, pub original_payment_id: Option, pub merchant_connector_id: Option, + pub updated_by: Option, } #[derive( @@ -69,6 +71,13 @@ pub struct MandateNew { pub connector_mandate_ids: Option, pub original_payment_id: Option, pub merchant_connector_id: Option, + pub updated_by: Option, +} + +impl MandateNew { + pub fn update_storage_scheme(&mut self, storage_scheme: MerchantStorageScheme) { + self.updated_by = Some(storage_scheme.to_string()); + } } #[derive(Debug)] @@ -90,6 +99,17 @@ pub enum MandateUpdate { }, } +impl MandateUpdate { + pub fn convert_to_mandate_update( + self, + storage_scheme: MerchantStorageScheme, + ) -> MandateUpdateInternal { + let mut updated_object = MandateUpdateInternal::from(self); + updated_object.updated_by = Some(storage_scheme.to_string()); + updated_object + } +} + #[derive(Clone, Eq, PartialEq, Copy, Debug, Default, serde::Serialize, serde::Deserialize)] pub struct SingleUseMandate { pub amount: i64, @@ -113,6 +133,7 @@ pub struct MandateUpdateInternal { connector_mandate_id: Option, payment_method_id: Option, original_payment_id: Option, + updated_by: Option, } impl From for MandateUpdateInternal { @@ -125,6 +146,7 @@ impl From for MandateUpdateInternal { connector_mandate_id: None, payment_method_id: None, original_payment_id: None, + updated_by: None, }, MandateUpdate::CaptureAmountUpdate { amount_captured } => Self { mandate_status: None, @@ -133,6 +155,7 @@ impl From for MandateUpdateInternal { connector_mandate_id: None, payment_method_id: None, original_payment_id: None, + updated_by: None, }, MandateUpdate::ConnectorReferenceUpdate { connector_mandate_ids, @@ -165,6 +188,7 @@ impl MandateUpdateInternal { connector_mandate_id, payment_method_id, original_payment_id, + updated_by, } = self; Mandate { @@ -174,6 +198,7 @@ impl MandateUpdateInternal { connector_mandate_id: connector_mandate_id.map_or(source.connector_mandate_id, Some), payment_method_id: payment_method_id.unwrap_or(source.payment_method_id), original_payment_id: original_payment_id.map_or(source.original_payment_id, Some), + updated_by: updated_by.map_or(source.updated_by, Some), ..source } } @@ -208,6 +233,7 @@ impl From<&MandateNew> for Mandate { connector_mandate_ids: mandate_new.connector_mandate_ids.clone(), original_payment_id: mandate_new.original_payment_id.clone(), merchant_connector_id: mandate_new.merchant_connector_id.clone(), + updated_by: mandate_new.updated_by.clone(), } } } diff --git a/crates/diesel_models/src/merchant_account.rs b/crates/diesel_models/src/merchant_account.rs index 65bba4797765..2bd77d4eb987 100644 --- a/crates/diesel_models/src/merchant_account.rs +++ b/crates/diesel_models/src/merchant_account.rs @@ -98,6 +98,6 @@ pub struct MerchantAccountUpdateInternal { pub organization_id: Option, pub is_recon_enabled: bool, pub default_profile: Option>, - pub recon_status: storage_enums::ReconStatus, + pub recon_status: Option, pub payment_link_config: Option, } diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index d4d2ddaef03e..6bb286b42a95 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -70,6 +70,9 @@ pub struct PaymentAttempt { pub mandate_data: Option, pub fingerprint_id: Option, pub payment_method_billing_address_id: Option, + pub charge_id: Option, + pub client_source: Option, + pub client_version: Option, } impl PaymentAttempt { @@ -150,6 +153,9 @@ pub struct PaymentAttemptNew { pub mandate_data: Option, pub fingerprint_id: Option, pub payment_method_billing_address_id: Option, + pub charge_id: Option, + pub client_source: Option, + pub client_version: Option, } impl PaymentAttemptNew { @@ -233,6 +239,8 @@ pub enum PaymentAttemptUpdate { authentication_connector: Option, authentication_id: Option, payment_method_billing_address_id: Option, + client_source: Option, + client_version: Option, }, VoidUpdate { status: storage_enums::AttemptStatus, @@ -275,6 +283,7 @@ pub enum PaymentAttemptUpdate { unified_code: Option>, unified_message: Option>, payment_method_data: Option, + charge_id: Option, }, UnresolvedResponseUpdate { status: storage_enums::AttemptStatus, @@ -328,6 +337,7 @@ pub enum PaymentAttemptUpdate { encoded_data: Option, connector_transaction_id: Option, connector: Option, + charge_id: Option, updated_by: String, }, IncrementalAuthorizationAmountUpdate { @@ -388,6 +398,9 @@ pub struct PaymentAttemptUpdateInternal { authentication_id: Option, fingerprint_id: Option, payment_method_billing_address_id: Option, + charge_id: Option, + client_source: Option, + client_version: Option, } impl PaymentAttemptUpdateInternal { @@ -453,6 +466,9 @@ impl PaymentAttemptUpdate { authentication_id, payment_method_billing_address_id, fingerprint_id, + charge_id, + client_source, + client_version, } = PaymentAttemptUpdateInternal::from(self).populate_derived_fields(&source); PaymentAttempt { amount: amount.unwrap_or(source.amount), @@ -501,6 +517,9 @@ impl PaymentAttemptUpdate { payment_method_billing_address_id: payment_method_billing_address_id .or(source.payment_method_billing_address_id), fingerprint_id: fingerprint_id.or(source.fingerprint_id), + charge_id: charge_id.or(source.charge_id), + client_source: client_source.or(source.client_source), + client_version: client_version.or(source.client_version), ..source } } @@ -587,6 +606,8 @@ impl From for PaymentAttemptUpdateInternal { payment_method_billing_address_id, fingerprint_id, payment_method_id, + client_source, + client_version, } => Self { amount: Some(amount), currency: Some(currency), @@ -616,6 +637,8 @@ impl From for PaymentAttemptUpdateInternal { fingerprint_id, payment_method_id, capture_method, + client_source, + client_version, ..Default::default() }, PaymentAttemptUpdate::VoidUpdate { @@ -682,6 +705,7 @@ impl From for PaymentAttemptUpdateInternal { unified_code, unified_message, payment_method_data, + charge_id, } => Self { status: Some(status), connector: connector.map(Some), @@ -703,6 +727,7 @@ impl From for PaymentAttemptUpdateInternal { unified_code, unified_message, payment_method_data, + charge_id, ..Default::default() }, PaymentAttemptUpdate::ErrorUpdate { @@ -825,12 +850,14 @@ impl From for PaymentAttemptUpdateInternal { connector_transaction_id, connector, updated_by, + charge_id, } => Self { authentication_data, encoded_data, connector_transaction_id, connector: connector.map(Some), updated_by, + charge_id, ..Default::default() }, PaymentAttemptUpdate::IncrementalAuthorizationAmountUpdate { diff --git a/crates/diesel_models/src/payment_intent.rs b/crates/diesel_models/src/payment_intent.rs index ee6e3960b735..2c992554671e 100644 --- a/crates/diesel_models/src/payment_intent.rs +++ b/crates/diesel_models/src/payment_intent.rs @@ -1,5 +1,5 @@ use common_enums::RequestIncrementalAuthorization; -use common_utils::pii; +use common_utils::{pii, types::MinorUnit}; use diesel::{AsChangeset, Identifiable, Insertable, Queryable}; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -13,9 +13,9 @@ pub struct PaymentIntent { pub payment_id: String, pub merchant_id: String, pub status: storage_enums::IntentStatus, - pub amount: i64, + pub amount: MinorUnit, pub currency: Option, - pub amount_captured: Option, + pub amount_captured: Option, pub customer_id: Option, pub description: Option, pub return_url: Option, @@ -58,6 +58,8 @@ pub struct PaymentIntent { pub session_expiry: Option, pub fingerprint_id: Option, pub request_external_three_ds_authentication: Option, + pub charges: Option, + pub frm_metadata: Option, } #[derive( @@ -68,9 +70,9 @@ pub struct PaymentIntentNew { pub payment_id: String, pub merchant_id: String, pub status: storage_enums::IntentStatus, - pub amount: i64, + pub amount: MinorUnit, pub currency: Option, - pub amount_captured: Option, + pub amount_captured: Option, pub customer_id: Option, pub description: Option, pub return_url: Option, @@ -111,13 +113,15 @@ pub struct PaymentIntentNew { pub session_expiry: Option, pub fingerprint_id: Option, pub request_external_three_ds_authentication: Option, + pub charges: Option, + pub frm_metadata: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum PaymentIntentUpdate { ResponseUpdate { status: storage_enums::IntentStatus, - amount_captured: Option, + amount_captured: Option, fingerprint_id: Option, return_url: Option, updated_by: String, @@ -147,7 +151,7 @@ pub enum PaymentIntentUpdate { incremental_authorization_allowed: Option, }, Update { - amount: i64, + amount: MinorUnit, currency: storage_enums::Currency, setup_future_usage: Option, status: storage_enums::IntentStatus, @@ -167,6 +171,7 @@ pub enum PaymentIntentUpdate { session_expiry: Option, fingerprint_id: Option, request_external_three_ds_authentication: Option, + frm_metadata: Option, }, PaymentAttemptAndAttemptCountUpdate { active_attempt_id: String, @@ -194,20 +199,23 @@ pub enum PaymentIntentUpdate { updated_by: String, }, IncrementalAuthorizationAmountUpdate { - amount: i64, + amount: MinorUnit, }, AuthorizationCountUpdate { authorization_count: i32, }, + CompleteAuthorizeUpdate { + shipping_address_id: Option, + }, } #[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)] #[diesel(table_name = payment_intent)] pub struct PaymentIntentUpdateInternal { - pub amount: Option, + pub amount: Option, pub currency: Option, pub status: Option, - pub amount_captured: Option, + pub amount_captured: Option, pub customer_id: Option, pub return_url: Option, pub setup_future_usage: Option, @@ -236,6 +244,8 @@ pub struct PaymentIntentUpdateInternal { pub session_expiry: Option, pub fingerprint_id: Option, pub request_external_three_ds_authentication: Option, + pub charges: Option, + pub frm_metadata: Option, } impl PaymentIntentUpdate { @@ -271,6 +281,8 @@ impl PaymentIntentUpdate { session_expiry, fingerprint_id, request_external_three_ds_authentication, + charges, + frm_metadata, } = self.into(); PaymentIntent { amount: amount.unwrap_or(source.amount), @@ -308,6 +320,9 @@ impl PaymentIntentUpdate { 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), + charges: charges.or(source.charges), + + frm_metadata: frm_metadata.or(source.frm_metadata), ..source } } @@ -337,6 +352,7 @@ impl From for PaymentIntentUpdateInternal { session_expiry, fingerprint_id, request_external_three_ds_authentication, + frm_metadata, } => Self { amount: Some(amount), currency: Some(currency), @@ -359,6 +375,7 @@ impl From for PaymentIntentUpdateInternal { session_expiry, fingerprint_id, request_external_three_ds_authentication, + frm_metadata, ..Default::default() }, PaymentIntentUpdate::MetadataUpdate { @@ -494,6 +511,12 @@ impl From for PaymentIntentUpdateInternal { authorization_count: Some(authorization_count), ..Default::default() }, + PaymentIntentUpdate::CompleteAuthorizeUpdate { + shipping_address_id, + } => Self { + shipping_address_id, + ..Default::default() + }, } } } @@ -542,7 +565,8 @@ mod tests { "incremental_authorization_allowed": null, "authorization_count": null, "session_expiry": null, - "fingerprint_id": null + "fingerprint_id": null, + "frm_metadata": null }"#; let deserialized_payment_intent = serde_json::from_str::(serialized_payment_intent); diff --git a/crates/diesel_models/src/payment_link.rs b/crates/diesel_models/src/payment_link.rs index ed0e979d0268..f4630c75d72e 100644 --- a/crates/diesel_models/src/payment_link.rs +++ b/crates/diesel_models/src/payment_link.rs @@ -1,3 +1,4 @@ +use common_utils::types::MinorUnit; use diesel::{Identifiable, Insertable, Queryable}; use serde::{self, Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -12,7 +13,7 @@ pub struct PaymentLink { pub payment_id: String, pub link_to_pay: String, pub merchant_id: String, - pub amount: i64, + pub amount: MinorUnit, pub currency: Option, #[serde(with = "common_utils::custom_serde::iso8601")] pub created_at: PrimitiveDateTime, @@ -42,7 +43,7 @@ pub struct PaymentLinkNew { pub payment_id: String, pub link_to_pay: String, pub merchant_id: String, - pub amount: i64, + pub amount: MinorUnit, pub currency: Option, #[serde(with = "common_utils::custom_serde::iso8601::option")] pub created_at: Option, diff --git a/crates/diesel_models/src/payment_method.rs b/crates/diesel_models/src/payment_method.rs index e71841743145..f4d46b1d28f2 100644 --- a/crates/diesel_models/src/payment_method.rs +++ b/crates/diesel_models/src/payment_method.rs @@ -1,3 +1,4 @@ +use common_enums::MerchantStorageScheme; use common_utils::pii; use diesel::{AsChangeset, Identifiable, Insertable, Queryable}; use masking::Secret; @@ -41,6 +42,7 @@ pub struct PaymentMethod { pub network_transaction_id: Option, pub client_secret: Option, pub payment_method_billing_address: Option, + pub updated_by: Option, } #[derive( @@ -77,6 +79,13 @@ pub struct PaymentMethodNew { pub network_transaction_id: Option, pub client_secret: Option, pub payment_method_billing_address: Option, + pub updated_by: Option, +} + +impl PaymentMethodNew { + pub fn update_storage_scheme(&mut self, storage_scheme: MerchantStorageScheme) { + self.updated_by = Some(storage_scheme.to_string()); + } } #[derive(Debug, Eq, PartialEq, Deserialize, Serialize)] @@ -116,6 +125,17 @@ pub enum PaymentMethodUpdate { }, } +impl PaymentMethodUpdate { + pub fn convert_to_payment_method_update( + self, + storage_scheme: MerchantStorageScheme, + ) -> PaymentMethodUpdateInternal { + let mut update_internal: PaymentMethodUpdateInternal = self.into(); + update_internal.updated_by = Some(storage_scheme.to_string()); + update_internal + } +} + #[derive( Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay, Serialize, Deserialize, )] @@ -129,6 +149,7 @@ pub struct PaymentMethodUpdateInternal { locker_id: Option, payment_method: Option, connector_mandate_details: Option, + updated_by: Option, payment_method_type: Option, payment_method_issuer: Option, } @@ -148,6 +169,7 @@ impl PaymentMethodUpdateInternal { network_transaction_id, status, connector_mandate_details, + updated_by, .. } = self; @@ -160,6 +182,7 @@ impl PaymentMethodUpdateInternal { status: status.unwrap_or(source.status), connector_mandate_details: connector_mandate_details .map_or(source.connector_mandate_details, Some), + updated_by: updated_by.map_or(source.updated_by, Some), ..source } } @@ -177,6 +200,7 @@ impl From for PaymentMethodUpdateInternal { locker_id: None, payment_method: None, connector_mandate_details: None, + updated_by: None, payment_method_issuer: None, payment_method_type: None, }, @@ -191,6 +215,7 @@ impl From for PaymentMethodUpdateInternal { locker_id: None, payment_method: None, connector_mandate_details: None, + updated_by: None, payment_method_issuer: None, payment_method_type: None, }, @@ -203,6 +228,7 @@ impl From for PaymentMethodUpdateInternal { locker_id: None, payment_method: None, connector_mandate_details: None, + updated_by: None, payment_method_issuer: None, payment_method_type: None, }, @@ -218,6 +244,7 @@ impl From for PaymentMethodUpdateInternal { locker_id: None, payment_method: None, connector_mandate_details: None, + updated_by: None, payment_method_issuer: None, payment_method_type: None, }, @@ -230,6 +257,7 @@ impl From for PaymentMethodUpdateInternal { locker_id: None, payment_method: None, connector_mandate_details: None, + updated_by: None, payment_method_issuer: None, payment_method_type: None, }, @@ -249,6 +277,7 @@ impl From for PaymentMethodUpdateInternal { locker_id, payment_method, connector_mandate_details: None, + updated_by: None, payment_method_issuer, payment_method_type, }, @@ -263,6 +292,7 @@ impl From for PaymentMethodUpdateInternal { payment_method: None, connector_mandate_details, network_transaction_id: None, + updated_by: None, payment_method_issuer: None, payment_method_type: None, }, @@ -302,6 +332,7 @@ impl From<&PaymentMethodNew> for PaymentMethod { status: payment_method_new.status, network_transaction_id: payment_method_new.network_transaction_id.clone(), client_secret: payment_method_new.client_secret.clone(), + updated_by: payment_method_new.updated_by.clone(), payment_method_billing_address: payment_method_new .payment_method_billing_address .clone(), diff --git a/crates/diesel_models/src/query/connector_response.rs b/crates/diesel_models/src/query/connector_response.rs index 952db945ae38..bdc24139ab6e 100644 --- a/crates/diesel_models/src/query/connector_response.rs +++ b/crates/diesel_models/src/query/connector_response.rs @@ -22,6 +22,7 @@ impl ConnectorResponseNew { connector_transaction_id: self.connector_transaction_id.clone(), connector: self.connector_name.clone(), updated_by: self.updated_by.clone(), + charge_id: self.charge_id.clone(), }; let _payment_attempt: Result = @@ -63,12 +64,14 @@ impl ConnectorResponse { authentication_data, encoded_data, connector_name, + charge_id, updated_by, } => PaymentAttemptUpdate::ConnectorResponse { authentication_data, encoded_data, connector_transaction_id, connector: connector_name, + charge_id, updated_by, }, ConnectorResponseUpdate::ErrorUpdate { @@ -79,6 +82,7 @@ impl ConnectorResponse { encoded_data: None, connector_transaction_id: None, connector: connector_name, + charge_id: None, updated_by, }, }; diff --git a/crates/diesel_models/src/query/merchant_account.rs b/crates/diesel_models/src/query/merchant_account.rs index 1d4eef752060..dd2f284305be 100644 --- a/crates/diesel_models/src/query/merchant_account.rs +++ b/crates/diesel_models/src/query/merchant_account.rs @@ -123,4 +123,16 @@ impl MerchantAccount { ) .await } + + pub async fn update_all_merchant_accounts( + conn: &PgPooledConn, + merchant_account: MerchantAccountUpdateInternal, + ) -> StorageResult> { + generics::generic_update_with_results::<::Table, _, _, _>( + conn, + dsl::merchant_id.ne_all(vec![""]), + merchant_account, + ) + .await + } } diff --git a/crates/diesel_models/src/refund.rs b/crates/diesel_models/src/refund.rs index 0ee486bae489..aac282992a8a 100644 --- a/crates/diesel_models/src/refund.rs +++ b/crates/diesel_models/src/refund.rs @@ -1,4 +1,4 @@ -use common_utils::pii; +use common_utils::{pii, types::ChargeRefunds}; use diesel::{AsChangeset, Identifiable, Insertable, Queryable}; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -39,6 +39,7 @@ pub struct Refund { pub profile_id: Option, pub updated_by: String, pub merchant_connector_id: Option, + pub charges: Option, } #[derive( @@ -81,6 +82,7 @@ pub struct RefundNew { pub profile_id: Option, pub updated_by: String, pub merchant_connector_id: Option, + pub charges: Option, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] @@ -275,7 +277,8 @@ mod tests { "refund_error_code": null, "profile_id": null, "updated_by": "admin", - "merchant_connector_id": null + "merchant_connector_id": null, + "charges": null }"#; let deserialized = serde_json::from_str::(serialized_refund); diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 4acc152244ca..913d02f7d218 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -115,6 +115,8 @@ diesel::table! { payment_id -> Nullable, #[max_length = 128] merchant_connector_id -> Varchar, + #[max_length = 128] + directory_server_id -> Nullable, } } @@ -295,6 +297,8 @@ diesel::table! { address_id -> Nullable, #[max_length = 64] default_payment_method_id -> Nullable, + #[max_length = 64] + updated_by -> Nullable, } } @@ -590,6 +594,8 @@ diesel::table! { original_payment_id -> Nullable, #[max_length = 32] merchant_connector_id -> Nullable, + #[max_length = 64] + updated_by -> Nullable, } } @@ -778,6 +784,12 @@ diesel::table! { fingerprint_id -> Nullable, #[max_length = 64] payment_method_billing_address_id -> Nullable, + #[max_length = 64] + charge_id -> Nullable, + #[max_length = 64] + client_source -> Nullable, + #[max_length = 64] + client_version -> Nullable, } } @@ -846,6 +858,8 @@ diesel::table! { #[max_length = 64] fingerprint_id -> Nullable, request_external_three_ds_authentication -> Nullable, + charges -> Nullable, + frm_metadata -> Nullable, } } @@ -928,6 +942,8 @@ diesel::table! { #[max_length = 128] client_secret -> Nullable, payment_method_billing_address -> Nullable, + #[max_length = 64] + updated_by -> Nullable, } } @@ -1081,6 +1097,7 @@ diesel::table! { updated_by -> Varchar, #[max_length = 32] merchant_connector_id -> Nullable, + charges -> Nullable, } } diff --git a/crates/diesel_models/src/user/sample_data.rs b/crates/diesel_models/src/user/sample_data.rs index 93beeff57d2a..1ffaa33a215b 100644 --- a/crates/diesel_models/src/user/sample_data.rs +++ b/crates/diesel_models/src/user/sample_data.rs @@ -73,6 +73,9 @@ pub struct PaymentAttemptBatchNew { pub mandate_data: Option, pub payment_method_billing_address_id: Option, pub fingerprint_id: Option, + pub charge_id: Option, + pub client_source: Option, + pub client_version: Option, } #[allow(dead_code)] @@ -133,6 +136,9 @@ impl PaymentAttemptBatchNew { mandate_data: self.mandate_data, payment_method_billing_address_id: self.payment_method_billing_address_id, fingerprint_id: self.fingerprint_id, + charge_id: self.charge_id, + client_source: self.client_source, + client_version: self.client_version, } } } diff --git a/crates/euclid/Cargo.toml b/crates/euclid/Cargo.toml index 3341746ab745..b408c013572e 100644 --- a/crates/euclid/Cargo.toml +++ b/crates/euclid/Cargo.toml @@ -21,7 +21,7 @@ utoipa = { version = "4.2.0", features = ["preserve_order", "preserve_path_order # First party dependencies common_enums = { version = "0.1.0", path = "../common_enums" } -hyperswitch_constraint_graph = { version = "0.1.0", path = "../hyperswitch_constraint_graph" } +hyperswitch_constraint_graph = { version = "0.1.0", path = "../hyperswitch_constraint_graph", features = ["viz"] } euclid_macros = { version = "0.1.0", path = "../euclid_macros" } [features] diff --git a/crates/euclid/src/dssa/graph.rs b/crates/euclid/src/dssa/graph.rs index 0ffafe4d48b5..ba9d5fdddc96 100644 --- a/crates/euclid/src/dssa/graph.rs +++ b/crates/euclid/src/dssa/graph.rs @@ -12,6 +12,7 @@ use crate::{ pub mod euclid_graph_prelude { pub use hyperswitch_constraint_graph as cgraph; pub use rustc_hash::{FxHashMap, FxHashSet}; + pub use strum::EnumIter; pub use crate::{ dssa::graph::*, @@ -22,6 +23,12 @@ pub mod euclid_graph_prelude { impl cgraph::KeyNode for dir::DirKey {} +impl cgraph::NodeViz for dir::DirKey { + fn viz(&self) -> String { + self.kind.to_string() + } +} + impl cgraph::ValueNode for dir::DirValue { type Key = dir::DirKey; @@ -30,6 +37,41 @@ impl cgraph::ValueNode for dir::DirValue { } } +impl cgraph::NodeViz for dir::DirValue { + fn viz(&self) -> String { + match self { + Self::PaymentMethod(pm) => pm.to_string(), + Self::CardBin(bin) => bin.value.clone(), + Self::CardType(ct) => ct.to_string(), + Self::CardNetwork(cn) => cn.to_string(), + Self::PayLaterType(plt) => plt.to_string(), + Self::WalletType(wt) => wt.to_string(), + Self::UpiType(ut) => ut.to_string(), + Self::BankTransferType(btt) => btt.to_string(), + Self::BankRedirectType(brt) => brt.to_string(), + Self::BankDebitType(bdt) => bdt.to_string(), + Self::CryptoType(ct) => ct.to_string(), + Self::RewardType(rt) => rt.to_string(), + Self::PaymentAmount(amt) => amt.number.to_string(), + Self::PaymentCurrency(curr) => curr.to_string(), + Self::AuthenticationType(at) => at.to_string(), + Self::CaptureMethod(cm) => cm.to_string(), + Self::BusinessCountry(bc) => bc.to_string(), + Self::BillingCountry(bc) => bc.to_string(), + Self::Connector(conn) => conn.connector.to_string(), + Self::MetaData(mv) => format!("[{} = {}]", mv.key, mv.value), + Self::MandateAcceptanceType(mat) => mat.to_string(), + Self::MandateType(mt) => mt.to_string(), + Self::PaymentType(pt) => pt.to_string(), + Self::VoucherType(vt) => vt.to_string(), + Self::GiftCardType(gct) => gct.to_string(), + Self::BusinessLabel(bl) => bl.value.to_string(), + Self::SetupFutureUsage(sfu) => sfu.to_string(), + Self::CardRedirectType(crt) => crt.to_string(), + } + } +} + #[derive(Debug, Clone, serde::Serialize)] #[serde(tag = "type", content = "details", rename_all = "snake_case")] pub enum AnalysisError { @@ -71,6 +113,7 @@ impl AnalysisError { } } +#[derive(Debug)] pub struct AnalysisContext { keywise_values: FxHashMap>, } diff --git a/crates/euclid/src/enums.rs b/crates/euclid/src/enums.rs index f473c5980c7c..8e65d23d5ea9 100644 --- a/crates/euclid/src/enums.rs +++ b/crates/euclid/src/enums.rs @@ -1,5 +1,5 @@ pub use common_enums::{ - AuthenticationType, CaptureMethod, CardNetwork, Country, Currency, + AuthenticationType, CaptureMethod, CardNetwork, Country, CountryAlpha2, Currency, FutureUsage as SetupFutureUsage, PaymentMethod, PaymentMethodType, RoutableConnectors, }; use strum::VariantNames; @@ -79,6 +79,8 @@ pub enum MandateAcceptanceType { pub enum PaymentType { SetupMandate, NonMandate, + NewMandate, + UpdateMandate, } #[derive( diff --git a/crates/euclid/src/frontend/dir.rs b/crates/euclid/src/frontend/dir.rs index 455330fcf765..f2b9ab990499 100644 --- a/crates/euclid/src/frontend/dir.rs +++ b/crates/euclid/src/frontend/dir.rs @@ -289,7 +289,6 @@ pub enum DirKeyKind { #[serde(rename = "billing_country")] BillingCountry, #[serde(skip_deserializing, rename = "connector")] - #[strum(disabled)] Connector, #[strum( serialize = "business_label", @@ -810,6 +809,10 @@ mod test { let mut key_names: FxHashMap = FxHashMap::default(); for key in DirKeyKind::iter() { + if matches!(key, DirKeyKind::Connector) { + continue; + } + let json_str = if let DirKeyKind::MetaData = key { r#""metadata""#.to_string() } else { diff --git a/crates/euclid/src/frontend/dir/enums.rs b/crates/euclid/src/frontend/dir/enums.rs index 941fc9d74656..c5f864bf7701 100644 --- a/crates/euclid/src/frontend/dir/enums.rs +++ b/crates/euclid/src/frontend/dir/enums.rs @@ -3,8 +3,8 @@ use strum::VariantNames; use crate::enums::collect_variants; pub use crate::enums::{ AuthenticationType, CaptureMethod, CardNetwork, Country, Country as BusinessCountry, - Country as BillingCountry, Currency as PaymentCurrency, MandateAcceptanceType, MandateType, - PaymentMethod, PaymentType, RoutableConnectors, SetupFutureUsage, + Country as BillingCountry, CountryAlpha2, Currency as PaymentCurrency, MandateAcceptanceType, + MandateType, PaymentMethod, PaymentType, RoutableConnectors, SetupFutureUsage, }; #[cfg(feature = "payouts")] pub use crate::enums::{PayoutBankTransferType, PayoutType, PayoutWalletType}; diff --git a/crates/euclid_wasm/src/lib.rs b/crates/euclid_wasm/src/lib.rs index 3668130608e4..df78786a5796 100644 --- a/crates/euclid_wasm/src/lib.rs +++ b/crates/euclid_wasm/src/lib.rs @@ -91,8 +91,12 @@ pub fn seed_knowledge_graph(mcas: JsValue) -> JsResult { .collect::>() .map_err(|_| "invalid connector name received") .err_to_js()?; - - let mca_graph = kgraph_utils::mca::make_mca_graph(mcas).err_to_js()?; + let pm_filter = kgraph_utils::types::PaymentMethodFilters(HashMap::new()); + let config = kgraph_utils::types::CountryCurrencyFilter { + connector_configs: HashMap::new(), + default_configs: Some(pm_filter), + }; + let mca_graph = kgraph_utils::mca::make_mca_graph(mcas, &config).err_to_js()?; let analysis_graph = hyperswitch_constraint_graph::ConstraintGraph::combine(&mca_graph, &truth::ANALYSIS_GRAPH) .err_to_js()?; diff --git a/crates/hyperswitch_constraint_graph/Cargo.toml b/crates/hyperswitch_constraint_graph/Cargo.toml index 425855a05b05..4fe97b2b1ac8 100644 --- a/crates/hyperswitch_constraint_graph/Cargo.toml +++ b/crates/hyperswitch_constraint_graph/Cargo.toml @@ -7,8 +7,12 @@ rust-version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +viz = ["dep:graphviz-rust"] + [dependencies] erased-serde = "0.3.28" +graphviz-rust = { version = "0.6.2", optional = true } rustc-hash = "1.1.0" serde = { version = "1.0.163", features = ["derive", "rc"] } serde_json = "1.0.96" diff --git a/crates/hyperswitch_constraint_graph/src/builder.rs b/crates/hyperswitch_constraint_graph/src/builder.rs index c1343eff8850..fdd87eac51c6 100644 --- a/crates/hyperswitch_constraint_graph/src/builder.rs +++ b/crates/hyperswitch_constraint_graph/src/builder.rs @@ -28,7 +28,7 @@ impl From for DomainIdOrIdentifier<'_> { Self::DomainId(value) } } - +#[derive(Debug)] pub struct ConstraintGraphBuilder<'a, V: ValueNode> { domain: DenseMap>, nodes: DenseMap>, diff --git a/crates/hyperswitch_constraint_graph/src/graph.rs b/crates/hyperswitch_constraint_graph/src/graph.rs index d0a98e19520f..d6d26c0e7148 100644 --- a/crates/hyperswitch_constraint_graph/src/graph.rs +++ b/crates/hyperswitch_constraint_graph/src/graph.rs @@ -13,6 +13,7 @@ use crate::{ }, }; +#[derive(Debug)] struct CheckNodeContext<'a, V: ValueNode, C: CheckingContext> { ctx: &'a C, node: &'a Node, @@ -24,6 +25,7 @@ struct CheckNodeContext<'a, V: ValueNode, C: CheckingContext> { domains: Option<&'a [DomainId]>, } +#[derive(Debug)] pub struct ConstraintGraph<'a, V: ValueNode> { pub domain: DenseMap>, pub domain_identifier_map: FxHashMap, DomainId>, @@ -139,6 +141,7 @@ where ctx, domains, }; + match &node.node_type { NodeType::AllAggregator => self.validate_all_aggregator(check_node_context), @@ -206,6 +209,7 @@ where } else { vald.memo .insert((vald.node_id, vald.relation, vald.strength), Ok(())); + Ok(()) } } @@ -585,3 +589,92 @@ where Ok(node_builder.build()) } } + +#[cfg(feature = "viz")] +mod viz { + use graphviz_rust::{ + dot_generator::*, + dot_structures::*, + printer::{DotPrinter, PrinterContext}, + }; + + use crate::{dense_map::EntityId, types, ConstraintGraph, NodeViz, ValueNode}; + + fn get_node_id(node_id: types::NodeId) -> String { + format!("N{}", node_id.get_id()) + } + + impl<'a, V> ConstraintGraph<'a, V> + where + V: ValueNode + NodeViz, + ::Key: NodeViz, + { + fn get_node_label(node: &types::Node) -> String { + let label = match &node.node_type { + types::NodeType::Value(types::NodeValue::Key(key)) => format!("any {}", key.viz()), + types::NodeType::Value(types::NodeValue::Value(val)) => { + format!("{} = {}", val.get_key().viz(), val.viz()) + } + types::NodeType::AllAggregator => "&&".to_string(), + types::NodeType::AnyAggregator => "| |".to_string(), + types::NodeType::InAggregator(agg) => { + let key = if let Some(val) = agg.iter().next() { + val.get_key().viz() + } else { + return "empty in".to_string(); + }; + + let nodes = agg.iter().map(NodeViz::viz).collect::>(); + format!("{key} in [{}]", nodes.join(", ")) + } + }; + + format!("\"{label}\"") + } + + fn build_node(cg_node_id: types::NodeId, cg_node: &types::Node) -> Node { + let viz_node_id = get_node_id(cg_node_id); + let viz_node_label = Self::get_node_label(cg_node); + + node!(viz_node_id; attr!("label", viz_node_label)) + } + + fn build_edge(cg_edge: &types::Edge) -> Edge { + let pred_vertex = get_node_id(cg_edge.pred); + let succ_vertex = get_node_id(cg_edge.succ); + let arrowhead = match cg_edge.strength { + types::Strength::Weak => "onormal", + types::Strength::Normal => "normal", + types::Strength::Strong => "normalnormal", + }; + let color = match cg_edge.relation { + types::Relation::Positive => "blue", + types::Relation::Negative => "red", + }; + + edge!( + node_id!(pred_vertex) => node_id!(succ_vertex); + attr!("arrowhead", arrowhead), + attr!("color", color) + ) + } + + pub fn get_viz_digraph(&self) -> Graph { + graph!( + strict di id!("constraint_graph"), + self.nodes + .iter() + .map(|(node_id, node)| Self::build_node(node_id, node)) + .map(Stmt::Node) + .chain(self.edges.values().map(Self::build_edge).map(Stmt::Edge)) + .collect::>() + ) + } + + pub fn get_viz_digraph_string(&self) -> String { + let mut ctx = PrinterContext::default(); + let digraph = self.get_viz_digraph(); + digraph.print(&mut ctx) + } + } +} diff --git a/crates/hyperswitch_constraint_graph/src/lib.rs b/crates/hyperswitch_constraint_graph/src/lib.rs index ade9a64272fb..6877169732ce 100644 --- a/crates/hyperswitch_constraint_graph/src/lib.rs +++ b/crates/hyperswitch_constraint_graph/src/lib.rs @@ -7,6 +7,8 @@ pub mod types; pub use builder::ConstraintGraphBuilder; pub use error::{AnalysisTrace, GraphError}; pub use graph::ConstraintGraph; +#[cfg(feature = "viz")] +pub use types::NodeViz; pub use types::{ CheckingContext, CycleCheck, DomainId, DomainIdentifier, Edge, EdgeId, KeyNode, Memoization, Node, NodeId, NodeValue, Relation, Strength, ValueNode, diff --git a/crates/hyperswitch_constraint_graph/src/types.rs b/crates/hyperswitch_constraint_graph/src/types.rs index d1d14bd7e5c1..51818f2fee5d 100644 --- a/crates/hyperswitch_constraint_graph/src/types.rs +++ b/crates/hyperswitch_constraint_graph/src/types.rs @@ -17,6 +17,11 @@ pub trait ValueNode: fmt::Debug + Clone + hash::Hash + serde::Serialize + Partia fn get_key(&self) -> Self::Key; } +#[cfg(feature = "viz")] +pub trait NodeViz { + fn viz(&self) -> String; +} + #[derive(Debug, Clone, Copy, serde::Serialize, PartialEq, Eq, Hash)] #[serde(transparent)] pub struct NodeId(usize); diff --git a/crates/hyperswitch_domain_models/Cargo.toml b/crates/hyperswitch_domain_models/Cargo.toml index 58eff10a3635..c1db2a4fb995 100644 --- a/crates/hyperswitch_domain_models/Cargo.toml +++ b/crates/hyperswitch_domain_models/Cargo.toml @@ -14,16 +14,25 @@ payouts = ["api_models/payouts"] [dependencies] # First party deps -api_models = { version = "0.1.0", path = "../api_models" } +api_models = { version = "0.1.0", path = "../api_models", features = ["errors"] } common_enums = { version = "0.1.0", path = "../common_enums" } common_utils = { version = "0.1.0", path = "../common_utils" } masking = { version = "0.1.0", path = "../masking" } diesel_models = { version = "0.1.0", path = "../diesel_models", features = ["kv_store"] } +cards = {version = "0.1.0", path = "../cards"} +router_derive = {version = "0.1.0", path = "../router_derive"} # Third party deps +actix-web = "4.5.1" async-trait = "0.1.79" error-stack = "0.4.1" +http = "0.2.12" +mime = "0.3.17" serde = { version = "1.0.197", features = ["derive"] } serde_json = "1.0.115" +serde_with = "3.7.0" thiserror = "1.0.58" time = { version = "0.3.35", features = ["serde", "serde-well-known", "std"] } +url = { version = "2.5.0", features = ["serde"] } +utoipa = { version = "4.2.0", features = ["preserve_order", "preserve_path_order", "time"] } + diff --git a/crates/hyperswitch_domain_models/src/errors.rs b/crates/hyperswitch_domain_models/src/errors.rs index bed1ab9ccbf5..bd05e1a37713 100644 --- a/crates/hyperswitch_domain_models/src/errors.rs +++ b/crates/hyperswitch_domain_models/src/errors.rs @@ -1,3 +1,4 @@ +pub mod api_error_response; use diesel_models::errors::DatabaseError; pub type StorageResult = error_stack::Result; diff --git a/crates/hyperswitch_domain_models/src/errors/api_error_response.rs b/crates/hyperswitch_domain_models/src/errors/api_error_response.rs new file mode 100644 index 000000000000..26a6f1618fa5 --- /dev/null +++ b/crates/hyperswitch_domain_models/src/errors/api_error_response.rs @@ -0,0 +1,634 @@ +use api_models::errors::types::Extra; +use common_utils::errors::ErrorSwitch; +use http::StatusCode; + +use crate::router_data; + +#[derive(Clone, Debug, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub enum ErrorType { + InvalidRequestError, + ObjectNotFound, + RouterError, + ProcessingError, + BadGateway, + ServerNotAvailable, + DuplicateRequest, + ValidationError, + ConnectorError, + LockTimeout, +} + +#[derive(Debug, Clone, router_derive::ApiError)] +#[error(error_type_enum = ErrorType)] +pub enum ApiErrorResponse { + #[error(error_type = ErrorType::ServerNotAvailable, code = "IR_00", message = "{message:?}")] + NotImplemented { message: NotImplementedMessage }, + #[error( + error_type = ErrorType::InvalidRequestError, code = "IR_01", + message = "API key not provided or invalid API key used" + )] + Unauthorized, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_02", message = "Unrecognized request URL")] + InvalidRequestUrl, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_03", message = "The HTTP method is not applicable for this API")] + InvalidHttpMethod, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_04", message = "Missing required param: {field_name}")] + MissingRequiredField { field_name: &'static str }, + #[error( + error_type = ErrorType::InvalidRequestError, code = "IR_05", + message = "{field_name} contains invalid data. Expected format is {expected_format}" + )] + InvalidDataFormat { + field_name: String, + expected_format: String, + }, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_06", message = "{message}")] + InvalidRequestData { message: String }, + /// Typically used when a field has invalid value, or deserialization of the value contained in a field fails. + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_07", message = "Invalid value provided: {field_name}")] + InvalidDataValue { field_name: &'static str }, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_08", message = "Client secret was not provided")] + ClientSecretNotGiven, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_08", message = "Client secret has expired")] + ClientSecretExpired, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_09", message = "The client_secret provided does not match the client_secret associated with the Payment")] + ClientSecretInvalid, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_10", message = "Customer has active mandate/subsciption")] + MandateActive, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_11", message = "Customer has already been redacted")] + CustomerRedacted, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_12", message = "Reached maximum refund attempts")] + MaximumRefundCount, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_13", message = "The refund amount exceeds the amount captured")] + RefundAmountExceedsPaymentAmount, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_14", message = "This Payment could not be {current_flow} because it has a {field_name} of {current_value}. The expected state is {states}")] + PaymentUnexpectedState { + current_flow: String, + field_name: String, + current_value: String, + states: String, + }, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_15", message = "Invalid Ephemeral Key for the customer")] + InvalidEphemeralKey, + /// Typically used when information involving multiple fields or previously provided information doesn't satisfy a condition. + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_16", message = "{message}")] + PreconditionFailed { message: String }, + #[error( + error_type = ErrorType::InvalidRequestError, code = "IR_17", + message = "Access forbidden, invalid JWT token was used" + )] + InvalidJwtToken, + #[error( + error_type = ErrorType::InvalidRequestError, code = "IR_18", + message = "{message}", + )] + GenericUnauthorized { message: String }, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_19", message = "{message}")] + NotSupported { message: String }, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_20", message = "{flow} flow not supported by the {connector} connector")] + FlowNotSupported { flow: String, connector: String }, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_21", message = "Missing required params")] + MissingRequiredFields { field_names: Vec<&'static str> }, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_22", message = "Access forbidden. Not authorized to access this resource {resource}")] + AccessForbidden { resource: String }, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_23", message = "{message}")] + FileProviderNotSupported { message: String }, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_23", message = "{message}")] + UnprocessableEntity { message: String }, + #[error( + error_type = ErrorType::ProcessingError, code = "IR_24", + message = "Invalid {wallet_name} wallet token" + )] + InvalidWalletToken { wallet_name: String }, + #[error(error_type = ErrorType::ConnectorError, code = "CE_00", message = "{code}: {message}", ignore = "status_code")] + ExternalConnectorError { + code: String, + message: String, + connector: String, + status_code: u16, + reason: Option, + }, + #[error(error_type = ErrorType::ProcessingError, code = "CE_01", message = "Payment failed during authorization with connector. Retry payment")] + PaymentAuthorizationFailed { data: Option }, + #[error(error_type = ErrorType::ProcessingError, code = "CE_02", message = "Payment failed during authentication with connector. Retry payment")] + PaymentAuthenticationFailed { data: Option }, + #[error(error_type = ErrorType::ProcessingError, code = "CE_03", message = "Capture attempt failed while processing with connector")] + PaymentCaptureFailed { data: Option }, + #[error(error_type = ErrorType::ProcessingError, code = "CE_04", message = "The card data is invalid")] + InvalidCardData { data: Option }, + #[error(error_type = ErrorType::InvalidRequestError, code = "CE_04", message = "Payout validation failed")] + PayoutFailed { data: Option }, + #[error(error_type = ErrorType::ProcessingError, code = "CE_05", message = "The card has expired")] + CardExpired { data: Option }, + #[error(error_type = ErrorType::ProcessingError, code = "CE_06", message = "Refund failed while processing with connector. Retry refund")] + RefundFailed { data: Option }, + #[error(error_type = ErrorType::ProcessingError, code = "CE_07", message = "Verification failed while processing with connector. Retry operation")] + VerificationFailed { data: Option }, + #[error(error_type = ErrorType::ProcessingError, code = "CE_08", message = "Dispute operation failed while processing with connector. Retry operation")] + DisputeFailed { data: Option }, + #[error(error_type = ErrorType::ServerNotAvailable, code = "HE_00", message = "Something went wrong")] + InternalServerError, + #[error(error_type = ErrorType::LockTimeout, code = "HE_00", message = "Resource is busy. Please try again later.")] + ResourceBusy, + #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "Duplicate refund request. Refund already attempted with the refund ID")] + DuplicateRefundRequest, + #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "Duplicate mandate request. Mandate already attempted with the Mandate ID")] + DuplicateMandate, + #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The merchant account with the specified details already exists in our records")] + DuplicateMerchantAccount, + #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The merchant connector account with the specified profile_id '{profile_id}' and connector_label '{connector_label}' already exists in our records")] + DuplicateMerchantConnectorAccount { + profile_id: String, + connector_label: String, + }, + #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The payment method with the specified details already exists in our records")] + DuplicatePaymentMethod, + #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The payment with the specified payment_id already exists in our records")] + DuplicatePayment { payment_id: String }, + #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The payout with the specified payout_id '{payout_id}' already exists in our records")] + DuplicatePayout { payout_id: String }, + #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The config with the specified key already exists in our records")] + DuplicateConfig, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Refund does not exist in our records")] + RefundNotFound, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Customer does not exist in our records")] + CustomerNotFound, + #[error(error_type = ErrorType::ObjectNotFound, code = "RE_02", message = "Config key does not exist in our records.")] + ConfigNotFound, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Payment does not exist in our records")] + PaymentNotFound, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Payment method does not exist in our records")] + PaymentMethodNotFound, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Merchant account does not exist in our records")] + MerchantAccountNotFound, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Merchant connector account does not exist in our records")] + MerchantConnectorAccountNotFound { id: String }, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Business profile with the given id '{id}' does not exist in our records")] + BusinessProfileNotFound { id: String }, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Poll with the given id '{id}' does not exist in our records")] + PollNotFound { id: String }, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Resource ID does not exist in our records")] + ResourceIdNotFound, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Mandate does not exist in our records")] + MandateNotFound, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Authentication does not exist in our records")] + AuthenticationNotFound { id: String }, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Failed to update mandate")] + MandateUpdateFailed, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "API Key does not exist in our records")] + ApiKeyNotFound, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Payout does not exist in our records")] + PayoutNotFound, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Event does not exist in our records")] + EventNotFound, + #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "Invalid mandate id passed from connector")] + MandateSerializationFailed, + #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "Unable to parse the mandate identifier passed from connector")] + MandateDeserializationFailed, + #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "Return URL is not configured and not passed in payments request")] + ReturnUrlUnavailable, + #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "This refund is not possible through Hyperswitch. Please raise the refund through {connector} dashboard")] + RefundNotPossible { connector: String }, + #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "Mandate Validation Failed" )] + MandateValidationFailed { reason: String }, + #[error(error_type= ErrorType::ValidationError, code = "HE_03", message = "The payment has not succeeded yet. Please pass a successful payment to initiate refund")] + PaymentNotSucceeded, + #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "The specified merchant connector account is disabled")] + MerchantConnectorAccountDisabled, + #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "{code}: {message}")] + PaymentBlockedError { + code: u16, + message: String, + status: String, + reason: String, + }, + #[error(error_type= ErrorType::ObjectNotFound, code = "HE_04", message = "Successful payment not found for the given payment id")] + SuccessfulPaymentNotFound, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_04", message = "The connector provided in the request is incorrect or not available")] + IncorrectConnectorNameGiven, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_04", message = "Address does not exist in our records")] + AddressNotFound, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_04", message = "Dispute does not exist in our records")] + DisputeNotFound { dispute_id: String }, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_04", message = "File does not exist in our records")] + FileNotFound, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_04", message = "File not available")] + FileNotAvailable, + #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "Dispute status validation failed")] + DisputeStatusValidationFailed { reason: String }, + #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "Card with the provided iin does not exist")] + InvalidCardIin, + #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "The provided card IIN length is invalid, please provide an iin with 6 or 8 digits")] + InvalidCardIinLength, + #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "File validation failed")] + FileValidationFailed { reason: String }, + #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "File not found / valid in the request")] + MissingFile, + #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "Dispute id not found in the request")] + MissingDisputeId, + #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "File purpose not found in the request or is invalid")] + MissingFilePurpose, + #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "File content type not found / valid")] + MissingFileContentType, + #[error(error_type = ErrorType::InvalidRequestError, code = "HE_05", message = "{message}")] + GenericNotFoundError { message: String }, + #[error(error_type = ErrorType::InvalidRequestError, code = "HE_01", message = "{message}")] + GenericDuplicateError { message: String }, + #[error(error_type = ErrorType::InvalidRequestError, code = "WE_01", message = "Failed to authenticate the webhook")] + WebhookAuthenticationFailed, + #[error(error_type = ErrorType::ObjectNotFound, code = "WE_04", message = "Webhook resource not found")] + WebhookResourceNotFound, + #[error(error_type = ErrorType::InvalidRequestError, code = "WE_02", message = "Bad request received in webhook")] + WebhookBadRequest, + #[error(error_type = ErrorType::RouterError, code = "WE_03", message = "There was some issue processing the webhook")] + WebhookProcessingFailure, + #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "required payment method is not configured or configured incorrectly for all configured connectors")] + IncorrectPaymentMethodConfiguration, + #[error(error_type = ErrorType::InvalidRequestError, code = "WE_05", message = "Unable to process the webhook body")] + WebhookUnprocessableEntity, + #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Payment Link does not exist in our records")] + PaymentLinkNotFound, + #[error(error_type = ErrorType::InvalidRequestError, code = "WE_05", message = "Merchant Secret set my merchant for webhook source verification is invalid")] + WebhookInvalidMerchantSecret, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_19", message = "{message}")] + CurrencyNotSupported { message: String }, + #[error(error_type = ErrorType::ServerNotAvailable, code= "HE_00", message = "{component} health check is failing with error: {message}")] + HealthCheckError { + component: &'static str, + message: String, + }, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_24", message = "Merchant connector account is configured with invalid {config}")] + InvalidConnectorConfiguration { config: String }, + #[error(error_type = ErrorType::ValidationError, code = "HE_01", message = "Failed to convert currency to minor unit")] + CurrencyConversionFailed, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_25", message = "Cannot delete the default payment method")] + PaymentMethodDeleteFailed, + #[error( + error_type = ErrorType::InvalidRequestError, code = "IR_26", + message = "Invalid Cookie" + )] + InvalidCookie, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_27", message = "Extended card info does not exist")] + ExtendedCardInfoNotFound, +} + +#[derive(Clone)] +pub enum NotImplementedMessage { + Reason(String), + Default, +} + +impl std::fmt::Debug for NotImplementedMessage { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Reason(message) => write!(fmt, "{message} is not implemented"), + Self::Default => { + write!( + fmt, + "This API is under development and will be made available soon." + ) + } + } + } +} + +impl ::core::fmt::Display for ApiErrorResponse { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + r#"{{"error":{}}}"#, + serde_json::to_string(self).unwrap_or_else(|_| "API error response".to_string()) + ) + } +} + +impl ErrorSwitch for ApiErrorResponse { + fn switch(&self) -> api_models::errors::types::ApiErrorResponse { + use api_models::errors::types::{ApiError, ApiErrorResponse as AER}; + + match self { + Self::NotImplemented { message } => { + AER::NotImplemented(ApiError::new("IR", 0, format!("{message:?}"), None)) + } + Self::Unauthorized => AER::Unauthorized(ApiError::new( + "IR", + 1, + "API key not provided or invalid API key used", None + )), + Self::InvalidRequestUrl => { + AER::NotFound(ApiError::new("IR", 2, "Unrecognized request URL", None)) + } + Self::InvalidHttpMethod => AER::MethodNotAllowed(ApiError::new( + "IR", + 3, + "The HTTP method is not applicable for this API", None + )), + Self::MissingRequiredField { field_name } => AER::BadRequest( + ApiError::new("IR", 4, format!("Missing required param: {field_name}"), None), + ), + Self::InvalidDataFormat { + field_name, + expected_format, + } => AER::Unprocessable(ApiError::new( + "IR", + 5, + format!( + "{field_name} contains invalid data. Expected format is {expected_format}" + ), None + )), + Self::InvalidRequestData { message } => { + AER::Unprocessable(ApiError::new("IR", 6, message.to_string(), None)) + } + Self::InvalidDataValue { field_name } => AER::BadRequest(ApiError::new( + "IR", + 7, + format!("Invalid value provided: {field_name}"), None + )), + Self::ClientSecretNotGiven => AER::BadRequest(ApiError::new( + "IR", + 8, + "client_secret was not provided", None + )), + Self::ClientSecretInvalid => { + AER::BadRequest(ApiError::new("IR", 9, "The client_secret provided does not match the client_secret associated with the Payment", None)) + } + Self::CurrencyNotSupported { message } => { + AER::BadRequest(ApiError::new("IR", 9, message, None)) + } + Self::MandateActive => { + AER::BadRequest(ApiError::new("IR", 10, "Customer has active mandate/subsciption", None)) + } + Self::CustomerRedacted => { + AER::BadRequest(ApiError::new("IR", 11, "Customer has already been redacted", None)) + } + Self::MaximumRefundCount => AER::BadRequest(ApiError::new("IR", 12, "Reached maximum refund attempts", None)), + Self::RefundAmountExceedsPaymentAmount => { + AER::BadRequest(ApiError::new("IR", 13, "The refund amount exceeds the amount captured", None)) + } + Self::PaymentUnexpectedState { + current_flow, + field_name, + current_value, + states, + } => AER::BadRequest(ApiError::new("IR", 14, format!("This Payment could not be {current_flow} because it has a {field_name} of {current_value}. The expected state is {states}"), None)), + Self::InvalidEphemeralKey => AER::Unauthorized(ApiError::new("IR", 15, "Invalid Ephemeral Key for the customer", None)), + Self::PreconditionFailed { message } => { + AER::BadRequest(ApiError::new("IR", 16, message.to_string(), None)) + } + Self::InvalidJwtToken => AER::Unauthorized(ApiError::new("IR", 17, "Access forbidden, invalid JWT token was used", None)), + Self::GenericUnauthorized { message } => { + AER::Unauthorized(ApiError::new("IR", 18, message.to_string(), None)) + }, + Self::ClientSecretExpired => AER::BadRequest(ApiError::new( + "IR", + 19, + "The provided client_secret has expired", None + )), + Self::MissingRequiredFields { field_names } => AER::BadRequest( + ApiError::new("IR", 21, "Missing required params".to_string(), Some(Extra {data: Some(serde_json::json!(field_names)), ..Default::default() })), + ), + Self::AccessForbidden {resource} => { + AER::ForbiddenCommonResource(ApiError::new("IR", 22, format!("Access forbidden. Not authorized to access this resource {resource}"), None)) + }, + Self::FileProviderNotSupported { message } => { + AER::BadRequest(ApiError::new("IR", 23, message.to_string(), None)) + }, + Self::UnprocessableEntity {message} => AER::Unprocessable(ApiError::new("IR", 23, message.to_string(), None)), + Self::InvalidWalletToken { wallet_name} => AER::Unprocessable(ApiError::new( + "IR", + 24, + format!("Invalid {wallet_name} wallet token"), None + )), + Self::ExternalConnectorError { + code, + message, + connector, + reason, + status_code, + } => AER::ConnectorError(ApiError::new("CE", 0, format!("{code}: {message}"), Some(Extra {connector: Some(connector.clone()), reason: reason.to_owned().map(Into::into), ..Default::default()})), StatusCode::from_u16(*status_code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)), + Self::PaymentAuthorizationFailed { data } => { + AER::BadRequest(ApiError::new("CE", 1, "Payment failed during authorization with connector. Retry payment", Some(Extra { data: data.clone(), ..Default::default()}))) + } + Self::PaymentAuthenticationFailed { data } => { + AER::BadRequest(ApiError::new("CE", 2, "Payment failed during authentication with connector. Retry payment", Some(Extra { data: data.clone(), ..Default::default()}))) + } + Self::PaymentCaptureFailed { data } => { + AER::BadRequest(ApiError::new("CE", 3, "Capture attempt failed while processing with connector", Some(Extra { data: data.clone(), ..Default::default()}))) + } + Self::DisputeFailed { data } => { + AER::BadRequest(ApiError::new("CE", 1, "Dispute operation failed while processing with connector. Retry operation", Some(Extra { data: data.clone(), ..Default::default()}))) + } + Self::InvalidCardData { data } => AER::BadRequest(ApiError::new("CE", 4, "The card data is invalid", Some(Extra { data: data.clone(), ..Default::default()}))), + Self::CardExpired { data } => AER::BadRequest(ApiError::new("CE", 5, "The card has expired", Some(Extra { data: data.clone(), ..Default::default()}))), + Self::RefundFailed { data } => AER::BadRequest(ApiError::new("CE", 6, "Refund failed while processing with connector. Retry refund", Some(Extra { data: data.clone(), ..Default::default()}))), + Self::VerificationFailed { data } => { + AER::BadRequest(ApiError::new("CE", 7, "Verification failed while processing with connector. Retry operation", Some(Extra { data: data.clone(), ..Default::default()}))) + }, + Self::MandateUpdateFailed | Self::MandateSerializationFailed | Self::MandateDeserializationFailed | Self::InternalServerError => { + AER::InternalServerError(ApiError::new("HE", 0, "Something went wrong", None)) + }, + Self::HealthCheckError { message,component } => { + AER::InternalServerError(ApiError::new("HE",0,format!("{} health check failed with error: {}",component,message),None)) + }, + Self::PayoutFailed { data } => { + AER::BadRequest(ApiError::new("CE", 4, "Payout failed while processing with connector.", Some(Extra { data: data.clone(), ..Default::default()}))) + }, + Self::DuplicateRefundRequest => AER::BadRequest(ApiError::new("HE", 1, "Duplicate refund request. Refund already attempted with the refund ID", None)), + Self::DuplicateMandate => AER::BadRequest(ApiError::new("HE", 1, "Duplicate mandate request. Mandate already attempted with the Mandate ID", None)), + Self::DuplicateMerchantAccount => AER::BadRequest(ApiError::new("HE", 1, "The merchant account with the specified details already exists in our records", None)), + Self::DuplicateMerchantConnectorAccount { profile_id, connector_label: connector_name } => { + AER::BadRequest(ApiError::new("HE", 1, format!("The merchant connector account with the specified profile_id '{profile_id}' and connector_label '{connector_name}' already exists in our records"), None)) + } + Self::DuplicatePaymentMethod => AER::BadRequest(ApiError::new("HE", 1, "The payment method with the specified details already exists in our records", None)), + Self::DuplicatePayment { payment_id } => { + AER::BadRequest(ApiError::new("HE", 1, "The payment with the specified payment_id already exists in our records", Some(Extra {reason: Some(format!("{payment_id} already exists")), ..Default::default()}))) + } + Self::DuplicatePayout { payout_id } => { + AER::BadRequest(ApiError::new("HE", 1, format!("The payout with the specified payout_id '{payout_id}' already exists in our records"), None)) + } + Self::GenericDuplicateError { message } => { + AER::BadRequest(ApiError::new("HE", 1, message, None)) + } + Self::RefundNotFound => { + AER::NotFound(ApiError::new("HE", 2, "Refund does not exist in our records.", None)) + } + Self::CustomerNotFound => { + AER::NotFound(ApiError::new("HE", 2, "Customer does not exist in our records", None)) + } + Self::ConfigNotFound => { + AER::NotFound(ApiError::new("HE", 2, "Config key does not exist in our records.", None)) + }, + Self::DuplicateConfig => { + AER::BadRequest(ApiError::new("HE", 1, "The config with the specified key already exists in our records", None)) + } + Self::PaymentNotFound => { + AER::NotFound(ApiError::new("HE", 2, "Payment does not exist in our records", None)) + } + Self::PaymentMethodNotFound => { + AER::NotFound(ApiError::new("HE", 2, "Payment method does not exist in our records", None)) + } + Self::MerchantAccountNotFound => { + AER::NotFound(ApiError::new("HE", 2, "Merchant account does not exist in our records", None)) + } + Self::MerchantConnectorAccountNotFound {id } => { + AER::NotFound(ApiError::new("HE", 2, "Merchant connector account does not exist in our records", Some(Extra {reason: Some(format!("{id} does not exist")), ..Default::default()}))) + } + Self::MerchantConnectorAccountDisabled => { + AER::BadRequest(ApiError::new("HE", 3, "The selected merchant connector account is disabled", None)) + } + Self::ResourceIdNotFound => { + AER::NotFound(ApiError::new("HE", 2, "Resource ID does not exist in our records", None)) + } + Self::MandateNotFound => { + AER::NotFound(ApiError::new("HE", 2, "Mandate does not exist in our records", None)) + } + Self::PayoutNotFound => { + AER::NotFound(ApiError::new("HE", 2, "Payout does not exist in our records", None)) + } + Self::EventNotFound => { + AER::NotFound(ApiError::new("HE", 2, "Event does not exist in our records", None)) + } + Self::ReturnUrlUnavailable => AER::NotFound(ApiError::new("HE", 3, "Return URL is not configured and not passed in payments request", None)), + Self::RefundNotPossible { connector } => { + AER::BadRequest(ApiError::new("HE", 3, format!("This refund is not possible through Hyperswitch. Please raise the refund through {connector} dashboard"), None)) + } + Self::MandateValidationFailed { reason } => { + AER::BadRequest(ApiError::new("HE", 3, "Mandate Validation Failed", Some(Extra { reason: Some(reason.to_owned()), ..Default::default() }))) + } + Self::PaymentNotSucceeded => AER::BadRequest(ApiError::new("HE", 3, "The payment has not succeeded yet. Please pass a successful payment to initiate refund", None)), + Self::PaymentBlockedError { + message, + reason, + .. + } => AER::DomainError(ApiError::new("HE", 3, message, Some(Extra { reason: Some(reason.clone()), ..Default::default() }))), + Self::SuccessfulPaymentNotFound => { + AER::NotFound(ApiError::new("HE", 4, "Successful payment not found for the given payment id", None)) + } + Self::IncorrectConnectorNameGiven => { + AER::NotFound(ApiError::new("HE", 4, "The connector provided in the request is incorrect or not available", None)) + } + Self::AddressNotFound => { + AER::NotFound(ApiError::new("HE", 4, "Address does not exist in our records", None)) + }, + Self::GenericNotFoundError { message } => { + AER::NotFound(ApiError::new("HE", 5, message, None)) + }, + Self::ApiKeyNotFound => { + AER::NotFound(ApiError::new("HE", 2, "API Key does not exist in our records", None)) + } + Self::NotSupported { message } => { + AER::BadRequest(ApiError::new("HE", 3, "Payment method type not supported", Some(Extra {reason: Some(message.to_owned()), ..Default::default()}))) + }, + Self::InvalidCardIin => AER::BadRequest(ApiError::new("HE", 3, "The provided card IIN does not exist", None)), + Self::InvalidCardIinLength => AER::BadRequest(ApiError::new("HE", 3, "The provided card IIN length is invalid, please provide an IIN with 6 digits", None)), + Self::FlowNotSupported { flow, connector } => { + AER::BadRequest(ApiError::new("IR", 20, format!("{flow} flow not supported"), Some(Extra {connector: Some(connector.to_owned()), ..Default::default()}))) //FIXME: error message + } + Self::DisputeNotFound { .. } => { + AER::NotFound(ApiError::new("HE", 2, "Dispute does not exist in our records", None)) + }, + Self::AuthenticationNotFound { .. } => { + AER::NotFound(ApiError::new("HE", 2, "Authentication does not exist in our records", None)) + }, + Self::BusinessProfileNotFound { id } => { + AER::NotFound(ApiError::new("HE", 2, format!("Business profile with the given id {id} does not exist"), None)) + } + Self::FileNotFound => { + AER::NotFound(ApiError::new("HE", 2, "File does not exist in our records", None)) + } + Self::PollNotFound { .. } => { + AER::NotFound(ApiError::new("HE", 2, "Poll does not exist in our records", None)) + }, + Self::FileNotAvailable => { + AER::NotFound(ApiError::new("HE", 2, "File not available", None)) + } + Self::DisputeStatusValidationFailed { .. } => { + AER::BadRequest(ApiError::new("HE", 2, "Dispute status validation failed", None)) + } + Self::FileValidationFailed { reason } => { + AER::BadRequest(ApiError::new("HE", 2, format!("File validation failed {reason}"), None)) + } + Self::MissingFile => { + AER::BadRequest(ApiError::new("HE", 2, "File not found in the request", None)) + } + Self::MissingFilePurpose => { + AER::BadRequest(ApiError::new("HE", 2, "File purpose not found in the request or is invalid", None)) + } + Self::MissingFileContentType => { + AER::BadRequest(ApiError::new("HE", 2, "File content type not found", None)) + } + Self::MissingDisputeId => { + AER::BadRequest(ApiError::new("HE", 2, "Dispute id not found in the request", None)) + } + Self::WebhookAuthenticationFailed => { + AER::Unauthorized(ApiError::new("WE", 1, "Webhook authentication failed", None)) + } + Self::WebhookResourceNotFound => { + AER::NotFound(ApiError::new("WE", 4, "Webhook resource was not found", None)) + } + Self::WebhookBadRequest => { + AER::BadRequest(ApiError::new("WE", 2, "Bad request body received", None)) + } + Self::WebhookProcessingFailure => { + AER::InternalServerError(ApiError::new("WE", 3, "There was an issue processing the webhook", None)) + }, + Self::WebhookInvalidMerchantSecret => { + AER::BadRequest(ApiError::new("WE", 2, "Merchant Secret set for webhook source verificartion is invalid", None)) + } + Self::IncorrectPaymentMethodConfiguration => { + AER::BadRequest(ApiError::new("HE", 4, "No eligible connector was found for the current payment method configuration", None)) + } + Self::WebhookUnprocessableEntity => { + AER::Unprocessable(ApiError::new("WE", 5, "There was an issue processing the webhook body", None)) + }, + Self::ResourceBusy => { + AER::Unprocessable(ApiError::new("WE", 5, "There was an issue processing the webhook body", None)) + } + Self::PaymentLinkNotFound => { + AER::NotFound(ApiError::new("HE", 2, "Payment Link does not exist in our records", None)) + } + Self::InvalidConnectorConfiguration {config} => { + AER::BadRequest(ApiError::new("IR", 24, format!("Merchant connector account is configured with invalid {config}"), None)) + } + Self::CurrencyConversionFailed => { + AER::Unprocessable(ApiError::new("HE", 2, "Failed to convert currency to minor unit", None)) + } + Self::PaymentMethodDeleteFailed => { + AER::BadRequest(ApiError::new("IR", 25, "Cannot delete the default payment method", None)) + } + Self::InvalidCookie => { + AER::BadRequest(ApiError::new("IR", 26, "Invalid Cookie", None)) + } + Self::ExtendedCardInfoNotFound => { + AER::NotFound(ApiError::new("IR", 27, "Extended card info does not exist", None)) + } + } + } +} + +impl actix_web::ResponseError for ApiErrorResponse { + fn status_code(&self) -> StatusCode { + ErrorSwitch::::switch(self).status_code() + } + + fn error_response(&self) -> actix_web::HttpResponse { + ErrorSwitch::::switch(self).error_response() + } +} + +impl From for router_data::ErrorResponse { + fn from(error: ApiErrorResponse) -> Self { + Self { + code: error.error_code(), + message: error.error_message(), + reason: None, + status_code: match error { + ApiErrorResponse::ExternalConnectorError { status_code, .. } => status_code, + _ => 500, + }, + attempt_status: None, + connector_transaction_id: None, + } + } +} diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index 30279fae549f..ba6ccdf839a4 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -1,8 +1,12 @@ pub mod errors; pub mod mandates; +pub mod payment_address; +pub mod payment_method_data; pub mod payments; #[cfg(feature = "payouts")] pub mod payouts; +pub mod router_data; +pub mod router_request_types; #[cfg(not(feature = "payouts"))] pub trait PayoutAttemptInterface {} diff --git a/crates/hyperswitch_domain_models/src/mandates.rs b/crates/hyperswitch_domain_models/src/mandates.rs index 180f733cfebf..d9080d6dc91d 100644 --- a/crates/hyperswitch_domain_models/src/mandates.rs +++ b/crates/hyperswitch_domain_models/src/mandates.rs @@ -4,7 +4,7 @@ use api_models::payments::{ OnlineMandate as ApiOnlineMandate, }; use common_enums::Currency; -use common_utils::{date_time, errors::ParsingError, pii}; +use common_utils::{date_time, errors::ParsingError, pii, types::MinorUnit}; use error_stack::ResultExt; use masking::{PeekInterface, Secret}; use time::PrimitiveDateTime; @@ -24,7 +24,7 @@ pub enum MandateDataType { #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq)] pub struct MandateAmountData { - pub amount: i64, + pub amount: MinorUnit, pub currency: Currency, pub start_date: Option, pub end_date: Option, diff --git a/crates/hyperswitch_domain_models/src/payment_address.rs b/crates/hyperswitch_domain_models/src/payment_address.rs new file mode 100644 index 000000000000..d4f2cf9f350f --- /dev/null +++ b/crates/hyperswitch_domain_models/src/payment_address.rs @@ -0,0 +1,82 @@ +use api_models::payments::Address; + +#[derive(Clone, Default, Debug)] +pub struct PaymentAddress { + shipping: Option
, + billing: Option
, + unified_payment_method_billing: Option
, + payment_method_billing: Option
, +} + +impl PaymentAddress { + pub fn new( + shipping: Option
, + billing: Option
, + payment_method_billing: Option
, + should_unify_address: Option, + ) -> Self { + // billing -> .billing, this is the billing details passed in the root of payments request + // payment_method_billing -> .payment_method_data.billing + + let unified_payment_method_billing = if should_unify_address.unwrap_or(true) { + // Merge the billing details field from both `payment.billing` and `payment.payment_method_data.billing` + // The unified payment_method_billing will be used as billing address and passed to the connector module + // This unification is required in order to provide backwards compatibility + // so that if `payment.billing` is passed it should be sent to the connector module + // Unify the billing details with `payment_method_data.billing` + payment_method_billing + .as_ref() + .map(|payment_method_billing| { + payment_method_billing + .clone() + .unify_address(billing.as_ref()) + }) + .or(billing.clone()) + } else { + payment_method_billing.clone() + }; + + Self { + shipping, + billing, + unified_payment_method_billing, + payment_method_billing, + } + } + + pub fn get_shipping(&self) -> Option<&Address> { + self.shipping.as_ref() + } + + pub fn get_payment_method_billing(&self) -> Option<&Address> { + self.unified_payment_method_billing.as_ref() + } + + /// Unify the billing details from `payment_method_data.[payment_method_data].billing details`. + pub fn unify_with_payment_method_data_billing( + self, + payment_method_data_billing: Option
, + ) -> Self { + // Unify the billing details with `payment_method_data.billing_details` + let unified_payment_method_billing = payment_method_data_billing + .map(|payment_method_data_billing| { + payment_method_data_billing.unify_address(self.get_payment_method_billing()) + }) + .or(self.get_payment_method_billing().cloned()); + + Self { + shipping: self.shipping, + billing: self.billing, + unified_payment_method_billing, + payment_method_billing: self.payment_method_billing, + } + } + + pub fn get_request_payment_method_billing(&self) -> Option<&Address> { + self.payment_method_billing.as_ref() + } + + pub fn get_payment_billing(&self) -> Option<&Address> { + self.billing.as_ref() + } +} diff --git a/crates/hyperswitch_domain_models/src/payment_method_data.rs b/crates/hyperswitch_domain_models/src/payment_method_data.rs new file mode 100644 index 000000000000..9dc85c103e97 --- /dev/null +++ b/crates/hyperswitch_domain_models/src/payment_method_data.rs @@ -0,0 +1,837 @@ +use common_utils::pii::{self, Email}; +use masking::Secret; +use serde::{Deserialize, Serialize}; + +// We need to derive Serialize and Deserialize because some parts of payment method data are being +// stored in the database as serde_json::Value +#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] +pub enum PaymentMethodData { + Card(Card), + CardRedirect(CardRedirectData), + Wallet(WalletData), + PayLater(PayLaterData), + BankRedirect(BankRedirectData), + BankDebit(BankDebitData), + BankTransfer(Box), + Crypto(CryptoData), + MandatePayment, + Reward, + Upi(UpiData), + Voucher(VoucherData), + GiftCard(Box), + CardToken(CardToken), +} + +impl PaymentMethodData { + pub fn get_payment_method(&self) -> Option { + match self { + Self::Card(_) => Some(common_enums::PaymentMethod::Card), + Self::CardRedirect(_) => Some(common_enums::PaymentMethod::CardRedirect), + Self::Wallet(_) => Some(common_enums::PaymentMethod::Wallet), + Self::PayLater(_) => Some(common_enums::PaymentMethod::PayLater), + Self::BankRedirect(_) => Some(common_enums::PaymentMethod::BankRedirect), + Self::BankDebit(_) => Some(common_enums::PaymentMethod::BankDebit), + Self::BankTransfer(_) => Some(common_enums::PaymentMethod::BankTransfer), + Self::Crypto(_) => Some(common_enums::PaymentMethod::Crypto), + Self::Reward => Some(common_enums::PaymentMethod::Reward), + Self::Upi(_) => Some(common_enums::PaymentMethod::Upi), + Self::Voucher(_) => Some(common_enums::PaymentMethod::Voucher), + Self::GiftCard(_) => Some(common_enums::PaymentMethod::GiftCard), + Self::CardToken(_) | Self::MandatePayment => None, + } + } +} + +#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Default)] +pub struct Card { + pub card_number: cards::CardNumber, + pub card_exp_month: Secret, + pub card_exp_year: Secret, + pub card_cvc: Secret, + pub card_issuer: Option, + pub card_network: Option, + pub card_type: Option, + pub card_issuing_country: Option, + pub bank_code: Option, + pub nick_name: Option>, +} + +#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] +pub enum CardRedirectData { + Knet {}, + Benefit {}, + MomoAtm {}, + CardRedirect {}, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub enum PayLaterData { + KlarnaRedirect {}, + KlarnaSdk { token: String }, + AffirmRedirect {}, + AfterpayClearpayRedirect {}, + PayBrightRedirect {}, + WalleyRedirect {}, + AlmaRedirect {}, + AtomeRedirect {}, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] + +pub enum WalletData { + AliPayQr(Box), + AliPayRedirect(AliPayRedirection), + AliPayHkRedirect(AliPayHkRedirection), + MomoRedirect(MomoRedirection), + KakaoPayRedirect(KakaoPayRedirection), + GoPayRedirect(GoPayRedirection), + GcashRedirect(GcashRedirection), + ApplePay(ApplePayWalletData), + ApplePayRedirect(Box), + ApplePayThirdPartySdk(Box), + DanaRedirect {}, + GooglePay(GooglePayWalletData), + GooglePayRedirect(Box), + GooglePayThirdPartySdk(Box), + MbWayRedirect(Box), + MobilePayRedirect(Box), + PaypalRedirect(PaypalRedirection), + PaypalSdk(PayPalWalletData), + SamsungPay(Box), + TwintRedirect {}, + VippsRedirect {}, + TouchNGoRedirect(Box), + WeChatPayRedirect(Box), + WeChatPayQr(Box), + CashappQr(Box), + SwishQr(SwishQrData), +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] + +pub struct SamsungPayWalletData { + /// The encrypted payment token from Samsung + pub token: Secret, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] + +pub struct GooglePayWalletData { + /// The type of payment method + pub pm_type: String, + /// User-facing message to describe the payment method that funds this transaction. + pub description: String, + /// The information of the payment method + pub info: GooglePayPaymentMethodInfo, + /// The tokenization data of Google pay + pub tokenization_data: GpayTokenizationData, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct ApplePayRedirectData {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct GooglePayRedirectData {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct GooglePayThirdPartySdkData {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct ApplePayThirdPartySdkData {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct WeChatPayRedirection {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct WeChatPay {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct WeChatPayQr {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct CashappQr {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct PaypalRedirection { + /// paypal's email address + pub email: Option, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct AliPayQr {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct AliPayRedirection {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct AliPayHkRedirection {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct MomoRedirection {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct KakaoPayRedirection {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct GoPayRedirection {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct GcashRedirection {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct MobilePayRedirection {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct MbWayRedirection {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] + +pub struct GooglePayPaymentMethodInfo { + /// The name of the card network + pub card_network: String, + /// The details of the card + pub card_details: String, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct PayPalWalletData { + /// Token generated for the Apple pay + pub token: String, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct TouchNGoRedirection {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct SwishQrData {} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct GpayTokenizationData { + /// The type of the token + pub token_type: String, + /// Token generated for the wallet + pub token: String, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct ApplePayWalletData { + /// The payment data of Apple pay + pub payment_data: String, + /// The payment method of Apple pay + pub payment_method: ApplepayPaymentMethod, + /// The unique identifier for the transaction + pub transaction_identifier: String, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct ApplepayPaymentMethod { + pub display_name: String, + pub network: String, + pub pm_type: String, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] + +pub enum BankRedirectData { + BancontactCard { + card_number: Option, + card_exp_month: Option>, + card_exp_year: Option>, + }, + Bizum {}, + Blik { + blik_code: Option, + }, + Eps { + bank_name: Option, + }, + Giropay { + bank_account_bic: Option>, + bank_account_iban: Option>, + }, + Ideal { + bank_name: Option, + }, + Interac {}, + OnlineBankingCzechRepublic { + issuer: common_enums::BankNames, + }, + OnlineBankingFinland {}, + OnlineBankingPoland { + issuer: common_enums::BankNames, + }, + OnlineBankingSlovakia { + issuer: common_enums::BankNames, + }, + OpenBankingUk { + issuer: Option, + }, + Przelewy24 { + bank_name: Option, + }, + Sofort { + preferred_language: Option, + }, + Trustly {}, + OnlineBankingFpx { + issuer: common_enums::BankNames, + }, + OnlineBankingThailand { + issuer: common_enums::BankNames, + }, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub struct CryptoData { + pub pay_currency: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub struct UpiData { + pub vpa_id: Option>, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum VoucherData { + Boleto(Box), + Efecty, + PagoEfectivo, + RedCompra, + RedPagos, + Alfamart(Box), + Indomaret(Box), + Oxxo, + SevenEleven(Box), + Lawson(Box), + MiniStop(Box), + FamilyMart(Box), + Seicomart(Box), + PayEasy(Box), +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct BoletoVoucherData { + /// The shopper's social security number + pub social_security_number: Option>, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct AlfamartVoucherData {} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct IndomaretVoucherData {} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct JCSVoucherData {} + +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum GiftCardData { + Givex(GiftCardDetails), + PaySafeCard {}, +} + +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +pub struct GiftCardDetails { + /// The gift card number + pub number: Secret, + /// The card verification code. + pub cvc: Secret, +} + +#[derive(Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone, Default)] +#[serde(rename_all = "snake_case")] +pub struct CardToken { + /// The card holder's name + pub card_holder_name: Option>, + + /// The CVC number for the card + pub card_cvc: Option>, +} + +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum BankDebitData { + AchBankDebit { + account_number: Secret, + routing_number: Secret, + bank_name: Option, + bank_type: Option, + bank_holder_type: Option, + }, + SepaBankDebit { + iban: Secret, + }, + BecsBankDebit { + account_number: Secret, + bsb_number: Secret, + }, + BacsBankDebit { + account_number: Secret, + sort_code: Secret, + }, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub enum BankTransferData { + AchBankTransfer {}, + SepaBankTransfer {}, + BacsBankTransfer {}, + MultibancoBankTransfer {}, + PermataBankTransfer {}, + BcaBankTransfer {}, + BniVaBankTransfer {}, + BriVaBankTransfer {}, + CimbVaBankTransfer {}, + DanamonVaBankTransfer {}, + MandiriVaBankTransfer {}, + Pix {}, + Pse {}, + LocalBankTransfer { bank_code: Option }, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct SepaAndBacsBillingDetails { + /// The Email ID for SEPA and BACS billing + pub email: Email, + /// The billing name for SEPA and BACS billing + pub name: Secret, +} + +impl From for PaymentMethodData { + fn from(api_model_payment_method_data: api_models::payments::PaymentMethodData) -> Self { + match api_model_payment_method_data { + api_models::payments::PaymentMethodData::Card(card_data) => { + Self::Card(Card::from(card_data)) + } + api_models::payments::PaymentMethodData::CardRedirect(card_redirect) => { + Self::CardRedirect(From::from(card_redirect)) + } + api_models::payments::PaymentMethodData::Wallet(wallet_data) => { + Self::Wallet(From::from(wallet_data)) + } + api_models::payments::PaymentMethodData::PayLater(pay_later_data) => { + Self::PayLater(From::from(pay_later_data)) + } + api_models::payments::PaymentMethodData::BankRedirect(bank_redirect_data) => { + Self::BankRedirect(From::from(bank_redirect_data)) + } + api_models::payments::PaymentMethodData::BankDebit(bank_debit_data) => { + Self::BankDebit(From::from(bank_debit_data)) + } + api_models::payments::PaymentMethodData::BankTransfer(bank_transfer_data) => { + Self::BankTransfer(Box::new(From::from(*bank_transfer_data))) + } + api_models::payments::PaymentMethodData::Crypto(crypto_data) => { + Self::Crypto(From::from(crypto_data)) + } + api_models::payments::PaymentMethodData::MandatePayment => Self::MandatePayment, + api_models::payments::PaymentMethodData::Reward => Self::Reward, + api_models::payments::PaymentMethodData::Upi(upi_data) => { + Self::Upi(From::from(upi_data)) + } + api_models::payments::PaymentMethodData::Voucher(voucher_data) => { + Self::Voucher(From::from(voucher_data)) + } + api_models::payments::PaymentMethodData::GiftCard(gift_card) => { + Self::GiftCard(Box::new(From::from(*gift_card))) + } + api_models::payments::PaymentMethodData::CardToken(card_token) => { + Self::CardToken(From::from(card_token)) + } + } + } +} + +impl From for Card { + fn from(value: api_models::payments::Card) -> Self { + let api_models::payments::Card { + card_number, + card_exp_month, + card_exp_year, + card_holder_name: _, + card_cvc, + card_issuer, + card_network, + card_type, + card_issuing_country, + bank_code, + nick_name, + } = value; + + Self { + card_number, + card_exp_month, + card_exp_year, + card_cvc, + card_issuer, + card_network, + card_type, + card_issuing_country, + bank_code, + nick_name, + } + } +} + +impl From for CardRedirectData { + fn from(value: api_models::payments::CardRedirectData) -> Self { + match value { + api_models::payments::CardRedirectData::Knet {} => Self::Knet {}, + api_models::payments::CardRedirectData::Benefit {} => Self::Benefit {}, + api_models::payments::CardRedirectData::MomoAtm {} => Self::MomoAtm {}, + api_models::payments::CardRedirectData::CardRedirect {} => Self::CardRedirect {}, + } + } +} + +impl From for WalletData { + fn from(value: api_models::payments::WalletData) -> Self { + match value { + api_models::payments::WalletData::AliPayQr(_) => Self::AliPayQr(Box::new(AliPayQr {})), + api_models::payments::WalletData::AliPayRedirect(_) => { + Self::AliPayRedirect(AliPayRedirection {}) + } + api_models::payments::WalletData::AliPayHkRedirect(_) => { + Self::AliPayHkRedirect(AliPayHkRedirection {}) + } + api_models::payments::WalletData::MomoRedirect(_) => { + Self::MomoRedirect(MomoRedirection {}) + } + api_models::payments::WalletData::KakaoPayRedirect(_) => { + Self::KakaoPayRedirect(KakaoPayRedirection {}) + } + api_models::payments::WalletData::GoPayRedirect(_) => { + Self::GoPayRedirect(GoPayRedirection {}) + } + api_models::payments::WalletData::GcashRedirect(_) => { + Self::GcashRedirect(GcashRedirection {}) + } + api_models::payments::WalletData::ApplePay(apple_pay_data) => { + Self::ApplePay(ApplePayWalletData::from(apple_pay_data)) + } + api_models::payments::WalletData::ApplePayRedirect(_) => { + Self::ApplePayRedirect(Box::new(ApplePayRedirectData {})) + } + api_models::payments::WalletData::ApplePayThirdPartySdk(_) => { + Self::ApplePayThirdPartySdk(Box::new(ApplePayThirdPartySdkData {})) + } + api_models::payments::WalletData::DanaRedirect {} => Self::DanaRedirect {}, + api_models::payments::WalletData::GooglePay(google_pay_data) => { + Self::GooglePay(GooglePayWalletData::from(google_pay_data)) + } + api_models::payments::WalletData::GooglePayRedirect(_) => { + Self::GooglePayRedirect(Box::new(GooglePayRedirectData {})) + } + api_models::payments::WalletData::GooglePayThirdPartySdk(_) => { + Self::GooglePayThirdPartySdk(Box::new(GooglePayThirdPartySdkData {})) + } + api_models::payments::WalletData::MbWayRedirect(..) => { + Self::MbWayRedirect(Box::new(MbWayRedirection {})) + } + api_models::payments::WalletData::MobilePayRedirect(_) => { + Self::MobilePayRedirect(Box::new(MobilePayRedirection {})) + } + api_models::payments::WalletData::PaypalRedirect(paypal_redirect_data) => { + Self::PaypalRedirect(PaypalRedirection { + email: paypal_redirect_data.email, + }) + } + api_models::payments::WalletData::PaypalSdk(paypal_sdk_data) => { + Self::PaypalSdk(PayPalWalletData { + token: paypal_sdk_data.token, + }) + } + api_models::payments::WalletData::SamsungPay(samsung_pay_data) => { + Self::SamsungPay(Box::new(SamsungPayWalletData { + token: samsung_pay_data.token, + })) + } + api_models::payments::WalletData::TwintRedirect {} => Self::TwintRedirect {}, + api_models::payments::WalletData::VippsRedirect {} => Self::VippsRedirect {}, + api_models::payments::WalletData::TouchNGoRedirect(_) => { + Self::TouchNGoRedirect(Box::new(TouchNGoRedirection {})) + } + api_models::payments::WalletData::WeChatPayRedirect(_) => { + Self::WeChatPayRedirect(Box::new(WeChatPayRedirection {})) + } + api_models::payments::WalletData::WeChatPayQr(_) => { + Self::WeChatPayQr(Box::new(WeChatPayQr {})) + } + api_models::payments::WalletData::CashappQr(_) => { + Self::CashappQr(Box::new(CashappQr {})) + } + api_models::payments::WalletData::SwishQr(_) => Self::SwishQr(SwishQrData {}), + } + } +} + +impl From for GooglePayWalletData { + fn from(value: api_models::payments::GooglePayWalletData) -> Self { + Self { + pm_type: value.pm_type, + description: value.description, + info: GooglePayPaymentMethodInfo { + card_network: value.info.card_network, + card_details: value.info.card_details, + }, + tokenization_data: GpayTokenizationData { + token_type: value.tokenization_data.token_type, + token: value.tokenization_data.token, + }, + } + } +} + +impl From for ApplePayWalletData { + fn from(value: api_models::payments::ApplePayWalletData) -> Self { + Self { + payment_data: value.payment_data, + payment_method: ApplepayPaymentMethod { + display_name: value.payment_method.display_name, + network: value.payment_method.network, + pm_type: value.payment_method.pm_type, + }, + transaction_identifier: value.transaction_identifier, + } + } +} + +impl From for PayLaterData { + fn from(value: api_models::payments::PayLaterData) -> Self { + match value { + api_models::payments::PayLaterData::KlarnaRedirect { .. } => Self::KlarnaRedirect {}, + api_models::payments::PayLaterData::KlarnaSdk { token } => Self::KlarnaSdk { token }, + api_models::payments::PayLaterData::AffirmRedirect {} => Self::AffirmRedirect {}, + api_models::payments::PayLaterData::AfterpayClearpayRedirect { .. } => { + Self::AfterpayClearpayRedirect {} + } + api_models::payments::PayLaterData::PayBrightRedirect {} => Self::PayBrightRedirect {}, + api_models::payments::PayLaterData::WalleyRedirect {} => Self::WalleyRedirect {}, + api_models::payments::PayLaterData::AlmaRedirect {} => Self::AlmaRedirect {}, + api_models::payments::PayLaterData::AtomeRedirect {} => Self::AtomeRedirect {}, + } + } +} + +impl From for BankRedirectData { + fn from(value: api_models::payments::BankRedirectData) -> Self { + match value { + api_models::payments::BankRedirectData::BancontactCard { + card_number, + card_exp_month, + card_exp_year, + .. + } => Self::BancontactCard { + card_number, + card_exp_month, + card_exp_year, + }, + 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::Giropay { + bank_account_bic, + bank_account_iban, + .. + } => Self::Giropay { + bank_account_bic, + bank_account_iban, + }, + api_models::payments::BankRedirectData::Ideal { bank_name, .. } => { + Self::Ideal { bank_name } + } + api_models::payments::BankRedirectData::Interac { .. } => Self::Interac {}, + api_models::payments::BankRedirectData::OnlineBankingCzechRepublic { issuer } => { + Self::OnlineBankingCzechRepublic { issuer } + } + api_models::payments::BankRedirectData::OnlineBankingFinland { .. } => { + Self::OnlineBankingFinland {} + } + api_models::payments::BankRedirectData::OnlineBankingPoland { issuer } => { + Self::OnlineBankingPoland { issuer } + } + api_models::payments::BankRedirectData::OnlineBankingSlovakia { issuer } => { + Self::OnlineBankingSlovakia { issuer } + } + api_models::payments::BankRedirectData::OpenBankingUk { issuer, .. } => { + Self::OpenBankingUk { 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 {}, + api_models::payments::BankRedirectData::OnlineBankingFpx { issuer } => { + Self::OnlineBankingFpx { issuer } + } + api_models::payments::BankRedirectData::OnlineBankingThailand { issuer } => { + Self::OnlineBankingThailand { issuer } + } + } + } +} + +impl From for CryptoData { + fn from(value: api_models::payments::CryptoData) -> Self { + let api_models::payments::CryptoData { pay_currency } = value; + Self { pay_currency } + } +} + +impl From for UpiData { + fn from(value: api_models::payments::UpiData) -> Self { + let api_models::payments::UpiData { vpa_id } = value; + Self { vpa_id } + } +} + +impl From for VoucherData { + fn from(value: api_models::payments::VoucherData) -> Self { + match value { + api_models::payments::VoucherData::Boleto(boleto_data) => { + Self::Boleto(Box::new(BoletoVoucherData { + social_security_number: boleto_data.social_security_number, + })) + } + api_models::payments::VoucherData::Alfamart(_) => { + Self::Alfamart(Box::new(AlfamartVoucherData {})) + } + api_models::payments::VoucherData::Indomaret(_) => { + Self::Indomaret(Box::new(IndomaretVoucherData {})) + } + api_models::payments::VoucherData::SevenEleven(_) + | api_models::payments::VoucherData::Lawson(_) + | api_models::payments::VoucherData::MiniStop(_) + | api_models::payments::VoucherData::FamilyMart(_) + | api_models::payments::VoucherData::Seicomart(_) + | api_models::payments::VoucherData::PayEasy(_) => { + Self::SevenEleven(Box::new(JCSVoucherData {})) + } + api_models::payments::VoucherData::Efecty => Self::Efecty, + api_models::payments::VoucherData::PagoEfectivo => Self::PagoEfectivo, + api_models::payments::VoucherData::RedCompra => Self::RedCompra, + api_models::payments::VoucherData::RedPagos => Self::RedPagos, + api_models::payments::VoucherData::Oxxo => Self::Oxxo, + } + } +} + +impl From for GiftCardData { + fn from(value: api_models::payments::GiftCardData) -> Self { + match value { + api_models::payments::GiftCardData::Givex(details) => Self::Givex(GiftCardDetails { + number: details.number, + cvc: details.cvc, + }), + api_models::payments::GiftCardData::PaySafeCard {} => Self::PaySafeCard {}, + } + } +} + +impl From for CardToken { + fn from(value: api_models::payments::CardToken) -> Self { + let api_models::payments::CardToken { + card_holder_name, + card_cvc, + } = value; + Self { + card_holder_name, + card_cvc, + } + } +} + +impl From for BankDebitData { + fn from(value: api_models::payments::BankDebitData) -> Self { + match value { + api_models::payments::BankDebitData::AchBankDebit { + account_number, + routing_number, + bank_name, + bank_type, + bank_holder_type, + .. + } => Self::AchBankDebit { + account_number, + routing_number, + bank_name, + bank_type, + bank_holder_type, + }, + api_models::payments::BankDebitData::SepaBankDebit { iban, .. } => { + Self::SepaBankDebit { iban } + } + api_models::payments::BankDebitData::BecsBankDebit { + account_number, + bsb_number, + .. + } => Self::BecsBankDebit { + account_number, + bsb_number, + }, + api_models::payments::BankDebitData::BacsBankDebit { + account_number, + sort_code, + .. + } => Self::BacsBankDebit { + account_number, + sort_code, + }, + } + } +} + +impl From for BankTransferData { + fn from(value: api_models::payments::BankTransferData) -> Self { + match value { + api_models::payments::BankTransferData::AchBankTransfer { .. } => { + Self::AchBankTransfer {} + } + api_models::payments::BankTransferData::SepaBankTransfer { .. } => { + Self::SepaBankTransfer {} + } + api_models::payments::BankTransferData::BacsBankTransfer { .. } => { + Self::BacsBankTransfer {} + } + api_models::payments::BankTransferData::MultibancoBankTransfer { .. } => { + Self::MultibancoBankTransfer {} + } + api_models::payments::BankTransferData::PermataBankTransfer { .. } => { + Self::PermataBankTransfer {} + } + api_models::payments::BankTransferData::BcaBankTransfer { .. } => { + Self::BcaBankTransfer {} + } + api_models::payments::BankTransferData::BniVaBankTransfer { .. } => { + Self::BniVaBankTransfer {} + } + api_models::payments::BankTransferData::BriVaBankTransfer { .. } => { + Self::BriVaBankTransfer {} + } + api_models::payments::BankTransferData::CimbVaBankTransfer { .. } => { + Self::CimbVaBankTransfer {} + } + api_models::payments::BankTransferData::DanamonVaBankTransfer { .. } => { + Self::DanamonVaBankTransfer {} + } + api_models::payments::BankTransferData::MandiriVaBankTransfer { .. } => { + Self::MandiriVaBankTransfer {} + } + api_models::payments::BankTransferData::Pix {} => Self::Pix {}, + api_models::payments::BankTransferData::Pse {} => Self::Pse {}, + api_models::payments::BankTransferData::LocalBankTransfer { bank_code } => { + Self::LocalBankTransfer { bank_code } + } + } + } +} diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index 8a7dfad5fcbe..b9adc162c471 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -1,4 +1,4 @@ -use common_utils::pii; +use common_utils::{self, pii, types::MinorUnit}; use time::PrimitiveDateTime; pub mod payment_attempt; @@ -15,9 +15,9 @@ pub struct PaymentIntent { pub payment_id: String, pub merchant_id: String, pub status: storage_enums::IntentStatus, - pub amount: i64, + pub amount: MinorUnit, pub currency: Option, - pub amount_captured: Option, + pub amount_captured: Option, pub customer_id: Option, pub description: Option, pub return_url: Option, @@ -60,4 +60,6 @@ pub struct PaymentIntent { #[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, } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index d6565a6be60f..ac0c74ea9134 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -1,5 +1,6 @@ use api_models::enums::Connector; use common_enums as storage_enums; +use common_utils::types::MinorUnit; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -111,15 +112,15 @@ pub struct PaymentAttempt { pub merchant_id: String, pub attempt_id: String, pub status: storage_enums::AttemptStatus, - pub amount: i64, - pub net_amount: i64, + pub amount: MinorUnit, + pub net_amount: MinorUnit, pub currency: Option, pub save_to_locker: Option, pub connector: Option, pub error_message: Option, - pub offer_amount: Option, - pub surcharge_amount: Option, - pub tax_amount: Option, + pub offer_amount: Option, + pub surcharge_amount: Option, + pub tax_amount: Option, pub payment_method_id: Option, pub payment_method: Option, pub connector_transaction_id: Option, @@ -135,7 +136,7 @@ pub struct PaymentAttempt { #[serde(default, with = "common_utils::custom_serde::iso8601::option")] pub last_synced: Option, pub cancellation_reason: Option, - pub amount_to_capture: Option, + pub amount_to_capture: Option, pub mandate_id: Option, pub browser_info: Option, pub error_code: Option, @@ -153,7 +154,7 @@ pub struct PaymentAttempt { pub multiple_capture_count: Option, // reference to the payment at connector side pub connector_response_reference_id: Option, - pub amount_capturable: i64, + pub amount_capturable: MinorUnit, pub updated_by: String, pub authentication_data: Option, pub encoded_data: Option, @@ -166,15 +167,21 @@ pub struct PaymentAttempt { pub mandate_data: Option, pub payment_method_billing_address_id: Option, pub fingerprint_id: Option, + pub charge_id: Option, + pub client_source: Option, + pub client_version: Option, } impl PaymentAttempt { - pub fn get_total_amount(&self) -> i64 { - self.amount + self.surcharge_amount.unwrap_or(0) + self.tax_amount.unwrap_or(0) + pub fn get_total_amount(&self) -> MinorUnit { + self.amount + + self.surcharge_amount.unwrap_or_default() + + self.tax_amount.unwrap_or_default() } - pub fn get_total_surcharge_amount(&self) -> Option { + + pub fn get_total_surcharge_amount(&self) -> Option { self.surcharge_amount - .map(|surcharge_amount| surcharge_amount + self.tax_amount.unwrap_or(0)) + .map(|surcharge_amount| surcharge_amount + self.tax_amount.unwrap_or_default()) } } @@ -194,18 +201,18 @@ pub struct PaymentAttemptNew { pub merchant_id: String, pub attempt_id: String, pub status: storage_enums::AttemptStatus, - pub amount: i64, + pub amount: MinorUnit, /// amount + surcharge_amount + tax_amount /// This field will always be derived before updating in the Database - pub net_amount: i64, + pub net_amount: MinorUnit, pub currency: Option, // pub auto_capture: Option, pub save_to_locker: Option, pub connector: Option, pub error_message: Option, - pub offer_amount: Option, - pub surcharge_amount: Option, - pub tax_amount: Option, + pub offer_amount: Option, + pub surcharge_amount: Option, + pub tax_amount: Option, pub payment_method_id: Option, pub payment_method: Option, pub capture_method: Option, @@ -220,7 +227,7 @@ pub struct PaymentAttemptNew { #[serde(default, with = "common_utils::custom_serde::iso8601::option")] pub last_synced: Option, pub cancellation_reason: Option, - pub amount_to_capture: Option, + pub amount_to_capture: Option, pub mandate_id: Option, pub browser_info: Option, pub payment_token: Option, @@ -236,7 +243,7 @@ pub struct PaymentAttemptNew { pub error_reason: Option, pub connector_response_reference_id: Option, pub multiple_capture_count: Option, - pub amount_capturable: i64, + pub amount_capturable: MinorUnit, pub updated_by: String, pub authentication_data: Option, pub encoded_data: Option, @@ -249,12 +256,17 @@ pub struct PaymentAttemptNew { pub mandate_data: Option, pub payment_method_billing_address_id: Option, pub fingerprint_id: Option, + pub charge_id: Option, + pub client_source: Option, + pub client_version: Option, } impl PaymentAttemptNew { /// returns amount + surcharge_amount + tax_amount - pub fn calculate_net_amount(&self) -> i64 { - self.amount + self.surcharge_amount.unwrap_or(0) + self.tax_amount.unwrap_or(0) + pub fn calculate_net_amount(&self) -> MinorUnit { + self.amount + + self.surcharge_amount.unwrap_or_default() + + self.tax_amount.unwrap_or_default() } pub fn populate_derived_fields(self) -> Self { @@ -267,7 +279,7 @@ impl PaymentAttemptNew { #[derive(Debug, Clone, Serialize, Deserialize)] pub enum PaymentAttemptUpdate { Update { - amount: i64, + amount: MinorUnit, currency: storage_enums::Currency, status: storage_enums::AttemptStatus, authentication_type: Option, @@ -277,10 +289,10 @@ pub enum PaymentAttemptUpdate { payment_method_type: Option, payment_experience: Option, business_sub_label: Option, - amount_to_capture: Option, + amount_to_capture: Option, capture_method: Option, - surcharge_amount: Option, - tax_amount: Option, + surcharge_amount: Option, + tax_amount: Option, fingerprint_id: Option, payment_method_billing_address_id: Option, updated_by: String, @@ -289,9 +301,9 @@ pub enum PaymentAttemptUpdate { payment_token: Option, connector: Option, straight_through_algorithm: Option, - amount_capturable: Option, - surcharge_amount: Option, - tax_amount: Option, + amount_capturable: Option, + surcharge_amount: Option, + tax_amount: Option, updated_by: String, merchant_connector_id: Option, }, @@ -300,7 +312,7 @@ pub enum PaymentAttemptUpdate { updated_by: String, }, ConfirmUpdate { - amount: i64, + amount: MinorUnit, currency: storage_enums::Currency, status: storage_enums::AttemptStatus, authentication_type: Option, @@ -316,10 +328,10 @@ pub enum PaymentAttemptUpdate { straight_through_algorithm: Option, error_code: Option>, error_message: Option>, - amount_capturable: Option, + amount_capturable: Option, updated_by: String, - surcharge_amount: Option, - tax_amount: Option, + surcharge_amount: Option, + tax_amount: Option, merchant_connector_id: Option, external_three_ds_authentication_attempted: Option, authentication_connector: Option, @@ -327,6 +339,8 @@ pub enum PaymentAttemptUpdate { payment_method_billing_address_id: Option, fingerprint_id: Option, payment_method_id: Option, + client_source: Option, + client_version: Option, }, RejectUpdate { status: storage_enums::AttemptStatus, @@ -362,13 +376,14 @@ pub enum PaymentAttemptUpdate { error_message: Option>, error_reason: Option>, connector_response_reference_id: Option, - amount_capturable: Option, + amount_capturable: Option, updated_by: String, authentication_data: Option, encoded_data: Option, unified_code: Option>, unified_message: Option>, payment_method_data: Option, + charge_id: Option, }, UnresolvedResponseUpdate { status: storage_enums::AttemptStatus, @@ -391,7 +406,7 @@ pub enum PaymentAttemptUpdate { error_code: Option>, error_message: Option>, error_reason: Option>, - amount_capturable: Option, + amount_capturable: Option, updated_by: String, unified_code: Option>, unified_message: Option>, @@ -399,13 +414,13 @@ pub enum PaymentAttemptUpdate { payment_method_data: Option, }, CaptureUpdate { - amount_to_capture: Option, + amount_to_capture: Option, multiple_capture_count: Option, updated_by: String, }, AmountToCaptureUpdate { status: storage_enums::AttemptStatus, - amount_capturable: i64, + amount_capturable: MinorUnit, updated_by: String, }, PreprocessingUpdate { @@ -422,11 +437,12 @@ pub enum PaymentAttemptUpdate { encoded_data: Option, connector_transaction_id: Option, connector: Option, + charge_id: Option, updated_by: String, }, IncrementalAuthorizationAmountUpdate { - amount: i64, - amount_capturable: i64, + amount: MinorUnit, + amount_capturable: MinorUnit, }, AuthenticationUpdate { status: storage_enums::AttemptStatus, diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs index a5e631b5ac33..5cf6f1219f5b 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs @@ -2,6 +2,7 @@ use common_enums as storage_enums; use common_utils::{ consts::{PAYMENTS_LIST_MAX_LIMIT_V1, PAYMENTS_LIST_MAX_LIMIT_V2}, pii, + types::MinorUnit, }; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -74,13 +75,14 @@ pub struct PaymentIntentNew { pub payment_id: String, pub merchant_id: String, pub status: storage_enums::IntentStatus, - pub amount: i64, + pub amount: MinorUnit, pub currency: Option, - pub amount_captured: 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, @@ -113,13 +115,14 @@ pub struct PaymentIntentNew { pub fingerprint_id: Option, pub session_expiry: Option, pub request_external_three_ds_authentication: Option, + pub charges: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum PaymentIntentUpdate { ResponseUpdate { status: storage_enums::IntentStatus, - amount_captured: Option, + amount_captured: Option, return_url: Option, updated_by: String, fingerprint_id: Option, @@ -149,7 +152,7 @@ pub enum PaymentIntentUpdate { updated_by: String, }, Update { - amount: i64, + amount: MinorUnit, currency: storage_enums::Currency, setup_future_usage: Option, status: storage_enums::IntentStatus, @@ -164,6 +167,7 @@ pub enum PaymentIntentUpdate { statement_descriptor_suffix: Option, order_details: Option>, metadata: Option, + frm_metadata: Option, payment_confirm_source: Option, updated_by: String, fingerprint_id: Option, @@ -196,19 +200,22 @@ pub enum PaymentIntentUpdate { updated_by: String, }, IncrementalAuthorizationAmountUpdate { - amount: i64, + amount: MinorUnit, }, AuthorizationCountUpdate { authorization_count: i32, }, + CompleteAuthorizeUpdate { + shipping_address_id: Option, + }, } #[derive(Clone, Debug, Default)] pub struct PaymentIntentUpdateInternal { - pub amount: Option, + pub amount: Option, pub currency: Option, pub status: Option, - pub amount_captured: Option, + pub amount_captured: Option, pub customer_id: Option, pub return_url: Option, pub setup_future_usage: Option, @@ -237,6 +244,7 @@ pub struct PaymentIntentUpdateInternal { pub fingerprint_id: Option, pub session_expiry: Option, pub request_external_three_ds_authentication: Option, + pub frm_metadata: Option, } impl From for PaymentIntentUpdateInternal { @@ -263,6 +271,7 @@ impl From for PaymentIntentUpdateInternal { fingerprint_id, session_expiry, request_external_three_ds_authentication, + frm_metadata, } => Self { amount: Some(amount), currency: Some(currency), @@ -285,6 +294,7 @@ impl From for PaymentIntentUpdateInternal { fingerprint_id, session_expiry, request_external_three_ds_authentication, + frm_metadata, ..Default::default() }, PaymentIntentUpdate::MetadataUpdate { @@ -420,6 +430,12 @@ impl From for PaymentIntentUpdateInternal { authorization_count: Some(authorization_count), ..Default::default() }, + PaymentIntentUpdate::CompleteAuthorizeUpdate { + shipping_address_id, + } => Self { + shipping_address_id, + ..Default::default() + }, } } } diff --git a/crates/hyperswitch_domain_models/src/router_data.rs b/crates/hyperswitch_domain_models/src/router_data.rs new file mode 100644 index 000000000000..d0481cecc539 --- /dev/null +++ b/crates/hyperswitch_domain_models/src/router_data.rs @@ -0,0 +1,207 @@ +use std::{collections::HashMap, marker::PhantomData}; + +use masking::Secret; + +use crate::payment_address::PaymentAddress; + +#[derive(Debug, Clone)] +pub struct RouterData { + pub flow: PhantomData, + pub merchant_id: String, + pub customer_id: Option, + pub connector_customer: Option, + pub connector: String, + pub payment_id: String, + pub attempt_id: String, + pub status: common_enums::enums::AttemptStatus, + pub payment_method: common_enums::enums::PaymentMethod, + pub connector_auth_type: ConnectorAuthType, + pub description: Option, + pub return_url: Option, + pub address: PaymentAddress, + pub auth_type: common_enums::enums::AuthenticationType, + pub connector_meta_data: Option, + pub amount_captured: Option, + pub access_token: Option, + pub session_token: Option, + pub reference_id: Option, + pub payment_method_token: Option, + pub recurring_mandate_payment_data: Option, + pub preprocessing_id: Option, + /// This is the balance amount for gift cards or voucher + pub payment_method_balance: Option, + + ///for switching between two different versions of the same connector + pub connector_api_version: Option, + + /// Contains flow-specific data required to construct a request and send it to the connector. + pub request: Request, + + /// Contains flow-specific data that the connector responds with. + pub response: Result, + + /// Contains a reference ID that should be sent in the connector request + pub connector_request_reference_id: String, + + #[cfg(feature = "payouts")] + /// Contains payout method data + pub payout_method_data: Option, + + #[cfg(feature = "payouts")] + /// Contains payout's quote ID + pub quote_id: Option, + + pub test_mode: Option, + pub connector_http_status_code: Option, + pub external_latency: Option, + /// Contains apple pay flow type simplified or manual + pub apple_pay_flow: Option, + + pub frm_metadata: Option, + + pub dispute_id: Option, + pub refund_id: Option, + + /// This field is used to store various data regarding the response from connector + pub connector_response: Option, + pub payment_method_status: Option, +} + +// Different patterns of authentication. +#[derive(Default, Debug, Clone, serde::Deserialize, serde::Serialize)] +#[serde(tag = "auth_type")] +pub enum ConnectorAuthType { + TemporaryAuth, + HeaderKey { + api_key: Secret, + }, + BodyKey { + api_key: Secret, + key1: Secret, + }, + SignatureKey { + api_key: Secret, + key1: Secret, + api_secret: Secret, + }, + MultiAuthKey { + api_key: Secret, + key1: Secret, + api_secret: Secret, + key2: Secret, + }, + CurrencyAuthKey { + auth_key_map: HashMap, + }, + CertificateAuth { + certificate: Secret, + private_key: Secret, + }, + #[default] + NoKey, +} + +#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] +pub struct AccessToken { + pub token: Secret, + pub expires: i64, +} + +#[derive(Debug, Clone, serde::Deserialize)] +pub enum PaymentMethodToken { + Token(Secret), + ApplePayDecrypt(Box), +} + +#[derive(Debug, Clone, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ApplePayPredecryptData { + pub application_primary_account_number: Secret, + pub application_expiration_date: String, + pub currency_code: String, + pub transaction_amount: i64, + pub device_manufacturer_identifier: Secret, + pub payment_data_type: Secret, + pub payment_data: ApplePayCryptogramData, +} + +#[derive(Debug, Clone, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ApplePayCryptogramData { + pub online_payment_cryptogram: Secret, + pub eci_indicator: Option, +} + +#[derive(Debug, Default, Clone)] +pub struct RecurringMandatePaymentData { + pub payment_method_type: Option, //required for making recurring payment using saved payment method through stripe + pub original_payment_authorized_amount: Option, + pub original_payment_authorized_currency: Option, +} + +#[derive(Debug, Clone)] +pub struct PaymentMethodBalance { + pub amount: i64, + pub currency: common_enums::enums::Currency, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct ConnectorResponseData { + pub additional_payment_method_data: Option, +} + +impl ConnectorResponseData { + pub fn with_additional_payment_method_data( + additional_payment_method_data: AdditionalPaymentMethodConnectorResponse, + ) -> Self { + Self { + additional_payment_method_data: Some(additional_payment_method_data), + } + } +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub enum AdditionalPaymentMethodConnectorResponse { + Card { + /// Details regarding the authentication details of the connector, if this is a 3ds payment. + authentication_data: Option, + /// Various payment checks that are done for a payment + payment_checks: Option, + }, +} + +#[derive(Clone, Debug, serde::Serialize)] +pub struct ErrorResponse { + pub code: String, + pub message: String, + pub reason: Option, + pub status_code: u16, + pub attempt_status: Option, + pub connector_transaction_id: Option, +} + +impl Default for ErrorResponse { + fn default() -> Self { + Self { + code: "HE_00".to_string(), + message: "Something went wrong".to_string(), + reason: None, + status_code: http::StatusCode::INTERNAL_SERVER_ERROR.as_u16(), + attempt_status: None, + connector_transaction_id: None, + } + } +} + +impl ErrorResponse { + pub fn get_not_implemented() -> Self { + Self { + code: "IR_00".to_string(), + message: "This API is under development and will be made available soon.".to_string(), + reason: None, + status_code: http::StatusCode::INTERNAL_SERVER_ERROR.as_u16(), + attempt_status: None, + connector_transaction_id: None, + } + } +} diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs new file mode 100644 index 000000000000..026a191977ce --- /dev/null +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -0,0 +1,480 @@ +pub mod authentication; +pub mod fraud_check; +use api_models::payments::RequestSurchargeDetails; +use common_utils::{consts, errors, pii}; +use diesel_models::enums as storage_enums; +use error_stack::ResultExt; +use masking::Secret; +use serde::Serialize; +use serde_with::serde_as; + +use super::payment_method_data::PaymentMethodData; +use crate::{errors::api_error_response, mandates, payments, router_data}; +#[derive(Debug, Clone)] +pub struct PaymentsAuthorizeData { + pub payment_method_data: PaymentMethodData, + /// total amount (original_amount + surcharge_amount + tax_on_surcharge_amount) + /// If connector supports separate field for surcharge amount, consider using below functions defined on `PaymentsAuthorizeData` to fetch original amount and surcharge amount separately + /// ``` + /// get_original_amount() + /// get_surcharge_amount() + /// get_tax_on_surcharge_amount() + /// get_total_surcharge_amount() // returns surcharge_amount + tax_on_surcharge_amount + /// ``` + pub amount: i64, + pub email: Option, + pub customer_name: Option>, + pub currency: storage_enums::Currency, + pub confirm: bool, + pub statement_descriptor_suffix: Option, + pub statement_descriptor: Option, + pub capture_method: Option, + pub router_return_url: Option, + pub webhook_url: Option, + pub complete_authorize_url: Option, + // Mandates + pub setup_future_usage: Option, + pub mandate_id: Option, + pub off_session: Option, + pub customer_acceptance: Option, + pub setup_mandate_details: Option, + pub browser_info: Option, + pub order_details: Option>, + pub order_category: Option, + pub session_token: Option, + pub enrolled_for_3ds: bool, + pub related_transaction_id: Option, + pub payment_experience: Option, + pub payment_method_type: Option, + pub surcharge_details: Option, + pub customer_id: Option, + pub request_incremental_authorization: bool, + pub metadata: Option, + pub authentication_data: Option, +} + +#[derive(Debug, Clone, Default)] +pub struct PaymentsCaptureData { + pub amount_to_capture: i64, + pub currency: storage_enums::Currency, + pub connector_transaction_id: String, + pub payment_amount: i64, + pub multiple_capture_data: Option, + pub connector_meta: Option, + pub browser_info: Option, + pub metadata: Option, + // This metadata is used to store the metadata shared during the payment intent request. +} + +#[derive(Debug, Clone, Default)] +pub struct PaymentsIncrementalAuthorizationData { + pub total_amount: i64, + pub additional_amount: i64, + pub currency: storage_enums::Currency, + pub reason: Option, + pub connector_transaction_id: String, +} + +#[derive(Debug, Clone, Default)] +pub struct MultipleCaptureRequestData { + pub capture_sequence: i16, + pub capture_reference: String, +} + +#[derive(Debug, Clone)] +pub struct AuthorizeSessionTokenData { + pub amount_to_capture: Option, + pub currency: storage_enums::Currency, + pub connector_transaction_id: String, + pub amount: Option, +} + +#[derive(Debug, Clone)] +pub struct ConnectorCustomerData { + pub description: Option, + pub email: Option, + pub phone: Option>, + pub name: Option>, + pub preprocessing_id: Option, + pub payment_method_data: PaymentMethodData, +} + +#[derive(Debug, Clone)] +pub struct PaymentMethodTokenizationData { + pub payment_method_data: PaymentMethodData, + pub browser_info: Option, + pub currency: storage_enums::Currency, + pub amount: Option, +} + +#[derive(Debug, Clone)] +pub struct PaymentsPreProcessingData { + pub payment_method_data: Option, + pub amount: Option, + pub email: Option, + pub currency: Option, + pub payment_method_type: Option, + pub setup_mandate_details: Option, + pub capture_method: Option, + pub order_details: Option>, + pub router_return_url: Option, + pub webhook_url: Option, + pub complete_authorize_url: Option, + pub surcharge_details: Option, + pub browser_info: Option, + pub connector_transaction_id: Option, + pub redirect_response: Option, +} + +#[derive(Debug, Clone)] +pub struct CompleteAuthorizeData { + pub payment_method_data: Option, + pub amount: i64, + pub email: Option, + pub currency: storage_enums::Currency, + pub confirm: bool, + pub statement_descriptor_suffix: Option, + pub capture_method: Option, + // Mandates + pub setup_future_usage: Option, + pub mandate_id: Option, + pub off_session: Option, + pub setup_mandate_details: Option, + pub redirect_response: Option, + pub browser_info: Option, + pub connector_transaction_id: Option, + pub connector_meta: Option, + pub complete_authorize_url: Option, + pub metadata: Option, +} + +#[derive(Debug, Clone)] +pub struct CompleteAuthorizeRedirectResponse { + pub params: Option>, + pub payload: Option, +} + +#[derive(Debug, Default, Clone)] +pub struct PaymentsSyncData { + //TODO : add fields based on the connector requirements + pub connector_transaction_id: ResponseId, + pub encoded_data: Option, + pub capture_method: Option, + pub connector_meta: Option, + pub sync_type: SyncRequestType, + pub mandate_id: Option, + pub payment_method_type: Option, + pub currency: storage_enums::Currency, +} + +#[derive(Debug, Default, Clone)] +pub enum SyncRequestType { + MultipleCaptureSync(Vec), + #[default] + SinglePaymentSync, +} + +#[derive(Debug, Default, Clone)] +pub struct PaymentsCancelData { + pub amount: Option, + pub currency: Option, + pub connector_transaction_id: String, + pub cancellation_reason: Option, + pub connector_meta: Option, + pub browser_info: Option, + pub metadata: Option, + // This metadata is used to store the metadata shared during the payment intent request. +} + +#[derive(Debug, Default, Clone)] +pub struct PaymentsRejectData { + pub amount: Option, + pub currency: Option, +} + +#[derive(Debug, Default, Clone)] +pub struct PaymentsApproveData { + pub amount: Option, + pub currency: Option, +} + +#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] +pub struct BrowserInformation { + pub color_depth: Option, + pub java_enabled: Option, + pub java_script_enabled: Option, + pub language: Option, + pub screen_height: Option, + pub screen_width: Option, + pub time_zone: Option, + pub ip_address: Option, + pub accept_header: Option, + pub user_agent: Option, +} + +#[derive(Debug, Clone, Default, Serialize)] +pub enum ResponseId { + ConnectorTransactionId(String), + EncodedData(String), + #[default] + NoResponseId, +} +impl ResponseId { + pub fn get_connector_transaction_id( + &self, + ) -> errors::CustomResult { + match self { + Self::ConnectorTransactionId(txn_id) => Ok(txn_id.to_string()), + _ => Err(errors::ValidationError::IncorrectValueProvided { + field_name: "connector_transaction_id", + }) + .attach_printable("Expected connector transaction ID not found"), + } + } +} + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct SurchargeDetails { + /// original_amount + pub original_amount: common_utils::types::MinorUnit, + /// surcharge value + pub surcharge: common_utils::types::Surcharge, + /// tax on surcharge value + pub tax_on_surcharge: + Option>, + /// surcharge amount for this payment + pub surcharge_amount: common_utils::types::MinorUnit, + /// tax on surcharge amount for this payment + pub tax_on_surcharge_amount: common_utils::types::MinorUnit, + /// sum of original amount, + pub final_amount: common_utils::types::MinorUnit, +} + +impl SurchargeDetails { + pub fn is_request_surcharge_matching( + &self, + request_surcharge_details: RequestSurchargeDetails, + ) -> bool { + request_surcharge_details.surcharge_amount == self.surcharge_amount + && request_surcharge_details.tax_amount.unwrap_or_default() + == self.tax_on_surcharge_amount + } + pub fn get_total_surcharge_amount(&self) -> common_utils::types::MinorUnit { + self.surcharge_amount + self.tax_on_surcharge_amount + } +} + +impl + From<( + &RequestSurchargeDetails, + &payments::payment_attempt::PaymentAttempt, + )> for SurchargeDetails +{ + fn from( + (request_surcharge_details, payment_attempt): ( + &RequestSurchargeDetails, + &payments::payment_attempt::PaymentAttempt, + ), + ) -> Self { + let surcharge_amount = request_surcharge_details.surcharge_amount; + let tax_on_surcharge_amount = request_surcharge_details.tax_amount.unwrap_or_default(); + Self { + original_amount: payment_attempt.amount, + surcharge: common_utils::types::Surcharge::Fixed( + request_surcharge_details.surcharge_amount, + ), + tax_on_surcharge: None, + surcharge_amount, + tax_on_surcharge_amount, + final_amount: payment_attempt.amount + surcharge_amount + tax_on_surcharge_amount, + } + } +} + +#[derive(Debug, Clone)] +pub struct AuthenticationData { + pub eci: Option, + pub cavv: String, + pub threeds_server_transaction_id: String, + pub message_version: String, +} + +#[derive(Debug, Clone)] +pub struct RefundsData { + pub refund_id: String, + pub connector_transaction_id: String, + + pub connector_refund_id: Option, + pub currency: storage_enums::Currency, + /// Amount for the payment against which this refund is issued + pub payment_amount: i64, + pub reason: Option, + pub webhook_url: Option, + /// Amount to be refunded + pub refund_amount: i64, + /// Arbitrary metadata required for refund + pub connector_metadata: Option, + pub browser_info: Option, + /// Charges associated with the payment + pub charges: Option, +} + +#[derive(Debug, serde::Deserialize, Clone)] +pub struct ChargeRefunds { + pub charge_id: String, + pub transfer_account_id: String, + pub charge_type: api_models::enums::PaymentChargeType, + pub options: ChargeRefundsOptions, +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub enum ChargeRefundsOptions { + Destination(DestinationChargeRefund), + Direct(DirectChargeRefund), +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct DirectChargeRefund { + pub revert_platform_fee: bool, +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct DestinationChargeRefund { + pub revert_platform_fee: bool, + pub revert_transfer: bool, +} + +#[derive(Debug, Clone)] +pub struct AccessTokenRequestData { + pub app_id: Secret, + pub id: Option>, + // Add more keys if required +} + +impl TryFrom for AccessTokenRequestData { + type Error = api_error_response::ApiErrorResponse; + fn try_from(connector_auth: router_data::ConnectorAuthType) -> Result { + match connector_auth { + router_data::ConnectorAuthType::HeaderKey { api_key } => Ok(Self { + app_id: api_key, + id: None, + }), + router_data::ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { + app_id: api_key, + id: Some(key1), + }), + router_data::ConnectorAuthType::SignatureKey { api_key, key1, .. } => Ok(Self { + app_id: api_key, + id: Some(key1), + }), + router_data::ConnectorAuthType::MultiAuthKey { api_key, key1, .. } => Ok(Self { + app_id: api_key, + id: Some(key1), + }), + + _ => Err(api_error_response::ApiErrorResponse::InvalidDataValue { + field_name: "connector_account_details", + }), + } + } +} + +#[derive(Default, Debug, Clone)] +pub struct AcceptDisputeRequestData { + pub dispute_id: String, + pub connector_dispute_id: String, +} + +#[derive(Default, Debug, Clone)] +pub struct DefendDisputeRequestData { + pub dispute_id: String, + pub connector_dispute_id: String, +} + +#[derive(Default, Debug, Clone)] +pub struct SubmitEvidenceRequestData { + pub dispute_id: String, + pub connector_dispute_id: String, + pub access_activity_log: Option, + pub billing_address: Option, + pub cancellation_policy: Option>, + pub cancellation_policy_provider_file_id: Option, + pub cancellation_policy_disclosure: Option, + pub cancellation_rebuttal: Option, + pub customer_communication: Option>, + pub customer_communication_provider_file_id: Option, + pub customer_email_address: Option, + pub customer_name: Option, + pub customer_purchase_ip: Option, + pub customer_signature: Option>, + pub customer_signature_provider_file_id: Option, + pub product_description: Option, + pub receipt: Option>, + pub receipt_provider_file_id: Option, + pub refund_policy: Option>, + pub refund_policy_provider_file_id: Option, + pub refund_policy_disclosure: Option, + pub refund_refusal_explanation: Option, + pub service_date: Option, + pub service_documentation: Option>, + pub service_documentation_provider_file_id: Option, + pub shipping_address: Option, + pub shipping_carrier: Option, + pub shipping_date: Option, + pub shipping_documentation: Option>, + pub shipping_documentation_provider_file_id: Option, + pub shipping_tracking_number: Option, + pub invoice_showing_distinct_transactions: Option>, + pub invoice_showing_distinct_transactions_provider_file_id: Option, + pub recurring_transaction_agreement: Option>, + pub recurring_transaction_agreement_provider_file_id: Option, + pub uncategorized_file: Option>, + pub uncategorized_file_provider_file_id: Option, + pub uncategorized_text: Option, +} + +#[derive(Clone, Debug)] +pub struct RetrieveFileRequestData { + pub provider_file_id: String, +} + +#[serde_as] +#[derive(Clone, Debug, serde::Serialize)] +pub struct UploadFileRequestData { + pub file_key: String, + #[serde(skip)] + pub file: Vec, + #[serde_as(as = "serde_with::DisplayFromStr")] + pub file_type: mime::Mime, + pub file_size: i32, +} + +#[cfg(feature = "payouts")] +#[derive(Debug, Clone)] +pub struct PayoutsData { + pub payout_id: String, + pub amount: i64, + pub connector_payout_id: Option, + pub destination_currency: storage_enums::Currency, + pub source_currency: storage_enums::Currency, + pub payout_type: storage_enums::PayoutType, + pub entity_type: storage_enums::PayoutEntityType, + pub customer_details: Option, + pub vendor_details: Option, +} + +#[derive(Debug, Default, Clone)] +pub struct CustomerDetails { + pub customer_id: Option, + pub name: Option>, + pub email: Option, + pub phone: Option>, + pub phone_country_code: Option, +} + +#[derive(Debug, Clone)] +pub struct VerifyWebhookSourceRequestData { + pub webhook_headers: actix_web::http::header::HeaderMap, + pub webhook_body: Vec, + pub merchant_secret: api_models::webhooks::ConnectorWebhookSecrets, +} diff --git a/crates/hyperswitch_domain_models/src/router_request_types/authentication.rs b/crates/hyperswitch_domain_models/src/router_request_types/authentication.rs new file mode 100644 index 000000000000..1a554e8054d4 --- /dev/null +++ b/crates/hyperswitch_domain_models/src/router_request_types/authentication.rs @@ -0,0 +1,182 @@ +use cards::CardNumber; +use common_utils::{ext_traits::OptionExt, pii::Email}; +use error_stack::{Report, ResultExt}; +use serde::{Deserialize, Serialize}; + +use crate::{ + errors::api_error_response::ApiErrorResponse, payment_method_data::PaymentMethodData, + router_request_types::BrowserInformation, +}; + +#[derive(Debug, Clone)] +pub enum AuthenticationResponseData { + PreAuthNResponse { + threeds_server_transaction_id: String, + maximum_supported_3ds_version: common_utils::types::SemanticVersion, + connector_authentication_id: String, + three_ds_method_data: Option, + three_ds_method_url: Option, + message_version: common_utils::types::SemanticVersion, + connector_metadata: Option, + }, + AuthNResponse { + authn_flow_type: AuthNFlowType, + authentication_value: Option, + trans_status: common_enums::TransactionStatus, + }, + PostAuthNResponse { + trans_status: common_enums::TransactionStatus, + authentication_value: Option, + eci: Option, + }, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ChallengeParams { + pub acs_url: Option, + pub challenge_request: Option, + pub acs_reference_number: Option, + pub acs_trans_id: Option, + pub three_dsserver_trans_id: Option, + pub acs_signed_content: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum AuthNFlowType { + Challenge(Box), + Frictionless, +} + +impl AuthNFlowType { + pub fn get_acs_url(&self) -> Option { + if let Self::Challenge(challenge_params) = self { + challenge_params.acs_url.as_ref().map(ToString::to_string) + } else { + None + } + } + pub fn get_challenge_request(&self) -> Option { + if let Self::Challenge(challenge_params) = self { + challenge_params.challenge_request.clone() + } else { + None + } + } + pub fn get_acs_reference_number(&self) -> Option { + if let Self::Challenge(challenge_params) = self { + challenge_params.acs_reference_number.clone() + } else { + None + } + } + pub fn get_acs_trans_id(&self) -> Option { + if let Self::Challenge(challenge_params) = self { + challenge_params.acs_trans_id.clone() + } else { + None + } + } + pub fn get_acs_signed_content(&self) -> Option { + if let Self::Challenge(challenge_params) = self { + challenge_params.acs_signed_content.clone() + } else { + None + } + } + pub fn get_decoupled_authentication_type(&self) -> common_enums::DecoupledAuthenticationType { + match self { + Self::Challenge(_) => common_enums::DecoupledAuthenticationType::Challenge, + Self::Frictionless => common_enums::DecoupledAuthenticationType::Frictionless, + } + } +} + +#[derive(Clone, Default, Debug)] +pub struct PreAuthNRequestData { + // card number + #[allow(dead_code)] + pub(crate) card_holder_account_number: CardNumber, +} + +#[derive(Clone, Debug)] +pub struct ConnectorAuthenticationRequestData { + pub payment_method_data: PaymentMethodData, + pub billing_address: api_models::payments::Address, + pub shipping_address: Option, + pub browser_details: Option, + pub amount: Option, + pub currency: Option, + pub message_category: MessageCategory, + pub device_channel: api_models::payments::DeviceChannel, + pub pre_authentication_data: PreAuthenticationData, + pub return_url: Option, + pub sdk_information: Option, + pub email: Option, + pub threeds_method_comp_ind: api_models::payments::ThreeDsCompletionIndicator, + pub three_ds_requestor_url: String, + pub webhook_url: String, +} + +#[derive(Clone, serde::Deserialize, Debug, serde::Serialize, PartialEq, Eq)] +pub enum MessageCategory { + Payment, + NonPayment, +} + +#[derive(Clone, Debug)] +pub struct ConnectorPostAuthenticationRequestData { + pub threeds_server_transaction_id: String, +} + +#[derive(Clone, Debug)] +pub struct PreAuthenticationData { + pub threeds_server_transaction_id: String, + pub message_version: common_utils::types::SemanticVersion, + pub acquirer_bin: Option, + pub acquirer_merchant_id: Option, + pub connector_metadata: Option, +} + +impl TryFrom<&diesel_models::authentication::Authentication> for PreAuthenticationData { + type Error = Report; + + fn try_from( + authentication: &diesel_models::authentication::Authentication, + ) -> Result { + let error_message = ApiErrorResponse::UnprocessableEntity { message: "Pre Authentication must be completed successfully before Authentication can be performed".to_string() }; + let threeds_server_transaction_id = authentication + .threeds_server_transaction_id + .clone() + .get_required_value("threeds_server_transaction_id") + .change_context(error_message.clone())?; + let message_version = authentication + .message_version + .clone() + .get_required_value("message_version") + .change_context(error_message)?; + Ok(Self { + threeds_server_transaction_id, + message_version, + acquirer_bin: authentication.acquirer_bin.clone(), + acquirer_merchant_id: authentication.acquirer_merchant_id.clone(), + connector_metadata: authentication.connector_metadata.clone(), + }) + } +} + +#[derive(Clone, Default, Debug, Serialize, Deserialize)] +pub struct ThreeDsMethodData { + pub three_ds_method_data_submission: bool, + pub three_ds_method_data: String, + pub three_ds_method_url: Option, +} +#[derive(Clone, Default, Debug, Serialize, Deserialize)] +pub struct AcquirerDetails { + pub acquirer_bin: String, + pub acquirer_merchant_id: String, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct ExternalThreeDSConnectorMetadata { + pub pull_mechanism_for_external_3ds_enabled: Option, +} diff --git a/crates/hyperswitch_domain_models/src/router_request_types/fraud_check.rs b/crates/hyperswitch_domain_models/src/router_request_types/fraud_check.rs new file mode 100644 index 000000000000..441d3a70b41f --- /dev/null +++ b/crates/hyperswitch_domain_models/src/router_request_types/fraud_check.rs @@ -0,0 +1,164 @@ +use api_models; +use common_enums; +use common_utils::{ + events::{ApiEventMetric, ApiEventsType}, + pii::Email, +}; +use masking::Secret; +use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; + +use crate::router_request_types; +#[derive(Debug, Clone)] +pub struct FraudCheckSaleData { + pub amount: i64, + pub order_details: Option>, + pub currency: Option, + pub email: Option, +} + +#[derive(Debug, Clone)] +pub struct FraudCheckCheckoutData { + pub amount: i64, + pub order_details: Option>, + pub currency: Option, + pub browser_info: Option, + pub payment_method_data: Option, + pub email: Option, + pub gateway: Option, +} + +#[derive(Debug, Clone)] +pub struct FraudCheckTransactionData { + pub amount: i64, + pub order_details: Option>, + pub currency: Option, + pub payment_method: Option, + pub error_code: Option, + pub error_message: Option, + pub connector_transaction_id: Option, + //The name of the payment gateway or financial institution that processed the transaction. + pub connector: Option, +} + +#[derive(Debug, Clone)] +pub struct FraudCheckRecordReturnData { + pub amount: i64, + pub currency: Option, + pub refund_method: RefundMethod, + pub refund_transaction_id: Option, +} + +#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum RefundMethod { + StoreCredit, + OriginalPaymentInstrument, + NewPaymentInstrument, +} + +#[derive(Debug, Clone)] +pub struct FraudCheckFulfillmentData { + pub amount: i64, + pub order_details: Option>>, + pub fulfillment_req: FrmFulfillmentRequest, +} + +#[derive(Debug, Deserialize, Serialize, Clone, ToSchema)] +#[serde(deny_unknown_fields)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "snake_case")] +pub struct FrmFulfillmentRequest { + ///unique payment_id for the transaction + #[schema(max_length = 255, example = "pay_qiYfHcDou1ycIaxVXKHF")] + pub payment_id: String, + ///unique order_id for the order_details in the transaction + #[schema(max_length = 255, example = "pay_qiYfHcDou1ycIaxVXKHF")] + pub order_id: String, + ///denotes the status of the fulfillment... can be one of PARTIAL, COMPLETE, REPLACEMENT, CANCELED + #[schema(value_type = Option, example = "COMPLETE")] + pub fulfillment_status: Option, + ///contains details of the fulfillment + #[schema(value_type = Vec)] + pub fulfillments: Vec, + //name of the tracking Company + #[schema(max_length = 255, example = "fedex")] + pub tracking_company: Option, + //tracking ID of the product + #[schema(example = r#"["track_8327446667", "track_8327446668"]"#)] + pub tracking_numbers: Option>, + //tracking_url for tracking the product + pub tracking_urls: Option>, + // The name of the Shipper. + pub carrier: Option, + // Fulfillment method for the shipment. + pub fulfillment_method: Option, + // Statuses to indicate shipment state. + pub shipment_status: Option, + // The date and time items are ready to be shipped. + pub shipped_at: Option, +} + +#[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "snake_case")] +pub struct Fulfillments { + ///shipment_id of the shipped items + #[schema(max_length = 255, example = "ship_101")] + pub shipment_id: String, + ///products sent in the shipment + #[schema(value_type = Option>)] + pub products: Option>, + ///destination address of the shipment + #[schema(value_type = Destination)] + pub destination: Destination, +} + +#[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] +#[serde(untagged)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "snake_case")] +pub enum FulfillmentStatus { + PARTIAL, + COMPLETE, + REPLACEMENT, + CANCELED, +} + +#[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "snake_case")] +pub struct Product { + pub item_name: String, + pub item_quantity: i64, + pub item_id: String, +} + +#[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "snake_case")] +pub struct Destination { + pub full_name: Secret, + pub organization: Option, + pub email: Option, + pub address: Address, +} + +#[derive(Debug, Serialize, Eq, PartialEq, Deserialize, Clone)] +#[serde_with::skip_serializing_none] +#[serde(rename_all = "snake_case")] +pub struct Address { + pub street_address: Secret, + pub unit: Option>, + pub postal_code: Secret, + pub city: String, + pub province_code: Secret, + pub country_code: common_enums::CountryAlpha2, +} + +impl ApiEventMetric for FrmFulfillmentRequest { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::FraudCheck) + } +} diff --git a/crates/kgraph_utils/Cargo.toml b/crates/kgraph_utils/Cargo.toml index 86de6002c323..aafec6c8e152 100644 --- a/crates/kgraph_utils/Cargo.toml +++ b/crates/kgraph_utils/Cargo.toml @@ -13,7 +13,7 @@ connector_choice_mca_id = ["api_models/connector_choice_mca_id", "euclid/connect [dependencies] api_models = { version = "0.1.0", path = "../api_models", package = "api_models" } common_enums = { version = "0.1.0", path = "../common_enums" } -hyperswitch_constraint_graph = { version = "0.1.0", path = "../hyperswitch_constraint_graph" } +hyperswitch_constraint_graph = { version = "0.1.0", path = "../hyperswitch_constraint_graph", features = ["viz"] } euclid = { version = "0.1.0", path = "../euclid" } masking = { version = "0.1.0", path = "../masking/" } @@ -21,6 +21,7 @@ masking = { version = "0.1.0", path = "../masking/" } serde = "1.0.197" serde_json = "1.0.115" thiserror = "1.0.58" +strum = { version = "0.26", features = ["derive"] } [dev-dependencies] criterion = "0.5" diff --git a/crates/kgraph_utils/benches/evaluation.rs b/crates/kgraph_utils/benches/evaluation.rs index 9921ee7af352..4cc526f973fd 100644 --- a/crates/kgraph_utils/benches/evaluation.rs +++ b/crates/kgraph_utils/benches/evaluation.rs @@ -1,6 +1,6 @@ #![allow(unused, clippy::expect_used)] -use std::str::FromStr; +use std::{collections::HashMap, str::FromStr}; use api_models::{ admin as admin_api, enums as api_enums, payment_methods::RequestPaymentMethodTypes, @@ -13,7 +13,7 @@ use euclid::{ types::{NumValue, NumValueRefinement}, }; use hyperswitch_constraint_graph::{CycleCheck, Memoization}; -use kgraph_utils::{error::KgraphError, transformers::IntoDirValue}; +use kgraph_utils::{error::KgraphError, transformers::IntoDirValue, types::CountryCurrencyFilter}; fn build_test_data<'a>( total_enabled: usize, @@ -71,8 +71,12 @@ fn build_test_data<'a>( pm_auth_config: None, status: api_enums::ConnectorStatus::Inactive, }; - - kgraph_utils::mca::make_mca_graph(vec![stripe_account]).expect("Failed graph construction") + let config = CountryCurrencyFilter { + connector_configs: HashMap::new(), + default_configs: None, + }; + kgraph_utils::mca::make_mca_graph(vec![stripe_account], &config) + .expect("Failed graph construction") } fn evaluation(c: &mut Criterion) { diff --git a/crates/kgraph_utils/src/lib.rs b/crates/kgraph_utils/src/lib.rs index eb8eef6dedb5..20c2abf0533f 100644 --- a/crates/kgraph_utils/src/lib.rs +++ b/crates/kgraph_utils/src/lib.rs @@ -1,3 +1,4 @@ pub mod error; pub mod mca; pub mod transformers; +pub mod types; diff --git a/crates/kgraph_utils/src/mca.rs b/crates/kgraph_utils/src/mca.rs index 14a88dd1c6e6..b32c8c23bd99 100644 --- a/crates/kgraph_utils/src/mca.rs +++ b/crates/kgraph_utils/src/mca.rs @@ -4,15 +4,138 @@ use api_models::{ admin as admin_api, enums as api_enums, payment_methods::RequestPaymentMethodTypes, }; use euclid::{ + dirval, frontend::{ast, dir}, types::{NumValue, NumValueRefinement}, }; use hyperswitch_constraint_graph as cgraph; +use strum::IntoEnumIterator; -use crate::{error::KgraphError, transformers::IntoDirValue}; +use crate::{error::KgraphError, transformers::IntoDirValue, types as kgraph_types}; pub const DOMAIN_IDENTIFIER: &str = "payment_methods_enabled_for_merchantconnectoraccount"; +fn get_dir_value_payment_method( + from: api_enums::PaymentMethodType, +) -> Result { + match from { + api_enums::PaymentMethodType::Credit => Ok(dirval!(CardType = Credit)), + api_enums::PaymentMethodType::Debit => Ok(dirval!(CardType = Debit)), + api_enums::PaymentMethodType::Giropay => Ok(dirval!(BankRedirectType = Giropay)), + api_enums::PaymentMethodType::Ideal => Ok(dirval!(BankRedirectType = Ideal)), + api_enums::PaymentMethodType::Sofort => Ok(dirval!(BankRedirectType = Sofort)), + api_enums::PaymentMethodType::Eps => Ok(dirval!(BankRedirectType = Eps)), + api_enums::PaymentMethodType::Klarna => Ok(dirval!(PayLaterType = Klarna)), + api_enums::PaymentMethodType::Affirm => Ok(dirval!(PayLaterType = Affirm)), + api_enums::PaymentMethodType::AfterpayClearpay => { + Ok(dirval!(PayLaterType = AfterpayClearpay)) + } + api_enums::PaymentMethodType::GooglePay => Ok(dirval!(WalletType = GooglePay)), + api_enums::PaymentMethodType::ApplePay => Ok(dirval!(WalletType = ApplePay)), + api_enums::PaymentMethodType::Paypal => Ok(dirval!(WalletType = Paypal)), + api_enums::PaymentMethodType::CryptoCurrency => Ok(dirval!(CryptoType = CryptoCurrency)), + api_enums::PaymentMethodType::Ach => Ok(dirval!(BankDebitType = Ach)), + + api_enums::PaymentMethodType::Bacs => Ok(dirval!(BankDebitType = Bacs)), + + api_enums::PaymentMethodType::Becs => Ok(dirval!(BankDebitType = Becs)), + api_enums::PaymentMethodType::Sepa => Ok(dirval!(BankDebitType = Sepa)), + + api_enums::PaymentMethodType::AliPay => Ok(dirval!(WalletType = AliPay)), + api_enums::PaymentMethodType::AliPayHk => Ok(dirval!(WalletType = AliPayHk)), + api_enums::PaymentMethodType::BancontactCard => { + Ok(dirval!(BankRedirectType = BancontactCard)) + } + api_enums::PaymentMethodType::Blik => Ok(dirval!(BankRedirectType = Blik)), + api_enums::PaymentMethodType::MbWay => Ok(dirval!(WalletType = MbWay)), + api_enums::PaymentMethodType::MobilePay => Ok(dirval!(WalletType = MobilePay)), + api_enums::PaymentMethodType::Cashapp => Ok(dirval!(WalletType = Cashapp)), + api_enums::PaymentMethodType::Multibanco => Ok(dirval!(BankTransferType = Multibanco)), + api_enums::PaymentMethodType::Pix => Ok(dirval!(BankTransferType = Pix)), + api_enums::PaymentMethodType::Pse => Ok(dirval!(BankTransferType = Pse)), + api_enums::PaymentMethodType::Interac => Ok(dirval!(BankRedirectType = Interac)), + api_enums::PaymentMethodType::OnlineBankingCzechRepublic => { + Ok(dirval!(BankRedirectType = OnlineBankingCzechRepublic)) + } + api_enums::PaymentMethodType::OnlineBankingFinland => { + Ok(dirval!(BankRedirectType = OnlineBankingFinland)) + } + api_enums::PaymentMethodType::OnlineBankingPoland => { + Ok(dirval!(BankRedirectType = OnlineBankingPoland)) + } + api_enums::PaymentMethodType::OnlineBankingSlovakia => { + Ok(dirval!(BankRedirectType = OnlineBankingSlovakia)) + } + api_enums::PaymentMethodType::Swish => Ok(dirval!(WalletType = Swish)), + api_enums::PaymentMethodType::Trustly => Ok(dirval!(BankRedirectType = Trustly)), + api_enums::PaymentMethodType::Bizum => Ok(dirval!(BankRedirectType = Bizum)), + + api_enums::PaymentMethodType::PayBright => Ok(dirval!(PayLaterType = PayBright)), + api_enums::PaymentMethodType::Walley => Ok(dirval!(PayLaterType = Walley)), + api_enums::PaymentMethodType::Przelewy24 => Ok(dirval!(BankRedirectType = Przelewy24)), + api_enums::PaymentMethodType::WeChatPay => Ok(dirval!(WalletType = WeChatPay)), + + api_enums::PaymentMethodType::ClassicReward => Ok(dirval!(RewardType = ClassicReward)), + api_enums::PaymentMethodType::Evoucher => Ok(dirval!(RewardType = Evoucher)), + api_enums::PaymentMethodType::UpiCollect => Ok(dirval!(UpiType = UpiCollect)), + api_enums::PaymentMethodType::SamsungPay => Ok(dirval!(WalletType = SamsungPay)), + api_enums::PaymentMethodType::GoPay => Ok(dirval!(WalletType = GoPay)), + api_enums::PaymentMethodType::KakaoPay => Ok(dirval!(WalletType = KakaoPay)), + api_enums::PaymentMethodType::Twint => Ok(dirval!(WalletType = Twint)), + api_enums::PaymentMethodType::Gcash => Ok(dirval!(WalletType = Gcash)), + api_enums::PaymentMethodType::Vipps => Ok(dirval!(WalletType = Vipps)), + api_enums::PaymentMethodType::Momo => Ok(dirval!(WalletType = Momo)), + api_enums::PaymentMethodType::Alma => Ok(dirval!(PayLaterType = Alma)), + api_enums::PaymentMethodType::Dana => Ok(dirval!(WalletType = Dana)), + api_enums::PaymentMethodType::OnlineBankingFpx => { + Ok(dirval!(BankRedirectType = OnlineBankingFpx)) + } + api_enums::PaymentMethodType::OnlineBankingThailand => { + Ok(dirval!(BankRedirectType = OnlineBankingThailand)) + } + api_enums::PaymentMethodType::TouchNGo => Ok(dirval!(WalletType = TouchNGo)), + api_enums::PaymentMethodType::Atome => Ok(dirval!(PayLaterType = Atome)), + api_enums::PaymentMethodType::Boleto => Ok(dirval!(VoucherType = Boleto)), + api_enums::PaymentMethodType::Efecty => Ok(dirval!(VoucherType = Efecty)), + api_enums::PaymentMethodType::PagoEfectivo => Ok(dirval!(VoucherType = PagoEfectivo)), + api_enums::PaymentMethodType::RedCompra => Ok(dirval!(VoucherType = RedCompra)), + api_enums::PaymentMethodType::RedPagos => Ok(dirval!(VoucherType = RedPagos)), + api_enums::PaymentMethodType::Alfamart => Ok(dirval!(VoucherType = Alfamart)), + api_enums::PaymentMethodType::BcaBankTransfer => { + Ok(dirval!(BankTransferType = BcaBankTransfer)) + } + api_enums::PaymentMethodType::BniVa => Ok(dirval!(BankTransferType = BniVa)), + api_enums::PaymentMethodType::BriVa => Ok(dirval!(BankTransferType = BriVa)), + api_enums::PaymentMethodType::CimbVa => Ok(dirval!(BankTransferType = CimbVa)), + api_enums::PaymentMethodType::DanamonVa => Ok(dirval!(BankTransferType = DanamonVa)), + api_enums::PaymentMethodType::Indomaret => Ok(dirval!(VoucherType = Indomaret)), + api_enums::PaymentMethodType::MandiriVa => Ok(dirval!(BankTransferType = MandiriVa)), + api_enums::PaymentMethodType::LocalBankTransfer => { + Ok(dirval!(BankTransferType = LocalBankTransfer)) + } + api_enums::PaymentMethodType::PermataBankTransfer => { + Ok(dirval!(BankTransferType = PermataBankTransfer)) + } + api_enums::PaymentMethodType::PaySafeCard => Ok(dirval!(GiftCardType = PaySafeCard)), + api_enums::PaymentMethodType::SevenEleven => Ok(dirval!(VoucherType = SevenEleven)), + api_enums::PaymentMethodType::Lawson => Ok(dirval!(VoucherType = Lawson)), + api_enums::PaymentMethodType::MiniStop => Ok(dirval!(VoucherType = MiniStop)), + api_enums::PaymentMethodType::FamilyMart => Ok(dirval!(VoucherType = FamilyMart)), + api_enums::PaymentMethodType::Seicomart => Ok(dirval!(VoucherType = Seicomart)), + api_enums::PaymentMethodType::PayEasy => Ok(dirval!(VoucherType = PayEasy)), + api_enums::PaymentMethodType::Givex => Ok(dirval!(GiftCardType = Givex)), + api_enums::PaymentMethodType::Benefit => Ok(dirval!(CardRedirectType = Benefit)), + api_enums::PaymentMethodType::Knet => Ok(dirval!(CardRedirectType = Knet)), + api_enums::PaymentMethodType::OpenBankingUk => { + Ok(dirval!(BankRedirectType = OpenBankingUk)) + } + api_enums::PaymentMethodType::MomoAtm => Ok(dirval!(CardRedirectType = MomoAtm)), + api_enums::PaymentMethodType::Oxxo => Ok(dirval!(VoucherType = Oxxo)), + api_enums::PaymentMethodType::CardRedirect => Ok(dirval!(CardRedirectType = CardRedirect)), + api_enums::PaymentMethodType::Venmo => Ok(dirval!(WalletType = Venmo)), + } +} + fn compile_request_pm_types( builder: &mut cgraph::ConstraintGraphBuilder<'_, dir::DirValue>, pm_types: RequestPaymentMethodTypes, @@ -258,16 +381,220 @@ fn compile_payment_method_enabled( Ok(agg_id) } +macro_rules! collect_global_variants { + ($parent_enum:ident) => { + &mut dir::enums::$parent_enum::iter() + .map(dir::DirValue::$parent_enum) + .collect::>() + }; +} +fn global_vec_pmt( + enabled_pmt: Vec, + builder: &mut cgraph::ConstraintGraphBuilder<'_, dir::DirValue>, +) -> Vec { + let mut global_vector: Vec = Vec::new(); + + global_vector.append(collect_global_variants!(PayLaterType)); + global_vector.append(collect_global_variants!(WalletType)); + global_vector.append(collect_global_variants!(BankRedirectType)); + global_vector.append(collect_global_variants!(BankDebitType)); + global_vector.append(collect_global_variants!(CryptoType)); + global_vector.append(collect_global_variants!(RewardType)); + global_vector.append(collect_global_variants!(UpiType)); + global_vector.append(collect_global_variants!(VoucherType)); + global_vector.append(collect_global_variants!(GiftCardType)); + global_vector.append(collect_global_variants!(BankTransferType)); + global_vector.append(collect_global_variants!(CardRedirectType)); + global_vector.push(dir::DirValue::PaymentMethod( + dir::enums::PaymentMethod::Card, + )); + let global_vector = global_vector + .into_iter() + .filter(|global_value| !enabled_pmt.contains(global_value)) + .collect::>(); + + global_vector + .into_iter() + .map(|dir_v| { + builder.make_value_node( + cgraph::NodeValue::Value(dir_v), + Some("Payment Method Type"), + None::<()>, + ) + }) + .collect::>() +} + +fn compile_graph_for_countries_and_currencies( + builder: &mut cgraph::ConstraintGraphBuilder<'_, dir::DirValue>, + config: &kgraph_types::CurrencyCountryFlowFilter, + payment_method_type_node: cgraph::NodeId, +) -> Result { + let mut agg_nodes: Vec<(cgraph::NodeId, cgraph::Relation, cgraph::Strength)> = Vec::new(); + agg_nodes.push(( + payment_method_type_node, + cgraph::Relation::Positive, + cgraph::Strength::Normal, + )); + if let Some(country) = config.country.clone() { + let node_country = country + .into_iter() + .map(|country| dir::DirValue::BillingCountry(api_enums::Country::from_alpha2(country))) + .collect(); + let country_agg = builder + .make_in_aggregator(node_country, Some("Configs for Country"), None::<()>) + .map_err(KgraphError::GraphConstructionError)?; + agg_nodes.push(( + country_agg, + cgraph::Relation::Positive, + cgraph::Strength::Weak, + )) + } + + if let Some(currency) = config.currency.clone() { + let node_currency = currency + .into_iter() + .map(IntoDirValue::into_dir_value) + .collect::, _>>()?; + let currency_agg = builder + .make_in_aggregator(node_currency, Some("Configs for Currency"), None::<()>) + .map_err(KgraphError::GraphConstructionError)?; + agg_nodes.push(( + currency_agg, + cgraph::Relation::Positive, + cgraph::Strength::Normal, + )) + } + if let Some(capture_method) = config + .not_available_flows + .and_then(|naf| naf.capture_method) + { + let make_capture_node = builder.make_value_node( + cgraph::NodeValue::Value(dir::DirValue::CaptureMethod(capture_method)), + Some("Configs for CaptureMethod"), + None::<()>, + ); + agg_nodes.push(( + make_capture_node, + cgraph::Relation::Negative, + cgraph::Strength::Normal, + )) + } + + builder + .make_all_aggregator( + &agg_nodes, + Some("Country & Currency Configs With Payment Method Type"), + None::<()>, + None, + ) + .map_err(KgraphError::GraphConstructionError) +} + +fn compile_config_graph( + builder: &mut cgraph::ConstraintGraphBuilder<'_, dir::DirValue>, + config: &kgraph_types::CountryCurrencyFilter, + connector: &api_enums::RoutableConnectors, +) -> Result { + let mut agg_node_id: Vec<(cgraph::NodeId, cgraph::Relation, cgraph::Strength)> = Vec::new(); + let mut pmt_enabled: Vec = Vec::new(); + if let Some(pmt) = config + .connector_configs + .get(connector) + .or(config.default_configs.as_ref()) + .map(|inner| inner.0.clone()) + { + for pm_filter_key in pmt { + match pm_filter_key { + (kgraph_types::PaymentMethodFilterKey::PaymentMethodType(pm), filter) => { + let dir_val_pm = get_dir_value_payment_method(pm)?; + + let pm_node = if pm == api_enums::PaymentMethodType::Credit + || pm == api_enums::PaymentMethodType::Debit + { + pmt_enabled + .push(dir::DirValue::PaymentMethod(api_enums::PaymentMethod::Card)); + builder.make_value_node( + cgraph::NodeValue::Value(dir::DirValue::PaymentMethod( + dir::enums::PaymentMethod::Card, + )), + Some("PaymentMethod"), + None::<()>, + ) + } else { + pmt_enabled.push(dir_val_pm.clone()); + builder.make_value_node( + cgraph::NodeValue::Value(dir_val_pm), + Some("PaymentMethodType"), + None::<()>, + ) + }; + + let node_config = + compile_graph_for_countries_and_currencies(builder, &filter, pm_node)?; + + agg_node_id.push(( + node_config, + cgraph::Relation::Positive, + cgraph::Strength::Normal, + )); + } + (kgraph_types::PaymentMethodFilterKey::CardNetwork(cn), filter) => { + let dir_val_cn = cn.clone().into_dir_value()?; + pmt_enabled.push(dir_val_cn); + let cn_node = builder.make_value_node( + cn.clone().into_dir_value().map(Into::into)?, + Some("CardNetwork"), + None::<()>, + ); + let node_config = + compile_graph_for_countries_and_currencies(builder, &filter, cn_node)?; + + agg_node_id.push(( + node_config, + cgraph::Relation::Positive, + cgraph::Strength::Normal, + )); + } + } + } + } + let global_vector_pmt: Vec = global_vec_pmt(pmt_enabled, builder); + let any_agg_pmt: Vec<(cgraph::NodeId, cgraph::Relation, cgraph::Strength)> = global_vector_pmt + .into_iter() + .map(|node| (node, cgraph::Relation::Positive, cgraph::Strength::Normal)) + .collect::>(); + let any_agg_node = builder + .make_any_aggregator( + &any_agg_pmt, + Some("Any Aggregator For Payment Method Types"), + None::<()>, + None, + ) + .map_err(KgraphError::GraphConstructionError)?; + + agg_node_id.push(( + any_agg_node, + cgraph::Relation::Positive, + cgraph::Strength::Normal, + )); + + builder + .make_any_aggregator(&agg_node_id, Some("Configs"), None::<()>, None) + .map_err(KgraphError::GraphConstructionError) +} + fn compile_merchant_connector_graph( builder: &mut cgraph::ConstraintGraphBuilder<'_, dir::DirValue>, mca: admin_api::MerchantConnectorResponse, + config: &kgraph_types::CountryCurrencyFilter, ) -> Result<(), KgraphError> { let connector = common_enums::RoutableConnectors::from_str(&mca.connector_name) .map_err(|_| KgraphError::InvalidConnectorName(mca.connector_name.clone()))?; let mut agg_nodes: Vec<(cgraph::NodeId, cgraph::Relation, cgraph::Strength)> = Vec::new(); - if let Some(pms_enabled) = mca.payment_methods_enabled { + if let Some(pms_enabled) = mca.payment_methods_enabled.clone() { for pm_enabled in pms_enabled { let maybe_pm_enabled_id = compile_payment_method_enabled(builder, pm_enabled)?; if let Some(pm_enabled_id) = maybe_pm_enabled_id { @@ -285,10 +612,33 @@ fn compile_merchant_connector_graph( .make_any_aggregator(&agg_nodes, Some(aggregator_info), None::<()>, None) .map_err(KgraphError::GraphConstructionError)?; + let config_info = "Config for respective PaymentMethodType for the connector"; + + let config_enabled_agg_id = compile_config_graph(builder, config, &connector)?; + + let domain_level_node_id = builder + .make_all_aggregator( + &[ + ( + config_enabled_agg_id, + cgraph::Relation::Positive, + cgraph::Strength::Normal, + ), + ( + pms_enabled_agg_id, + cgraph::Relation::Positive, + cgraph::Strength::Normal, + ), + ], + Some(config_info), + None::<()>, + None, + ) + .map_err(KgraphError::GraphConstructionError)?; let connector_dir_val = dir::DirValue::Connector(Box::new(ast::ConnectorChoice { connector, #[cfg(not(feature = "connector_choice_mca_id"))] - sub_label: mca.business_sub_label, + sub_label: mca.business_sub_label.clone(), })); let connector_info = "Connector"; @@ -297,7 +647,7 @@ fn compile_merchant_connector_graph( builder .make_edge( - pms_enabled_agg_id, + domain_level_node_id, connector_node_id, cgraph::Strength::Normal, cgraph::Relation::Positive, @@ -310,6 +660,7 @@ fn compile_merchant_connector_graph( pub fn make_mca_graph<'a>( accts: Vec, + config: &kgraph_types::CountryCurrencyFilter, ) -> Result, KgraphError> { let mut builder = cgraph::ConstraintGraphBuilder::new(); let _domain = builder.make_domain( @@ -317,7 +668,7 @@ pub fn make_mca_graph<'a>( "Payment methods enabled for MerchantConnectorAccount", ); for acct in accts { - compile_merchant_connector_graph(&mut builder, acct)?; + compile_merchant_connector_graph(&mut builder, acct, config)?; } Ok(builder.build()) @@ -327,6 +678,8 @@ pub fn make_mca_graph<'a>( mod tests { #![allow(clippy::expect_used)] + use std::collections::{HashMap, HashSet}; + use api_models::enums as api_enums; use euclid::{ dirval, @@ -335,6 +688,7 @@ mod tests { use hyperswitch_constraint_graph::{ConstraintGraph, CycleCheck, Memoization}; use super::*; + use crate::types as kgraph_types; fn build_test_data<'a>() -> ConstraintGraph<'a, dir::DirValue> { use api_models::{admin::*, payment_methods::*}; @@ -362,7 +716,6 @@ mod tests { api_enums::CardNetwork::Mastercard, ]), accepted_currencies: Some(AcceptedCurrencies::EnableOnly(vec![ - api_enums::Currency::USD, api_enums::Currency::INR, ])), accepted_countries: None, @@ -380,7 +733,6 @@ mod tests { ]), accepted_currencies: Some(AcceptedCurrencies::EnableOnly(vec![ api_enums::Currency::GBP, - api_enums::Currency::PHP, ])), accepted_countries: None, minimum_amount: Some(10), @@ -398,7 +750,46 @@ mod tests { status: api_enums::ConnectorStatus::Inactive, }; - make_mca_graph(vec![stripe_account]).expect("Failed graph construction") + let config_map = kgraph_types::CountryCurrencyFilter { + connector_configs: HashMap::from([( + api_enums::RoutableConnectors::Stripe, + kgraph_types::PaymentMethodFilters(HashMap::from([ + ( + kgraph_types::PaymentMethodFilterKey::PaymentMethodType( + api_enums::PaymentMethodType::Credit, + ), + kgraph_types::CurrencyCountryFlowFilter { + currency: Some(HashSet::from([ + api_enums::Currency::INR, + api_enums::Currency::USD, + ])), + country: Some(HashSet::from([api_enums::CountryAlpha2::IN])), + not_available_flows: Some(kgraph_types::NotAvailableFlows { + capture_method: Some(api_enums::CaptureMethod::Manual), + }), + }, + ), + ( + kgraph_types::PaymentMethodFilterKey::PaymentMethodType( + api_enums::PaymentMethodType::Debit, + ), + kgraph_types::CurrencyCountryFlowFilter { + currency: Some(HashSet::from([ + api_enums::Currency::GBP, + api_enums::Currency::PHP, + ])), + country: Some(HashSet::from([api_enums::CountryAlpha2::IN])), + not_available_flows: Some(kgraph_types::NotAvailableFlows { + capture_method: Some(api_enums::CaptureMethod::Manual), + }), + }, + ), + ])), + )]), + default_configs: None, + }; + + make_mca_graph(vec![stripe_account], &config_map).expect("Failed graph construction") } #[test] @@ -412,8 +803,8 @@ mod tests { dirval!(PaymentMethod = Card), dirval!(CardType = Credit), dirval!(CardNetwork = Visa), - dirval!(PaymentCurrency = USD), - dirval!(PaymentAmount = 100), + dirval!(PaymentCurrency = INR), + dirval!(PaymentAmount = 101), ]), &mut Memoization::new(), &mut CycleCheck::new(), @@ -455,8 +846,8 @@ mod tests { dirval!(Connector = Stripe), dirval!(PaymentMethod = Card), dirval!(CardType = Debit), - dirval!(CardNetwork = DinersClub), - dirval!(PaymentCurrency = GBP), + dirval!(CardNetwork = Maestro), + dirval!(PaymentCurrency = PHP), dirval!(PaymentAmount = 100), ]), &mut Memoization::new(), @@ -711,8 +1102,11 @@ mod tests { let data: Vec = serde_json::from_value(value).expect("data"); - - let graph = make_mca_graph(data).expect("graph"); + let config = kgraph_types::CountryCurrencyFilter { + connector_configs: HashMap::new(), + default_configs: None, + }; + let graph = make_mca_graph(data, &config).expect("graph"); let context = AnalysisContext::from_dir_values([ dirval!(Connector = Stripe), dirval!(PaymentAmount = 212), diff --git a/crates/kgraph_utils/src/types.rs b/crates/kgraph_utils/src/types.rs new file mode 100644 index 000000000000..26f27896e0a5 --- /dev/null +++ b/crates/kgraph_utils/src/types.rs @@ -0,0 +1,35 @@ +use std::collections::{HashMap, HashSet}; + +use api_models::enums as api_enums; +use serde::Deserialize; +#[derive(Debug, Deserialize, Clone, Default)] + +pub struct CountryCurrencyFilter { + pub connector_configs: HashMap, + pub default_configs: Option, +} + +#[derive(Debug, Deserialize, Clone, Default)] +#[serde(transparent)] +pub struct PaymentMethodFilters(pub HashMap); + +#[derive(Debug, Deserialize, Clone, PartialEq, Eq, Hash)] +#[serde(untagged)] +pub enum PaymentMethodFilterKey { + PaymentMethodType(api_enums::PaymentMethodType), + CardNetwork(api_enums::CardNetwork), +} + +#[derive(Debug, Deserialize, Clone, Default)] +#[serde(default)] +pub struct CurrencyCountryFlowFilter { + pub currency: Option>, + pub country: Option>, + pub not_available_flows: Option, +} + +#[derive(Debug, Deserialize, Copy, Clone, Default)] +#[serde(default)] +pub struct NotAvailableFlows { + pub capture_method: Option, +} diff --git a/crates/openapi/Cargo.toml b/crates/openapi/Cargo.toml index 8cf8c156ba0c..b8c55084a505 100644 --- a/crates/openapi/Cargo.toml +++ b/crates/openapi/Cargo.toml @@ -12,3 +12,4 @@ serde_json = "1.0.115" utoipa = { version = "4.2.0", features = ["preserve_order", "preserve_path_order", "time"] } api_models = { version = "0.1.0", path = "../api_models", features = ["frm", "payouts", "openapi"] } +common_utils = {version = "0.1.0", path = "../common_utils"} diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 5f5597387b1e..8830c6899376 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -81,6 +81,7 @@ Never share your secret api keys. Keep them guarded and secure. routes::payments::payments_incremental_authorization, routes::payment_link::payment_link_retrieve, routes::payments::payments_external_authentication, + routes::payments::payments_complete_authorize, // Routes for refunds routes::refunds::refunds_create, @@ -183,6 +184,7 @@ Never share your secret api keys. Keep them guarded and secure. routes::poll::retrieve_poll_status, ), components(schemas( + common_utils::types::MinorUnit, api_models::refunds::RefundRequest, api_models::refunds::RefundType, api_models::refunds::RefundResponse, @@ -402,6 +404,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::CaptureResponse, api_models::payments::PaymentsIncrementalAuthorizationRequest, api_models::payments::IncrementalAuthorizationResponse, + api_models::payments::PaymentsCompleteAuthorizeRequest, api_models::payments::PaymentsExternalAuthenticationRequest, api_models::payments::PaymentsExternalAuthenticationResponse, api_models::payments::SdkInformation, @@ -518,6 +521,11 @@ Never share your secret api keys. Keep them guarded and secure. api_models::webhook_events::OutgoingWebhookRequestContent, api_models::webhook_events::OutgoingWebhookResponseContent, api_models::enums::WebhookDeliveryAttempt, + api_models::enums::PaymentChargeType, + api_models::enums::StripeChargeType, + api_models::payments::PaymentChargeRequest, + api_models::payments::PaymentChargeResponse, + api_models::refunds::ChargeRefunds, )), modifiers(&SecurityAddon) )] diff --git a/crates/openapi/src/routes/payments.rs b/crates/openapi/src/routes/payments.rs index 20d87b91e79f..1273bde7a366 100644 --- a/crates/openapi/src/routes/payments.rs +++ b/crates/openapi/src/routes/payments.rs @@ -486,3 +486,23 @@ pub fn payments_incremental_authorization() {} security(("publishable_key" = [])) )] pub fn payments_external_authentication() {} + +/// Payments - Complete Authorize +/// +/// +#[utoipa::path( + post, + path = "/{payment_id}/complete_authorize", + request_body=PaymentsCompleteAuthorizeRequest, + params( + ("payment_id" =String, Path, description = "The identifier for payment") + ), + responses( + (status = 200, description = "Payments Complete Authorize Success", body = PaymentsResponse), + (status = 400, description = "Missing mandatory fields") + ), + tag = "Payments", + operation_id = "Complete Authorize a Payment", + security(("publishable_key" = [])) +)] +pub fn payments_complete_authorize() {} diff --git a/crates/redis_interface/Cargo.toml b/crates/redis_interface/Cargo.toml index 1fb74be79d9b..d55ff86bceaf 100644 --- a/crates/redis_interface/Cargo.toml +++ b/crates/redis_interface/Cargo.toml @@ -15,10 +15,10 @@ serde = { version = "1.0.197", features = ["derive"] } thiserror = "1.0.58" tokio = "1.37.0" tokio-stream = {version = "0.1.15", features = ["sync"]} +tracing = { workspace = true } # First party crates common_utils = { version = "0.1.0", path = "../common_utils", features = ["async_ext"] } -router_env = { version = "0.1.0", path = "../router_env", features = ["log_extra_implicit_fields", "log_custom_entries_to_extra"] } [dev-dependencies] tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread"] } diff --git a/crates/redis_interface/src/commands.rs b/crates/redis_interface/src/commands.rs index 46e3a35fd334..504820822c08 100644 --- a/crates/redis_interface/src/commands.rs +++ b/crates/redis_interface/src/commands.rs @@ -23,7 +23,7 @@ use fred::{ }, }; use futures::StreamExt; -use router_env::{instrument, logger, tracing}; +use tracing::instrument; use crate::{ errors, @@ -379,7 +379,7 @@ impl super::RedisConnectionPool { Some(futures::stream::iter(v)) } Err(err) => { - logger::error!(?err); + tracing::error!(?err); None } } diff --git a/crates/redis_interface/src/lib.rs b/crates/redis_interface/src/lib.rs index 0ab1ea394c9a..df74d728331f 100644 --- a/crates/redis_interface/src/lib.rs +++ b/crates/redis_interface/src/lib.rs @@ -26,7 +26,6 @@ use common_utils::errors::CustomResult; use error_stack::ResultExt; pub use fred::interfaces::PubsubInterface; use fred::{interfaces::ClientLike, prelude::EventInterface}; -use router_env::logger; pub use self::types::*; @@ -189,10 +188,10 @@ impl RedisConnectionPool { let mut error_rx = futures::stream::select_all(error_rxs); loop { if let Some(Ok(error)) = error_rx.next().await { - logger::error!(?error, "Redis protocol or connection error"); + tracing::error!(?error, "Redis protocol or connection error"); if self.pool.state() == fred::types::ClientState::Disconnected { if tx.send(()).is_err() { - logger::error!("The redis shutdown signal sender failed to signal"); + tracing::error!("The redis shutdown signal sender failed to signal"); } self.is_redis_available .store(false, atomic::Ordering::SeqCst); @@ -205,7 +204,7 @@ impl RedisConnectionPool { pub async fn on_unresponsive(&self) { let _ = self.pool.clients().iter().map(|client| { client.on_unresponsive(|server| { - logger::warn!(redis_server =?server.host, "Redis server is unresponsive"); + tracing::warn!(redis_server =?server.host, "Redis server is unresponsive"); Ok(()) }) }); diff --git a/crates/router/src/compatibility/stripe/errors.rs b/crates/router/src/compatibility/stripe/errors.rs index bca1cbb64b87..528381061cf5 100644 --- a/crates/router/src/compatibility/stripe/errors.rs +++ b/crates/router/src/compatibility/stripe/errors.rs @@ -1,7 +1,7 @@ -#![allow(unused_variables)] use common_utils::errors::ErrorSwitch; +use hyperswitch_domain_models::errors::api_error_response as errors; -use crate::core::errors::{self, CustomersErrorResponse}; +use crate::core::errors::CustomersErrorResponse; #[derive(Debug, router_derive::ApiError, Clone)] #[error(error_type_enum = StripeErrorType)] @@ -481,11 +481,11 @@ impl From for StripeErrorCode { Self::PaymentIntentPaymentAttemptFailed { data } } errors::ApiErrorResponse::DisputeFailed { data } => Self::DisputeFailed { data }, - errors::ApiErrorResponse::InvalidCardData { data } => Self::InvalidCardType, // Maybe it is better to de generalize this router error - errors::ApiErrorResponse::CardExpired { data } => Self::ExpiredCard, - errors::ApiErrorResponse::RefundNotPossible { connector } => Self::RefundFailed, - errors::ApiErrorResponse::RefundFailed { data } => Self::RefundFailed, // Nothing at stripe to map - errors::ApiErrorResponse::PayoutFailed { data } => Self::PayoutFailed, + errors::ApiErrorResponse::InvalidCardData { data: _ } => Self::InvalidCardType, // Maybe it is better to de generalize this router error + errors::ApiErrorResponse::CardExpired { data: _ } => Self::ExpiredCard, + errors::ApiErrorResponse::RefundNotPossible { connector: _ } => Self::RefundFailed, + errors::ApiErrorResponse::RefundFailed { data: _ } => Self::RefundFailed, // Nothing at stripe to map + errors::ApiErrorResponse::PayoutFailed { data: _ } => Self::PayoutFailed, errors::ApiErrorResponse::MandateUpdateFailed | errors::ApiErrorResponse::MandateSerializationFailed @@ -605,7 +605,7 @@ impl From for StripeErrorCode { object: "poll".to_owned(), id, }, - errors::ApiErrorResponse::DisputeStatusValidationFailed { reason } => { + errors::ApiErrorResponse::DisputeStatusValidationFailed { reason: _ } => { Self::InternalServerError } errors::ApiErrorResponse::FileValidationFailed { .. } => Self::FileValidationFailed, diff --git a/crates/router/src/compatibility/stripe/payment_intents/types.rs b/crates/router/src/compatibility/stripe/payment_intents/types.rs index 04bb99ef75c1..d615acff1caf 100644 --- a/crates/router/src/compatibility/stripe/payment_intents/types.rs +++ b/crates/router/src/compatibility/stripe/payment_intents/types.rs @@ -6,6 +6,7 @@ use common_utils::{ date_time, ext_traits::StringExt, pii::{IpAddress, SecretSerdeValue, UpiVpaMaskingStrategy}, + types::MinorUnit, }; use error_stack::ResultExt; use serde::{Deserialize, Serialize}; @@ -312,9 +313,11 @@ impl TryFrom for payments::PaymentsRequest { expected_format: "127.0.0.1".to_string(), })?; + let amount = item.amount.map(|amount| MinorUnit::new(amount).into()); + let request = Ok(Self { payment_id: item.id.map(payments::PaymentIdType::PaymentIntentId), - amount: item.amount.map(|amount| amount.into()), + amount, currency: item .currency .as_ref() @@ -324,7 +327,7 @@ impl TryFrom for payments::PaymentsRequest { field_name: "currency", })?, capture_method: item.capture_method, - amount_to_capture: item.amount_capturable, + amount_to_capture: item.amount_capturable.map(MinorUnit::new), confirm: item.confirm, customer_id: item.customer, email: item.receipt_email, @@ -508,9 +511,9 @@ impl From for StripePaymentIntentResponse { object: "payment_intent", id: resp.payment_id, status: StripePaymentStatus::from(resp.status), - amount: resp.amount, - amount_capturable: resp.amount_capturable, - amount_received: resp.amount_received, + amount: resp.amount.get_amount_as_i64(), + amount_capturable: resp.amount_capturable.map(|amt| amt.get_amount_as_i64()), + amount_received: resp.amount_received.map(|amt| amt.get_amount_as_i64()), connector: resp.connector, client_secret: resp.client_secret, created: resp.created.map(|t| t.assume_utc().unix_timestamp()), @@ -729,7 +732,7 @@ impl ForeignTryFrom<(Option, Option)> for Option match item { StripeMandateType::SingleUse => Some(payments::MandateType::SingleUse( payments::MandateAmountData { - amount: mandate.amount.unwrap_or_default(), + amount: MinorUnit::new(mandate.amount.unwrap_or_default()), currency, start_date: mandate.start_date, end_date: mandate.end_date, @@ -738,7 +741,7 @@ impl ForeignTryFrom<(Option, Option)> for Option Some(payments::MandateType::MultiUse(Some( payments::MandateAmountData { - amount: mandate.amount.unwrap_or_default(), + amount: MinorUnit::new(mandate.amount.unwrap_or_default()), currency, start_date: mandate.start_date, end_date: mandate.end_date, @@ -748,7 +751,7 @@ impl ForeignTryFrom<(Option, Option)> for Option Some(payments::MandateType::MultiUse(Some( payments::MandateAmountData { - amount: mandate.amount.unwrap_or_default(), + amount: MinorUnit::new(mandate.amount.unwrap_or_default()), currency, start_date: mandate.start_date, end_date: mandate.end_date, diff --git a/crates/router/src/configs/defaults.rs b/crates/router/src/configs/defaults.rs index 667bc90fe45f..1fe86a209e3f 100644 --- a/crates/router/src/configs/defaults.rs +++ b/crates/router/src/configs/defaults.rs @@ -123,7 +123,10 @@ impl Default for super::settings::DrainerSettings { #[cfg(feature = "kv_store")] impl Default for super::settings::KvConfig { fn default() -> Self { - Self { ttl: 900 } + Self { + ttl: 900, + soft_kill: Some(false), + } } } @@ -5669,23 +5672,32 @@ impl Default for super::settings::RequiredFields { non_mandate: HashMap::new(), common: HashMap::from([ ( - "payment_method_data.bank_redirect.bancontact_card.billing_details.email".to_string(), + "billing.email".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.bancontact_card.billing_details.email".to_string(), + required_field: "payment_method_data.billing.email".to_string(), display_name: "email".to_string(), field_type: enums::FieldType::UserEmailAddress, value: None, } ), ( - "payment_method_data.bank_redirect.bancontact_card.billing_details.billing_name".to_string(), + "billing.address.first_name".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.bancontact_card.billing_details.billing_name".to_string(), + required_field: "payment_method_data.billing.address.first_name".to_string(), display_name: "billing_name".to_string(), - field_type: enums::FieldType::UserBillingName, + 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: "billing_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), ]), } ), @@ -5723,14 +5735,23 @@ impl Default for super::settings::RequiredFields { } ), ( - "payment_method_data.bank_redirect.bancontact_card.card_holder_name".to_string(), + "billing.address.first_name".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.bancontact_card.card_holder_name".to_string(), + 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, + } + ), ]), } ) @@ -5747,9 +5768,9 @@ impl Default for super::settings::RequiredFields { mandate: HashMap::new(), non_mandate: HashMap::from([ ( - "payment_method_data.bank_redirect.giropay.country".to_string(), + "billing.address.country".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.giropay.country".to_string(), + required_field: "payment_method_data.billing.address.country".to_string(), display_name: "country".to_string(), field_type: enums::FieldType::UserCountry { options: vec![ @@ -5853,9 +5874,10 @@ impl Default for super::settings::RequiredFields { RequiredFieldFinal { mandate: HashMap::new(), non_mandate: HashMap::from([ - ("payment_method_data.bank_redirect.giropay.country".to_string(), + ( + "billing.address.country".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.giropay.country".to_string(), + required_field: "payment_method_data.billing.address.country".to_string(), display_name: "country".to_string(), field_type: enums::FieldType::UserCountry { options: vec![ @@ -5866,14 +5888,23 @@ impl Default for super::settings::RequiredFields { } ), ( - "payment_method_data.bank_redirect.giropay.billing_details.billing_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.giropay.billing_details.billing_name".to_string(), - display_name: "billing_name".to_string(), - field_type: enums::FieldType::UserBillingName, - 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, + } + ) ]), common: HashMap::new(), } @@ -5883,14 +5914,24 @@ impl Default for super::settings::RequiredFields { RequiredFieldFinal { mandate: HashMap::new(), non_mandate: HashMap::from([ - ("payment_method_data.bank_redirect.giropay.billing_details.billing_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.giropay.billing_details.billing_name".to_string(), - display_name: "billing_name".to_string(), - field_type: enums::FieldType::UserBillingName, - 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, + } + ) ]), common: HashMap::new(), } @@ -5983,9 +6024,9 @@ impl Default for super::settings::RequiredFields { } ), ( - "payment_method_data.bank_redirect.ideal.country".to_string(), + "billing.address.country".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.ideal.country".to_string(), + required_field: "payment_method_data.billing.address.country".to_string(), display_name: "country".to_string(), field_type: enums::FieldType::UserCountry { options: vec![ @@ -6097,9 +6138,9 @@ impl Default for super::settings::RequiredFields { mandate: HashMap::new(), non_mandate: HashMap::from([ ( - "payment_method_data.bank_redirect.ideal.country".to_string(), + "billing.address.country".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.ideal.country".to_string(), + required_field: "payment_method_data.billing.address.country".to_string(), display_name: "country".to_string(), field_type: enums::FieldType::UserCountry{ options: vec![ @@ -6118,19 +6159,28 @@ impl Default for super::settings::RequiredFields { RequiredFieldFinal { mandate: HashMap::new(), non_mandate: HashMap::from([ - ( - "payment_method_data.bank_redirect.ideal.billing_details.billing_name".to_string(), + ( + "billing.address.first_name".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.ideal.billing_details.billing_name".to_string(), + required_field: "payment_method_data.billing.address.first_name".to_string(), display_name: "billing_name".to_string(), - field_type: enums::FieldType::UserFullName, + field_type: enums::FieldType::UserBillingName, value: None, } ), ( - "payment_method_data.bank_redirect.ideal.country".to_string(), + "billing.address.last_name".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.ideal.country".to_string(), + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "billing_name".to_string(), + field_type: enums::FieldType::UserBillingName, + 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::UserCountry{ options: vec![ @@ -6149,18 +6199,27 @@ impl Default for super::settings::RequiredFields { RequiredFieldFinal { mandate: HashMap::from([ ( - "payment_method_data.bank_redirect.ideal.billing_details.billing_name".to_string(), + "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::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.ideal.billing_details.billing_name".to_string(), + required_field: "payment_method_data.billing.address.last_name".to_string(), display_name: "billing_name".to_string(), field_type: enums::FieldType::UserFullName, value: None, } ), ( - "payment_method_data.bank_redirect.ideal.billing_details.email".to_string(), + "billing.email".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.ideal.billing_details.email".to_string(), + required_field: "payment_method_data.billing.email".to_string(), display_name: "billing_email".to_string(), field_type: enums::FieldType::UserEmailAddress, value: None, @@ -6241,9 +6300,9 @@ impl Default for super::settings::RequiredFields { RequiredFieldFinal { mandate: HashMap::new(), non_mandate: HashMap::from([ - ("payment_method_data.bank_redirect.sofort.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.sofort.country".to_string(), + ("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::UserCountry { options: vec![ @@ -6381,9 +6440,9 @@ impl Default for super::settings::RequiredFields { RequiredFieldFinal { mandate: HashMap::new(), non_mandate: HashMap::from([ - ("payment_method_data.bank_redirect.sofort.country".to_string(), + ( "billing.address.country".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.sofort.country".to_string(), + required_field: "payment_method_data.billing.address.country".to_string(), display_name: "country".to_string(), field_type: enums::FieldType::UserCountry { options: vec![ @@ -6398,15 +6457,24 @@ impl Default for super::settings::RequiredFields { value: None, } ), - ( - "payment_method_data.bank_redirect.sofort.billing_details.billing_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.sofort.billing_details.billing_name".to_string(), - display_name: "billing_name".to_string(), - field_type: enums::FieldType::UserBillingName, - 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, + } + ) ]), common: HashMap::new(), } @@ -6424,18 +6492,27 @@ impl Default for super::settings::RequiredFields { RequiredFieldFinal { mandate: HashMap::from([ ( - "payment_method_data.bank_redirect.sofort.billing_details.email".to_string(), + "billing.email".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.sofort.billing_details.email".to_string(), + required_field: "payment_method_data.billing.email".to_string(), display_name: "email".to_string(), field_type: enums::FieldType::UserEmailAddress, value: None, } ), ( - "payment_method_data.bank_redirect.sofort.billing_details.billing_name".to_string(), + "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.bank_redirect.sofort.billing_details.billing_name".to_string(), + required_field: "payment_method_data.billing.address.last_name".to_string(), display_name: "billing_name".to_string(), field_type: enums::FieldType::UserBillingName, value: None, @@ -6443,9 +6520,9 @@ impl Default for super::settings::RequiredFields { ) ]), non_mandate : HashMap::from([ - ("payment_method_data.bank_redirect.sofort.country".to_string(), + ("billing.address.country".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.sofort.country".to_string(), + required_field: "payment_method_data.billing.address.country".to_string(), display_name: "country".to_string(), field_type: enums::FieldType::UserCountry { options: vec![ @@ -6564,14 +6641,23 @@ impl Default for super::settings::RequiredFields { mandate: HashMap::new(), non_mandate: HashMap::from([ ( - "payment_method_data.bank_redirect.eps.billing_details.billing_name".to_string(), + "billing.address.first_name".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.eps.billing_details.billing_name".to_string(), + required_field: "payment_method_data.billing.address.first_name".to_string(), display_name: "billing_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: "billing_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), ]), common: HashMap::new(), } @@ -6582,9 +6668,9 @@ impl Default for super::settings::RequiredFields { mandate: HashMap::new(), non_mandate: HashMap::from([ ( - "payment_method_data.bank_redirect.eps.country".to_string(), + "billing.address.country".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.eps.country".to_string(), + required_field: "payment_method_data.billing.address.country".to_string(), display_name: "bank_account_country".to_string(), field_type: enums::FieldType::UserCountry { options: vec![ @@ -6634,18 +6720,27 @@ impl Default for super::settings::RequiredFields { mandate: HashMap::new(), non_mandate: HashMap::from([ ( - "payment_method_data.bank_redirect.eps.billing_details.billing_name".to_string(), + "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::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.eps.billing_details.billing_name".to_string(), + required_field: "payment_method_data.billing.address.last_name".to_string(), display_name: "billing_name".to_string(), field_type: enums::FieldType::UserFullName, value: None, } ), ( - "payment_method_data.bank_redirect.eps.country".to_string(), + "billing.address.country".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.eps.country".to_string(), + required_field: "payment_method_data.billing.address.country".to_string(), display_name: "bank_account_country".to_string(), field_type: enums::FieldType::UserCountry { options: vec![ diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index 2dfae47bff13..b8a315f944b8 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -18,7 +18,7 @@ use external_services::{ }, }; use hyperswitch_interfaces::secrets_interface::secret_state::{ - SecretState, SecretStateContainer, SecuredSecret, + RawSecret, SecretState, SecretStateContainer, SecuredSecret, }; use masking::Secret; use redis_interface::RedisSettings; @@ -138,6 +138,7 @@ pub struct Frm { #[derive(Debug, Deserialize, Clone)] pub struct KvConfig { pub ttl: u32, + pub soft_kill: Option, } #[derive(Debug, Deserialize, Clone, Default)] @@ -394,6 +395,7 @@ pub struct Secrets { #[derive(Debug, Clone, Default, Deserialize)] pub struct UserSettings { pub password_validity_in_days: u16, + pub two_factor_auth_expiry_in_secs: i64, } #[derive(Debug, Deserialize, Clone)] @@ -759,6 +761,18 @@ impl Settings { } } +impl Settings { + #[cfg(feature = "kv_store")] + pub fn is_kv_soft_kill_mode(&self) -> bool { + self.kv_config.soft_kill.unwrap_or(false) + } + + #[cfg(not(feature = "kv_store"))] + pub fn is_kv_soft_kill_mode(&self) -> bool { + false + } +} + #[cfg(feature = "payouts")] #[derive(Debug, Deserialize, Clone, Default)] pub struct Payouts { diff --git a/crates/router/src/connector/aci/transformers.rs b/crates/router/src/connector/aci/transformers.rs index 9353b848bdb8..8415453b6ae5 100644 --- a/crates/router/src/connector/aci/transformers.rs +++ b/crates/router/src/connector/aci/transformers.rs @@ -163,14 +163,10 @@ impl ) -> Result { let (item, bank_redirect_data) = value; let payment_data = match bank_redirect_data { - domain::BankRedirectData::Eps { country, .. } => { + domain::BankRedirectData::Eps { .. } => { Self::BankRedirect(Box::new(BankRedirectionPMData { payment_brand: PaymentBrand::Eps, - bank_account_country: Some(country.ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "eps.country", - }, - )?), + bank_account_country: Some(item.router_data.get_billing_country()?), bank_account_bank_name: None, bank_account_bic: None, bank_account_iban: None, @@ -183,15 +179,10 @@ impl domain::BankRedirectData::Giropay { bank_account_bic, bank_account_iban, - country, .. } => Self::BankRedirect(Box::new(BankRedirectionPMData { payment_brand: PaymentBrand::Giropay, - bank_account_country: Some(country.ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "giropay.country", - }, - )?), + bank_account_country: Some(item.router_data.get_billing_country()?), bank_account_bank_name: None, bank_account_bic: bank_account_bic.clone(), bank_account_iban: bank_account_iban.clone(), @@ -200,35 +191,27 @@ impl merchant_transaction_id: None, customer_email: None, })), - domain::BankRedirectData::Ideal { - bank_name, country, .. - } => Self::BankRedirect(Box::new(BankRedirectionPMData { - payment_brand: PaymentBrand::Ideal, - bank_account_country: Some(country.ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "ideal.country", - }, - )?), - bank_account_bank_name: Some(bank_name.ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "ideal.bank_name", - }, - )?), - bank_account_bic: None, - bank_account_iban: None, - billing_country: None, - merchant_customer_id: None, - merchant_transaction_id: None, - customer_email: None, - })), - domain::BankRedirectData::Sofort { country, .. } => { + domain::BankRedirectData::Ideal { bank_name, .. } => { Self::BankRedirect(Box::new(BankRedirectionPMData { - payment_brand: PaymentBrand::Sofortueberweisung, - bank_account_country: Some(country.to_owned().ok_or( + payment_brand: PaymentBrand::Ideal, + bank_account_country: Some(item.router_data.get_billing_country()?), + bank_account_bank_name: Some(bank_name.ok_or( errors::ConnectorError::MissingRequiredField { - field_name: "sofort.country", + field_name: "ideal.bank_name", }, )?), + bank_account_bic: None, + bank_account_iban: None, + billing_country: None, + merchant_customer_id: None, + merchant_transaction_id: None, + customer_email: None, + })) + } + domain::BankRedirectData::Sofort { .. } => { + Self::BankRedirect(Box::new(BankRedirectionPMData { + payment_brand: PaymentBrand::Sofortueberweisung, + bank_account_country: Some(item.router_data.get_billing_country()?), bank_account_bank_name: None, bank_account_bic: None, bank_account_iban: None, @@ -238,40 +221,40 @@ impl customer_email: None, })) } - domain::BankRedirectData::Przelewy24 { - billing_details, .. - } => Self::BankRedirect(Box::new(BankRedirectionPMData { - payment_brand: PaymentBrand::Przelewy, - bank_account_country: None, - bank_account_bank_name: None, - bank_account_bic: None, - bank_account_iban: None, - billing_country: None, - merchant_customer_id: None, - merchant_transaction_id: None, - customer_email: billing_details.email.to_owned(), - })), - domain::BankRedirectData::Interac { email, country } => { + domain::BankRedirectData::Przelewy24 { .. } => { + Self::BankRedirect(Box::new(BankRedirectionPMData { + payment_brand: PaymentBrand::Przelewy, + bank_account_country: None, + bank_account_bank_name: None, + bank_account_bic: None, + bank_account_iban: None, + billing_country: None, + merchant_customer_id: None, + merchant_transaction_id: None, + customer_email: Some(item.router_data.get_billing_email()?), + })) + } + domain::BankRedirectData::Interac {} => { Self::BankRedirect(Box::new(BankRedirectionPMData { payment_brand: PaymentBrand::InteracOnline, - bank_account_country: Some(country.to_owned()), + bank_account_country: Some(item.router_data.get_billing_country()?), bank_account_bank_name: None, bank_account_bic: None, bank_account_iban: None, billing_country: None, merchant_customer_id: None, merchant_transaction_id: None, - customer_email: Some(email.to_owned()), + customer_email: Some(item.router_data.get_billing_email()?), })) } - domain::BankRedirectData::Trustly { country } => { + domain::BankRedirectData::Trustly {} => { Self::BankRedirect(Box::new(BankRedirectionPMData { payment_brand: PaymentBrand::Trustly, bank_account_country: None, bank_account_bank_name: None, bank_account_bic: None, bank_account_iban: None, - billing_country: Some(country.to_owned()), + billing_country: Some(item.router_data.get_billing_country()?), merchant_customer_id: Some(Secret::new(item.router_data.get_customer_id()?)), merchant_transaction_id: Some(Secret::new( item.router_data.connector_request_reference_id.clone(), @@ -788,6 +771,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.id), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/adyen.rs b/crates/router/src/connector/adyen.rs index 3c0f55d71ca0..ee6ce95ba324 100644 --- a/crates/router/src/connector/adyen.rs +++ b/crates/router/src/connector/adyen.rs @@ -30,7 +30,7 @@ use crate::{ self, api::{self, ConnectorCommon}, domain, - transformers::ForeignFrom, + transformers::{ForeignFrom, ForeignTryFrom}, }, utils::{crypto, ByteSliceExt, BytesExt, OptionExt}, }; @@ -231,7 +231,7 @@ impl ConnectorValidation for Adyen { fn validate_mandate_payment( &self, pm_type: Option, - pm_data: types::domain::payments::PaymentMethodData, + pm_data: domain::payments::PaymentMethodData, ) -> CustomResult<(), errors::ConnectorError> { let mandate_supported_pmd = std::collections::HashSet::from([ PaymentMethodDataType::Card, @@ -364,7 +364,7 @@ impl req: &types::SetupMandateRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - let authorize_req = types::PaymentsAuthorizeRouterData::from(( + let authorize_req = types::PaymentsAuthorizeRouterData::foreign_from(( req, types::PaymentsAuthorizeData::from(req), )); @@ -419,7 +419,7 @@ impl .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(( + types::RouterData::foreign_try_from(( types::ResponseRouterData { response, data: data.clone(), @@ -703,7 +703,7 @@ impl types::SyncRequestType::MultipleCaptureSync(_) => true, types::SyncRequestType::SinglePaymentSync => false, }; - types::RouterData::try_from(( + types::RouterData::foreign_try_from(( types::ResponseRouterData { response, data: data.clone(), @@ -830,7 +830,7 @@ impl .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(( + types::RouterData::foreign_try_from(( types::ResponseRouterData { response, data: data.clone(), diff --git a/crates/router/src/connector/adyen/transformers.rs b/crates/router/src/connector/adyen/transformers.rs index 82468f06a641..f8f11e9286cc 100644 --- a/crates/router/src/connector/adyen/transformers.rs +++ b/crates/router/src/connector/adyen/transformers.rs @@ -2242,17 +2242,26 @@ impl<'a> } } -impl<'a> TryFrom<(&domain::BankRedirectData, Option)> for AdyenPaymentMethod<'a> { +impl<'a> + TryFrom<( + &domain::BankRedirectData, + Option, + &types::PaymentsAuthorizeRouterData, + )> for AdyenPaymentMethod<'a> +{ type Error = Error; fn try_from( - (bank_redirect_data, test_mode): (&domain::BankRedirectData, Option), + (bank_redirect_data, test_mode, item): ( + &domain::BankRedirectData, + Option, + &types::PaymentsAuthorizeRouterData, + ), ) -> Result { match bank_redirect_data { domain::BankRedirectData::BancontactCard { card_number, card_exp_month, card_exp_year, - card_holder_name, .. } => Ok(AdyenPaymentMethod::BancontactCard(Box::new( BancontactCardData { @@ -2276,12 +2285,7 @@ impl<'a> TryFrom<(&domain::BankRedirectData, Option)> for AdyenPaymentMeth field_name: "bancontact_card.card_exp_year", })? .clone(), - holder_name: card_holder_name - .as_ref() - .ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "bancontact_card.card_holder_name", - })? - .clone(), + holder_name: item.get_billing_full_name()?, }, ))), domain::BankRedirectData::Bizum { .. } => { @@ -2886,8 +2890,11 @@ impl<'a> let browser_info = get_browser_info(item.router_data)?; let additional_data = get_additional_data(item.router_data); let return_url = item.router_data.request.get_return_url()?; - let payment_method = - AdyenPaymentMethod::try_from((bank_redirect_data, item.router_data.test_mode))?; + let payment_method = AdyenPaymentMethod::try_from(( + bank_redirect_data, + item.router_data.test_mode, + item.router_data, + ))?; let (shopper_locale, country) = get_redirect_extra_details(item.router_data)?; let line_items = Some(get_line_items(item)); @@ -2922,18 +2929,18 @@ impl<'a> fn get_redirect_extra_details( item: &types::PaymentsAuthorizeRouterData, -) -> Result<(Option, Option), errors::ConnectorError> { +) -> errors::CustomResult<(Option, Option), errors::ConnectorError> +{ match item.request.payment_method_data { domain::PaymentMethodData::BankRedirect(ref redirect_data) => match redirect_data { domain::BankRedirectData::Sofort { - country, - preferred_language, - .. - } => Ok((preferred_language.clone(), *country)), - domain::BankRedirectData::OpenBankingUk { country, .. } => { - let country = country.ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "country", - })?; + preferred_language, .. + } => { + let country = item.get_billing_country()?; + Ok((preferred_language.clone(), Some(country))) + } + domain::BankRedirectData::OpenBankingUk { .. } => { + let country = item.get_billing_country()?; Ok((None, Some(country))) } _ => Ok((None, None)), @@ -3178,6 +3185,7 @@ impl TryFrom> network_txn_id: None, connector_response_reference_id: Some(item.response.reference), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -3212,6 +3220,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), payment_method_balance: Some(types::PaymentMethodBalance { amount: item.response.balance.value, @@ -3276,6 +3285,7 @@ pub fn get_adyen_response( network_txn_id, connector_response_reference_id: Some(response.merchant_reference), incremental_authorization_allowed: None, + charge_id: None, }; Ok((status, error, payments_response_data)) } @@ -3338,6 +3348,7 @@ pub fn get_webhook_response( network_txn_id: None, connector_response_reference_id: Some(response.merchant_reference_id), incremental_authorization_allowed: None, + charge_id: None, }; Ok((status, error, payments_response_data)) } @@ -3410,6 +3421,7 @@ pub fn get_redirection_response( .clone() .or(response.psp_reference), incremental_authorization_allowed: None, + charge_id: None, }; Ok((status, error, payments_response_data)) } @@ -3467,6 +3479,7 @@ pub fn get_present_to_shopper_response( .clone() .or(response.psp_reference), incremental_authorization_allowed: None, + charge_id: None, }; Ok((status, error, payments_response_data)) } @@ -3523,6 +3536,7 @@ pub fn get_qr_code_response( .clone() .or(response.psp_reference), incremental_authorization_allowed: None, + charge_id: None, }; Ok((status, error, payments_response_data)) } @@ -3559,6 +3573,7 @@ pub fn get_redirection_error_response( network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }; Ok((status, error, payments_response_data)) @@ -3808,7 +3823,7 @@ pub fn get_present_to_shopper_metadata( } impl - TryFrom<( + ForeignTryFrom<( types::ResponseRouterData, Option, bool, @@ -3816,7 +3831,7 @@ impl )> for types::RouterData { type Error = Error; - fn try_from( + fn foreign_try_from( (item, capture_method, is_multiple_capture_psync_flow, pmt): ( types::ResponseRouterData, Option, @@ -3925,6 +3940,7 @@ impl TryFrom> network_txn_id: None, connector_response_reference_id: Some(item.response.reference), incremental_authorization_allowed: None, + charge_id: None, }), amount_captured: Some(0), ..item.data diff --git a/crates/router/src/connector/airwallex.rs b/crates/router/src/connector/airwallex.rs index bdd842bf958c..ca511534b974 100644 --- a/crates/router/src/connector/airwallex.rs +++ b/crates/router/src/connector/airwallex.rs @@ -1,5 +1,4 @@ pub mod transformers; - use std::fmt::Debug; use common_utils::{ @@ -28,6 +27,7 @@ use crate::{ types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, + transformers::ForeignFrom, ErrorResponse, Response, RouterData, }, utils::{crypto, BytesExt}, @@ -349,7 +349,7 @@ impl ConnectorIntegration = Box::new(&Self); - let authorize_data = &types::PaymentsInitRouterData::from(( + let authorize_data = &types::PaymentsInitRouterData::foreign_from(( &router_data.to_owned(), router_data.request.clone(), )); diff --git a/crates/router/src/connector/airwallex/transformers.rs b/crates/router/src/connector/airwallex/transformers.rs index fe0cd021ebcb..587a14caedfd 100644 --- a/crates/router/src/connector/airwallex/transformers.rs +++ b/crates/router/src/connector/airwallex/transformers.rs @@ -557,6 +557,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -599,6 +600,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/authorizedotnet.rs b/crates/router/src/connector/authorizedotnet.rs index 2ca78e630fa3..a9e000b82b99 100644 --- a/crates/router/src/connector/authorizedotnet.rs +++ b/crates/router/src/connector/authorizedotnet.rs @@ -1,5 +1,4 @@ pub mod transformers; - use std::fmt::Debug; use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; @@ -24,6 +23,7 @@ use crate::{ types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt, PaymentsCompleteAuthorize}, + transformers::ForeignTryFrom, }, utils::BytesExt, }; @@ -121,20 +121,84 @@ impl types::PaymentsResponseData, > for Authorizedotnet { - // Issue: #173 - fn build_request( + fn get_headers( &self, - _req: &types::RouterData< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - >, + req: &types::SetupMandateRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + // This connector does not require an auth header, the authentication details are sent in the request body + self.build_headers(req, connectors) + } + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + fn get_url( + &self, + _req: &types::SetupMandateRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(self.base_url(connectors).to_string()) + } + fn get_request_body( + &self, + req: &types::SetupMandateRouterData, _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - Err(errors::ConnectorError::NotImplemented( - "Setup Mandate flow for Authorizedotnet".to_string(), - ) - .into()) + ) -> CustomResult { + let connector_req = authorizedotnet::CreateCustomerProfileRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &types::SetupMandateRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::SetupMandateType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::SetupMandateType::get_headers(self, req, connectors)?) + .set_body(types::SetupMandateType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::SetupMandateRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: types::Response, + ) -> CustomResult { + use bytes::Buf; + + // Handle the case where response bytes contains U+FEFF (BOM) character sent by connector + let encoding = encoding_rs::UTF_8; + let intermediate_response = encoding.decode_with_bom_removal(res.response.chunk()); + let intermediate_response = + bytes::Bytes::copy_from_slice(intermediate_response.0.as_bytes()); + let response: authorizedotnet::AuthorizedotnetSetupMandateResponse = intermediate_response + .parse_struct("AuthorizedotnetPaymentsResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: types::Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + get_error_response(res, event_builder) } } @@ -218,7 +282,7 @@ impl ConnectorIntegration, -) -> Result< - ( - PaymentDetails, - Option, - Option, - ), - error_stack::Report, -> { - match item - .router_data - .request - .mandate_id - .to_owned() - .and_then(|mandate_ids| mandate_ids.mandate_reference_id) - { - Some(api_models::payments::MandateReferenceId::NetworkMandateId(network_trans_id)) => { - let processing_options = Some(ProcessingOptions { - is_subsequent_auth: true, - }); - let subseuent_auth_info = Some(SubsequentAuthInformation { - original_network_trans_id: Secret::new(network_trans_id), - reason: Reason::Resubmission, - }); - match item.router_data.request.payment_method_data { - domain::PaymentMethodData::Card(ref ccard) => { - let payment_details = PaymentDetails::CreditCard(CreditCardDetails { - card_number: (*ccard.card_number).clone(), - expiration_date: ccard.get_expiry_date_as_yyyymm("-"), - card_code: None, - }); - Ok((payment_details, processing_options, subseuent_auth_info)) - } - domain::PaymentMethodData::CardRedirect(_) - | domain::PaymentMethodData::Wallet(_) - | domain::PaymentMethodData::PayLater(_) - | domain::PaymentMethodData::BankRedirect(_) - | domain::PaymentMethodData::BankDebit(_) - | domain::PaymentMethodData::BankTransfer(_) - | domain::PaymentMethodData::Crypto(_) - | domain::PaymentMethodData::MandatePayment - | domain::PaymentMethodData::Reward - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::CardToken(_) => { - Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("authorizedotnet"), - ))? - } - } - } - Some(api_models::payments::MandateReferenceId::ConnectorMandateId(_)) | None => { - match item.router_data.request.payment_method_data { - domain::PaymentMethodData::Card(ref ccard) => { - Ok(( - PaymentDetails::CreditCard(CreditCardDetails { - card_number: (*ccard.card_number).clone(), - // expiration_date: format!("{expiry_year}-{expiry_month}").into(), - expiration_date: ccard.get_expiry_date_as_yyyymm("-"), - card_code: Some(ccard.card_cvc.clone()), - }), - Some(ProcessingOptions { - is_subsequent_auth: true, - }), - None, - )) - } - domain::PaymentMethodData::Wallet(ref wallet_data) => Ok(( - get_wallet_data( - wallet_data, - &item.router_data.request.complete_authorize_url, - )?, - None, - None, - )), - domain::PaymentMethodData::CardRedirect(_) - | domain::PaymentMethodData::PayLater(_) - | domain::PaymentMethodData::BankRedirect(_) - | domain::PaymentMethodData::BankDebit(_) - | domain::PaymentMethodData::BankTransfer(_) - | domain::PaymentMethodData::Crypto(_) - | domain::PaymentMethodData::MandatePayment - | domain::PaymentMethodData::Reward - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::CardToken(_) => { - Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("authorizedotnet"), - ))? - } - } - } - } -} - #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] struct TransactionRequest { transaction_type: TransactionType, amount: f64, currency_code: String, - payment: PaymentDetails, + #[serde(skip_serializing_if = "Option::is_none")] + profile: Option, + #[serde(skip_serializing_if = "Option::is_none")] + payment: Option, order: Order, + #[serde(skip_serializing_if = "Option::is_none")] bill_to: Option, processing_options: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -239,6 +147,19 @@ struct TransactionRequest { authorization_indicator_type: Option, } +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct CustomerProfileDetails { + customer_profile_id: Secret, + payment_profile: PaymentProfileDetails, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct PaymentProfileDetails { + payment_profile_id: Secret, +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct ProcessingOptions { @@ -312,6 +233,193 @@ pub struct AuthorizedotnetPaymentCancelOrCaptureRequest { transaction_request: TransactionVoidOrCaptureRequest, } +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +// The connector enforces field ordering, it expects fields to be in the same order as in their API documentation +pub struct CreateCustomerProfileRequest { + create_customer_profile_request: AuthorizedotnetZeroMandateRequest, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AuthorizedotnetZeroMandateRequest { + merchant_authentication: AuthorizedotnetAuthType, + profile: Profile, + validation_mode: ValidationMode, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +struct Profile { + merchant_customer_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + description: Option, + email: Option, + payment_profiles: PaymentProfiles, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +struct PaymentProfiles { + customer_type: CustomerType, + payment: PaymentDetails, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum CustomerType { + Individual, + Business, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub enum ValidationMode { + // testMode performs a Luhn mod-10 check on the card number, without further validation at connector. + TestMode, + // liveMode submits a zero-dollar or one-cent transaction (depending on card type and processor support) to confirm that the card number belongs to an active credit or debit account. + LiveMode, +} + +impl TryFrom<&types::SetupMandateRouterData> for CreateCustomerProfileRequest { + type Error = error_stack::Report; + fn try_from(item: &types::SetupMandateRouterData) -> Result { + match item.request.payment_method_data.clone() { + domain::PaymentMethodData::Card(ccard) => { + let merchant_authentication = + AuthorizedotnetAuthType::try_from(&item.connector_auth_type)?; + let validation_mode = match item.test_mode { + Some(true) | None => ValidationMode::TestMode, + Some(false) => ValidationMode::LiveMode, + }; + Ok(Self { + create_customer_profile_request: AuthorizedotnetZeroMandateRequest { + merchant_authentication, + profile: Profile { + merchant_customer_id: item + .customer_id + .clone() + .ok_or_else(missing_field_err("customer_id"))?, + description: item.description.clone(), + email: item.request.email.clone(), + payment_profiles: PaymentProfiles { + customer_type: CustomerType::Individual, + payment: PaymentDetails::CreditCard(CreditCardDetails { + card_number: (*ccard.card_number).clone(), + expiration_date: ccard.get_expiry_date_as_yyyymm("-"), + card_code: Some(ccard.card_cvc.clone()), + }), + }, + }, + validation_mode, + }, + }) + } + domain::PaymentMethodData::CardRedirect(_) + | domain::PaymentMethodData::Wallet(_) + | domain::PaymentMethodData::PayLater(_) + | domain::PaymentMethodData::BankRedirect(_) + | domain::PaymentMethodData::BankDebit(_) + | domain::PaymentMethodData::BankTransfer(_) + | domain::PaymentMethodData::Crypto(_) + | domain::PaymentMethodData::MandatePayment + | domain::PaymentMethodData::Reward + | domain::PaymentMethodData::Upi(_) + | domain::PaymentMethodData::Voucher(_) + | domain::PaymentMethodData::GiftCard(_) + | domain::PaymentMethodData::CardToken(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("authorizedotnet"), + ))? + } + } + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AuthorizedotnetSetupMandateResponse { + customer_profile_id: Option, + customer_payment_profile_id_list: Vec, + validation_direct_response_list: Option>>, + pub messages: ResponseMessages, +} + +// zero dollar response +impl + TryFrom< + types::ResponseRouterData< + F, + AuthorizedotnetSetupMandateResponse, + T, + types::PaymentsResponseData, + >, + > for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + AuthorizedotnetSetupMandateResponse, + T, + types::PaymentsResponseData, + >, + ) -> Result { + match item.response.messages.result_code { + ResultCode::Ok => Ok(Self { + status: enums::AttemptStatus::Charged, + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::NoResponseId, + redirection_data: None, + mandate_reference: item.response.customer_profile_id.map(|mandate_id| { + types::MandateReference { + connector_mandate_id: Some(mandate_id), + payment_method_id: item + .response + .customer_payment_profile_id_list + .first() + .cloned(), + } + }), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }), + ResultCode::Error => { + let error_code = match item.response.messages.message.first() { + Some(first_error_message) => first_error_message.code.clone(), + None => crate::consts::NO_ERROR_CODE.to_string(), + }; + let error_reason = item + .response + .messages + .message + .iter() + .map(|error: &ResponseMessage| error.text.clone()) + .collect::>() + .join(" "); + let response = Err(types::ErrorResponse { + code: error_code, + message: item.response.messages.result_code.to_string(), + reason: Some(error_reason), + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: None, + }); + Ok(Self { + response, + status: enums::AttemptStatus::Failure, + ..item.data + }) + } + } + } +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] // The connector enforces field ordering, it expects fields to be in the same order as in their API documentation @@ -353,31 +461,153 @@ impl TryFrom<&AuthorizedotnetRouterData<&types::PaymentsAuthorizeRouterData>> fn try_from( item: &AuthorizedotnetRouterData<&types::PaymentsAuthorizeRouterData>, ) -> Result { - let (payment_details, processing_options, subsequent_auth_information) = - get_pm_and_subsequent_auth_detail(item)?; + let (payment_details, processing_options, subsequent_auth_information, profile) = match item + .router_data + .request + .mandate_id + .to_owned() + .and_then(|mandate_ids| mandate_ids.mandate_reference_id) + { + Some(api_models::payments::MandateReferenceId::NetworkMandateId(network_trans_id)) => { + let processing_options = Some(ProcessingOptions { + is_subsequent_auth: true, + }); + let subsequent_auth_info = Some(SubsequentAuthInformation { + original_network_trans_id: Secret::new(network_trans_id), + reason: Reason::Resubmission, + }); + match item.router_data.request.payment_method_data { + domain::PaymentMethodData::Card(ref ccard) => { + let payment_details = PaymentDetails::CreditCard(CreditCardDetails { + card_number: (*ccard.card_number).clone(), + expiration_date: ccard.get_expiry_date_as_yyyymm("-"), + card_code: None, + }); + ( + Some(payment_details), + processing_options, + subsequent_auth_info, + None, + ) + } + domain::PaymentMethodData::CardRedirect(_) + | domain::PaymentMethodData::Wallet(_) + | domain::PaymentMethodData::PayLater(_) + | domain::PaymentMethodData::BankRedirect(_) + | domain::PaymentMethodData::BankDebit(_) + | domain::PaymentMethodData::BankTransfer(_) + | domain::PaymentMethodData::Crypto(_) + | domain::PaymentMethodData::MandatePayment + | domain::PaymentMethodData::Reward + | domain::PaymentMethodData::Upi(_) + | domain::PaymentMethodData::Voucher(_) + | domain::PaymentMethodData::GiftCard(_) + | domain::PaymentMethodData::CardToken(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message( + "authorizedotnet", + ), + ))? + } + } + } + Some(api_models::payments::MandateReferenceId::ConnectorMandateId( + connector_mandate_id, + )) => ( + None, + Some(ProcessingOptions { + is_subsequent_auth: true, + }), + None, + Some(CustomerProfileDetails { + customer_profile_id: Secret::from( + connector_mandate_id + .connector_mandate_id + .ok_or(errors::ConnectorError::MissingConnectorMandateID)?, + ), + payment_profile: PaymentProfileDetails { + payment_profile_id: Secret::from( + connector_mandate_id + .payment_method_id + .ok_or(errors::ConnectorError::MissingConnectorMandateID)?, + ), + }, + }), + ), + None => { + match item.router_data.request.payment_method_data { + domain::PaymentMethodData::Card(ref ccard) => { + ( + Some(PaymentDetails::CreditCard(CreditCardDetails { + card_number: (*ccard.card_number).clone(), + // expiration_date: format!("{expiry_year}-{expiry_month}").into(), + expiration_date: ccard.get_expiry_date_as_yyyymm("-"), + card_code: Some(ccard.card_cvc.clone()), + })), + Some(ProcessingOptions { + is_subsequent_auth: true, + }), + None, + None, + ) + } + domain::PaymentMethodData::Wallet(ref wallet_data) => ( + Some(get_wallet_data( + wallet_data, + &item.router_data.request.complete_authorize_url, + )?), + None, + None, + None, + ), + domain::PaymentMethodData::CardRedirect(_) + | domain::PaymentMethodData::PayLater(_) + | domain::PaymentMethodData::BankRedirect(_) + | domain::PaymentMethodData::BankDebit(_) + | domain::PaymentMethodData::BankTransfer(_) + | domain::PaymentMethodData::Crypto(_) + | domain::PaymentMethodData::MandatePayment + | domain::PaymentMethodData::Reward + | domain::PaymentMethodData::Upi(_) + | domain::PaymentMethodData::Voucher(_) + | domain::PaymentMethodData::GiftCard(_) + | domain::PaymentMethodData::CardToken(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message( + "authorizedotnet", + ), + ))? + } + } + } + }; let authorization_indicator_type = match item.router_data.request.capture_method { Some(capture_method) => Some(AuthorizationIndicator { authorization_indicator: capture_method.try_into()?, }), None => None, }; - let bill_to = item - .router_data - .get_optional_billing() - .and_then(|billing_address| billing_address.address.as_ref()) - .map(|address| BillTo { - first_name: address.first_name.clone(), - last_name: address.last_name.clone(), - address: address.line1.clone(), - city: address.city.clone(), - state: address.state.clone(), - zip: address.zip.clone(), - country: address.country, - }); + let bill_to = match profile { + Some(_) => None, + None => item + .router_data + .get_optional_billing() + .and_then(|billing_address| billing_address.address.as_ref()) + .map(|address| BillTo { + first_name: address.first_name.clone(), + last_name: address.last_name.clone(), + address: address.line1.clone(), + city: address.city.clone(), + state: address.state.clone(), + zip: address.zip.clone(), + country: address.country, + }), + }; let transaction_request = TransactionRequest { transaction_type: TransactionType::try_from(item.router_data.request.capture_method)?, amount: item.amount, currency_code: item.router_data.request.currency.to_string(), + profile, payment: payment_details, order: Order { description: item.router_data.connector_request_reference_id.clone(), @@ -506,7 +736,7 @@ pub struct ResponseMessage { pub text: String, } -#[derive(Debug, Default, Clone, Deserialize, PartialEq, Serialize)] +#[derive(Debug, Default, Clone, Deserialize, PartialEq, Serialize, strum::Display)] enum ResultCode { #[default] Ok, @@ -620,7 +850,7 @@ impl From for enums::AttemptStatus { } impl - TryFrom<( + ForeignTryFrom<( types::ResponseRouterData< F, AuthorizedotnetPaymentsResponse, @@ -631,7 +861,7 @@ impl )> for types::RouterData { type Error = error_stack::Report; - fn try_from( + fn foreign_try_from( (item, is_auto_capture): ( types::ResponseRouterData< F, @@ -693,6 +923,7 @@ impl transaction_response.transaction_id.clone(), ), incremental_authorization_allowed: None, + charge_id: None, }), }, ..item.data @@ -765,6 +996,7 @@ impl transaction_response.transaction_id.clone(), ), incremental_authorization_allowed: None, + charge_id: None, }), }, ..item.data @@ -1089,6 +1321,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(transaction.transaction_id.clone()), incremental_authorization_allowed: None, + charge_id: None, }), status: payment_status, ..item.data diff --git a/crates/router/src/connector/bambora/transformers.rs b/crates/router/src/connector/bambora/transformers.rs index 61ed8bd1ee47..af4afeca10de 100644 --- a/crates/router/src/connector/bambora/transformers.rs +++ b/crates/router/src/connector/bambora/transformers.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Deserializer, Serialize}; use crate::{ connector::utils::{ - AddressDetailsData, BrowserInformationData, CardData as OtherCardData, + self, AddressDetailsData, BrowserInformationData, CardData as OtherCardData, PaymentsAuthorizeRequestData, PaymentsCompleteAuthorizeRequestData, PaymentsSyncRequestData, RouterData, }, @@ -26,7 +26,7 @@ impl TryFrom<(&api::CurrencyUnit, enums::Currency, i64, T)> for BamboraRouter fn try_from( (currency_unit, currency, amount, item): (&api::CurrencyUnit, enums::Currency, i64, T), ) -> Result { - let amount = crate::connector::utils::get_amount_as_f64(currency_unit, amount, currency)?; + let amount = utils::get_amount_as_f64(currency_unit, amount, currency)?; Ok(Self { amount, router_data: item, @@ -185,7 +185,10 @@ impl TryFrom> for Bambora | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::CardToken(_) => { - Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()) + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("bambora"), + ) + .into()) } } } @@ -444,6 +447,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(pg_response.order_number.to_string()), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }), @@ -470,6 +474,7 @@ impl item.data.connector_request_reference_id.to_string(), ), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -519,6 +524,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.order_number.to_string()), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -571,6 +577,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.order_number.to_string()), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -612,6 +619,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.order_number.to_string()), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -653,6 +661,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.order_number.to_string()), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/bankofamerica/transformers.rs b/crates/router/src/connector/bankofamerica/transformers.rs index 1e8a167a1793..90151f12aebf 100644 --- a/crates/router/src/connector/bankofamerica/transformers.rs +++ b/crates/router/src/connector/bankofamerica/transformers.rs @@ -375,7 +375,7 @@ impl .consumer_authentication_information .as_ref() .map(|consumer_auth_information| { - types::AdditionalPaymentMethodConnectorResponse::from(( + types::AdditionalPaymentMethodConnectorResponse::foreign_from(( processor_information, consumer_auth_information, )) @@ -415,6 +415,7 @@ impl .unwrap_or(info_response.id), ), incremental_authorization_allowed: None, + charge_id: None, }), }, connector_response, @@ -422,7 +423,10 @@ impl }) } BankOfAmericaSetupMandatesResponse::ErrorInformation(ref error_response) => { - let response = Err(types::ErrorResponse::from((error_response, item.http_code))); + let response = Err(types::ErrorResponse::foreign_from(( + error_response, + item.http_code, + ))); Ok(Self { response, status: enums::AttemptStatus::Failure, @@ -1538,13 +1542,13 @@ pub struct BankOfAmericaErrorInformation { } impl - From<( + ForeignFrom<( &BankOfAmericaErrorInformationResponse, types::ResponseRouterData, Option, )> for types::RouterData { - fn from( + fn foreign_from( (error_response, item, transaction_status): ( &BankOfAmericaErrorInformationResponse, types::ResponseRouterData< @@ -1594,7 +1598,7 @@ fn get_error_response_if_failure( ), ) -> Option { if utils::is_payment_failure(status) { - Some(types::ErrorResponse::from(( + Some(types::ErrorResponse::foreign_from(( &info_response.error_information, &info_response.risk_information, Some(status), @@ -1642,6 +1646,7 @@ fn get_payment_response( .unwrap_or(info_response.id.clone()), ), incremental_authorization_allowed: None, + charge_id: None, }) } } @@ -1694,6 +1699,7 @@ impl .unwrap_or(info_response.id.clone()), ), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }), @@ -1993,7 +1999,7 @@ impl let status = enums::AttemptStatus::from(info_response.status); let risk_info: Option = None; if utils::is_payment_failure(status) { - let response = Err(types::ErrorResponse::from(( + let response = Err(types::ErrorResponse::foreign_from(( &info_response.error_information, &risk_info, Some(status), @@ -2049,13 +2055,17 @@ impl network_txn_id: None, connector_response_reference_id, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) } } BankOfAmericaPreProcessingResponse::ErrorInformation(ref error_response) => { - let response = Err(types::ErrorResponse::from((error_response, item.http_code))); + let response = Err(types::ErrorResponse::foreign_from(( + error_response, + item.http_code, + ))); Ok(Self { response, status: enums::AttemptStatus::AuthenticationFailed, @@ -2102,7 +2112,7 @@ impl .consumer_authentication_information .as_ref() .map(|consumer_auth_information| { - types::AdditionalPaymentMethodConnectorResponse::from(( + types::AdditionalPaymentMethodConnectorResponse::foreign_from(( processor_information, consumer_auth_information, )) @@ -2130,7 +2140,7 @@ impl }) } BankOfAmericaPaymentsResponse::ErrorInformation(ref error_response) => { - Ok(Self::from(( + Ok(Self::foreign_from(( &error_response.clone(), item, Some(enums::AttemptStatus::Failure), @@ -2175,7 +2185,7 @@ impl .consumer_authentication_information .as_ref() .map(|consumer_auth_information| { - types::AdditionalPaymentMethodConnectorResponse::from(( + types::AdditionalPaymentMethodConnectorResponse::foreign_from(( processor_information, consumer_auth_information, )) @@ -2203,7 +2213,7 @@ impl }) } BankOfAmericaPaymentsResponse::ErrorInformation(ref error_response) => { - Ok(Self::from(( + Ok(Self::foreign_from(( &error_response.clone(), item, Some(enums::AttemptStatus::Failure), @@ -2214,12 +2224,12 @@ impl } impl - From<( + ForeignFrom<( &ClientProcessorInformation, &ConsumerAuthenticationInformation, )> for types::AdditionalPaymentMethodConnectorResponse { - fn from( + fn foreign_from( item: ( &ClientProcessorInformation, &ConsumerAuthenticationInformation, @@ -2281,7 +2291,7 @@ impl }) } BankOfAmericaPaymentsResponse::ErrorInformation(ref error_response) => { - Ok(Self::from((&error_response.clone(), item, None))) + Ok(Self::foreign_from((&error_response.clone(), item, None))) } } } @@ -2318,7 +2328,7 @@ impl }) } BankOfAmericaPaymentsResponse::ErrorInformation(ref error_response) => { - Ok(Self::from((&error_response.clone(), item, None))) + Ok(Self::foreign_from((&error_response.clone(), item, None))) } } } @@ -2396,7 +2406,7 @@ impl .consumer_authentication_information .as_ref() .map(|consumer_auth_information| { - types::AdditionalPaymentMethodConnectorResponse::from(( + types::AdditionalPaymentMethodConnectorResponse::foreign_from(( processor_information, consumer_auth_information, )) @@ -2419,7 +2429,7 @@ impl let risk_info: Option = None; if utils::is_payment_failure(status) { Ok(Self { - response: Err(types::ErrorResponse::from(( + response: Err(types::ErrorResponse::foreign_from(( &app_response.error_information, &risk_info, Some(status), @@ -2446,6 +2456,7 @@ impl .map(|cref| cref.code) .unwrap_or(Some(app_response.id)), incremental_authorization_allowed: None, + charge_id: None, }), connector_response, ..item.data @@ -2464,6 +2475,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(error_response.id), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }), @@ -2626,7 +2638,7 @@ impl TryFrom Result { let refund_status = enums::RefundStatus::from(item.response.status.clone()); let response = if utils::is_refund_failure(refund_status) { - Err(types::ErrorResponse::from(( + Err(types::ErrorResponse::foreign_from(( &item.response.error_information, &None, None, @@ -2689,7 +2701,7 @@ impl TryFrom, &Option, Option, @@ -2797,7 +2809,7 @@ impl String, )> for types::ErrorResponse { - fn from( + fn foreign_from( (error_data, risk_information, attempt_status, status_code, transaction_id): ( &Option, &Option, @@ -3059,8 +3071,10 @@ impl From<&domain::GooglePayWalletData> for PaymentInformation { } } -impl From<(&BankOfAmericaErrorInformationResponse, u16)> for types::ErrorResponse { - fn from((error_response, status_code): (&BankOfAmericaErrorInformationResponse, u16)) -> Self { +impl ForeignFrom<(&BankOfAmericaErrorInformationResponse, u16)> for types::ErrorResponse { + fn foreign_from( + (error_response, status_code): (&BankOfAmericaErrorInformationResponse, u16), + ) -> Self { let error_reason = error_response .error_information .message diff --git a/crates/router/src/connector/billwerk/transformers.rs b/crates/router/src/connector/billwerk/transformers.rs index 3e5e53286e31..811076a11da6 100644 --- a/crates/router/src/connector/billwerk/transformers.rs +++ b/crates/router/src/connector/billwerk/transformers.rs @@ -178,7 +178,7 @@ impl TryFrom<&BillwerkRouterData<&types::PaymentsAuthorizeRouterData>> for Billw .into()); }; let source = match item.router_data.get_payment_method_token()? { - types::PaymentMethodToken::Token(pm_token) => Ok(Secret::new(pm_token)), + types::PaymentMethodToken::Token(pm_token) => Ok(pm_token), _ => Err(errors::ConnectorError::MissingRequiredField { field_name: "payment_method_token", }), @@ -276,6 +276,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.handle), incremental_authorization_allowed: None, + charge_id: None, }; Ok(Self { status: enums::AttemptStatus::from(item.response.state), diff --git a/crates/router/src/connector/bitpay/transformers.rs b/crates/router/src/connector/bitpay/transformers.rs index a70c4ba3ac26..8a54c79b1854 100644 --- a/crates/router/src/connector/bitpay/transformers.rs +++ b/crates/router/src/connector/bitpay/transformers.rs @@ -172,6 +172,7 @@ impl .order_id .or(Some(item.response.data.id)), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/bluesnap.rs b/crates/router/src/connector/bluesnap.rs index dff76576ace4..f2a903c91f36 100644 --- a/crates/router/src/connector/bluesnap.rs +++ b/crates/router/src/connector/bluesnap.rs @@ -728,6 +728,7 @@ impl ConnectorIntegration network_txn_id: None, connector_response_reference_id: Some(item.response.transaction_id), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/boku/transformers.rs b/crates/router/src/connector/boku/transformers.rs index c22b65677320..2ff3e2476533 100644 --- a/crates/router/src/connector/boku/transformers.rs +++ b/crates/router/src/connector/boku/transformers.rs @@ -274,6 +274,7 @@ impl TryFrom network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -263,6 +264,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }), @@ -427,6 +429,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -445,6 +448,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }), @@ -489,6 +493,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -534,6 +539,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -1062,6 +1068,7 @@ impl TryFrom> network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -1160,6 +1167,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -1258,6 +1266,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -1324,7 +1333,7 @@ impl variables: VariablePaymentInput { input: PaymentInput { payment_method_id: match item.router_data.get_payment_method_token()? { - types::PaymentMethodToken::Token(token) => token.into(), + types::PaymentMethodToken::Token(token) => token, types::PaymentMethodToken::ApplePayDecrypt(_) => Err( unimplemented_payment_method!("Apple Pay", "Simplified", "Braintree"), )?, @@ -1405,7 +1414,7 @@ fn get_braintree_redirect_form( .client_token .expose(), card_token: match payment_method_token { - types::PaymentMethodToken::Token(token) => token, + types::PaymentMethodToken::Token(token) => token.expose(), types::PaymentMethodToken::ApplePayDecrypt(_) => Err(unimplemented_payment_method!( "Apple Pay", "Simplified", diff --git a/crates/router/src/connector/braintree/transformers.rs b/crates/router/src/connector/braintree/transformers.rs index e278ff40b437..a939f0a83f7a 100644 --- a/crates/router/src/connector/braintree/transformers.rs +++ b/crates/router/src/connector/braintree/transformers.rs @@ -282,6 +282,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(id), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/cashtocode/transformers.rs b/crates/router/src/connector/cashtocode/transformers.rs index f8c275df33b6..ba55fca9b809 100644 --- a/crates/router/src/connector/cashtocode/transformers.rs +++ b/crates/router/src/connector/cashtocode/transformers.rs @@ -268,6 +268,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ) } @@ -312,6 +313,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/checkout/transformers.rs b/crates/router/src/connector/checkout/transformers.rs index cc937c0ef60c..c92d75f1de88 100644 --- a/crates/router/src/connector/checkout/transformers.rs +++ b/crates/router/src/connector/checkout/transformers.rs @@ -294,7 +294,7 @@ impl TryFrom<&CheckoutRouterData<&types::PaymentsAuthorizeRouterData>> for Payme domain::WalletData::GooglePay(_) => Ok(PaymentSource::Wallets(WalletSource { source_type: CheckoutSourceTypes::Token, token: match item.router_data.get_payment_method_token()? { - types::PaymentMethodToken::Token(token) => token.into(), + types::PaymentMethodToken::Token(token) => token, types::PaymentMethodToken::ApplePayDecrypt(_) => Err( unimplemented_payment_method!("Apple Pay", "Simplified", "Checkout"), )?, @@ -306,7 +306,7 @@ impl TryFrom<&CheckoutRouterData<&types::PaymentsAuthorizeRouterData>> for Payme types::PaymentMethodToken::Token(apple_pay_payment_token) => { Ok(PaymentSource::Wallets(WalletSource { source_type: CheckoutSourceTypes::Token, - token: apple_pay_payment_token.into(), + token: apple_pay_payment_token, })) } types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { @@ -681,6 +681,7 @@ impl TryFrom> item.response.reference.unwrap_or(item.response.id), ), incremental_authorization_allowed: None, + charge_id: None, }; Ok(Self { status, @@ -733,6 +734,7 @@ impl TryFrom> item.response.reference.unwrap_or(item.response.id), ), incremental_authorization_allowed: None, + charge_id: None, }; Ok(Self { status, @@ -808,6 +810,7 @@ impl TryFrom> network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), status: response.into(), ..item.data @@ -908,6 +911,7 @@ impl TryFrom> network_txn_id: None, connector_response_reference_id: item.response.reference, incremental_authorization_allowed: None, + charge_id: None, }), status, amount_captured, diff --git a/crates/router/src/connector/coinbase/transformers.rs b/crates/router/src/connector/coinbase/transformers.rs index f02984136bdf..82a6add72ee8 100644 --- a/crates/router/src/connector/coinbase/transformers.rs +++ b/crates/router/src/connector/coinbase/transformers.rs @@ -149,6 +149,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.data.id.clone()), incremental_authorization_allowed: None, + charge_id: None, }), |context| { Ok(types::PaymentsResponseData::TransactionUnresolvedResponse{ diff --git a/crates/router/src/connector/cryptopay.rs b/crates/router/src/connector/cryptopay.rs index 7245694bd441..f7d137eff208 100644 --- a/crates/router/src/connector/cryptopay.rs +++ b/crates/router/src/connector/cryptopay.rs @@ -30,6 +30,7 @@ use crate::{ types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, + transformers::ForeignTryFrom, ErrorResponse, Response, }, utils::BytesExt, @@ -287,7 +288,7 @@ impl ConnectorIntegration - TryFrom<( + ForeignTryFrom<( types::ResponseRouterData, diesel_models::enums::Currency, )> for types::RouterData { type Error = error_stack::Report; - fn try_from( + fn foreign_try_from( (item, currency): ( types::ResponseRouterData, diesel_models::enums::Currency, @@ -190,6 +190,7 @@ impl .custom_id .or(Some(item.response.data.id)), incremental_authorization_allowed: None, + charge_id: None, }) }; diff --git a/crates/router/src/connector/cybersource.rs b/crates/router/src/connector/cybersource.rs index 763680d75405..43e5660d2b90 100644 --- a/crates/router/src/connector/cybersource.rs +++ b/crates/router/src/connector/cybersource.rs @@ -31,6 +31,7 @@ use crate::{ types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, + transformers::ForeignTryFrom, }, utils::BytesExt, }; @@ -1600,7 +1601,7 @@ impl .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(( + types::RouterData::foreign_try_from(( types::ResponseRouterData { response, data: data.clone(), diff --git a/crates/router/src/connector/cybersource/transformers.rs b/crates/router/src/connector/cybersource/transformers.rs index f8e0df7c0dd2..150a54c90482 100644 --- a/crates/router/src/connector/cybersource/transformers.rs +++ b/crates/router/src/connector/cybersource/transformers.rs @@ -26,7 +26,7 @@ use crate::{ api::{self, enums as api_enums}, domain, storage::enums, - transformers::ForeignFrom, + transformers::{ForeignFrom, ForeignTryFrom}, ApplePayPredecryptData, }, unimplemented_payment_method, @@ -1670,13 +1670,13 @@ pub struct CybersourceErrorInformation { } impl - From<( + ForeignFrom<( &CybersourceErrorInformationResponse, types::ResponseRouterData, Option, )> for types::RouterData { - fn from( + fn foreign_from( (error_response, item, transaction_status): ( &CybersourceErrorInformationResponse, types::ResponseRouterData< @@ -1726,7 +1726,7 @@ fn get_error_response_if_failure( ), ) -> Option { if utils::is_payment_failure(status) { - Some(types::ErrorResponse::from(( + Some(types::ErrorResponse::foreign_from(( &info_response.error_information, &info_response.risk_information, Some(status), @@ -1778,6 +1778,7 @@ fn get_payment_response( .unwrap_or(info_response.id.clone()), ), incremental_authorization_allowed, + charge_id: None, }) } } @@ -1822,11 +1823,13 @@ impl ..item.data }) } - CybersourcePaymentsResponse::ErrorInformation(ref error_response) => Ok(Self::from(( - &error_response.clone(), - item, - Some(enums::AttemptStatus::Failure), - ))), + CybersourcePaymentsResponse::ErrorInformation(ref error_response) => { + Ok(Self::foreign_from(( + &error_response.clone(), + item, + Some(enums::AttemptStatus::Failure), + ))) + } } } } @@ -1876,6 +1879,7 @@ impl .unwrap_or(info_response.id.clone()), ), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }), @@ -2189,7 +2193,7 @@ impl let status = enums::AttemptStatus::from(info_response.status); let risk_info: Option = None; if utils::is_payment_failure(status) { - let response = Err(types::ErrorResponse::from(( + let response = Err(types::ErrorResponse::foreign_from(( &info_response.error_information, &risk_info, Some(status), @@ -2244,6 +2248,7 @@ impl network_txn_id: None, connector_response_reference_id, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -2315,11 +2320,13 @@ impl ..item.data }) } - CybersourcePaymentsResponse::ErrorInformation(ref error_response) => Ok(Self::from(( - &error_response.clone(), - item, - Some(enums::AttemptStatus::Failure), - ))), + CybersourcePaymentsResponse::ErrorInformation(ref error_response) => { + Ok(Self::foreign_from(( + &error_response.clone(), + item, + Some(enums::AttemptStatus::Failure), + ))) + } } } } @@ -2368,7 +2375,7 @@ impl }) } CybersourcePaymentsResponse::ErrorInformation(ref error_response) => { - Ok(Self::from((&error_response.clone(), item, None))) + Ok(Self::foreign_from((&error_response.clone(), item, None))) } } } @@ -2405,7 +2412,7 @@ impl }) } CybersourcePaymentsResponse::ErrorInformation(ref error_response) => { - Ok(Self::from((&error_response.clone(), item, None))) + Ok(Self::foreign_from((&error_response.clone(), item, None))) } } } @@ -2482,6 +2489,7 @@ impl incremental_authorization_allowed: Some( mandate_status == enums::AttemptStatus::Authorized, ), + charge_id: None, }), }, connector_response, @@ -2516,7 +2524,7 @@ impl } impl - TryFrom<( + ForeignTryFrom<( types::ResponseRouterData< F, CybersourcePaymentsIncrementalAuthorizationResponse, @@ -2527,7 +2535,7 @@ impl )> for types::RouterData { type Error = error_stack::Report; - fn try_from( + fn foreign_try_from( data: ( types::ResponseRouterData< F, @@ -2615,7 +2623,7 @@ impl let risk_info: Option = None; if utils::is_payment_failure(status) { Ok(Self { - response: Err(types::ErrorResponse::from(( + response: Err(types::ErrorResponse::foreign_from(( &app_response.error_information, &risk_info, Some(status), @@ -2641,6 +2649,7 @@ impl .map(|cref| cref.code) .unwrap_or(Some(app_response.id)), incremental_authorization_allowed, + charge_id: None, }), ..item.data }) @@ -2658,6 +2667,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(error_response.id), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }), @@ -2733,7 +2743,7 @@ impl TryFrom Result { let refund_status = enums::RefundStatus::from(item.response.status.clone()); let response = if utils::is_refund_failure(refund_status) { - Err(types::ErrorResponse::from(( + Err(types::ErrorResponse::foreign_from(( &item.response.error_information, &None, None, @@ -2784,7 +2794,7 @@ impl TryFrom, &Option, Option, @@ -3125,7 +3135,7 @@ impl String, )> for types::ErrorResponse { - fn from( + fn foreign_from( (error_data, risk_information, attempt_status, status_code, transaction_id): ( &Option, &Option, diff --git a/crates/router/src/connector/dlocal/transformers.rs b/crates/router/src/connector/dlocal/transformers.rs index c073dc03cc9a..b736c6a6c5aa 100644 --- a/crates/router/src/connector/dlocal/transformers.rs +++ b/crates/router/src/connector/dlocal/transformers.rs @@ -330,6 +330,7 @@ impl network_txn_id: None, connector_response_reference_id: item.response.order_id.clone(), incremental_authorization_allowed: None, + charge_id: None, }; Ok(Self { status: enums::AttemptStatus::from(item.response.status), @@ -370,6 +371,7 @@ impl network_txn_id: None, connector_response_reference_id: item.response.order_id.clone(), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -407,6 +409,7 @@ impl network_txn_id: None, connector_response_reference_id: item.response.order_id.clone(), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -444,6 +447,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.order_id.clone()), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/dummyconnector/transformers.rs b/crates/router/src/connector/dummyconnector/transformers.rs index 52c9522842f8..2cf5960bf753 100644 --- a/crates/router/src/connector/dummyconnector/transformers.rs +++ b/crates/router/src/connector/dummyconnector/transformers.rs @@ -259,6 +259,7 @@ impl TryFrom gateway_resp.transaction_processing_details.order_id, ), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -412,6 +413,7 @@ impl TryFrom network_txn_id: None, connector_response_reference_id: Some(transaction_id.to_string()), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -326,6 +327,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(transaction_id.to_string()), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -394,6 +396,7 @@ impl TryFrom> network_txn_id: None, connector_response_reference_id: Some(item.response.transaction_id.to_string()), incremental_authorization_allowed: None, + charge_id: None, }), amount_captured: None, ..item.data @@ -462,6 +465,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(transaction_id.to_string()), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/globalpay.rs b/crates/router/src/connector/globalpay.rs index d98281cb8481..d00b0e545a98 100644 --- a/crates/router/src/connector/globalpay.rs +++ b/crates/router/src/connector/globalpay.rs @@ -2,7 +2,6 @@ mod requests; use super::utils as connector_utils; mod response; pub mod transformers; - use std::fmt::Debug; use ::common_utils::{errors::ReportSwitchExt, ext_traits::ByteSliceExt, request::RequestContent}; @@ -35,6 +34,7 @@ use crate::{ types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt, PaymentsCompleteAuthorize}, + transformers::ForeignTryFrom, ErrorResponse, }, utils::{crypto, BytesExt}, @@ -547,7 +547,7 @@ impl ConnectorIntegration true, types::SyncRequestType::SinglePaymentSync => false, }; - types::RouterData::try_from(( + types::RouterData::foreign_try_from(( types::ResponseRouterData { response, data: data.clone(), diff --git a/crates/router/src/connector/globalpay/transformers.rs b/crates/router/src/connector/globalpay/transformers.rs index 478da2cf9023..3bdc3360a797 100644 --- a/crates/router/src/connector/globalpay/transformers.rs +++ b/crates/router/src/connector/globalpay/transformers.rs @@ -17,7 +17,7 @@ use crate::{ consts, core::errors, services::{self, RedirectForm}, - types::{self, api, domain, storage::enums, ErrorResponse}, + types::{self, api, domain, storage::enums, transformers::ForeignTryFrom, ErrorResponse}, }; type Error = error_stack::Report; @@ -235,6 +235,7 @@ fn get_payment_response( network_txn_id: None, connector_response_reference_id: response.reference, incremental_authorization_allowed: None, + charge_id: None, }), } } @@ -279,14 +280,14 @@ impl } impl - TryFrom<( + ForeignTryFrom<( types::PaymentsSyncResponseRouterData, bool, )> for types::PaymentsSyncRouterData { type Error = Error; - fn try_from( + fn foreign_try_from( (value, is_multiple_capture_sync): ( types::PaymentsSyncResponseRouterData, bool, diff --git a/crates/router/src/connector/globepay/transformers.rs b/crates/router/src/connector/globepay/transformers.rs index efe96454933f..dc0f970481c1 100644 --- a/crates/router/src/connector/globepay/transformers.rs +++ b/crates/router/src/connector/globepay/transformers.rs @@ -194,6 +194,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -268,6 +269,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/gocardless/transformers.rs b/crates/router/src/connector/gocardless/transformers.rs index 97f1146b5060..ee1ff91cd478 100644 --- a/crates/router/src/connector/gocardless/transformers.rs +++ b/crates/router/src/connector/gocardless/transformers.rs @@ -433,7 +433,7 @@ impl TryFrom<&types::SetupMandateRouterData> for GocardlessMandateRequest { }, payer_ip_address, links: MandateLink { - customer_bank_account: Secret::new(customer_bank_account), + customer_bank_account, }, }, }) @@ -512,6 +512,7 @@ impl redirection_data: None, mandate_reference, network_txn_id: None, + charge_id: None, }), status: enums::AttemptStatus::Charged, ..item.data @@ -664,6 +665,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -699,6 +701,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/gpayments.rs b/crates/router/src/connector/gpayments.rs index 9608f44b9daf..71c6a65e6d4c 100644 --- a/crates/router/src/connector/gpayments.rs +++ b/crates/router/src/connector/gpayments.rs @@ -204,6 +204,7 @@ impl api::IncomingWebhook for Gpayments { impl api::ExternalAuthentication for Gpayments {} impl api::ConnectorAuthentication for Gpayments {} impl api::ConnectorPreAuthentication for Gpayments {} +impl api::ConnectorPreAuthenticationVersionCall for Gpayments {} impl api::ConnectorPostAuthentication for Gpayments {} impl ConnectorIntegration< @@ -221,6 +222,14 @@ impl > for Gpayments { } +impl + ConnectorIntegration< + api::PreAuthenticationVersionCall, + types::authentication::PreAuthNRequestData, + types::authentication::AuthenticationResponseData, + > for Gpayments +{ +} impl ConnectorIntegration< api::PostAuthentication, diff --git a/crates/router/src/connector/helcim/transformers.rs b/crates/router/src/connector/helcim/transformers.rs index 655d8a8663ba..a3341ddab471 100644 --- a/crates/router/src/connector/helcim/transformers.rs +++ b/crates/router/src/connector/helcim/transformers.rs @@ -369,6 +369,7 @@ impl network_txn_id: None, connector_response_reference_id: item.response.invoice_number.clone(), incremental_authorization_allowed: None, + charge_id: None, }), status: enums::AttemptStatus::from(item.response), ..item.data @@ -424,6 +425,7 @@ impl network_txn_id: None, connector_response_reference_id: item.response.invoice_number.clone(), incremental_authorization_allowed: None, + charge_id: None, }), status: enums::AttemptStatus::from(item.response), ..item.data @@ -483,6 +485,7 @@ impl network_txn_id: None, connector_response_reference_id: item.response.invoice_number.clone(), incremental_authorization_allowed: None, + charge_id: None, }), status: enums::AttemptStatus::from(item.response), ..item.data @@ -569,6 +572,7 @@ impl network_txn_id: None, connector_response_reference_id: item.response.invoice_number.clone(), incremental_authorization_allowed: None, + charge_id: None, }), status: enums::AttemptStatus::from(item.response), ..item.data @@ -631,6 +635,7 @@ impl network_txn_id: None, connector_response_reference_id: item.response.invoice_number.clone(), incremental_authorization_allowed: None, + charge_id: None, }), status: enums::AttemptStatus::from(item.response), ..item.data diff --git a/crates/router/src/connector/iatapay/transformers.rs b/crates/router/src/connector/iatapay/transformers.rs index 3e337d3a74e7..5daa33ab9443 100644 --- a/crates/router/src/connector/iatapay/transformers.rs +++ b/crates/router/src/connector/iatapay/transformers.rs @@ -300,6 +300,7 @@ fn get_iatpay_response( network_txn_id: None, connector_response_reference_id: connector_response_reference_id.clone(), incremental_authorization_allowed: None, + charge_id: None, }, |checkout_methods| types::PaymentsResponseData::TransactionResponse { resource_id: id, @@ -313,6 +314,7 @@ fn get_iatpay_response( network_txn_id: None, connector_response_reference_id: connector_response_reference_id.clone(), incremental_authorization_allowed: None, + charge_id: None, }, ); Ok((status, error, payment_response_data)) diff --git a/crates/router/src/connector/klarna/transformers.rs b/crates/router/src/connector/klarna/transformers.rs index db77b9c30cd0..15fed065f06e 100644 --- a/crates/router/src/connector/klarna/transformers.rs +++ b/crates/router/src/connector/klarna/transformers.rs @@ -161,6 +161,7 @@ impl TryFrom> network_txn_id: None, connector_response_reference_id: Some(item.response.order_id.clone()), incremental_authorization_allowed: None, + charge_id: None, }), status: item.response.fraud_status.into(), ..item.data diff --git a/crates/router/src/connector/mifinity/transformers.rs b/crates/router/src/connector/mifinity/transformers.rs index e1c3d18d0be9..b37602e8d762 100644 --- a/crates/router/src/connector/mifinity/transformers.rs +++ b/crates/router/src/connector/mifinity/transformers.rs @@ -13,22 +13,10 @@ pub struct MifinityRouterData { pub router_data: T, } -impl - TryFrom<( - &types::api::CurrencyUnit, - types::storage::enums::Currency, - i64, - T, - )> for MifinityRouterData -{ +impl TryFrom<(&api::CurrencyUnit, enums::Currency, i64, T)> for MifinityRouterData { type Error = error_stack::Report; fn try_from( - (_currency_unit, _currency, amount, item): ( - &types::api::CurrencyUnit, - types::storage::enums::Currency, - i64, - T, - ), + (_currency_unit, _currency, amount, item): (&api::CurrencyUnit, enums::Currency, i64, T), ) -> Result { //Todo : use utils to convert the amount to the type of amount that a connector accepts Ok(Self { @@ -146,6 +134,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/mollie/transformers.rs b/crates/router/src/connector/mollie/transformers.rs index e2c3fc669925..9320aee8d7a3 100644 --- a/crates/router/src/connector/mollie/transformers.rs +++ b/crates/router/src/connector/mollie/transformers.rs @@ -175,7 +175,7 @@ impl TryFrom<&MollieRouterData<&types::PaymentsAuthorizeRouterData>> for MollieP CreditCardMethodData { billing_address: get_billing_details(item.router_data)?, shipping_address: get_shipping_details(item.router_data)?, - card_token: Some(Secret::new(match pm_token { + card_token: Some(match pm_token { types::PaymentMethodToken::Token(token) => token, types::PaymentMethodToken::ApplePayDecrypt(_) => { Err(unimplemented_payment_method!( @@ -184,12 +184,12 @@ impl TryFrom<&MollieRouterData<&types::PaymentsAuthorizeRouterData>> for MollieP "Mollie" ))? } - })), + }), }, ))) } domain::PaymentMethodData::BankRedirect(ref redirect_data) => { - PaymentMethodData::try_from(redirect_data) + PaymentMethodData::try_from((item.router_data, redirect_data)) } domain::PaymentMethodData::Wallet(ref wallet_data) => { get_payment_method_for_wallet(item.router_data, wallet_data) @@ -230,9 +230,19 @@ impl TryFrom<&MollieRouterData<&types::PaymentsAuthorizeRouterData>> for MollieP } } -impl TryFrom<&domain::BankRedirectData> for PaymentMethodData { +impl + TryFrom<( + &types::PaymentsAuthorizeRouterData, + &domain::BankRedirectData, + )> for PaymentMethodData +{ type Error = Error; - fn try_from(value: &domain::BankRedirectData) -> Result { + fn try_from( + (item, value): ( + &types::PaymentsAuthorizeRouterData, + &domain::BankRedirectData, + ), + ) -> Result { match value { domain::BankRedirectData::Eps { .. } => Ok(Self::Eps), domain::BankRedirectData::Giropay { .. } => Ok(Self::Giropay), @@ -243,11 +253,11 @@ impl TryFrom<&domain::BankRedirectData> for PaymentMethodData { }))) } domain::BankRedirectData::Sofort { .. } => Ok(Self::Sofort), - domain::BankRedirectData::Przelewy24 { - billing_details, .. - } => Ok(Self::Przelewy24(Box::new(Przelewy24MethodData { - billing_email: billing_details.email.clone(), - }))), + domain::BankRedirectData::Przelewy24 { .. } => { + Ok(Self::Przelewy24(Box::new(Przelewy24MethodData { + billing_email: item.get_optional_billing_email(), + }))) + } domain::BankRedirectData::BancontactCard { .. } => Ok(Self::Bancontact), _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), } @@ -497,7 +507,7 @@ impl Ok(Self { status: storage_enums::AttemptStatus::Pending, payment_method_token: Some(types::PaymentMethodToken::Token( - item.response.card_token.clone().expose(), + item.response.card_token.clone(), )), response: Ok(types::PaymentsResponseData::TokenizationResponse { token: item.response.card_token.expose(), @@ -530,6 +540,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.id), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/multisafepay.rs b/crates/router/src/connector/multisafepay.rs index a8a79095d36a..fd3edd7c0d93 100644 --- a/crates/router/src/connector/multisafepay.rs +++ b/crates/router/src/connector/multisafepay.rs @@ -23,6 +23,7 @@ use crate::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, storage::enums, + transformers::ForeignFrom, ErrorResponse, Response, }, utils::BytesExt, @@ -88,7 +89,7 @@ impl ConnectorCommon for Multisafepay { let attempt_status = Option::::from(response.clone()); - Ok(ErrorResponse::from(( + Ok(ErrorResponse::foreign_from(( Some(response.error_code.to_string()), Some(response.error_info.clone()), Some(response.error_info), @@ -119,7 +120,7 @@ impl ConnectorValidation for Multisafepay { fn validate_mandate_payment( &self, - pm_type: Option, + pm_type: Option, pm_data: types::domain::payments::PaymentMethodData, ) -> CustomResult<(), errors::ConnectorError> { let mandate_supported_pmd = std::collections::HashSet::from([ diff --git a/crates/router/src/connector/multisafepay/transformers.rs b/crates/router/src/connector/multisafepay/transformers.rs index f993928d94f1..8f2cf5f833d0 100644 --- a/crates/router/src/connector/multisafepay/transformers.rs +++ b/crates/router/src/connector/multisafepay/transformers.rs @@ -11,7 +11,7 @@ use crate::{ core::errors, pii::Secret, services, - types::{self, api, domain, storage::enums}, + types::{self, api, domain, storage::enums, transformers::ForeignFrom}, }; #[derive(Debug, Serialize)] @@ -671,7 +671,7 @@ impl Ok(Self { status, response: if utils::is_payment_failure(status) { - Err(types::ErrorResponse::from(( + Err(types::ErrorResponse::foreign_from(( payment_response.data.reason_code, payment_response.data.reason.clone(), payment_response.data.reason, @@ -699,6 +699,7 @@ impl payment_response.data.order_id.clone(), ), incremental_authorization_allowed: None, + charge_id: None, }) }, ..item.data @@ -707,7 +708,7 @@ impl MultisafepayAuthResponse::ErrorResponse(error_response) => { let attempt_status = Option::::from(error_response.clone()); Ok(Self { - response: Err(types::ErrorResponse::from(( + response: Err(types::ErrorResponse::foreign_from(( Some(error_response.error_code.to_string()), Some(error_response.error_info.clone()), Some(error_response.error_info), @@ -857,7 +858,7 @@ impl TryFrom Ok(Self { - response: Err(types::ErrorResponse::from(( + response: Err(types::ErrorResponse::foreign_from(( Some(error_response.error_code.to_string()), Some(error_response.error_info.clone()), Some(error_response.error_info), diff --git a/crates/router/src/connector/netcetera.rs b/crates/router/src/connector/netcetera.rs index 71072a046dea..43df87db40f4 100644 --- a/crates/router/src/connector/netcetera.rs +++ b/crates/router/src/connector/netcetera.rs @@ -228,6 +228,7 @@ fn build_endpoint( } impl api::ConnectorPreAuthentication for Netcetera {} +impl api::ConnectorPreAuthenticationVersionCall for Netcetera {} impl api::ExternalAuthentication for Netcetera {} impl api::ConnectorAuthentication for Netcetera {} impl api::ConnectorPostAuthentication for Netcetera {} @@ -451,3 +452,12 @@ impl > for Netcetera { } + +impl + ConnectorIntegration< + api::PreAuthenticationVersionCall, + types::authentication::PreAuthNRequestData, + types::authentication::AuthenticationResponseData, + > for Netcetera +{ +} diff --git a/crates/router/src/connector/netcetera/transformers.rs b/crates/router/src/connector/netcetera/transformers.rs index 90cf465e8205..fffacc3ea4dd 100644 --- a/crates/router/src/connector/netcetera/transformers.rs +++ b/crates/router/src/connector/netcetera/transformers.rs @@ -96,6 +96,9 @@ impl three_ds_method_url, message_version: maximum_supported_3ds_version, connector_metadata: None, + directory_server_id: card_range + .as_ref() + .and_then(|card_range| card_range.directory_server_id.clone()), }, ) } diff --git a/crates/router/src/connector/nexinets.rs b/crates/router/src/connector/nexinets.rs index 97dd22a58eb0..113b2924fec3 100644 --- a/crates/router/src/connector/nexinets.rs +++ b/crates/router/src/connector/nexinets.rs @@ -159,7 +159,7 @@ impl ConnectorValidation for Nexinets { fn validate_mandate_payment( &self, - pm_type: Option, + pm_type: Option, pm_data: types::domain::payments::PaymentMethodData, ) -> CustomResult<(), errors::ConnectorError> { let mandate_supported_pmd = std::collections::HashSet::from([ diff --git a/crates/router/src/connector/nexinets/transformers.rs b/crates/router/src/connector/nexinets/transformers.rs index c6ef4ea1ba82..e8c606d7c58d 100644 --- a/crates/router/src/connector/nexinets/transformers.rs +++ b/crates/router/src/connector/nexinets/transformers.rs @@ -372,6 +372,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.order_id), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -455,6 +456,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.order.order_id), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/nmi/transformers.rs b/crates/router/src/connector/nmi/transformers.rs index fdc65963fe8c..2a8498bfbadf 100644 --- a/crates/router/src/connector/nmi/transformers.rs +++ b/crates/router/src/connector/nmi/transformers.rs @@ -225,6 +225,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.transactionid), incremental_authorization_allowed: None, + charge_id: None, }), enums::AttemptStatus::AuthenticationPending, ), @@ -378,6 +379,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.orderid), incremental_authorization_allowed: None, + charge_id: None, }), if let Some(diesel_models::enums::CaptureMethod::Automatic) = item.data.request.capture_method @@ -754,6 +756,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.orderid), incremental_authorization_allowed: None, + charge_id: None, }), enums::AttemptStatus::CaptureInitiated, ), @@ -848,6 +851,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.orderid), incremental_authorization_allowed: None, + charge_id: None, }), enums::AttemptStatus::Charged, ), @@ -904,6 +908,7 @@ impl TryFrom> network_txn_id: None, connector_response_reference_id: Some(item.response.orderid), incremental_authorization_allowed: None, + charge_id: None, }), if let Some(diesel_models::enums::CaptureMethod::Automatic) = item.data.request.capture_method @@ -954,6 +959,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.orderid), incremental_authorization_allowed: None, + charge_id: None, }), enums::AttemptStatus::VoidInitiated, ), @@ -1004,6 +1010,7 @@ impl TryFrom for NoonPaymentsRequest { )) | Some(hyperswitch_domain_models::mandates::MandateDataType::MultiUse(Some( mandate, - ))) => conn_utils::to_currency_base_unit(mandate.amount, mandate.currency), + ))) => conn_utils::to_currency_base_unit( + mandate.amount.get_amount_as_i64(), + mandate.currency, + ), Some(hyperswitch_domain_models::mandates::MandateDataType::MultiUse(None)) => { Err(errors::ConnectorError::MissingRequiredField { field_name: @@ -596,6 +599,7 @@ impl network_txn_id: None, connector_response_reference_id, incremental_authorization_allowed: None, + charge_id: None, }) } }, diff --git a/crates/router/src/connector/nuvei.rs b/crates/router/src/connector/nuvei.rs index 1f9fd861c030..e4d6ab0be4f6 100644 --- a/crates/router/src/connector/nuvei.rs +++ b/crates/router/src/connector/nuvei.rs @@ -26,6 +26,7 @@ use crate::{ self, api::{self, ConnectorCommon, ConnectorCommonExt, InitPayment}, storage::enums, + transformers::ForeignFrom, ErrorResponse, Response, }, utils::ByteSliceExt, @@ -89,7 +90,7 @@ impl ConnectorValidation for Nuvei { fn validate_mandate_payment( &self, - pm_type: Option, + pm_type: Option, pm_data: types::domain::payments::PaymentMethodData, ) -> CustomResult<(), errors::ConnectorError> { let mandate_supported_pmd = std::collections::HashSet::from([PaymentMethodDataType::Card]); @@ -536,7 +537,7 @@ impl ConnectorIntegration = Box::new(&Self); - let authorize_data = &types::PaymentsAuthorizeSessionTokenRouterData::from(( + let authorize_data = &types::PaymentsAuthorizeSessionTokenRouterData::foreign_from(( &router_data.to_owned(), types::AuthorizeSessionTokenData::from(&router_data), )); @@ -564,7 +565,7 @@ impl ConnectorIntegration = Box::new(&Self); - let init_data = &types::PaymentsInitRouterData::from(( + let init_data = &types::PaymentsInitRouterData::foreign_from(( &router_data.to_owned(), router_data.request.clone(), )); diff --git a/crates/router/src/connector/nuvei/transformers.rs b/crates/router/src/connector/nuvei/transformers.rs index e0d7a58566d6..ace33e3e30fe 100644 --- a/crates/router/src/connector/nuvei/transformers.rs +++ b/crates/router/src/connector/nuvei/transformers.rs @@ -1472,6 +1472,7 @@ where network_txn_id: None, connector_response_reference_id: response.order_id, incremental_authorization_allowed: None, + charge_id: None, }) }, ..item.data diff --git a/crates/router/src/connector/opayo/transformers.rs b/crates/router/src/connector/opayo/transformers.rs index e00f9a331a19..327ec5def052 100644 --- a/crates/router/src/connector/opayo/transformers.rs +++ b/crates/router/src/connector/opayo/transformers.rs @@ -128,6 +128,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.transaction_id), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/opennode/transformers.rs b/crates/router/src/connector/opennode/transformers.rs index 09da5fc8f941..8f1579ac1ff4 100644 --- a/crates/router/src/connector/opennode/transformers.rs +++ b/crates/router/src/connector/opennode/transformers.rs @@ -144,6 +144,7 @@ impl network_txn_id: None, connector_response_reference_id: item.response.data.order_id, incremental_authorization_allowed: None, + charge_id: None, }) } else { Ok(types::PaymentsResponseData::TransactionUnresolvedResponse { diff --git a/crates/router/src/connector/payeezy/transformers.rs b/crates/router/src/connector/payeezy/transformers.rs index a8442f7ea8a4..9b6da9a0e93c 100644 --- a/crates/router/src/connector/payeezy/transformers.rs +++ b/crates/router/src/connector/payeezy/transformers.rs @@ -433,6 +433,7 @@ impl .unwrap_or(item.response.transaction_id), ), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/payme.rs b/crates/router/src/connector/payme.rs index b7d458d6da5b..397715d102db 100644 --- a/crates/router/src/connector/payme.rs +++ b/crates/router/src/connector/payme.rs @@ -135,7 +135,7 @@ impl ConnectorValidation for Payme { fn validate_mandate_payment( &self, pm_type: Option, - pm_data: types::domain::payments::PaymentMethodData, + pm_data: domain::payments::PaymentMethodData, ) -> CustomResult<(), errors::ConnectorError> { let mandate_supported_pmd = std::collections::HashSet::from([ PaymentMethodDataType::Card, diff --git a/crates/router/src/connector/payme/transformers.rs b/crates/router/src/connector/payme/transformers.rs index b04b8cb47457..634c0a17c7ab 100644 --- a/crates/router/src/connector/payme/transformers.rs +++ b/crates/router/src/connector/payme/transformers.rs @@ -17,7 +17,10 @@ use crate::{ consts, core::errors, services, - types::{self, api, domain, domain::PaymentMethodData, storage::enums, MandateReference}, + types::{ + self, api, domain, domain::PaymentMethodData, storage::enums, transformers::ForeignFrom, + MandateReference, + }, unimplemented_payment_method, }; @@ -68,7 +71,7 @@ pub struct MandateRequest { pub struct Pay3dsRequest { buyer_name: Secret, buyer_email: pii::Email, - buyer_key: String, + buyer_key: Secret, payme_sale_id: String, meta_data_jwt: Secret, } @@ -188,7 +191,10 @@ impl let status = enums::AttemptStatus::from(item.response.sale_status.clone()); let response = if is_payment_failure(status) { // To populate error message in case of failure - Err(types::ErrorResponse::from((&item.response, item.http_code))) + Err(types::ErrorResponse::foreign_from(( + &item.response, + item.http_code, + ))) } else { Ok(types::PaymentsResponseData::try_from(&item.response)?) }; @@ -200,8 +206,8 @@ impl } } -impl From<(&PaymePaySaleResponse, u16)> for types::ErrorResponse { - fn from((pay_sale_response, http_code): (&PaymePaySaleResponse, u16)) -> Self { +impl ForeignFrom<(&PaymePaySaleResponse, u16)> for types::ErrorResponse { + fn foreign_from((pay_sale_response, http_code): (&PaymePaySaleResponse, u16)) -> Self { let code = pay_sale_response .status_error_code .map(|error_code| error_code.to_string()) @@ -245,6 +251,7 @@ impl TryFrom<&PaymePaySaleResponse> for types::PaymentsResponseData { network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }) } } @@ -266,7 +273,7 @@ impl TryFrom TryFrom for types::ErrorResponse { - fn from((sale_query_response, http_code): (&SaleQuery, u16)) -> Self { +impl ForeignFrom<(&SaleQuery, u16)> for types::ErrorResponse { + fn foreign_from((sale_query_response, http_code): (&SaleQuery, u16)) -> Self { Self { code: sale_query_response .sale_error_code @@ -311,6 +318,7 @@ impl From<&SaleQuery> for types::PaymentsResponseData { network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, } } } @@ -519,6 +527,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }), @@ -880,7 +889,7 @@ impl ) -> Result { Ok(Self { payment_method_token: Some(types::PaymentMethodToken::Token( - item.response.buyer_key.clone().expose(), + item.response.buyer_key.clone(), )), response: Ok(types::PaymentsResponseData::TokenizationResponse { token: item.response.buyer_key.expose(), @@ -1074,6 +1083,7 @@ impl TryFrom> network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }) }; Ok(Self { diff --git a/crates/router/src/connector/payone/transformers.rs b/crates/router/src/connector/payone/transformers.rs index 48a5d8cbfebd..ebe34111abb9 100644 --- a/crates/router/src/connector/payone/transformers.rs +++ b/crates/router/src/connector/payone/transformers.rs @@ -13,22 +13,10 @@ pub struct PayoneRouterData { pub router_data: T, } -impl - TryFrom<( - &types::api::CurrencyUnit, - types::storage::enums::Currency, - i64, - T, - )> for PayoneRouterData -{ +impl TryFrom<(&api::CurrencyUnit, enums::Currency, i64, T)> for PayoneRouterData { type Error = error_stack::Report; fn try_from( - (_currency_unit, _currency, amount, item): ( - &types::api::CurrencyUnit, - types::storage::enums::Currency, - i64, - T, - ), + (_currency_unit, _currency, amount, item): (&api::CurrencyUnit, enums::Currency, i64, T), ) -> Result { //Todo : use utils to convert the amount to the type of amount that a connector accepts Ok(Self { @@ -141,6 +129,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/paypal.rs b/crates/router/src/connector/paypal.rs index cf81fec63417..9ff6b84a0646 100644 --- a/crates/router/src/connector/paypal.rs +++ b/crates/router/src/connector/paypal.rs @@ -120,7 +120,7 @@ impl Paypal { .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), reason: error_reason.or(Some(response.message)), attempt_status: None, - connector_transaction_id: None, + connector_transaction_id: response.debug_id, }) } } @@ -292,7 +292,7 @@ impl ConnectorCommon for Paypal { .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), reason, attempt_status: None, - connector_transaction_id: None, + connector_transaction_id: response.debug_id, }) } } @@ -787,7 +787,8 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - }), + charge_id: None, + }), ..data.clone() }) } @@ -837,6 +838,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..data.clone() }) diff --git a/crates/router/src/connector/paypal/transformers.rs b/crates/router/src/connector/paypal/transformers.rs index aa254be456df..31c307f37a32 100644 --- a/crates/router/src/connector/paypal/transformers.rs +++ b/crates/router/src/connector/paypal/transformers.rs @@ -11,8 +11,8 @@ use url::Url; use crate::{ connector::utils::{ - self, to_connector_meta, AccessTokenRequestInfo, AddressDetailsData, - BankRedirectBillingData, CardData, PaymentsAuthorizeRequestData, RouterData, + self, to_connector_meta, AccessTokenRequestInfo, AddressDetailsData, CardData, + PaymentsAuthorizeRequestData, RouterData, }, consts, core::errors, @@ -280,95 +280,59 @@ fn get_payment_source( bank_redirection_data: &domain::BankRedirectData, ) -> Result> { match bank_redirection_data { - domain::BankRedirectData::Eps { - billing_details, - bank_name: _, - country, - } => Ok(PaymentSourceItem::Eps(RedirectRequest { - name: billing_details - .clone() - .ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "eps.billing_details", - })? - .get_billing_name()?, - country_code: country.ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "eps.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() { - ShippingPreference::SetProvidedAddress - } else { - ShippingPreference::GetFromFile + 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() { + ShippingPreference::SetProvidedAddress + } else { + ShippingPreference::GetFromFile + }, + user_action: Some(UserAction::PayNow), }, - user_action: Some(UserAction::PayNow), - }, - })), - domain::BankRedirectData::Giropay { - billing_details, - country, - .. - } => Ok(PaymentSourceItem::Giropay(RedirectRequest { - name: billing_details - .clone() - .ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "giropay.billing_details", - })? - .get_billing_name()?, - country_code: country.ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "giropay.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() { - ShippingPreference::SetProvidedAddress - } else { - ShippingPreference::GetFromFile + })) + } + domain::BankRedirectData::Giropay { .. } => { + Ok(PaymentSourceItem::Giropay(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() { + ShippingPreference::SetProvidedAddress + } else { + ShippingPreference::GetFromFile + }, + user_action: Some(UserAction::PayNow), }, - user_action: Some(UserAction::PayNow), - }, - })), - domain::BankRedirectData::Ideal { - billing_details, - bank_name: _, - country, - } => Ok(PaymentSourceItem::IDeal(RedirectRequest { - name: billing_details - .clone() - .ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "ideal.billing_details", - })? - .get_billing_name()?, - country_code: country.ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "ideal.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() { - ShippingPreference::SetProvidedAddress - } else { - ShippingPreference::GetFromFile + })) + } + domain::BankRedirectData::Ideal { bank_name: _, .. } => { + Ok(PaymentSourceItem::IDeal(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() { + ShippingPreference::SetProvidedAddress + } else { + ShippingPreference::GetFromFile + }, + user_action: Some(UserAction::PayNow), }, - user_action: Some(UserAction::PayNow), - }, - })), + })) + } domain::BankRedirectData::Sofort { - country, preferred_language: _, - billing_details, } => Ok(PaymentSourceItem::Sofort(RedirectRequest { - name: billing_details - .clone() - .ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "sofort.billing_details", - })? - .get_billing_name()?, - country_code: country.ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "sofort.country", - })?, + 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(), @@ -1199,6 +1163,7 @@ impl .clone() .or(Some(item.response.id)), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -1304,6 +1269,7 @@ impl purchase_units.map_or(item.response.id, |item| item.invoice_id.clone()), ), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -1341,6 +1307,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -1391,6 +1358,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -1459,6 +1427,7 @@ impl .clone() .or(Some(item.response.supplementary_data.related_ids.order_id)), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -1794,6 +1763,7 @@ impl TryFrom> .invoice_id .or(Some(item.response.id)), incremental_authorization_allowed: None, + charge_id: None, }), amount_captured: Some(amount_captured), ..item.data @@ -1845,6 +1815,7 @@ impl .invoice_id .or(Some(item.response.id)), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/payu/transformers.rs b/crates/router/src/connector/payu/transformers.rs index 575f199b7306..5365fc74ef2e 100644 --- a/crates/router/src/connector/payu/transformers.rs +++ b/crates/router/src/connector/payu/transformers.rs @@ -211,6 +211,7 @@ impl .ext_order_id .or(Some(item.response.order_id)), incremental_authorization_allowed: None, + charge_id: None, }), amount_captured: None, ..item.data @@ -264,6 +265,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), amount_captured: None, ..item.data @@ -350,6 +352,7 @@ impl .ext_order_id .or(Some(item.response.order_id)), incremental_authorization_allowed: None, + charge_id: None, }), amount_captured: None, ..item.data @@ -484,6 +487,7 @@ impl .clone() .or(Some(order.order_id.clone())), incremental_authorization_allowed: None, + charge_id: None, }), amount_captured: Some( order diff --git a/crates/router/src/connector/placetopay/transformers.rs b/crates/router/src/connector/placetopay/transformers.rs index e400dc9b3998..ef6bde8f1a21 100644 --- a/crates/router/src/connector/placetopay/transformers.rs +++ b/crates/router/src/connector/placetopay/transformers.rs @@ -279,6 +279,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/powertranz/transformers.rs b/crates/router/src/connector/powertranz/transformers.rs index 6b0675b16110..e47e0f512068 100644 --- a/crates/router/src/connector/powertranz/transformers.rs +++ b/crates/router/src/connector/powertranz/transformers.rs @@ -337,6 +337,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.order_identifier), incremental_authorization_allowed: None, + charge_id: None, }), Err, ); diff --git a/crates/router/src/connector/prophetpay/transformers.rs b/crates/router/src/connector/prophetpay/transformers.rs index 7a8cd6e14d04..6862c7c4c807 100644 --- a/crates/router/src/connector/prophetpay/transformers.rs +++ b/crates/router/src/connector/prophetpay/transformers.rs @@ -207,6 +207,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -406,6 +407,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -456,6 +458,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -506,6 +509,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/rapyd/transformers.rs b/crates/router/src/connector/rapyd/transformers.rs index d949a30a011c..128fca65a927 100644 --- a/crates/router/src/connector/rapyd/transformers.rs +++ b/crates/router/src/connector/rapyd/transformers.rs @@ -480,6 +480,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ) } diff --git a/crates/router/src/connector/riskified.rs b/crates/router/src/connector/riskified.rs index c7cb321f7198..28678bbd9392 100644 --- a/crates/router/src/connector/riskified.rs +++ b/crates/router/src/connector/riskified.rs @@ -2,19 +2,24 @@ pub mod transformers; use std::fmt::Debug; #[cfg(feature = "frm")] -use common_utils::request::RequestContent; -use error_stack::{report, ResultExt}; +use base64::Engine; +#[cfg(feature = "frm")] +use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; +#[cfg(feature = "frm")] +use error_stack::ResultExt; +#[cfg(feature = "frm")] use masking::{ExposeInterface, PeekInterface}; +#[cfg(feature = "frm")] use ring::hmac; +#[cfg(feature = "frm")] use transformers as riskified; #[cfg(feature = "frm")] -use super::utils::FrmTransactionRouterDataRequest; +use super::utils::{self as connector_utils, FrmTransactionRouterDataRequest}; use crate::{ configs::settings, core::errors::{self, CustomResult}, - headers, - services::{self, request, ConnectorIntegration, ConnectorValidation}, + services::{self, ConnectorIntegration, ConnectorValidation}, types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, @@ -22,8 +27,13 @@ use crate::{ }; #[cfg(feature = "frm")] use crate::{ + consts, events::connector_api_logs::ConnectorEvent, - types::{api::fraud_check as frm_api, fraud_check as frm_types, ErrorResponse, Response}, + headers, + services::request, + types::{ + api::fraud_check as frm_api, domain, fraud_check as frm_types, ErrorResponse, Response, + }, utils::BytesExt, }; @@ -31,6 +41,7 @@ use crate::{ pub struct Riskified; impl Riskified { + #[cfg(feature = "frm")] pub fn generate_authorization_signature( &self, auth: &riskified::RiskifiedAuthType, @@ -53,6 +64,7 @@ impl ConnectorCommonExt for Ri where Self: ConnectorIntegration, { + #[cfg(feature = "frm")] fn build_headers( &self, req: &types::RouterData, @@ -122,7 +134,7 @@ impl ConnectorCommon for Riskified { Ok(ErrorResponse { status_code: res.status_code, attempt_status: None, - code: crate::consts::NO_ERROR_CODE.to_string(), + code: consts::NO_ERROR_CODE.to_string(), message: response.error.message.clone(), reason: None, connector_transaction_id: None, @@ -268,11 +280,7 @@ impl self.base_url(connectors), "/checkout_denied" )), - Some(true) => Ok(format!("{}{}", self.base_url(connectors), "/decision")), - None => Err(errors::ConnectorError::FlowNotSupported { - flow: "Transaction".to_owned(), - connector: req.connector.to_string(), - })?, + _ => Ok(format!("{}{}", self.base_url(connectors), "/decision")), } } @@ -286,14 +294,10 @@ impl let req_obj = riskified::TransactionFailedRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(req_obj))) } - Some(true) => { + _ => { let req_obj = riskified::TransactionSuccessRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(req_obj))) } - None => Err(errors::ConnectorError::FlowNotSupported { - flow: "Transaction".to_owned(), - connector: req.connector.to_owned(), - })?, } } @@ -545,26 +549,101 @@ impl frm_api::FraudCheckFulfillment for Riskified {} #[cfg(feature = "frm")] impl frm_api::FraudCheckRecordReturn for Riskified {} +#[cfg(feature = "frm")] #[async_trait::async_trait] impl api::IncomingWebhook for Riskified { - fn get_webhook_object_reference_id( + fn get_webhook_source_verification_algorithm( &self, _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Ok(Box::new(crypto::HmacSha256)) + } + + fn get_webhook_source_verification_signature( + &self, + request: &api::IncomingWebhookRequestDetails<'_>, + _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, + ) -> CustomResult, errors::ConnectorError> { + let header_value = + connector_utils::get_header_key_value("x-riskified-hmac-sha256", request.headers)?; + Ok(header_value.as_bytes().to_vec()) + } + + async fn verify_webhook_source( + &self, + request: &api::IncomingWebhookRequestDetails<'_>, + merchant_account: &domain::MerchantAccount, + merchant_connector_account: domain::MerchantConnectorAccount, + connector_label: &str, + ) -> CustomResult { + let connector_webhook_secrets = self + .get_webhook_source_verification_merchant_secret( + merchant_account, + connector_label, + merchant_connector_account, + ) + .await + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; + + let signature = self + .get_webhook_source_verification_signature(request, &connector_webhook_secrets) + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; + + let message = self + .get_webhook_source_verification_message( + request, + &merchant_account.merchant_id, + &connector_webhook_secrets, + ) + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; + + let signing_key = hmac::Key::new(hmac::HMAC_SHA256, &connector_webhook_secrets.secret); + let signed_message = hmac::sign(&signing_key, &message); + let payload_sign = consts::BASE64_ENGINE.encode(signed_message.as_ref()); + Ok(payload_sign.as_bytes().eq(&signature)) + } + + fn get_webhook_source_verification_message( + &self, + request: &api::IncomingWebhookRequestDetails<'_>, + _merchant_id: &str, + _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, + ) -> CustomResult, errors::ConnectorError> { + Ok(request.body.to_vec()) + } + + fn get_webhook_object_reference_id( + &self, + request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let resource: riskified::RiskifiedWebhookBody = request + .body + .parse_struct("RiskifiedWebhookBody") + .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; + Ok(api::webhooks::ObjectReferenceId::PaymentId( + api_models::payments::PaymentIdType::PaymentAttemptId(resource.id), + )) } fn get_webhook_event_type( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let resource: riskified::RiskifiedWebhookBody = request + .body + .parse_struct("RiskifiedWebhookBody") + .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?; + Ok(api::IncomingWebhookEvent::from(resource.status)) } fn get_webhook_resource_object( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let resource: riskified::RiskifiedWebhookBody = request + .body + .parse_struct("RiskifiedWebhookBody") + .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?; + Ok(Box::new(resource)) } } diff --git a/crates/router/src/connector/riskified/transformers/api.rs b/crates/router/src/connector/riskified/transformers/api.rs index f20b6103cc8b..f0d47274d138 100644 --- a/crates/router/src/connector/riskified/transformers/api.rs +++ b/crates/router/src/connector/riskified/transformers/api.rs @@ -11,7 +11,7 @@ use crate::{ }, core::{errors, fraud_check::types as core_types}, types::{ - self, api::Fulfillment, fraud_check as frm_types, storage::enums as storage_enums, + self, api, api::Fulfillment, fraud_check as frm_types, storage::enums as storage_enums, ResponseId, ResponseRouterData, }, }; @@ -142,8 +142,9 @@ impl TryFrom<&frm_types::FrmCheckoutRouterData> for RiskifiedPaymentsCheckoutReq field_name: "frm_metadata", })? .parse_value("Riskified Metadata") - .change_context(errors::ConnectorError::RequestEncodingFailed)?; - + .change_context(errors::ConnectorError::InvalidDataFormat { + field_name: "frm_metadata", + })?; let billing_address = payment_data.get_billing()?; let shipping_address = payment_data.get_shipping_address_with_phone_number()?; let address = payment_data.get_billing_address()?; @@ -606,3 +607,25 @@ fn get_fulfillment_status( core_types::FulfillmentStatus::PARTIAL | core_types::FulfillmentStatus::REPLACEMENT => None, } } + +#[derive(Debug, Clone, Deserialize, Serialize)] + +pub struct RiskifiedWebhookBody { + pub id: String, + pub status: RiskifiedWebhookStatus, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub enum RiskifiedWebhookStatus { + Approved, + Declined, +} + +impl From for api::IncomingWebhookEvent { + fn from(value: RiskifiedWebhookStatus) -> Self { + match value { + RiskifiedWebhookStatus::Declined => Self::FrmRejected, + RiskifiedWebhookStatus::Approved => Self::FrmApproved, + } + } +} diff --git a/crates/router/src/connector/shift4.rs b/crates/router/src/connector/shift4.rs index e7487e795b67..6c54c7019c83 100644 --- a/crates/router/src/connector/shift4.rs +++ b/crates/router/src/connector/shift4.rs @@ -25,6 +25,7 @@ use crate::{ types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, + transformers::ForeignFrom, ErrorResponse, }, utils::BytesExt, @@ -229,7 +230,7 @@ impl ConnectorIntegration = Box::new(&Self); - let init_data = &types::PaymentsInitRouterData::from(( + let init_data = &types::PaymentsInitRouterData::foreign_from(( &router_data.to_owned(), router_data.request.clone(), )); diff --git a/crates/router/src/connector/shift4/transformers.rs b/crates/router/src/connector/shift4/transformers.rs index 30f327c700e8..7a8bc2587f99 100644 --- a/crates/router/src/connector/shift4/transformers.rs +++ b/crates/router/src/connector/shift4/transformers.rs @@ -677,6 +677,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -719,6 +720,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.id), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/signifyd.rs b/crates/router/src/connector/signifyd.rs index 1c045b8da011..9e591a336f16 100644 --- a/crates/router/src/connector/signifyd.rs +++ b/crates/router/src/connector/signifyd.rs @@ -1,16 +1,23 @@ pub mod transformers; use std::fmt::Debug; +#[cfg(feature = "frm")] use base64::Engine; #[cfg(feature = "frm")] -use common_utils::request::RequestContent; -use error_stack::{report, ResultExt}; +use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; +#[cfg(feature = "frm")] +use error_stack::ResultExt; +#[cfg(feature = "frm")] use masking::PeekInterface; +#[cfg(feature = "frm")] +use ring::hmac; +#[cfg(feature = "frm")] use transformers as signifyd; +#[cfg(feature = "frm")] +use super::utils as connector_utils; use crate::{ configs::settings, - consts, core::errors::{self, CustomResult}, headers, services::{self, request, ConnectorIntegration, ConnectorValidation}, @@ -21,8 +28,11 @@ use crate::{ }; #[cfg(feature = "frm")] use crate::{ + consts, events::connector_api_logs::ConnectorEvent, - types::{api::fraud_check as frm_api, fraud_check as frm_types, ErrorResponse, Response}, + types::{ + api::fraud_check as frm_api, domain, fraud_check as frm_types, ErrorResponse, Response, + }, utils::BytesExt, }; @@ -61,6 +71,7 @@ impl ConnectorCommon for Signifyd { connectors.signifyd.base_url.as_ref() } + #[cfg(feature = "frm")] fn get_auth_header( &self, auth_type: &types::ConnectorAuthType, @@ -646,26 +657,101 @@ impl } } +#[cfg(feature = "frm")] #[async_trait::async_trait] impl api::IncomingWebhook for Signifyd { - fn get_webhook_object_reference_id( + fn get_webhook_source_verification_algorithm( &self, _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Ok(Box::new(crypto::HmacSha256)) + } + + fn get_webhook_source_verification_signature( + &self, + request: &api::IncomingWebhookRequestDetails<'_>, + _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, + ) -> CustomResult, errors::ConnectorError> { + let header_value = + connector_utils::get_header_key_value("x-signifyd-sec-hmac-sha256", request.headers)?; + Ok(header_value.as_bytes().to_vec()) + } + + fn get_webhook_source_verification_message( + &self, + request: &api::IncomingWebhookRequestDetails<'_>, + _merchant_id: &str, + _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, + ) -> CustomResult, errors::ConnectorError> { + Ok(request.body.to_vec()) + } + + async fn verify_webhook_source( + &self, + request: &api::IncomingWebhookRequestDetails<'_>, + merchant_account: &domain::MerchantAccount, + merchant_connector_account: domain::MerchantConnectorAccount, + connector_label: &str, + ) -> CustomResult { + let connector_webhook_secrets = self + .get_webhook_source_verification_merchant_secret( + merchant_account, + connector_label, + merchant_connector_account, + ) + .await + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; + + let signature = self + .get_webhook_source_verification_signature(request, &connector_webhook_secrets) + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; + + let message = self + .get_webhook_source_verification_message( + request, + &merchant_account.merchant_id, + &connector_webhook_secrets, + ) + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; + + let signing_key = hmac::Key::new(hmac::HMAC_SHA256, &connector_webhook_secrets.secret); + let signed_message = hmac::sign(&signing_key, &message); + let payload_sign = consts::BASE64_ENGINE.encode(signed_message.as_ref()); + Ok(payload_sign.as_bytes().eq(&signature)) + } + + fn get_webhook_object_reference_id( + &self, + request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let resource: signifyd::SignifydWebhookBody = request + .body + .parse_struct("SignifydWebhookBody") + .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; + Ok(api::webhooks::ObjectReferenceId::PaymentId( + api_models::payments::PaymentIdType::PaymentAttemptId(resource.order_id), + )) } fn get_webhook_event_type( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let resource: signifyd::SignifydWebhookBody = request + .body + .parse_struct("SignifydWebhookBody") + .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?; + Ok(api::IncomingWebhookEvent::from(resource.review_disposition)) } fn get_webhook_resource_object( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + request: &api::IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let resource: signifyd::SignifydWebhookBody = request + .body + .parse_struct("SignifydWebhookBody") + .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?; + Ok(Box::new(resource)) } } diff --git a/crates/router/src/connector/signifyd/transformers/api.rs b/crates/router/src/connector/signifyd/transformers/api.rs index b56c7f7011ef..9ffae8a02de6 100644 --- a/crates/router/src/connector/signifyd/transformers/api.rs +++ b/crates/router/src/connector/signifyd/transformers/api.rs @@ -1,6 +1,7 @@ use bigdecimal::ToPrimitive; use common_utils::{ext_traits::ValueExt, pii::Email}; use error_stack::{self, ResultExt}; +pub use hyperswitch_domain_models::router_request_types::fraud_check::RefundMethod; use masking::Secret; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -13,8 +14,8 @@ use crate::{ }, core::{errors, fraud_check::types as core_types}, types::{ - self, api::Fulfillment, fraud_check as frm_types, storage::enums as storage_enums, - ResponseId, ResponseRouterData, + self, api, api::Fulfillment, fraud_check as frm_types, storage::enums as storage_enums, + transformers::ForeignFrom, ResponseId, ResponseRouterData, }, }; @@ -162,7 +163,9 @@ impl TryFrom<&frm_types::FrmSaleRouterData> for SignifydPaymentsSaleRequest { field_name: "frm_metadata", })? .parse_value("Signifyd Frm Metadata") - .change_context(errors::ConnectorError::RequestEncodingFailed)?; + .change_context(errors::ConnectorError::InvalidDataFormat { + field_name: "frm_metadata", + })?; let ship_address = item.get_shipping_address()?; let billing_address = item.get_billing()?; let street_addr = ship_address.get_line1()?; @@ -397,7 +400,9 @@ impl TryFrom<&frm_types::FrmCheckoutRouterData> for SignifydPaymentsCheckoutRequ field_name: "frm_metadata", })? .parse_value("Signifyd Frm Metadata") - .change_context(errors::ConnectorError::RequestEncodingFailed)?; + .change_context(errors::ConnectorError::InvalidDataFormat { + field_name: "frm_metadata", + })?; let ship_address = item.get_shipping_address()?; let street_addr = ship_address.get_line1()?; let city_addr = ship_address.get_city()?; @@ -505,7 +510,7 @@ impl TryFrom<&frm_types::FrmFulfillmentRouterData> for FrmFulfillmentSignifydReq .fulfillment_status .as_ref() .map(|fulfillment_status| FulfillmentStatus::from(&fulfillment_status.clone())), - fulfillments: Vec::::from(&item.request.fulfillment_req), + fulfillments: Vec::::foreign_from(&item.request.fulfillment_req), }) } } @@ -521,8 +526,8 @@ impl From<&core_types::FulfillmentStatus> for FulfillmentStatus { } } -impl From<&core_types::FrmFulfillmentRequest> for Vec { - fn from(fulfillment_req: &core_types::FrmFulfillmentRequest) -> Self { +impl ForeignFrom<&core_types::FrmFulfillmentRequest> for Vec { + fn foreign_from(fulfillment_req: &core_types::FrmFulfillmentRequest) -> Self { fulfillment_req .fulfillments .iter() @@ -639,15 +644,6 @@ pub struct SignifydPaymentsRecordReturnRequest { refund: SignifydRefund, } -#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)] -#[serde_with::skip_serializing_none] -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] -pub enum RefundMethod { - StoreCredit, - OriginalPaymentInstrument, - NewPaymentInstrument, -} - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde_with::skip_serializing_none] #[serde(rename_all = "camelCase")] @@ -703,3 +699,27 @@ impl TryFrom<&frm_types::FrmRecordReturnRouterData> for SignifydPaymentsRecordRe }) } } + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] + +pub struct SignifydWebhookBody { + pub order_id: String, + pub review_disposition: ReviewDisposition, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum ReviewDisposition { + Fraudulent, + Good, +} + +impl From for api::IncomingWebhookEvent { + fn from(value: ReviewDisposition) -> Self { + match value { + ReviewDisposition::Fraudulent => Self::FrmRejected, + ReviewDisposition::Good => Self::FrmApproved, + } + } +} diff --git a/crates/router/src/connector/square.rs b/crates/router/src/connector/square.rs index 915a9a9ca114..f9a81031b07a 100644 --- a/crates/router/src/connector/square.rs +++ b/crates/router/src/connector/square.rs @@ -27,6 +27,7 @@ use crate::{ types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, + transformers::ForeignFrom, ErrorResponse, Response, }, utils::BytesExt, @@ -214,7 +215,7 @@ impl amount: router_data.request.amount, }; - let authorize_data = &types::PaymentsAuthorizeSessionTokenRouterData::from(( + let authorize_data = &types::PaymentsAuthorizeSessionTokenRouterData::foreign_from(( &router_data.to_owned(), authorize_session_token_data, )); diff --git a/crates/router/src/connector/square/transformers.rs b/crates/router/src/connector/square/transformers.rs index c980a4e74946..184b18243624 100644 --- a/crates/router/src/connector/square/transformers.rs +++ b/crates/router/src/connector/square/transformers.rs @@ -258,12 +258,12 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for SquarePaymentsRequest { let pm_token = item.get_payment_method_token()?; Ok(Self { idempotency_key: Secret::new(item.attempt_id.clone()), - source_id: Secret::new(match pm_token { + source_id: match pm_token { types::PaymentMethodToken::Token(token) => token, types::PaymentMethodToken::ApplePayDecrypt(_) => Err( unimplemented_payment_method!("Apple Pay", "Simplified", "Square"), )?, - }), + }, amount_money: SquarePaymentsAmountData { amount: item.request.amount, currency: item.request.currency, @@ -381,6 +381,7 @@ impl network_txn_id: None, connector_response_reference_id: item.response.payment.reference_id, incremental_authorization_allowed: None, + charge_id: None, }), amount_captured, ..item.data diff --git a/crates/router/src/connector/stax/transformers.rs b/crates/router/src/connector/stax/transformers.rs index 95e5dbe4f4f9..d3072654028e 100644 --- a/crates/router/src/connector/stax/transformers.rs +++ b/crates/router/src/connector/stax/transformers.rs @@ -67,12 +67,12 @@ impl TryFrom<&StaxRouterData<&types::PaymentsAuthorizeRouterData>> for StaxPayme total, is_refundable: true, pre_auth, - payment_method_id: Secret::new(match pm_token { + payment_method_id: match pm_token { types::PaymentMethodToken::Token(token) => token, types::PaymentMethodToken::ApplePayDecrypt(_) => Err( unimplemented_payment_method!("Apple Pay", "Simplified", "Stax"), )?, - }), + }, idempotency_id: Some(item.router_data.connector_request_reference_id.clone()), }) } @@ -86,12 +86,12 @@ impl TryFrom<&StaxRouterData<&types::PaymentsAuthorizeRouterData>> for StaxPayme total, is_refundable: true, pre_auth, - payment_method_id: Secret::new(match pm_token { + payment_method_id: match pm_token { types::PaymentMethodToken::Token(token) => token, types::PaymentMethodToken::ApplePayDecrypt(_) => Err( unimplemented_payment_method!("Apple Pay", "Simplified", "Stax"), )?, - }), + }, idempotency_id: Some(item.router_data.connector_request_reference_id.clone()), }) } @@ -360,6 +360,7 @@ impl item.response.idempotency_id.unwrap_or(item.response.id), ), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) diff --git a/crates/router/src/connector/stripe.rs b/crates/router/src/connector/stripe.rs index 900ebd40bb08..febb039768c7 100644 --- a/crates/router/src/connector/stripe.rs +++ b/crates/router/src/connector/stripe.rs @@ -136,7 +136,7 @@ impl ConnectorValidation for Stripe { fn validate_mandate_payment( &self, pm_type: Option, - pm_data: types::domain::payments::PaymentMethodData, + pm_data: domain::payments::PaymentMethodData, ) -> CustomResult<(), errors::ConnectorError> { let mandate_supported_pmd = std::collections::HashSet::from([ PaymentMethodDataType::Card, @@ -877,6 +877,21 @@ impl )]; let mut api_key = self.get_auth_header(&req.connector_auth_type)?; header.append(&mut api_key); + + req.request + .charges + .as_ref() + .map(|charge| match &charge.charge_type { + api::enums::PaymentChargeType::Stripe(stripe_charge) => { + if stripe_charge == &api::enums::StripeChargeType::Direct { + let mut customer_account_header = vec![( + headers::STRIPE_COMPATIBLE_CONNECT_ACCOUNT.to_string(), + charge.transfer_account_id.clone().into_masked(), + )]; + header.append(&mut customer_account_header); + } + } + }); Ok(header) } @@ -1355,6 +1370,21 @@ impl services::ConnectorIntegration { + if stripe_charge == &api::enums::StripeChargeType::Direct { + let mut customer_account_header = vec![( + headers::STRIPE_COMPATIBLE_CONNECT_ACCOUNT.to_string(), + charge.transfer_account_id.clone().into_masked(), + )]; + header.append(&mut customer_account_header); + } + } + }); Ok(header) } @@ -1375,8 +1405,13 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_req = stripe::RefundRequest::try_from(req)?; - Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) + let request_body = match req.request.charges.as_ref() { + None => RequestContent::FormUrlEncoded(Box::new(stripe::RefundRequest::try_from(req)?)), + Some(_) => RequestContent::FormUrlEncoded(Box::new( + stripe::ChargeRefundRequest::try_from(req)?, + )), + }; + Ok(request_body) } fn build_request( diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index 1e58a144b9db..55b0d7f4675e 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -22,8 +22,7 @@ pub use self::connect::*; use crate::{ collect_missing_value_keys, connector::utils::{ - self as connector_util, ApplePay, ApplePayDecrypt, BankRedirectBillingData, - PaymentsPreProcessingData, RouterData, + self as connector_util, ApplePay, ApplePayDecrypt, PaymentsPreProcessingData, RouterData, }, consts, core::errors, @@ -158,6 +157,18 @@ pub struct PaymentIntentRequest { pub expand: Option, #[serde(flatten)] pub browser_info: Option, + #[serde(flatten)] + pub charges: Option, +} + +#[derive(Debug, Eq, PartialEq, Serialize)] +pub struct IntentCharges { + pub application_fee_amount: i64, + #[serde( + rename = "transfer_data[destination]", + skip_serializing_if = "Option::is_none" + )] + pub destination_account_id: Option, } // Field rename is required only in case of serialization as it is passed in the request to the connector. @@ -1057,106 +1068,6 @@ impl From<&domain::BankDebitData> for StripePaymentMethodType { } } -impl TryFrom<(&domain::BankRedirectData, Option)> for StripeBillingAddress { - type Error = error_stack::Report; - - fn try_from( - (bank_redirection_data, is_customer_initiated_mandate_payment): ( - &domain::BankRedirectData, - Option, - ), - ) -> Result { - match bank_redirection_data { - domain::BankRedirectData::Eps { - billing_details, .. - } => Ok({ - let billing_data = billing_details.clone().ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "billing_details", - }, - )?; - Self { - name: Some(BankRedirectBillingData::get_billing_name(&billing_data)?), - ..Self::default() - } - }), - domain::BankRedirectData::Giropay { - billing_details, .. - } => Ok(Self { - name: Some( - billing_details - .clone() - .ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "giropay.billing_details", - })? - .get_billing_name()?, - ), - ..Self::default() - }), - domain::BankRedirectData::Ideal { - billing_details, .. - } => Ok(get_stripe_sepa_dd_mandate_billing_details( - billing_details, - is_customer_initiated_mandate_payment, - )?), - domain::BankRedirectData::Przelewy24 { - billing_details, .. - } => Ok(Self { - email: billing_details.email.clone(), - ..Self::default() - }), - domain::BankRedirectData::BancontactCard { - billing_details, .. - } => { - let billing_details = billing_details.as_ref().ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "billing_details", - }, - )?; - Ok(Self { - name: Some( - billing_details - .billing_name - .as_ref() - .ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "billing_details.billing_name", - })? - .to_owned(), - ), - email: Some( - billing_details - .email - .as_ref() - .ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "billing_details.email", - })? - .to_owned(), - ), - ..Self::default() - }) - } - domain::BankRedirectData::Sofort { - billing_details, .. - } => Ok(get_stripe_sepa_dd_mandate_billing_details( - billing_details, - is_customer_initiated_mandate_payment, - )?), - - domain::BankRedirectData::Bizum {} - | domain::BankRedirectData::Blik { .. } - | domain::BankRedirectData::Interac { .. } - | domain::BankRedirectData::OnlineBankingCzechRepublic { .. } - | domain::BankRedirectData::OnlineBankingFinland { .. } - | domain::BankRedirectData::OnlineBankingPoland { .. } - | domain::BankRedirectData::OnlineBankingSlovakia { .. } - | domain::BankRedirectData::Trustly { .. } - | domain::BankRedirectData::OnlineBankingFpx { .. } - | domain::BankRedirectData::OnlineBankingThailand { .. } - | domain::BankRedirectData::OpenBankingUk { .. } => Ok(Self::default()), - } - } -} - fn get_bank_debit_data( bank_debit_data: &domain::BankDebitData, ) -> (StripePaymentMethodType, BankDebitData) { @@ -1242,12 +1153,19 @@ fn create_stripe_payment_method( )) } domain::PaymentMethodData::BankRedirect(bank_redirect_data) => { - let billing_address = StripeBillingAddress::try_from(( + let billing_address = if is_customer_initiated_mandate_payment == Some(true) { + mandatory_parameters_for_sepa_bank_debit_mandates( + &Some(billing_address.to_owned()), + is_customer_initiated_mandate_payment, + )? + } else { + billing_address + }; + let pm_type = StripePaymentMethodType::try_from(bank_redirect_data)?; + let bank_redirect_data = StripePaymentMethodData::try_from(( bank_redirect_data, - is_customer_initiated_mandate_payment, + Some(billing_address.to_owned()), ))?; - let pm_type = StripePaymentMethodType::try_from(bank_redirect_data)?; - let bank_redirect_data = StripePaymentMethodData::try_from(bank_redirect_data)?; Ok((bank_redirect_data, Some(pm_type), billing_address)) } @@ -1530,9 +1448,16 @@ impl TryFrom<(&domain::WalletData, Option)> for Strip } } -impl TryFrom<&domain::BankRedirectData> for StripePaymentMethodData { +impl TryFrom<(&domain::BankRedirectData, Option)> + for StripePaymentMethodData +{ type Error = error_stack::Report; - fn try_from(bank_redirect_data: &domain::BankRedirectData) -> Result { + fn try_from( + (bank_redirect_data, billing_address): ( + &domain::BankRedirectData, + Option, + ), + ) -> Result { let payment_method_data_type = StripePaymentMethodType::try_from(bank_redirect_data)?; match bank_redirect_data { domain::BankRedirectData::BancontactCard { .. } => Ok(Self::BankRedirect( @@ -1586,15 +1511,15 @@ impl TryFrom<&domain::BankRedirectData> for StripePaymentMethodData { )) } domain::BankRedirectData::Sofort { - country, - preferred_language, - .. + preferred_language, .. } => Ok(Self::BankRedirect(StripeBankRedirectData::StripeSofort( Box::new(StripeSofort { payment_method_data_type, - country: country.ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "sofort.country", - })?, + country: billing_address + .and_then(|billing_data| billing_data.country) + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "billing_address.country", + })?, preferred_language: preferred_language.clone(), }), ))), @@ -1828,7 +1753,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest { }; Some(StripePaymentMethodData::Wallet( StripeWallet::ApplepayPayment(ApplepayPayment { - token: Secret::new(payment_method_token), + token: payment_method_token, payment_method_types: StripePaymentMethodType::Card, }), )) @@ -1913,6 +1838,25 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest { None }; + let (charges, customer) = match &item.request.charges { + Some(charges) => { + let charges = match &charges.charge_type { + api_enums::PaymentChargeType::Stripe(charge_type) => match charge_type { + api_enums::StripeChargeType::Direct => Some(IntentCharges { + application_fee_amount: charges.fees, + destination_account_id: None, + }), + api_enums::StripeChargeType::Destination => Some(IntentCharges { + application_fee_amount: charges.fees, + destination_account_id: Some(charges.transfer_account_id.clone()), + }), + }, + }; + (charges, None) + } + None => (None, item.connector_customer.to_owned().map(Secret::new)), + }; + Ok(Self { amount: item.request.amount, //hopefully we don't loose some cents here currency: item.request.currency.to_string(), //we need to copy the value and not transfer ownership @@ -1932,13 +1876,14 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest { payment_data, payment_method_options, payment_method, - customer: item.connector_customer.to_owned().map(Secret::new), + customer, setup_mandate_details, off_session: item.request.off_session, setup_future_usage: item.request.setup_future_usage, payment_method_types, expand: Some(ExpandableObjects::LatestCharge), browser_info, + charges, }) } } @@ -2437,6 +2382,14 @@ impl item.response.id.clone(), )) } else { + let charge_id = item + .response + .latest_charge + .as_ref() + .map(|charge| match charge { + StripeChargeEnum::ChargeId(charge_id) => charge_id.clone(), + StripeChargeEnum::ChargeObject(charge) => charge.id.clone(), + }); Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()), redirection_data, @@ -2445,6 +2398,7 @@ impl network_txn_id, connector_response_reference_id: Some(item.response.id), incremental_authorization_allowed: None, + charge_id, }) }; @@ -2620,6 +2574,14 @@ impl }), _ => None, }; + let charge_id = item + .response + .latest_charge + .as_ref() + .map(|charge| match charge { + StripeChargeEnum::ChargeId(charge_id) => charge_id.clone(), + StripeChargeEnum::ChargeObject(charge) => charge.id.clone(), + }); Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()), redirection_data, @@ -2628,6 +2590,7 @@ impl network_txn_id: network_transaction_id, connector_response_reference_id: Some(item.response.id.clone()), incremental_authorization_allowed: None, + charge_id, }) }; @@ -2701,6 +2664,7 @@ impl network_txn_id: network_transaction_id, connector_response_reference_id: Some(item.response.id), incremental_authorization_allowed: None, + charge_id: None, }) }; @@ -2904,6 +2868,50 @@ impl TryFrom<&types::RefundsRouterData> for RefundRequest { } } +#[derive(Debug, Serialize)] +pub struct ChargeRefundRequest { + pub charge: String, + pub refund_application_fee: Option, + pub reverse_transfer: Option, + pub amount: Option, //amount in cents, hence passed as integer + #[serde(flatten)] + pub meta_data: StripeMetadata, +} + +impl TryFrom<&types::RefundsRouterData> for ChargeRefundRequest { + type Error = error_stack::Report; + fn try_from(item: &types::RefundsRouterData) -> Result { + let amount = item.request.refund_amount; + match item.request.charges.as_ref() { + None => Err(errors::ConnectorError::MissingRequiredField { + field_name: "charges", + } + .into()), + Some(charges) => { + let (refund_application_fee, reverse_transfer) = match charges.options { + types::ChargeRefundsOptions::Direct(types::DirectChargeRefund { + revert_platform_fee, + }) => (Some(revert_platform_fee), None), + types::ChargeRefundsOptions::Destination(types::DestinationChargeRefund { + revert_platform_fee, + revert_transfer, + }) => (Some(revert_platform_fee), Some(revert_transfer)), + }; + Ok(Self { + charge: charges.charge_id.clone(), + refund_application_fee, + reverse_transfer, + amount: Some(amount), + meta_data: StripeMetadata { + order_id: Some(item.request.refund_id.clone()), + is_refund_id_as_reference: Some("true".to_string()), + }, + }) + } + } + } +} + // Type definition for Stripe Refund Response #[derive(Default, Debug, Serialize, Deserialize, Clone)] @@ -3050,7 +3058,7 @@ pub struct StripeShippingAddress { pub phone: Option>, } -#[derive(Debug, Default, Eq, PartialEq, Serialize)] +#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize)] pub struct StripeBillingAddress { #[serde(rename = "payment_method_data[billing_details][email]")] pub email: Option, @@ -3335,8 +3343,9 @@ impl TryFrom { - Ok(Self::try_from(bank_redirect_data)?) + Ok(Self::try_from((bank_redirect_data, None))?) } domain::PaymentMethodData::Wallet(ref wallet_data) => { Ok(Self::try_from((wallet_data, None))?) @@ -3786,13 +3795,13 @@ pub struct Evidence { } // Mandates for bank redirects - ideal and sofort happens through sepa direct debit in stripe -fn get_stripe_sepa_dd_mandate_billing_details( - billing_details: &Option, +fn mandatory_parameters_for_sepa_bank_debit_mandates( + billing_details: &Option, is_customer_initiated_mandate_payment: Option, ) -> Result { let billing_name = billing_details .clone() - .and_then(|billing_data| billing_data.billing_name.clone()); + .and_then(|billing_data| billing_data.name.clone()); let billing_email = billing_details .clone() diff --git a/crates/router/src/connector/threedsecureio.rs b/crates/router/src/connector/threedsecureio.rs index 77fda3e864e8..75a746648f10 100644 --- a/crates/router/src/connector/threedsecureio.rs +++ b/crates/router/src/connector/threedsecureio.rs @@ -211,6 +211,7 @@ impl api::IncomingWebhook for Threedsecureio { } impl api::ConnectorPreAuthentication for Threedsecureio {} +impl api::ConnectorPreAuthenticationVersionCall for Threedsecureio {} impl api::ExternalAuthentication for Threedsecureio {} impl api::ConnectorAuthentication for Threedsecureio {} impl api::ConnectorPostAuthentication for Threedsecureio {} @@ -409,10 +410,6 @@ impl data: data.clone(), http_code: res.status_code, }) - // Ok(types::authentication::PreAuthNRouterData { - // response, - // ..data.clone() - // }) } fn get_error_response( @@ -527,3 +524,12 @@ impl self.build_error_response(res, event_builder) } } + +impl + ConnectorIntegration< + api::PreAuthenticationVersionCall, + types::authentication::PreAuthNRequestData, + types::authentication::AuthenticationResponseData, + > for Threedsecureio +{ +} diff --git a/crates/router/src/connector/threedsecureio/transformers.rs b/crates/router/src/connector/threedsecureio/transformers.rs index d6a77fef31eb..fe26e509c120 100644 --- a/crates/router/src/connector/threedsecureio/transformers.rs +++ b/crates/router/src/connector/threedsecureio/transformers.rs @@ -108,6 +108,7 @@ impl ) .change_context(errors::ConnectorError::ParsingFailed)?, connector_metadata: Some(connector_metadata), + directory_server_id: None, }, ) } diff --git a/crates/router/src/connector/trustpay/transformers.rs b/crates/router/src/connector/trustpay/transformers.rs index db225585f412..f6c494170f25 100644 --- a/crates/router/src/connector/trustpay/transformers.rs +++ b/crates/router/src/connector/trustpay/transformers.rs @@ -735,6 +735,7 @@ fn handle_cards_response( network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }; Ok((status, error, payment_response_data)) } @@ -764,6 +765,7 @@ fn handle_bank_redirects_response( network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }; Ok((status, error, payment_response_data)) } @@ -797,6 +799,7 @@ fn handle_bank_redirects_error_response( network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }; Ok((status, error, payment_response_data)) } @@ -857,6 +860,7 @@ fn handle_bank_redirects_sync_response( network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }; Ok((status, error, payment_response_data)) } @@ -904,6 +908,7 @@ pub fn handle_webhook_response( network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }; Ok((status, error, payment_response_data)) } diff --git a/crates/router/src/connector/tsys/transformers.rs b/crates/router/src/connector/tsys/transformers.rs index dfc7e61c0004..2febb9c72ec1 100644 --- a/crates/router/src/connector/tsys/transformers.rs +++ b/crates/router/src/connector/tsys/transformers.rs @@ -218,6 +218,7 @@ fn get_payments_response(connector_response: TsysResponse) -> types::PaymentsRes network_txn_id: None, connector_response_reference_id: Some(connector_response.transaction_id), incremental_authorization_allowed: None, + charge_id: None, } } @@ -242,6 +243,7 @@ fn get_payments_sync_response( .clone(), ), incremental_authorization_allowed: None, + charge_id: None, } } diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index 14d856bb09f8..8ff7d350f24a 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -10,7 +10,9 @@ use base64::Engine; use common_utils::{ date_time, errors::ReportSwitchExt, + ext_traits::StringExt, pii::{self, Email, IpAddress}, + types::MinorUnit, }; use diesel_models::enums; use error_stack::{report, ResultExt}; @@ -27,11 +29,13 @@ use crate::{ consts, core::{ errors::{self, ApiErrorResponse, CustomResult}, - payments::{types::AuthenticationData, PaymentData, RecurringMandatePaymentData}, + payments::{types::AuthenticationData, PaymentData}, }, pii::PeekInterface, types::{ - self, api, domain, storage::enums as storage_enums, transformers::ForeignTryFrom, + self, api, domain, + storage::enums as storage_enums, + transformers::{ForeignFrom, ForeignTryFrom}, ApplePayPredecryptData, BrowserInformation, PaymentsCancelData, ResponseId, }, utils::{OptionExt, ValueExt}, @@ -86,7 +90,9 @@ pub trait RouterData { fn get_customer_id(&self) -> Result; fn get_connector_customer_id(&self) -> Result; fn get_preprocessing_id(&self) -> Result; - fn get_recurring_mandate_payment_data(&self) -> Result; + fn get_recurring_mandate_payment_data( + &self, + ) -> Result; #[cfg(feature = "payouts")] fn get_payout_method_data(&self) -> Result; #[cfg(feature = "payouts")] @@ -131,7 +137,7 @@ where { match self.status { enums::AttemptStatus::Voided => { - if payment_data.payment_intent.amount_captured > Some(0) { + if payment_data.payment_intent.amount_captured > Some(MinorUnit::new(0)) { enums::AttemptStatus::PartialCharged } else { self.status @@ -141,7 +147,7 @@ where let captured_amount = types::Capturable::get_captured_amount(&self.request, payment_data); let total_capturable_amount = payment_data.payment_attempt.get_total_amount(); - if Some(total_capturable_amount) == captured_amount { + if Some(total_capturable_amount) == captured_amount.map(MinorUnit::new) { enums::AttemptStatus::Charged } else if captured_amount.is_some() { enums::AttemptStatus::PartialCharged @@ -411,7 +417,9 @@ impl RouterData for types::RouterData Result { + fn get_recurring_mandate_payment_data( + &self, + ) -> Result { self.recurring_mandate_payment_data .to_owned() .ok_or_else(missing_field_err("recurring_mandate_payment_data")) @@ -685,23 +693,27 @@ impl PaymentsAuthorizeRequestData for types::PaymentsAuthorizeData { fn get_original_amount(&self) -> i64 { self.surcharge_details .as_ref() - .map(|surcharge_details| surcharge_details.original_amount) + .map(|surcharge_details| surcharge_details.original_amount.get_amount_as_i64()) .unwrap_or(self.amount) } fn get_surcharge_amount(&self) -> Option { self.surcharge_details .as_ref() - .map(|surcharge_details| surcharge_details.surcharge_amount) + .map(|surcharge_details| surcharge_details.surcharge_amount.get_amount_as_i64()) } fn get_tax_on_surcharge_amount(&self) -> Option { - self.surcharge_details - .as_ref() - .map(|surcharge_details| surcharge_details.tax_on_surcharge_amount) + self.surcharge_details.as_ref().map(|surcharge_details| { + surcharge_details + .tax_on_surcharge_amount + .get_amount_as_i64() + }) } fn get_total_surcharge_amount(&self) -> Option { - self.surcharge_details - .as_ref() - .map(|surcharge_details| surcharge_details.get_total_surcharge_amount()) + self.surcharge_details.as_ref().map(|surcharge_details| { + surcharge_details + .get_total_surcharge_amount() + .get_amount_as_i64() + }) } fn is_customer_initiated_mandate_payment(&self) -> bool { @@ -1508,18 +1520,6 @@ impl AddressDetailsData for api::AddressDetails { } } -pub trait BankRedirectBillingData { - fn get_billing_name(&self) -> Result, Error>; -} - -impl BankRedirectBillingData for domain::BankRedirectBilling { - fn get_billing_name(&self) -> Result, Error> { - self.billing_name - .clone() - .ok_or_else(missing_field_err("billing_details.billing_name")) - } -} - pub trait MandateData { fn get_end_date(&self, format: date_time::DateFormat) -> Result; fn get_metadata(&self) -> Result; @@ -1545,7 +1545,7 @@ pub trait RecurringMandateData { fn get_original_payment_currency(&self) -> Result; } -impl RecurringMandateData for RecurringMandatePaymentData { +impl RecurringMandateData for types::RecurringMandatePaymentData { fn get_original_payment_amount(&self) -> Result { self.original_payment_authorized_amount .ok_or_else(missing_field_err("original_payment_authorized_amount")) @@ -1795,72 +1795,80 @@ pub fn get_webhook_merchant_secret_key(connector_label: &str, merchant_id: &str) impl ForeignTryFrom for UsStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - 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", + 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()), + } } - .into()), } } } @@ -1868,26 +1876,33 @@ impl ForeignTryFrom for UsStatesAbbreviation { impl ForeignTryFrom for CanadaStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - 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", + 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()), + } } - .into()), } } } @@ -1970,7 +1985,9 @@ where status: capture_sync_response.get_capture_attempt_status(), connector_response_reference_id: capture_sync_response .get_connector_reference_id(), - amount: capture_sync_response.get_amount_captured(), + amount: capture_sync_response + .get_amount_captured() + .map(MinorUnit::new), }, ); } @@ -2181,7 +2198,7 @@ pub fn is_refund_failure(status: enums::RefundStatus) -> bool { } impl - From<( + ForeignFrom<( Option, Option, Option, @@ -2190,7 +2207,7 @@ impl Option, )> for types::ErrorResponse { - fn from( + fn foreign_from( (code, message, reason, http_code, attempt_status, connector_transaction_id): ( Option, Option, @@ -2429,205 +2446,174 @@ pub enum PaymentMethodDataType { LocalBankTransfer, } -impl From for PaymentMethodDataType { - fn from(pm_data: types::domain::payments::PaymentMethodData) -> Self { +impl From for PaymentMethodDataType { + fn from(pm_data: domain::payments::PaymentMethodData) -> Self { match pm_data { - types::domain::payments::PaymentMethodData::Card(_) => Self::Card, - types::domain::payments::PaymentMethodData::CardRedirect(card_redirect_data) => { + domain::payments::PaymentMethodData::Card(_) => Self::Card, + domain::payments::PaymentMethodData::CardRedirect(card_redirect_data) => { match card_redirect_data { - types::domain::CardRedirectData::Knet {} => Self::Knet, - types::domain::payments::CardRedirectData::Benefit {} => Self::Benefit, - types::domain::payments::CardRedirectData::MomoAtm {} => Self::MomoAtm, - types::domain::payments::CardRedirectData::CardRedirect {} => { - Self::CardRedirect - } + domain::CardRedirectData::Knet {} => Self::Knet, + domain::payments::CardRedirectData::Benefit {} => Self::Benefit, + domain::payments::CardRedirectData::MomoAtm {} => Self::MomoAtm, + domain::payments::CardRedirectData::CardRedirect {} => Self::CardRedirect, } } - types::domain::payments::PaymentMethodData::Wallet(wallet_data) => match wallet_data { - types::domain::payments::WalletData::AliPayQr(_) => Self::AliPayQr, - types::domain::payments::WalletData::AliPayRedirect(_) => Self::AliPayRedirect, - types::domain::payments::WalletData::AliPayHkRedirect(_) => Self::AliPayHkRedirect, - types::domain::payments::WalletData::MomoRedirect(_) => Self::MomoRedirect, - types::domain::payments::WalletData::KakaoPayRedirect(_) => Self::KakaoPayRedirect, - types::domain::payments::WalletData::GoPayRedirect(_) => Self::GoPayRedirect, - types::domain::payments::WalletData::GcashRedirect(_) => Self::GcashRedirect, - types::domain::payments::WalletData::ApplePay(_) => Self::ApplePay, - types::domain::payments::WalletData::ApplePayRedirect(_) => Self::ApplePayRedirect, - types::domain::payments::WalletData::ApplePayThirdPartySdk(_) => { + domain::payments::PaymentMethodData::Wallet(wallet_data) => match wallet_data { + domain::payments::WalletData::AliPayQr(_) => Self::AliPayQr, + domain::payments::WalletData::AliPayRedirect(_) => Self::AliPayRedirect, + domain::payments::WalletData::AliPayHkRedirect(_) => Self::AliPayHkRedirect, + domain::payments::WalletData::MomoRedirect(_) => Self::MomoRedirect, + domain::payments::WalletData::KakaoPayRedirect(_) => Self::KakaoPayRedirect, + domain::payments::WalletData::GoPayRedirect(_) => Self::GoPayRedirect, + domain::payments::WalletData::GcashRedirect(_) => Self::GcashRedirect, + domain::payments::WalletData::ApplePay(_) => Self::ApplePay, + domain::payments::WalletData::ApplePayRedirect(_) => Self::ApplePayRedirect, + domain::payments::WalletData::ApplePayThirdPartySdk(_) => { Self::ApplePayThirdPartySdk } - types::domain::payments::WalletData::DanaRedirect {} => Self::DanaRedirect, - types::domain::payments::WalletData::GooglePay(_) => Self::GooglePay, - types::domain::payments::WalletData::GooglePayRedirect(_) => { - Self::GooglePayRedirect - } - types::domain::payments::WalletData::GooglePayThirdPartySdk(_) => { + domain::payments::WalletData::DanaRedirect {} => Self::DanaRedirect, + domain::payments::WalletData::GooglePay(_) => Self::GooglePay, + domain::payments::WalletData::GooglePayRedirect(_) => Self::GooglePayRedirect, + domain::payments::WalletData::GooglePayThirdPartySdk(_) => { Self::GooglePayThirdPartySdk } - types::domain::payments::WalletData::MbWayRedirect(_) => Self::MbWayRedirect, - types::domain::payments::WalletData::MobilePayRedirect(_) => { - Self::MobilePayRedirect - } - types::domain::payments::WalletData::PaypalRedirect(_) => Self::PaypalRedirect, - types::domain::payments::WalletData::PaypalSdk(_) => Self::PaypalSdk, - types::domain::payments::WalletData::SamsungPay(_) => Self::SamsungPay, - types::domain::payments::WalletData::TwintRedirect {} => Self::TwintRedirect, - types::domain::payments::WalletData::VippsRedirect {} => Self::VippsRedirect, - types::domain::payments::WalletData::TouchNGoRedirect(_) => Self::TouchNGoRedirect, - types::domain::payments::WalletData::WeChatPayRedirect(_) => { - Self::WeChatPayRedirect - } - types::domain::payments::WalletData::WeChatPayQr(_) => Self::WeChatPayQr, - types::domain::payments::WalletData::CashappQr(_) => Self::CashappQr, - types::domain::payments::WalletData::SwishQr(_) => Self::SwishQr, + domain::payments::WalletData::MbWayRedirect(_) => Self::MbWayRedirect, + domain::payments::WalletData::MobilePayRedirect(_) => Self::MobilePayRedirect, + domain::payments::WalletData::PaypalRedirect(_) => Self::PaypalRedirect, + domain::payments::WalletData::PaypalSdk(_) => Self::PaypalSdk, + domain::payments::WalletData::SamsungPay(_) => Self::SamsungPay, + domain::payments::WalletData::TwintRedirect {} => Self::TwintRedirect, + domain::payments::WalletData::VippsRedirect {} => Self::VippsRedirect, + domain::payments::WalletData::TouchNGoRedirect(_) => Self::TouchNGoRedirect, + domain::payments::WalletData::WeChatPayRedirect(_) => Self::WeChatPayRedirect, + domain::payments::WalletData::WeChatPayQr(_) => Self::WeChatPayQr, + domain::payments::WalletData::CashappQr(_) => Self::CashappQr, + domain::payments::WalletData::SwishQr(_) => Self::SwishQr, }, - types::domain::payments::PaymentMethodData::PayLater(pay_later_data) => { - match pay_later_data { - types::domain::payments::PayLaterData::KlarnaRedirect { .. } => { - Self::KlarnaRedirect - } - types::domain::payments::PayLaterData::KlarnaSdk { .. } => Self::KlarnaSdk, - types::domain::payments::PayLaterData::AffirmRedirect {} => { - Self::AffirmRedirect - } - types::domain::payments::PayLaterData::AfterpayClearpayRedirect { .. } => { - Self::AfterpayClearpayRedirect - } - types::domain::payments::PayLaterData::PayBrightRedirect {} => { - Self::PayBrightRedirect - } - types::domain::payments::PayLaterData::WalleyRedirect {} => { - Self::WalleyRedirect - } - types::domain::payments::PayLaterData::AlmaRedirect {} => Self::AlmaRedirect, - types::domain::payments::PayLaterData::AtomeRedirect {} => Self::AtomeRedirect, + domain::payments::PaymentMethodData::PayLater(pay_later_data) => match pay_later_data { + domain::payments::PayLaterData::KlarnaRedirect { .. } => Self::KlarnaRedirect, + domain::payments::PayLaterData::KlarnaSdk { .. } => Self::KlarnaSdk, + domain::payments::PayLaterData::AffirmRedirect {} => Self::AffirmRedirect, + domain::payments::PayLaterData::AfterpayClearpayRedirect { .. } => { + Self::AfterpayClearpayRedirect } - } - types::domain::payments::PaymentMethodData::BankRedirect(bank_redirect_data) => { + domain::payments::PayLaterData::PayBrightRedirect {} => Self::PayBrightRedirect, + domain::payments::PayLaterData::WalleyRedirect {} => Self::WalleyRedirect, + domain::payments::PayLaterData::AlmaRedirect {} => Self::AlmaRedirect, + domain::payments::PayLaterData::AtomeRedirect {} => Self::AtomeRedirect, + }, + domain::payments::PaymentMethodData::BankRedirect(bank_redirect_data) => { match bank_redirect_data { - types::domain::payments::BankRedirectData::BancontactCard { .. } => { + domain::payments::BankRedirectData::BancontactCard { .. } => { Self::BancontactCard } - types::domain::payments::BankRedirectData::Bizum {} => Self::Bizum, - types::domain::payments::BankRedirectData::Blik { .. } => Self::Blik, - types::domain::payments::BankRedirectData::Eps { .. } => Self::Eps, - types::domain::payments::BankRedirectData::Giropay { .. } => Self::Giropay, - types::domain::payments::BankRedirectData::Ideal { .. } => Self::Ideal, - types::domain::payments::BankRedirectData::Interac { .. } => Self::Interac, - types::domain::payments::BankRedirectData::OnlineBankingCzechRepublic { - .. - } => Self::OnlineBankingCzechRepublic, - types::domain::payments::BankRedirectData::OnlineBankingFinland { .. } => { + domain::payments::BankRedirectData::Bizum {} => Self::Bizum, + domain::payments::BankRedirectData::Blik { .. } => Self::Blik, + domain::payments::BankRedirectData::Eps { .. } => Self::Eps, + domain::payments::BankRedirectData::Giropay { .. } => Self::Giropay, + domain::payments::BankRedirectData::Ideal { .. } => Self::Ideal, + domain::payments::BankRedirectData::Interac { .. } => Self::Interac, + domain::payments::BankRedirectData::OnlineBankingCzechRepublic { .. } => { + Self::OnlineBankingCzechRepublic + } + domain::payments::BankRedirectData::OnlineBankingFinland { .. } => { Self::OnlineBankingFinland } - types::domain::payments::BankRedirectData::OnlineBankingPoland { .. } => { + domain::payments::BankRedirectData::OnlineBankingPoland { .. } => { Self::OnlineBankingPoland } - types::domain::payments::BankRedirectData::OnlineBankingSlovakia { .. } => { + domain::payments::BankRedirectData::OnlineBankingSlovakia { .. } => { Self::OnlineBankingSlovakia } - types::domain::payments::BankRedirectData::OpenBankingUk { .. } => { - Self::OpenBankingUk - } - types::domain::payments::BankRedirectData::Przelewy24 { .. } => { - Self::Przelewy24 - } - types::domain::payments::BankRedirectData::Sofort { .. } => Self::Sofort, - types::domain::payments::BankRedirectData::Trustly { .. } => Self::Trustly, - types::domain::payments::BankRedirectData::OnlineBankingFpx { .. } => { + domain::payments::BankRedirectData::OpenBankingUk { .. } => Self::OpenBankingUk, + domain::payments::BankRedirectData::Przelewy24 { .. } => Self::Przelewy24, + domain::payments::BankRedirectData::Sofort { .. } => Self::Sofort, + domain::payments::BankRedirectData::Trustly { .. } => Self::Trustly, + domain::payments::BankRedirectData::OnlineBankingFpx { .. } => { Self::OnlineBankingFpx } - types::domain::payments::BankRedirectData::OnlineBankingThailand { .. } => { + domain::payments::BankRedirectData::OnlineBankingThailand { .. } => { Self::OnlineBankingThailand } } } - types::domain::payments::PaymentMethodData::BankDebit(bank_debit_data) => { + domain::payments::PaymentMethodData::BankDebit(bank_debit_data) => { match bank_debit_data { - types::domain::payments::BankDebitData::AchBankDebit { .. } => { - Self::AchBankDebit - } - types::domain::payments::BankDebitData::SepaBankDebit { .. } => { - Self::SepaBankDebit - } - types::domain::payments::BankDebitData::BecsBankDebit { .. } => { - Self::BecsBankDebit - } - types::domain::payments::BankDebitData::BacsBankDebit { .. } => { - Self::BacsBankDebit - } + domain::payments::BankDebitData::AchBankDebit { .. } => Self::AchBankDebit, + domain::payments::BankDebitData::SepaBankDebit { .. } => Self::SepaBankDebit, + domain::payments::BankDebitData::BecsBankDebit { .. } => Self::BecsBankDebit, + domain::payments::BankDebitData::BacsBankDebit { .. } => Self::BacsBankDebit, } } - types::domain::payments::PaymentMethodData::BankTransfer(bank_transfer_data) => { + domain::payments::PaymentMethodData::BankTransfer(bank_transfer_data) => { match *bank_transfer_data { - types::domain::payments::BankTransferData::AchBankTransfer { .. } => { + domain::payments::BankTransferData::AchBankTransfer { .. } => { Self::AchBankTransfer } - types::domain::payments::BankTransferData::SepaBankTransfer { .. } => { + domain::payments::BankTransferData::SepaBankTransfer { .. } => { Self::SepaBankTransfer } - types::domain::payments::BankTransferData::BacsBankTransfer { .. } => { + domain::payments::BankTransferData::BacsBankTransfer { .. } => { Self::BacsBankTransfer } - types::domain::payments::BankTransferData::MultibancoBankTransfer { - .. - } => Self::MultibancoBankTransfer, - types::domain::payments::BankTransferData::PermataBankTransfer { .. } => { + domain::payments::BankTransferData::MultibancoBankTransfer { .. } => { + Self::MultibancoBankTransfer + } + domain::payments::BankTransferData::PermataBankTransfer { .. } => { Self::PermataBankTransfer } - types::domain::payments::BankTransferData::BcaBankTransfer { .. } => { + domain::payments::BankTransferData::BcaBankTransfer { .. } => { Self::BcaBankTransfer } - types::domain::payments::BankTransferData::BniVaBankTransfer { .. } => { + domain::payments::BankTransferData::BniVaBankTransfer { .. } => { Self::BniVaBankTransfer } - types::domain::payments::BankTransferData::BriVaBankTransfer { .. } => { + domain::payments::BankTransferData::BriVaBankTransfer { .. } => { Self::BriVaBankTransfer } - types::domain::payments::BankTransferData::CimbVaBankTransfer { .. } => { + domain::payments::BankTransferData::CimbVaBankTransfer { .. } => { Self::CimbVaBankTransfer } - types::domain::payments::BankTransferData::DanamonVaBankTransfer { .. } => { + domain::payments::BankTransferData::DanamonVaBankTransfer { .. } => { Self::DanamonVaBankTransfer } - types::domain::payments::BankTransferData::MandiriVaBankTransfer { .. } => { + domain::payments::BankTransferData::MandiriVaBankTransfer { .. } => { Self::MandiriVaBankTransfer } - types::domain::payments::BankTransferData::Pix {} => Self::Pix, - types::domain::payments::BankTransferData::Pse {} => Self::Pse, - types::domain::payments::BankTransferData::LocalBankTransfer { .. } => { + domain::payments::BankTransferData::Pix {} => Self::Pix, + domain::payments::BankTransferData::Pse {} => Self::Pse, + domain::payments::BankTransferData::LocalBankTransfer { .. } => { Self::LocalBankTransfer } } } - types::domain::payments::PaymentMethodData::Crypto(_) => Self::Crypto, - types::domain::payments::PaymentMethodData::MandatePayment => Self::MandatePayment, - types::domain::payments::PaymentMethodData::Reward => Self::Reward, - types::domain::payments::PaymentMethodData::Upi(_) => Self::Upi, - types::domain::payments::PaymentMethodData::Voucher(voucher_data) => match voucher_data - { - types::domain::payments::VoucherData::Boleto(_) => Self::Boleto, - types::domain::payments::VoucherData::Efecty => Self::Efecty, - types::domain::payments::VoucherData::PagoEfectivo => Self::PagoEfectivo, - types::domain::payments::VoucherData::RedCompra => Self::RedCompra, - types::domain::payments::VoucherData::RedPagos => Self::RedPagos, - types::domain::payments::VoucherData::Alfamart(_) => Self::Alfamart, - types::domain::payments::VoucherData::Indomaret(_) => Self::Indomaret, - types::domain::payments::VoucherData::Oxxo => Self::Oxxo, - types::domain::payments::VoucherData::SevenEleven(_) => Self::SevenEleven, - types::domain::payments::VoucherData::Lawson(_) => Self::Lawson, - types::domain::payments::VoucherData::MiniStop(_) => Self::MiniStop, - types::domain::payments::VoucherData::FamilyMart(_) => Self::FamilyMart, - types::domain::payments::VoucherData::Seicomart(_) => Self::Seicomart, - types::domain::payments::VoucherData::PayEasy(_) => Self::PayEasy, + domain::payments::PaymentMethodData::Crypto(_) => Self::Crypto, + domain::payments::PaymentMethodData::MandatePayment => Self::MandatePayment, + domain::payments::PaymentMethodData::Reward => Self::Reward, + domain::payments::PaymentMethodData::Upi(_) => Self::Upi, + domain::payments::PaymentMethodData::Voucher(voucher_data) => match voucher_data { + domain::payments::VoucherData::Boleto(_) => Self::Boleto, + domain::payments::VoucherData::Efecty => Self::Efecty, + domain::payments::VoucherData::PagoEfectivo => Self::PagoEfectivo, + domain::payments::VoucherData::RedCompra => Self::RedCompra, + domain::payments::VoucherData::RedPagos => Self::RedPagos, + domain::payments::VoucherData::Alfamart(_) => Self::Alfamart, + domain::payments::VoucherData::Indomaret(_) => Self::Indomaret, + domain::payments::VoucherData::Oxxo => Self::Oxxo, + domain::payments::VoucherData::SevenEleven(_) => Self::SevenEleven, + domain::payments::VoucherData::Lawson(_) => Self::Lawson, + domain::payments::VoucherData::MiniStop(_) => Self::MiniStop, + domain::payments::VoucherData::FamilyMart(_) => Self::FamilyMart, + domain::payments::VoucherData::Seicomart(_) => Self::Seicomart, + domain::payments::VoucherData::PayEasy(_) => Self::PayEasy, }, - types::domain::payments::PaymentMethodData::GiftCard(gift_card_data) => { + domain::payments::PaymentMethodData::GiftCard(gift_card_data) => { match *gift_card_data { - types::domain::payments::GiftCardData::Givex(_) => Self::Givex, - types::domain::payments::GiftCardData::PaySafeCard {} => Self::PaySafeCar, + domain::payments::GiftCardData::Givex(_) => Self::Givex, + domain::payments::GiftCardData::PaySafeCard {} => Self::PaySafeCar, } } - types::domain::payments::PaymentMethodData::CardToken(_) => Self::CardToken, + domain::payments::PaymentMethodData::CardToken(_) => Self::CardToken, } } } diff --git a/crates/router/src/connector/volt/transformers.rs b/crates/router/src/connector/volt/transformers.rs index 814eefaf5ffb..a78ecc94443b 100644 --- a/crates/router/src/connector/volt/transformers.rs +++ b/crates/router/src/connector/volt/transformers.rs @@ -283,6 +283,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.id), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -364,6 +365,7 @@ impl .merchant_internal_reference .or(Some(payment_response.id)), incremental_authorization_allowed: None, + charge_id: None, }) }, ..item.data @@ -404,6 +406,7 @@ impl .merchant_internal_reference .or(Some(webhook_response.payment)), incremental_authorization_allowed: None, + charge_id: None, }) }, ..item.data diff --git a/crates/router/src/connector/wise.rs b/crates/router/src/connector/wise.rs index 1e2f9bbaf76a..8b01d35a7ae9 100644 --- a/crates/router/src/connector/wise.rs +++ b/crates/router/src/connector/wise.rs @@ -27,7 +27,7 @@ use crate::{ utils::BytesExt, }; #[cfg(feature = "payouts")] -use crate::{core::payments, routes}; +use crate::{core::payments, routes, types::transformers::ForeignFrom}; #[derive(Debug, Clone)] pub struct Wise; @@ -510,7 +510,7 @@ impl services::ConnectorIntegration CustomResult<(), errors::ConnectorError> { // Create a quote let quote_router_data = - &types::PayoutsRouterData::from((&router_data, router_data.request.clone())); + &types::PayoutsRouterData::foreign_from((&router_data, router_data.request.clone())); let quote_connector_integration: Box< &(dyn services::ConnectorIntegration< api::PoQuote, diff --git a/crates/router/src/connector/worldline/transformers.rs b/crates/router/src/connector/worldline/transformers.rs index 795133f602fb..06d608a172e1 100644 --- a/crates/router/src/connector/worldline/transformers.rs +++ b/crates/router/src/connector/worldline/transformers.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use url::Url; use crate::{ - connector::utils::{self, BankRedirectBillingData, CardData, RouterData}, + connector::utils::{self, CardData, RouterData}, core::errors, services, types::{ @@ -234,7 +234,7 @@ impl } domain::PaymentMethodData::BankRedirect(bank_redirect) => { WorldlinePaymentMethod::RedirectPaymentMethodSpecificInput(Box::new( - make_bank_redirect_request(&item.router_data.request, bank_redirect)?, + make_bank_redirect_request(item.router_data, bank_redirect)?, )) } domain::PaymentMethodData::CardRedirect(_) @@ -360,26 +360,19 @@ fn make_card_request( } fn make_bank_redirect_request( - req: &PaymentsAuthorizeData, + req: &types::PaymentsAuthorizeRouterData, bank_redirect: &domain::BankRedirectData, ) -> Result> { - let return_url = req.router_return_url.clone(); + let return_url = req.request.router_return_url.clone(); let redirection_data = RedirectionData { return_url }; let (payment_method_specific_data, payment_product_id) = match bank_redirect { domain::BankRedirectData::Giropay { - billing_details, - bank_account_iban, - .. + bank_account_iban, .. } => ( { PaymentMethodSpecificData::PaymentProduct816SpecificInput(Box::new(Giropay { bank_account_iban: BankAccountIban { - account_holder_name: billing_details - .clone() - .ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "giropay.billing_details", - })? - .get_billing_name()?, + account_holder_name: req.get_billing_full_name()?.to_owned(), iban: bank_account_iban.clone(), }, })) @@ -589,6 +582,7 @@ impl TryFrom TryFrom> for ResponseIdStr { } } -impl TryFrom> for types::ResponseId { +impl ForeignTryFrom> for types::ResponseId { type Error = error_stack::Report; - fn try_from(links: Option) -> Result { + fn foreign_try_from(links: Option) -> Result { get_resource_id(links, Self::ConnectorTransactionId) } } diff --git a/crates/router/src/connector/worldpay/transformers.rs b/crates/router/src/connector/worldpay/transformers.rs index 9c908cf749da..8a61f0f693f3 100644 --- a/crates/router/src/connector/worldpay/transformers.rs +++ b/crates/router/src/connector/worldpay/transformers.rs @@ -9,7 +9,9 @@ use crate::{ connector::utils, consts, core::errors, - types::{self, domain, PaymentsAuthorizeData, PaymentsResponseData}, + types::{ + self, domain, transformers::ForeignTryFrom, PaymentsAuthorizeData, PaymentsResponseData, + }, }; #[derive(Debug, Serialize)] @@ -245,13 +247,14 @@ impl TryFrom> }, description: item.response.description, response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::try_from(item.response.links)?, + resource_id: types::ResponseId::foreign_try_from(item.response.links)?, 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 }) diff --git a/crates/router/src/connector/zen/transformers.rs b/crates/router/src/connector/zen/transformers.rs index 67538b819d55..0f067ab935c6 100644 --- a/crates/router/src/connector/zen/transformers.rs +++ b/crates/router/src/connector/zen/transformers.rs @@ -946,6 +946,7 @@ fn get_zen_response( network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }; Ok((status, error, payment_response_data)) } @@ -989,6 +990,7 @@ impl TryFrom network_txn_id: None, connector_response_reference_id: Some(item.response.mer_ref.clone()), incremental_authorization_allowed: None, + charge_id: None, }), ..item.data }) @@ -425,6 +426,7 @@ impl TryFrom { + if state.conf.as_ref().is_kv_soft_kill_mode() { + Err(errors::ApiErrorResponse::InvalidRequestData { + message: "Kv cannot be enabled when application is in soft_kill_mode" + .to_owned(), + })? + } + db.update_merchant( merchant_account, storage::MerchantAccountUpdate::StorageSchemeUpdate { @@ -1420,6 +1416,36 @@ pub async fn kv_for_merchant( )) } +pub async fn toggle_kv_for_all_merchants( + state: AppState, + enable: bool, +) -> RouterResponse { + let db = state.store.as_ref(); + let storage_scheme = if enable { + MerchantStorageScheme::RedisKv + } else { + MerchantStorageScheme::PostgresOnly + }; + + let total_update = db + .update_all_merchant_account(storage::MerchantAccountUpdate::StorageSchemeUpdate { + storage_scheme, + }) + .await + .map_err(|error| { + error + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to switch merchant_storage_scheme for all merchants") + })?; + + Ok(service_api::ApplicationResponse::Json( + api_models::admin::ToggleAllKVResponse { + total_updated: total_update, + kv_enabled: enable, + }, + )) +} + pub async fn check_merchant_account_kv_status( state: AppState, merchant_id: String, diff --git a/crates/router/src/core/authentication.rs b/crates/router/src/core/authentication.rs index e2f605304a7a..5acd14bb3ee2 100644 --- a/crates/router/src/core/authentication.rs +++ b/crates/router/src/core/authentication.rs @@ -28,7 +28,7 @@ pub async fn perform_authentication( browser_details: Option, business_profile: storage::BusinessProfile, merchant_connector_account: payments_core::helpers::MerchantConnectorAccountType, - amount: Option, + amount: Option, currency: Option, message_category: api::authentication::MessageCategory, device_channel: payments::DeviceChannel, @@ -140,12 +140,41 @@ pub async fn perform_pre_authentication( ) .await?; - let router_data = transformers::construct_pre_authentication_router_data( - authentication_connector_name.clone(), - card_number, - &three_ds_connector_account, - business_profile.merchant_id.clone(), - )?; + let authentication = if authentication_connector.is_separate_version_call_required() { + let router_data: core_types::authentication::PreAuthNVersionCallRouterData = + transformers::construct_pre_authentication_router_data( + authentication_connector_name.clone(), + card_number.clone(), + &three_ds_connector_account, + business_profile.merchant_id.clone(), + )?; + let router_data = utils::do_auth_connector_call( + state, + authentication_connector_name.clone(), + router_data, + ) + .await?; + + let updated_authentication = + utils::update_trackers(state, router_data, authentication, acquirer_details.clone()) + .await?; + // from version call response, we will get to know the maximum supported 3ds version. + // If the version is not greater than or equal to 3DS 2.0, We should not do the successive pre authentication call. + if !updated_authentication.is_separate_authn_required() { + return Ok(updated_authentication); + } + updated_authentication + } else { + authentication + }; + + let router_data: core_types::authentication::PreAuthNRouterData = + transformers::construct_pre_authentication_router_data( + authentication_connector_name.clone(), + card_number, + &three_ds_connector_account, + business_profile.merchant_id.clone(), + )?; let router_data = utils::do_auth_connector_call(state, authentication_connector_name, router_data).await?; diff --git a/crates/router/src/core/authentication/transformers.rs b/crates/router/src/core/authentication/transformers.rs index 9e00dfe70c98..e0e9ee0b5263 100644 --- a/crates/router/src/core/authentication/transformers.rs +++ b/crates/router/src/core/authentication/transformers.rs @@ -32,7 +32,7 @@ pub fn construct_authentication_router_data( billing_address: payments::Address, shipping_address: Option, browser_details: Option, - amount: Option, + amount: Option, currency: Option, message_category: types::api::authentication::MessageCategory, device_channel: payments::DeviceChannel, @@ -61,7 +61,7 @@ pub fn construct_authentication_router_data( billing_address, shipping_address, browser_details, - amount, + amount: amount.map(|amt| amt.get_amount_as_i64()), currency, message_category, device_channel, @@ -109,12 +109,18 @@ pub fn construct_post_authentication_router_data( ) } -pub fn construct_pre_authentication_router_data( +pub fn construct_pre_authentication_router_data( authentication_connector: String, card_holder_account_number: cards::CardNumber, merchant_connector_account: &payments_helpers::MerchantConnectorAccountType, merchant_id: String, -) -> RouterResult { +) -> RouterResult< + types::RouterData< + F, + types::authentication::PreAuthNRequestData, + types::authentication::AuthenticationResponseData, + >, +> { let router_request = types::authentication::PreAuthNRequestData { card_holder_account_number, }; diff --git a/crates/router/src/core/authentication/utils.rs b/crates/router/src/core/authentication/utils.rs index e29c8b10232e..0152fb4321de 100644 --- a/crates/router/src/core/authentication/utils.rs +++ b/crates/router/src/core/authentication/utils.rs @@ -63,6 +63,7 @@ pub async fn update_trackers( three_ds_method_url, message_version, connector_metadata, + directory_server_id, } => storage::AuthenticationUpdate::PreAuthenticationUpdate { threeds_server_transaction_id, maximum_supported_3ds_version, @@ -77,6 +78,7 @@ pub async fn update_trackers( .map(|acquirer_details| acquirer_details.acquirer_bin.clone()), acquirer_merchant_id: acquirer_details .map(|acquirer_details| acquirer_details.acquirer_merchant_id), + directory_server_id, }, AuthenticationResponseData::AuthNResponse { authn_flow_type, @@ -181,6 +183,7 @@ pub async fn create_new_authentication( profile_id, payment_id, merchant_connector_id, + directory_server_id: None, }; state .store diff --git a/crates/router/src/core/blocklist/utils.rs b/crates/router/src/core/blocklist/utils.rs index 7c01a9aca768..bc8724da8235 100644 --- a/crates/router/src/core/blocklist/utils.rs +++ b/crates/router/src/core/blocklist/utils.rs @@ -319,7 +319,7 @@ where { generate_fingerprint( state, - StrongSecret::new(card.card_number.clone().get_card_no()), + StrongSecret::new(card.card_number.get_card_no()), StrongSecret::new(merchant_fingerprint_secret.clone()), api_models::enums::LockerChoice::HyperswitchCardVault, ) @@ -343,7 +343,7 @@ where .as_ref() .and_then(|pm_data| match pm_data { api_models::payments::PaymentMethodData::Card(card) => { - Some(card.card_number.clone().get_card_isin()) + Some(card.card_number.get_card_isin()) } _ => None, }); @@ -355,7 +355,7 @@ where .as_ref() .and_then(|pm_data| match pm_data { api_models::payments::PaymentMethodData::Card(card) => { - Some(card.card_number.clone().get_extended_card_bin()) + Some(card.card_number.get_extended_card_bin()) } _ => None, }); @@ -464,7 +464,7 @@ pub async fn generate_payment_fingerprint( { generate_fingerprint( state, - StrongSecret::new(card.card_number.clone().get_card_no()), + StrongSecret::new(card.card_number.get_card_no()), StrongSecret::new(merchant_fingerprint_secret), api_models::enums::LockerChoice::HyperswitchCardVault, ) diff --git a/crates/router/src/core/customers.rs b/crates/router/src/core/customers.rs index 3b10b408f9e6..0f3d60cccd79 100644 --- a/crates/router/src/core/customers.rs +++ b/crates/router/src/core/customers.rs @@ -116,6 +116,7 @@ pub async fn create_customer( created_at: common_utils::date_time::now(), modified_at: common_utils::date_time::now(), default_payment_method_id: None, + updated_by: None, }) } .await diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs index a70a97e7bf0a..3a8cab8c5161 100644 --- a/crates/router/src/core/errors.rs +++ b/crates/router/src/core/errors.rs @@ -1,4 +1,3 @@ -pub mod api_error_response; pub mod customers_error_response; pub mod error_handlers; pub mod transformers; @@ -11,7 +10,10 @@ use std::fmt::Display; use actix_web::{body::BoxBody, ResponseError}; pub use common_utils::errors::{CustomResult, ParsingError, ValidationError}; use diesel_models::errors as storage_errors; -pub use hyperswitch_domain_models::errors::StorageError as DataStorageError; +pub use hyperswitch_domain_models::errors::{ + api_error_response::{ApiErrorResponse, ErrorType, NotImplementedMessage}, + StorageError as DataStorageError, +}; pub use redis_interface::errors::RedisError; use scheduler::errors as sch_errors; use storage_impl::errors as storage_impl_errors; @@ -19,7 +21,6 @@ use storage_impl::errors as storage_impl_errors; pub use user::*; pub use self::{ - api_error_response::{ApiErrorResponse, NotImplementedMessage}, customers_error_response::CustomersErrorResponse, sch_errors::*, storage_errors::*, diff --git a/crates/router/src/core/errors/api_error_response.rs b/crates/router/src/core/errors/api_error_response.rs deleted file mode 100644 index 0c492f0f089c..000000000000 --- a/crates/router/src/core/errors/api_error_response.rs +++ /dev/null @@ -1,328 +0,0 @@ -#![allow(dead_code, unused_variables)] - -use http::StatusCode; -use scheduler::errors::{PTError, ProcessTrackerError}; - -#[derive(Clone, Debug, serde::Serialize)] -#[serde(rename_all = "snake_case")] -pub enum ErrorType { - InvalidRequestError, - ObjectNotFound, - RouterError, - ProcessingError, - BadGateway, - ServerNotAvailable, - DuplicateRequest, - ValidationError, - ConnectorError, - LockTimeout, -} - -#[allow(dead_code)] -#[derive(Debug, Clone, router_derive::ApiError)] -#[error(error_type_enum = ErrorType)] -pub enum ApiErrorResponse { - #[error(error_type = ErrorType::ServerNotAvailable, code = "IR_00", message = "{message:?}")] - NotImplemented { message: NotImplementedMessage }, - #[error( - error_type = ErrorType::InvalidRequestError, code = "IR_01", - message = "API key not provided or invalid API key used" - )] - Unauthorized, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_02", message = "Unrecognized request URL")] - InvalidRequestUrl, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_03", message = "The HTTP method is not applicable for this API")] - InvalidHttpMethod, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_04", message = "Missing required param: {field_name}")] - MissingRequiredField { field_name: &'static str }, - #[error( - error_type = ErrorType::InvalidRequestError, code = "IR_05", - message = "{field_name} contains invalid data. Expected format is {expected_format}" - )] - InvalidDataFormat { - field_name: String, - expected_format: String, - }, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_06", message = "{message}")] - InvalidRequestData { message: String }, - /// Typically used when a field has invalid value, or deserialization of the value contained in a field fails. - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_07", message = "Invalid value provided: {field_name}")] - InvalidDataValue { field_name: &'static str }, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_08", message = "Client secret was not provided")] - ClientSecretNotGiven, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_08", message = "Client secret has expired")] - ClientSecretExpired, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_09", message = "The client_secret provided does not match the client_secret associated with the Payment")] - ClientSecretInvalid, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_10", message = "Customer has active mandate/subsciption")] - MandateActive, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_11", message = "Customer has already been redacted")] - CustomerRedacted, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_12", message = "Reached maximum refund attempts")] - MaximumRefundCount, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_13", message = "The refund amount exceeds the amount captured")] - RefundAmountExceedsPaymentAmount, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_14", message = "This Payment could not be {current_flow} because it has a {field_name} of {current_value}. The expected state is {states}")] - PaymentUnexpectedState { - current_flow: String, - field_name: String, - current_value: String, - states: String, - }, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_15", message = "Invalid Ephemeral Key for the customer")] - InvalidEphemeralKey, - /// Typically used when information involving multiple fields or previously provided information doesn't satisfy a condition. - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_16", message = "{message}")] - PreconditionFailed { message: String }, - #[error( - error_type = ErrorType::InvalidRequestError, code = "IR_17", - message = "Access forbidden, invalid JWT token was used" - )] - InvalidJwtToken, - #[error( - error_type = ErrorType::InvalidRequestError, code = "IR_18", - message = "{message}", - )] - GenericUnauthorized { message: String }, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_19", message = "{message}")] - NotSupported { message: String }, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_20", message = "{flow} flow not supported by the {connector} connector")] - FlowNotSupported { flow: String, connector: String }, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_21", message = "Missing required params")] - MissingRequiredFields { field_names: Vec<&'static str> }, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_22", message = "Access forbidden. Not authorized to access this resource {resource}")] - AccessForbidden { resource: String }, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_23", message = "{message}")] - FileProviderNotSupported { message: String }, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_23", message = "{message}")] - UnprocessableEntity { message: String }, - #[error( - error_type = ErrorType::ProcessingError, code = "IR_24", - message = "Invalid {wallet_name} wallet token" - )] - InvalidWalletToken { wallet_name: String }, - #[error(error_type = ErrorType::ConnectorError, code = "CE_00", message = "{code}: {message}", ignore = "status_code")] - ExternalConnectorError { - code: String, - message: String, - connector: String, - status_code: u16, - reason: Option, - }, - #[error(error_type = ErrorType::ProcessingError, code = "CE_01", message = "Payment failed during authorization with connector. Retry payment")] - PaymentAuthorizationFailed { data: Option }, - #[error(error_type = ErrorType::ProcessingError, code = "CE_02", message = "Payment failed during authentication with connector. Retry payment")] - PaymentAuthenticationFailed { data: Option }, - #[error(error_type = ErrorType::ProcessingError, code = "CE_03", message = "Capture attempt failed while processing with connector")] - PaymentCaptureFailed { data: Option }, - #[error(error_type = ErrorType::ProcessingError, code = "CE_04", message = "The card data is invalid")] - InvalidCardData { data: Option }, - #[error(error_type = ErrorType::InvalidRequestError, code = "CE_04", message = "Payout validation failed")] - PayoutFailed { data: Option }, - #[error(error_type = ErrorType::ProcessingError, code = "CE_05", message = "The card has expired")] - CardExpired { data: Option }, - #[error(error_type = ErrorType::ProcessingError, code = "CE_06", message = "Refund failed while processing with connector. Retry refund")] - RefundFailed { data: Option }, - #[error(error_type = ErrorType::ProcessingError, code = "CE_07", message = "Verification failed while processing with connector. Retry operation")] - VerificationFailed { data: Option }, - #[error(error_type = ErrorType::ProcessingError, code = "CE_08", message = "Dispute operation failed while processing with connector. Retry operation")] - DisputeFailed { data: Option }, - #[error(error_type = ErrorType::ServerNotAvailable, code = "HE_00", message = "Something went wrong")] - InternalServerError, - #[error(error_type = ErrorType::LockTimeout, code = "HE_00", message = "Resource is busy. Please try again later.")] - ResourceBusy, - #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "Duplicate refund request. Refund already attempted with the refund ID")] - DuplicateRefundRequest, - #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "Duplicate mandate request. Mandate already attempted with the Mandate ID")] - DuplicateMandate, - #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The merchant account with the specified details already exists in our records")] - DuplicateMerchantAccount, - #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The merchant connector account with the specified profile_id '{profile_id}' and connector_label '{connector_label}' already exists in our records")] - DuplicateMerchantConnectorAccount { - profile_id: String, - connector_label: String, - }, - #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The payment method with the specified details already exists in our records")] - DuplicatePaymentMethod, - #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The payment with the specified payment_id already exists in our records")] - DuplicatePayment { payment_id: String }, - #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The payout with the specified payout_id '{payout_id}' already exists in our records")] - DuplicatePayout { payout_id: String }, - #[error(error_type = ErrorType::DuplicateRequest, code = "HE_01", message = "The config with the specified key already exists in our records")] - DuplicateConfig, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Refund does not exist in our records")] - RefundNotFound, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Customer does not exist in our records")] - CustomerNotFound, - #[error(error_type = ErrorType::ObjectNotFound, code = "RE_02", message = "Config key does not exist in our records.")] - ConfigNotFound, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Payment does not exist in our records")] - PaymentNotFound, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Payment method does not exist in our records")] - PaymentMethodNotFound, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Merchant account does not exist in our records")] - MerchantAccountNotFound, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Merchant connector account does not exist in our records")] - MerchantConnectorAccountNotFound { id: String }, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Business profile with the given id '{id}' does not exist in our records")] - BusinessProfileNotFound { id: String }, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Poll with the given id '{id}' does not exist in our records")] - PollNotFound { id: String }, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Resource ID does not exist in our records")] - ResourceIdNotFound, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Mandate does not exist in our records")] - MandateNotFound, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Authentication does not exist in our records")] - AuthenticationNotFound { id: String }, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Failed to update mandate")] - MandateUpdateFailed, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "API Key does not exist in our records")] - ApiKeyNotFound, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Payout does not exist in our records")] - PayoutNotFound, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Event does not exist in our records")] - EventNotFound, - #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "Invalid mandate id passed from connector")] - MandateSerializationFailed, - #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "Unable to parse the mandate identifier passed from connector")] - MandateDeserializationFailed, - #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "Return URL is not configured and not passed in payments request")] - ReturnUrlUnavailable, - #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "This refund is not possible through Hyperswitch. Please raise the refund through {connector} dashboard")] - RefundNotPossible { connector: String }, - #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "Mandate Validation Failed" )] - MandateValidationFailed { reason: String }, - #[error(error_type= ErrorType::ValidationError, code = "HE_03", message = "The payment has not succeeded yet. Please pass a successful payment to initiate refund")] - PaymentNotSucceeded, - #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "The specified merchant connector account is disabled")] - MerchantConnectorAccountDisabled, - #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "{code}: {message}")] - PaymentBlockedError { - code: u16, - message: String, - status: String, - reason: String, - }, - #[error(error_type= ErrorType::ObjectNotFound, code = "HE_04", message = "Successful payment not found for the given payment id")] - SuccessfulPaymentNotFound, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_04", message = "The connector provided in the request is incorrect or not available")] - IncorrectConnectorNameGiven, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_04", message = "Address does not exist in our records")] - AddressNotFound, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_04", message = "Dispute does not exist in our records")] - DisputeNotFound { dispute_id: String }, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_04", message = "File does not exist in our records")] - FileNotFound, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_04", message = "File not available")] - FileNotAvailable, - #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "Dispute status validation failed")] - DisputeStatusValidationFailed { reason: String }, - #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "Card with the provided iin does not exist")] - InvalidCardIin, - #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "The provided card IIN length is invalid, please provide an iin with 6 or 8 digits")] - InvalidCardIinLength, - #[error(error_type = ErrorType::ValidationError, code = "HE_03", message = "File validation failed")] - FileValidationFailed { reason: String }, - #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "File not found / valid in the request")] - MissingFile, - #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "Dispute id not found in the request")] - MissingDisputeId, - #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "File purpose not found in the request or is invalid")] - MissingFilePurpose, - #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "File content type not found / valid")] - MissingFileContentType, - #[error(error_type = ErrorType::InvalidRequestError, code = "HE_05", message = "{message}")] - GenericNotFoundError { message: String }, - #[error(error_type = ErrorType::InvalidRequestError, code = "HE_01", message = "{message}")] - GenericDuplicateError { message: String }, - #[error(error_type = ErrorType::InvalidRequestError, code = "WE_01", message = "Failed to authenticate the webhook")] - WebhookAuthenticationFailed, - #[error(error_type = ErrorType::ObjectNotFound, code = "WE_04", message = "Webhook resource not found")] - WebhookResourceNotFound, - #[error(error_type = ErrorType::InvalidRequestError, code = "WE_02", message = "Bad request received in webhook")] - WebhookBadRequest, - #[error(error_type = ErrorType::RouterError, code = "WE_03", message = "There was some issue processing the webhook")] - WebhookProcessingFailure, - #[error(error_type = ErrorType::InvalidRequestError, code = "HE_04", message = "required payment method is not configured or configured incorrectly for all configured connectors")] - IncorrectPaymentMethodConfiguration, - #[error(error_type = ErrorType::InvalidRequestError, code = "WE_05", message = "Unable to process the webhook body")] - WebhookUnprocessableEntity, - #[error(error_type = ErrorType::ObjectNotFound, code = "HE_02", message = "Payment Link does not exist in our records")] - PaymentLinkNotFound, - #[error(error_type = ErrorType::InvalidRequestError, code = "WE_05", message = "Merchant Secret set my merchant for webhook source verification is invalid")] - WebhookInvalidMerchantSecret, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_19", message = "{message}")] - CurrencyNotSupported { message: String }, - #[error(error_type = ErrorType::ServerNotAvailable, code= "HE_00", message = "{component} health check is failing with error: {message}")] - HealthCheckError { - component: &'static str, - message: String, - }, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_24", message = "Merchant connector account is configured with invalid {config}")] - InvalidConnectorConfiguration { config: String }, - #[error(error_type = ErrorType::ValidationError, code = "HE_01", message = "Failed to convert currency to minor unit")] - CurrencyConversionFailed, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_25", message = "Cannot delete the default payment method")] - PaymentMethodDeleteFailed, - #[error( - error_type = ErrorType::InvalidRequestError, code = "IR_26", - message = "Invalid Cookie" - )] - InvalidCookie, - #[error(error_type = ErrorType::InvalidRequestError, code = "IR_27", message = "Extended card info does not exist")] - ExtendedCardInfoNotFound, -} - -impl PTError for ApiErrorResponse { - fn to_pt_error(&self) -> ProcessTrackerError { - ProcessTrackerError::EApiErrorResponse - } -} - -#[derive(Clone)] -pub enum NotImplementedMessage { - Reason(String), - Default, -} - -impl std::fmt::Debug for NotImplementedMessage { - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Reason(message) => write!(fmt, "{message} is not implemented"), - Self::Default => { - write!( - fmt, - "This API is under development and will be made available soon." - ) - } - } - } -} - -impl ::core::fmt::Display for ApiErrorResponse { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - r#"{{"error":{}}}"#, - serde_json::to_string(self).unwrap_or_else(|_| "API error response".to_string()) - ) - } -} - -impl actix_web::ResponseError for ApiErrorResponse { - fn status_code(&self) -> StatusCode { - common_utils::errors::ErrorSwitch::::switch( - self, - ) - .status_code() - } - - fn error_response(&self) -> actix_web::HttpResponse { - common_utils::errors::ErrorSwitch::::switch( - self, - ) - .error_response() - } -} - -impl crate::services::EmbedError for error_stack::Report {} diff --git a/crates/router/src/core/errors/transformers.rs b/crates/router/src/core/errors/transformers.rs index 880c0d7b20b1..df529f818034 100644 --- a/crates/router/src/core/errors/transformers.rs +++ b/crates/router/src/core/errors/transformers.rs @@ -1,312 +1,7 @@ -use api_models::errors::types::Extra; use common_utils::errors::ErrorSwitch; -use http::StatusCode; +use hyperswitch_domain_models::errors::api_error_response::ApiErrorResponse; -use super::{ApiErrorResponse, ConnectorError, CustomersErrorResponse, StorageError}; - -impl ErrorSwitch for ApiErrorResponse { - fn switch(&self) -> api_models::errors::types::ApiErrorResponse { - use api_models::errors::types::{ApiError, ApiErrorResponse as AER}; - - match self { - Self::NotImplemented { message } => { - AER::NotImplemented(ApiError::new("IR", 0, format!("{message:?}"), None)) - } - Self::Unauthorized => AER::Unauthorized(ApiError::new( - "IR", - 1, - "API key not provided or invalid API key used", None - )), - Self::InvalidRequestUrl => { - AER::NotFound(ApiError::new("IR", 2, "Unrecognized request URL", None)) - } - Self::InvalidHttpMethod => AER::MethodNotAllowed(ApiError::new( - "IR", - 3, - "The HTTP method is not applicable for this API", None - )), - Self::MissingRequiredField { field_name } => AER::BadRequest( - ApiError::new("IR", 4, format!("Missing required param: {field_name}"), None), - ), - Self::InvalidDataFormat { - field_name, - expected_format, - } => AER::Unprocessable(ApiError::new( - "IR", - 5, - format!( - "{field_name} contains invalid data. Expected format is {expected_format}" - ), None - )), - Self::InvalidRequestData { message } => { - AER::Unprocessable(ApiError::new("IR", 6, message.to_string(), None)) - } - Self::InvalidDataValue { field_name } => AER::BadRequest(ApiError::new( - "IR", - 7, - format!("Invalid value provided: {field_name}"), None - )), - Self::ClientSecretNotGiven => AER::BadRequest(ApiError::new( - "IR", - 8, - "client_secret was not provided", None - )), - Self::ClientSecretInvalid => { - AER::BadRequest(ApiError::new("IR", 9, "The client_secret provided does not match the client_secret associated with the Payment", None)) - } - Self::CurrencyNotSupported { message } => { - AER::BadRequest(ApiError::new("IR", 9, message, None)) - } - Self::MandateActive => { - AER::BadRequest(ApiError::new("IR", 10, "Customer has active mandate/subsciption", None)) - } - Self::CustomerRedacted => { - AER::BadRequest(ApiError::new("IR", 11, "Customer has already been redacted", None)) - } - Self::MaximumRefundCount => AER::BadRequest(ApiError::new("IR", 12, "Reached maximum refund attempts", None)), - Self::RefundAmountExceedsPaymentAmount => { - AER::BadRequest(ApiError::new("IR", 13, "The refund amount exceeds the amount captured", None)) - } - Self::PaymentUnexpectedState { - current_flow, - field_name, - current_value, - states, - } => AER::BadRequest(ApiError::new("IR", 14, format!("This Payment could not be {current_flow} because it has a {field_name} of {current_value}. The expected state is {states}"), None)), - Self::InvalidEphemeralKey => AER::Unauthorized(ApiError::new("IR", 15, "Invalid Ephemeral Key for the customer", None)), - Self::PreconditionFailed { message } => { - AER::BadRequest(ApiError::new("IR", 16, message.to_string(), None)) - } - Self::InvalidJwtToken => AER::Unauthorized(ApiError::new("IR", 17, "Access forbidden, invalid JWT token was used", None)), - Self::GenericUnauthorized { message } => { - AER::Unauthorized(ApiError::new("IR", 18, message.to_string(), None)) - }, - Self::ClientSecretExpired => AER::BadRequest(ApiError::new( - "IR", - 19, - "The provided client_secret has expired", None - )), - Self::MissingRequiredFields { field_names } => AER::BadRequest( - ApiError::new("IR", 21, "Missing required params".to_string(), Some(Extra {data: Some(serde_json::json!(field_names)), ..Default::default() })), - ), - Self::AccessForbidden {resource} => { - AER::ForbiddenCommonResource(ApiError::new("IR", 22, format!("Access forbidden. Not authorized to access this resource {resource}"), None)) - }, - Self::FileProviderNotSupported { message } => { - AER::BadRequest(ApiError::new("IR", 23, message.to_string(), None)) - }, - Self::UnprocessableEntity {message} => AER::Unprocessable(ApiError::new("IR", 23, message.to_string(), None)), - Self::InvalidWalletToken { wallet_name} => AER::Unprocessable(ApiError::new( - "IR", - 24, - format!("Invalid {wallet_name} wallet token"), None - )), - Self::ExternalConnectorError { - code, - message, - connector, - reason, - status_code, - } => AER::ConnectorError(ApiError::new("CE", 0, format!("{code}: {message}"), Some(Extra {connector: Some(connector.clone()), reason: reason.to_owned().map(Into::into), ..Default::default()})), StatusCode::from_u16(*status_code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)), - Self::PaymentAuthorizationFailed { data } => { - AER::BadRequest(ApiError::new("CE", 1, "Payment failed during authorization with connector. Retry payment", Some(Extra { data: data.clone(), ..Default::default()}))) - } - Self::PaymentAuthenticationFailed { data } => { - AER::BadRequest(ApiError::new("CE", 2, "Payment failed during authentication with connector. Retry payment", Some(Extra { data: data.clone(), ..Default::default()}))) - } - Self::PaymentCaptureFailed { data } => { - AER::BadRequest(ApiError::new("CE", 3, "Capture attempt failed while processing with connector", Some(Extra { data: data.clone(), ..Default::default()}))) - } - Self::DisputeFailed { data } => { - AER::BadRequest(ApiError::new("CE", 1, "Dispute operation failed while processing with connector. Retry operation", Some(Extra { data: data.clone(), ..Default::default()}))) - } - Self::InvalidCardData { data } => AER::BadRequest(ApiError::new("CE", 4, "The card data is invalid", Some(Extra { data: data.clone(), ..Default::default()}))), - Self::CardExpired { data } => AER::BadRequest(ApiError::new("CE", 5, "The card has expired", Some(Extra { data: data.clone(), ..Default::default()}))), - Self::RefundFailed { data } => AER::BadRequest(ApiError::new("CE", 6, "Refund failed while processing with connector. Retry refund", Some(Extra { data: data.clone(), ..Default::default()}))), - Self::VerificationFailed { data } => { - AER::BadRequest(ApiError::new("CE", 7, "Verification failed while processing with connector. Retry operation", Some(Extra { data: data.clone(), ..Default::default()}))) - }, - Self::MandateUpdateFailed | Self::MandateSerializationFailed | Self::MandateDeserializationFailed | Self::InternalServerError => { - AER::InternalServerError(ApiError::new("HE", 0, "Something went wrong", None)) - }, - Self::HealthCheckError { message,component } => { - AER::InternalServerError(ApiError::new("HE",0,format!("{} health check failed with error: {}",component,message),None)) - }, - Self::PayoutFailed { data } => { - AER::BadRequest(ApiError::new("CE", 4, "Payout failed while processing with connector.", Some(Extra { data: data.clone(), ..Default::default()}))) - }, - Self::DuplicateRefundRequest => AER::BadRequest(ApiError::new("HE", 1, "Duplicate refund request. Refund already attempted with the refund ID", None)), - Self::DuplicateMandate => AER::BadRequest(ApiError::new("HE", 1, "Duplicate mandate request. Mandate already attempted with the Mandate ID", None)), - Self::DuplicateMerchantAccount => AER::BadRequest(ApiError::new("HE", 1, "The merchant account with the specified details already exists in our records", None)), - Self::DuplicateMerchantConnectorAccount { profile_id, connector_label: connector_name } => { - AER::BadRequest(ApiError::new("HE", 1, format!("The merchant connector account with the specified profile_id '{profile_id}' and connector_label '{connector_name}' already exists in our records"), None)) - } - Self::DuplicatePaymentMethod => AER::BadRequest(ApiError::new("HE", 1, "The payment method with the specified details already exists in our records", None)), - Self::DuplicatePayment { payment_id } => { - AER::BadRequest(ApiError::new("HE", 1, "The payment with the specified payment_id already exists in our records", Some(Extra {reason: Some(format!("{payment_id} already exists")), ..Default::default()}))) - } - Self::DuplicatePayout { payout_id } => { - AER::BadRequest(ApiError::new("HE", 1, format!("The payout with the specified payout_id '{payout_id}' already exists in our records"), None)) - } - Self::GenericDuplicateError { message } => { - AER::BadRequest(ApiError::new("HE", 1, message, None)) - } - Self::RefundNotFound => { - AER::NotFound(ApiError::new("HE", 2, "Refund does not exist in our records.", None)) - } - Self::CustomerNotFound => { - AER::NotFound(ApiError::new("HE", 2, "Customer does not exist in our records", None)) - } - Self::ConfigNotFound => { - AER::NotFound(ApiError::new("HE", 2, "Config key does not exist in our records.", None)) - }, - Self::DuplicateConfig => { - AER::BadRequest(ApiError::new("HE", 1, "The config with the specified key already exists in our records", None)) - } - Self::PaymentNotFound => { - AER::NotFound(ApiError::new("HE", 2, "Payment does not exist in our records", None)) - } - Self::PaymentMethodNotFound => { - AER::NotFound(ApiError::new("HE", 2, "Payment method does not exist in our records", None)) - } - Self::MerchantAccountNotFound => { - AER::NotFound(ApiError::new("HE", 2, "Merchant account does not exist in our records", None)) - } - Self::MerchantConnectorAccountNotFound {id } => { - AER::NotFound(ApiError::new("HE", 2, "Merchant connector account does not exist in our records", Some(Extra {reason: Some(format!("{id} does not exist")), ..Default::default()}))) - } - Self::MerchantConnectorAccountDisabled => { - AER::BadRequest(ApiError::new("HE", 3, "The selected merchant connector account is disabled", None)) - } - Self::ResourceIdNotFound => { - AER::NotFound(ApiError::new("HE", 2, "Resource ID does not exist in our records", None)) - } - Self::MandateNotFound => { - AER::NotFound(ApiError::new("HE", 2, "Mandate does not exist in our records", None)) - } - Self::PayoutNotFound => { - AER::NotFound(ApiError::new("HE", 2, "Payout does not exist in our records", None)) - } - Self::EventNotFound => { - AER::NotFound(ApiError::new("HE", 2, "Event does not exist in our records", None)) - } - Self::ReturnUrlUnavailable => AER::NotFound(ApiError::new("HE", 3, "Return URL is not configured and not passed in payments request", None)), - Self::RefundNotPossible { connector } => { - AER::BadRequest(ApiError::new("HE", 3, format!("This refund is not possible through Hyperswitch. Please raise the refund through {connector} dashboard"), None)) - } - Self::MandateValidationFailed { reason } => { - AER::BadRequest(ApiError::new("HE", 3, "Mandate Validation Failed", Some(Extra { reason: Some(reason.to_owned()), ..Default::default() }))) - } - Self::PaymentNotSucceeded => AER::BadRequest(ApiError::new("HE", 3, "The payment has not succeeded yet. Please pass a successful payment to initiate refund", None)), - Self::PaymentBlockedError { - message, - reason, - .. - } => AER::DomainError(ApiError::new("HE", 3, message, Some(Extra { reason: Some(reason.clone()), ..Default::default() }))), - Self::SuccessfulPaymentNotFound => { - AER::NotFound(ApiError::new("HE", 4, "Successful payment not found for the given payment id", None)) - } - Self::IncorrectConnectorNameGiven => { - AER::NotFound(ApiError::new("HE", 4, "The connector provided in the request is incorrect or not available", None)) - } - Self::AddressNotFound => { - AER::NotFound(ApiError::new("HE", 4, "Address does not exist in our records", None)) - }, - Self::GenericNotFoundError { message } => { - AER::NotFound(ApiError::new("HE", 5, message, None)) - }, - Self::ApiKeyNotFound => { - AER::NotFound(ApiError::new("HE", 2, "API Key does not exist in our records", None)) - } - Self::NotSupported { message } => { - AER::BadRequest(ApiError::new("HE", 3, "Payment method type not supported", Some(Extra {reason: Some(message.to_owned()), ..Default::default()}))) - }, - Self::InvalidCardIin => AER::BadRequest(ApiError::new("HE", 3, "The provided card IIN does not exist", None)), - Self::InvalidCardIinLength => AER::BadRequest(ApiError::new("HE", 3, "The provided card IIN length is invalid, please provide an IIN with 6 digits", None)), - Self::FlowNotSupported { flow, connector } => { - AER::BadRequest(ApiError::new("IR", 20, format!("{flow} flow not supported"), Some(Extra {connector: Some(connector.to_owned()), ..Default::default()}))) //FIXME: error message - } - Self::DisputeNotFound { .. } => { - AER::NotFound(ApiError::new("HE", 2, "Dispute does not exist in our records", None)) - }, - Self::AuthenticationNotFound { .. } => { - AER::NotFound(ApiError::new("HE", 2, "Authentication does not exist in our records", None)) - }, - Self::BusinessProfileNotFound { id } => { - AER::NotFound(ApiError::new("HE", 2, format!("Business profile with the given id {id} does not exist"), None)) - } - Self::FileNotFound => { - AER::NotFound(ApiError::new("HE", 2, "File does not exist in our records", None)) - } - Self::PollNotFound { .. } => { - AER::NotFound(ApiError::new("HE", 2, "Poll does not exist in our records", None)) - }, - Self::FileNotAvailable => { - AER::NotFound(ApiError::new("HE", 2, "File not available", None)) - } - Self::DisputeStatusValidationFailed { .. } => { - AER::BadRequest(ApiError::new("HE", 2, "Dispute status validation failed", None)) - } - Self::FileValidationFailed { reason } => { - AER::BadRequest(ApiError::new("HE", 2, format!("File validation failed {reason}"), None)) - } - Self::MissingFile => { - AER::BadRequest(ApiError::new("HE", 2, "File not found in the request", None)) - } - Self::MissingFilePurpose => { - AER::BadRequest(ApiError::new("HE", 2, "File purpose not found in the request or is invalid", None)) - } - Self::MissingFileContentType => { - AER::BadRequest(ApiError::new("HE", 2, "File content type not found", None)) - } - Self::MissingDisputeId => { - AER::BadRequest(ApiError::new("HE", 2, "Dispute id not found in the request", None)) - } - Self::WebhookAuthenticationFailed => { - AER::Unauthorized(ApiError::new("WE", 1, "Webhook authentication failed", None)) - } - Self::WebhookResourceNotFound => { - AER::NotFound(ApiError::new("WE", 4, "Webhook resource was not found", None)) - } - Self::WebhookBadRequest => { - AER::BadRequest(ApiError::new("WE", 2, "Bad request body received", None)) - } - Self::WebhookProcessingFailure => { - AER::InternalServerError(ApiError::new("WE", 3, "There was an issue processing the webhook", None)) - }, - Self::WebhookInvalidMerchantSecret => { - AER::BadRequest(ApiError::new("WE", 2, "Merchant Secret set for webhook source verificartion is invalid", None)) - } - Self::IncorrectPaymentMethodConfiguration => { - AER::BadRequest(ApiError::new("HE", 4, "No eligible connector was found for the current payment method configuration", None)) - } - Self::WebhookUnprocessableEntity => { - AER::Unprocessable(ApiError::new("WE", 5, "There was an issue processing the webhook body", None)) - }, - Self::ResourceBusy => { - AER::Unprocessable(ApiError::new("WE", 5, "There was an issue processing the webhook body", None)) - } - Self::PaymentLinkNotFound => { - AER::NotFound(ApiError::new("HE", 2, "Payment Link does not exist in our records", None)) - } - Self::InvalidConnectorConfiguration {config} => { - AER::BadRequest(ApiError::new("IR", 24, format!("Merchant connector account is configured with invalid {config}"), None)) - } - Self::CurrencyConversionFailed => { - AER::Unprocessable(ApiError::new("HE", 2, "Failed to convert currency to minor unit", None)) - } - Self::PaymentMethodDeleteFailed => { - AER::BadRequest(ApiError::new("IR", 25, "Cannot delete the default payment method", None)) - } - Self::InvalidCookie => { - AER::BadRequest(ApiError::new("IR", 26, "Invalid Cookie", None)) - } - Self::ExtendedCardInfoNotFound => { - AER::NotFound(ApiError::new("IR", 27, "Extended card info does not exist", None)) - } - } - } -} +use super::{ConnectorError, CustomersErrorResponse, StorageError}; impl ErrorSwitch for ConnectorError { fn switch(&self) -> ApiErrorResponse { diff --git a/crates/router/src/core/errors/user.rs b/crates/router/src/core/errors/user.rs index 580e34ba7e81..adcf8794b83d 100644 --- a/crates/router/src/core/errors/user.rs +++ b/crates/router/src/core/errors/user.rs @@ -66,10 +66,18 @@ pub enum UserErrors { RoleNameParsingError, #[error("RoleNameAlreadyExists")] RoleNameAlreadyExists, - #[error("TOTPNotSetup")] + #[error("TotpNotSetup")] TotpNotSetup, - #[error("InvalidTOTP")] + #[error("InvalidTotp")] InvalidTotp, + #[error("TotpRequired")] + TotpRequired, + #[error("InvalidRecoveryCode")] + InvalidRecoveryCode, + #[error("TwoFactorAuthRequired")] + TwoFactorAuthRequired, + #[error("TwoFactorAuthNotSetup")] + TwoFactorAuthNotSetup, } impl common_utils::errors::ErrorSwitch for UserErrors { @@ -179,6 +187,18 @@ impl common_utils::errors::ErrorSwitch { AER::BadRequest(ApiError::new(sub_code, 37, self.get_error_message(), None)) } + Self::TotpRequired => { + AER::BadRequest(ApiError::new(sub_code, 38, self.get_error_message(), None)) + } + Self::InvalidRecoveryCode => { + AER::BadRequest(ApiError::new(sub_code, 39, self.get_error_message(), None)) + } + Self::TwoFactorAuthRequired => { + AER::BadRequest(ApiError::new(sub_code, 40, self.get_error_message(), None)) + } + Self::TwoFactorAuthNotSetup => { + AER::BadRequest(ApiError::new(sub_code, 41, self.get_error_message(), None)) + } } } } @@ -217,6 +237,10 @@ impl UserErrors { Self::RoleNameAlreadyExists => "Role name already exists", Self::TotpNotSetup => "TOTP not setup", Self::InvalidTotp => "Invalid TOTP", + Self::TotpRequired => "TOTP required", + Self::InvalidRecoveryCode => "Invalid Recovery Code", + Self::TwoFactorAuthRequired => "Two factor auth required", + Self::TwoFactorAuthNotSetup => "Two factor auth not setup", } } } diff --git a/crates/router/src/core/errors/utils.rs b/crates/router/src/core/errors/utils.rs index 678a351a4c31..1e2f5a2c3057 100644 --- a/crates/router/src/core/errors/utils.rs +++ b/crates/router/src/core/errors/utils.rs @@ -158,9 +158,7 @@ impl ConnectorErrorExt for error_stack::Result } errors::ConnectorError::NotImplemented(reason) => { errors::ApiErrorResponse::NotImplemented { - message: errors::api_error_response::NotImplementedMessage::Reason( - reason.to_string(), - ), + message: errors::NotImplementedMessage::Reason(reason.to_string()), } .into() } @@ -249,7 +247,7 @@ impl ConnectorErrorExt for error_stack::Result } errors::ConnectorError::NotImplemented(reason) => { errors::ApiErrorResponse::NotImplemented { - message: errors::api_error_response::NotImplementedMessage::Reason( + message: errors::NotImplementedMessage::Reason( reason.to_string(), ), } @@ -476,9 +474,7 @@ impl ConnectorErrorExt for error_stack::Result } errors::ConnectorError::NotImplemented(reason) => { errors::ApiErrorResponse::NotImplemented { - message: errors::api_error_response::NotImplementedMessage::Reason( - reason.to_string(), - ), + message: errors::NotImplementedMessage::Reason(reason.to_string()), } } _ => errors::ApiErrorResponse::InternalServerError, diff --git a/crates/router/src/core/fraud_check.rs b/crates/router/src/core/fraud_check.rs index 4d614662a4dd..63bf80bac020 100644 --- a/crates/router/src/core/fraud_check.rs +++ b/crates/router/src/core/fraud_check.rs @@ -1,6 +1,6 @@ use std::fmt::Debug; -use api_models::{admin::FrmConfigs, enums as api_enums, payments::AdditionalPaymentData}; +use api_models::{admin::FrmConfigs, enums as api_enums}; use common_enums::CaptureMethod; use error_stack::ResultExt; use masking::{ExposeInterface, PeekInterface}; @@ -20,10 +20,7 @@ use super::errors::{ConnectorErrorExt, RouterResponse}; use crate::{ core::{ errors::{self, RouterResult}, - payments::{ - self, flows::ConstructFlowSpecificData, helpers::get_additional_payment_data, - operations::BoxedOperation, - }, + payments::{self, flows::ConstructFlowSpecificData, operations::BoxedOperation}, utils as core_utils, }, db::StorageInterface, @@ -185,15 +182,14 @@ where .expose() .parse_value("FrmConfigs") .change_context(errors::ApiErrorResponse::InvalidDataFormat { - field_name: "frm_configs".to_string(), - expected_format: r#"[{ "gateway": "stripe", "payment_methods": [{ "payment_method": "card","payment_method_types": [{"payment_method_type": "credit","card_networks": ["Visa"],"flow": "pre","action": "cancel_txn"}]}]}]"#.to_string(), - }) + field_name: "frm_configs".to_string(), + expected_format: r#"[{ "gateway": "stripe", "payment_methods": [{ "payment_method": "card","flow": "post"}]}]"#.to_string(), + }) }) .collect::, _>>()?; let mut is_frm_connector_enabled = false; let mut is_frm_pm_enabled = false; - let mut is_frm_pmt_enabled = false; let filtered_frm_config = frm_configs_struct .iter() .filter(|frm_config| { @@ -243,76 +239,11 @@ where }) .collect::>() .concat(); - - let additional_payment_data = match &payment_data.payment_method_data { - Some(pmd) => { - let additional_payment_data = - get_additional_payment_data(pmd, db, &profile_id).await; - Some(additional_payment_data) - } - None => payment_data - .payment_attempt - .payment_method_data - .as_ref() - .map(|pm_data| { - pm_data.clone().parse_value::( - "AdditionalPaymentData", - ) - }) - .transpose() - .unwrap_or_default(), // Making this default in case of error as we don't want to fail payment for frm errors - }; - let filtered_payment_method_types = filtered_payment_methods - .iter() - .map(|frm_pm_config| { - let filtered_pm_config_by_pmt = frm_pm_config - .payment_method_types - .iter() - .filter(|frm_pm_config_by_pmt| { - match ( - &payment_data - .clone() - .payment_attempt - .payment_method_type, - frm_pm_config_by_pmt.payment_method_type, - ) { - (Some(curr), Some(conf)) - if curr.to_string() == conf.to_string() => - { - is_frm_pmt_enabled = true; - true - } - (None, Some(conf)) => match additional_payment_data - .clone() - { - Some(AdditionalPaymentData::Card(card)) => { - let card_type = card - .card_type - .unwrap_or_else(|| "debit".to_string()); - let is_enabled = card_type.to_lowercase() - == conf.to_string().to_lowercase(); - if is_enabled { - is_frm_pmt_enabled = true; - } - is_enabled - } - _ => false, - }, - _ => false, - } - }) - .collect::>(); - filtered_pm_config_by_pmt - }) - .collect::>() - .concat(); - let is_frm_enabled = - is_frm_connector_enabled && is_frm_pm_enabled && is_frm_pmt_enabled; + let is_frm_enabled = is_frm_connector_enabled && is_frm_pm_enabled; logger::debug!( - "is_frm_connector_enabled {:?}, is_frm_pm_enabled: {:?},is_frm_pmt_enabled : {:?}, is_frm_enabled :{:?}", + "is_frm_connector_enabled {:?}, is_frm_pm_enabled: {:?}, is_frm_enabled :{:?}", is_frm_connector_enabled, is_frm_pm_enabled, - is_frm_pmt_enabled, is_frm_enabled ); // filtered_frm_config... @@ -324,18 +255,19 @@ where frm_enabled_pm: filtered_payment_methods .first() .and_then(|pm| pm.payment_method), - frm_enabled_pm_type: filtered_payment_method_types + // flow type should be consumed from payment_method.flow. To provide backward compatibility, if we don't find it there, we consume it from payment_method.payment_method_types[0].flow_type. + frm_preferred_flow_type: filtered_payment_methods .first() - .and_then(|pmt| pmt.payment_method_type), - frm_action: filtered_payment_method_types - // .clone() - .first() - .map(|pmt| pmt.action.clone()) - .unwrap_or(api_enums::FrmAction::ManualReview), - frm_preferred_flow_type: filtered_payment_method_types - .first() - .map(|pmt| pmt.flow.clone()) - .unwrap_or(api_enums::FrmPreferredFlowTypes::Pre), + .and_then(|pm| pm.flow.clone()) + .or(filtered_payment_methods.first().and_then(|pm| { + pm.payment_method_types.as_ref().and_then(|pmt| { + pmt.first().map(|pmts| pmts.flow.clone()) + }) + })) + .ok_or(errors::ApiErrorResponse::InvalidDataFormat { + field_name: "frm_configs".to_string(), + expected_format: r#"[{ "gateway": "stripe", "payment_methods": [{ "payment_method": "card","flow": "post"}]}]"#.to_string(), + })?, }; logger::debug!( "frm_routing_configs: {:?} {:?} {:?} {:?}", @@ -411,13 +343,13 @@ where let payment_to_frm_data = PaymentToFrmData { amount: payment_data.amount, - payment_intent: payment_data.payment_intent, + payment_intent: payment_data.payment_intent.to_owned(), payment_attempt: payment_data.payment_attempt, merchant_account: merchant_account.to_owned(), address: payment_data.address.clone(), connector_details: frm_connector_details.clone(), order_details, - frm_metadata: payment_data.frm_metadata.clone(), + frm_metadata: payment_data.payment_intent.frm_metadata, }; let fraud_check_operation: operation::BoxedFraudCheckOperation = @@ -495,12 +427,7 @@ where payment_data.frm_message = Some(frm_fraud_check.clone()); if matches!(frm_fraud_check.frm_status, FraudCheckStatus::Fraud) { *should_continue_transaction = false; - if matches!(frm_configs.frm_action, api_enums::FrmAction::CancelTxn) { - frm_info.suggested_action = Some(FrmSuggestion::FrmCancelTransaction); - } else if matches!(frm_configs.frm_action, api_enums::FrmAction::ManualReview) { - *should_continue_capture = false; - frm_info.suggested_action = Some(FrmSuggestion::FrmManualReview); - } + frm_info.suggested_action = Some(FrmSuggestion::FrmCancelTransaction); } logger::debug!( "frm_updated_data: {:?} {:?}", @@ -511,6 +438,9 @@ where } else if matches!( frm_configs.frm_preferred_flow_type, api_enums::FrmPreferredFlowTypes::Post + ) && !matches!( + frm_data.fraud_check.frm_status, + FraudCheckStatus::TransactionFailure // Incase of TransactionFailure frm status(No frm decision is taken by frm processor), if capture method is automatic we should not change it to manual. ) { *should_continue_capture = false; Some(frm_data.to_owned()) @@ -571,11 +501,7 @@ where let mut frm_suggestion = None; payment_data.frm_message = Some(frm_fraud_check.clone()); if matches!(frm_fraud_check.frm_status, FraudCheckStatus::Fraud) { - if matches!(frm_configs.frm_action, api_enums::FrmAction::CancelTxn) { - frm_info.suggested_action = Some(FrmSuggestion::FrmCancelTransaction); - } else if matches!(frm_configs.frm_action, api_enums::FrmAction::ManualReview) { - frm_info.suggested_action = Some(FrmSuggestion::FrmManualReview); - } + frm_info.suggested_action = Some(FrmSuggestion::FrmCancelTransaction); } else if matches!(frm_fraud_check.frm_status, FraudCheckStatus::ManualReview) { frm_info.suggested_action = Some(FrmSuggestion::FrmManualReview); } @@ -706,7 +632,7 @@ pub fn is_operation_allowed(operation: &Op) -> bool { impl From for PaymentDetails { fn from(payment_data: PaymentToFrmData) -> Self { Self { - amount: payment_data.amount.into(), + amount: common_utils::types::MinorUnit::from(payment_data.amount).get_amount_as_i64(), currency: payment_data.payment_attempt.currency, payment_method: payment_data.payment_attempt.payment_method, payment_method_type: payment_data.payment_attempt.payment_method_type, diff --git a/crates/router/src/core/fraud_check/flows/checkout_flow.rs b/crates/router/src/core/fraud_check/flows/checkout_flow.rs index d0e5376b2ca4..c754682f72d5 100644 --- a/crates/router/src/core/fraud_check/flows/checkout_flow.rs +++ b/crates/router/src/core/fraud_check/flows/checkout_flow.rs @@ -69,7 +69,7 @@ impl ConstructFlowSpecificData( address: PaymentAddress::default(), auth_type: payment_attempt.authentication_type.unwrap_or_default(), connector_meta_data: merchant_connector_account.get_metadata(), - amount_captured: payment_intent.amount_captured, + amount_captured: payment_intent + .amount_captured + .map(|amt| amt.get_amount_as_i64()), payment_method_status: None, request: FraudCheckFulfillmentData { - amount: payment_attempt.amount, + amount: payment_attempt.amount.get_amount_as_i64(), order_details: payment_intent.order_details.clone(), fulfillment_req: fulfillment_request, }, diff --git a/crates/router/src/core/fraud_check/flows/record_return.rs b/crates/router/src/core/fraud_check/flows/record_return.rs index ad74f7f70274..b854a3337700 100644 --- a/crates/router/src/core/fraud_check/flows/record_return.rs +++ b/crates/router/src/core/fraud_check/flows/record_return.rs @@ -67,7 +67,7 @@ impl ConstructFlowSpecificData Domain for FraudCheckPost { req_state: ReqState, frm_data: &mut FrmData, merchant_account: &domain::MerchantAccount, - frm_configs: FrmConfigsObject, + _frm_configs: FrmConfigsObject, frm_suggestion: &mut Option, key_store: domain::MerchantKeyStore, payment_data: &mut payments::PaymentData, @@ -194,7 +194,6 @@ impl Domain for FraudCheckPost { _should_continue_capture: &mut bool, ) -> RouterResult> { if matches!(frm_data.fraud_check.frm_status, FraudCheckStatus::Fraud) - && matches!(frm_configs.frm_action, FrmAction::CancelTxn) && matches!( frm_data.fraud_check.last_step, FraudCheckLastStep::CheckoutOrSale @@ -242,9 +241,10 @@ impl Domain for FraudCheckPost { ) .await?; frm_data.fraud_check.last_step = FraudCheckLastStep::TransactionOrRecordRefund; - } else if matches!(frm_data.fraud_check.frm_status, FraudCheckStatus::Fraud) - && matches!(frm_configs.frm_action, FrmAction::ManualReview) - { + } else if matches!( + frm_data.fraud_check.frm_status, + FraudCheckStatus::ManualReview + ) { *frm_suggestion = Some(FrmSuggestion::FrmManualReview); } else if matches!(frm_data.fraud_check.frm_status, FraudCheckStatus::Legit) && matches!( @@ -477,25 +477,34 @@ impl UpdateTracker for FraudCheckPost { }; if let Some(frm_suggestion) = frm_suggestion { - let (payment_attempt_status, payment_intent_status) = match frm_suggestion { - FrmSuggestion::FrmCancelTransaction => { - (AttemptStatus::Failure, IntentStatus::Failed) - } - FrmSuggestion::FrmManualReview => ( - AttemptStatus::Unresolved, - IntentStatus::RequiresMerchantAction, - ), - FrmSuggestion::FrmAuthorizeTransaction => { - (AttemptStatus::Authorized, IntentStatus::RequiresCapture) - } - }; + let (payment_attempt_status, payment_intent_status, merchant_decision, error_message) = + match frm_suggestion { + FrmSuggestion::FrmCancelTransaction => ( + AttemptStatus::Failure, + IntentStatus::Failed, + Some(MerchantDecision::Rejected.to_string()), + Some(Some(CANCEL_INITIATED.to_string())), + ), + FrmSuggestion::FrmManualReview => ( + AttemptStatus::Unresolved, + IntentStatus::RequiresMerchantAction, + None, + None, + ), + FrmSuggestion::FrmAuthorizeTransaction => ( + AttemptStatus::Authorized, + IntentStatus::RequiresCapture, + None, + None, + ), + }; payment_data.payment_attempt = db .update_payment_attempt_with_attempt_id( payment_data.payment_attempt.clone(), PaymentAttemptUpdate::RejectUpdate { status: payment_attempt_status, error_code: Some(Some(frm_data.fraud_check.frm_status.to_string())), - error_message: Some(Some(CANCEL_INITIATED.to_string())), + error_message, updated_by: frm_data.merchant_account.storage_scheme.to_string(), }, frm_data.merchant_account.storage_scheme, @@ -508,7 +517,7 @@ impl UpdateTracker for FraudCheckPost { payment_data.payment_intent.clone(), PaymentIntentUpdate::RejectUpdate { status: payment_intent_status, - merchant_decision: Some(MerchantDecision::Rejected.to_string()), + merchant_decision, updated_by: frm_data.merchant_account.storage_scheme.to_string(), }, frm_data.merchant_account.storage_scheme, diff --git a/crates/router/src/core/fraud_check/types.rs b/crates/router/src/core/fraud_check/types.rs index 5acd722077fb..bc30ad83b145 100644 --- a/crates/router/src/core/fraud_check/types.rs +++ b/crates/router/src/core/fraud_check/types.rs @@ -5,20 +5,20 @@ use api_models::{ refunds::RefundResponse, }; use common_enums::FrmSuggestion; -use common_utils::pii::Email; +use common_utils::pii::SecretSerdeValue; use hyperswitch_domain_models::payments::{payment_attempt::PaymentAttempt, PaymentIntent}; +pub use hyperswitch_domain_models::router_request_types::fraud_check::{ + Address, Destination, FrmFulfillmentRequest, FulfillmentStatus, Fulfillments, Product, +}; use masking::Serialize; use serde::Deserialize; use utoipa::ToSchema; use super::operation::BoxedFraudCheckOperation; -use crate::{ - pii::Secret, - types::{ - domain::MerchantAccount, - storage::{enums as storage_enums, fraud_check::FraudCheck}, - PaymentAddress, - }, +use crate::types::{ + domain::MerchantAccount, + storage::{enums as storage_enums, fraud_check::FraudCheck}, + PaymentAddress, }; #[derive(Clone, Default, Debug)] @@ -56,7 +56,7 @@ pub struct FrmData { pub connector_details: ConnectorDetailsCore, pub order_details: Option>, pub refund: Option, - pub frm_metadata: Option, + pub frm_metadata: Option, } #[derive(Debug)] @@ -80,15 +80,13 @@ pub struct PaymentToFrmData { pub address: PaymentAddress, pub connector_details: ConnectorDetailsCore, pub order_details: Option>, - pub frm_metadata: Option, + pub frm_metadata: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FrmConfigsObject { pub frm_enabled_pm: Option, - pub frm_enabled_pm_type: Option, pub frm_enabled_gateway: Option, - pub frm_action: api_enums::FrmAction, pub frm_preferred_flow_type: api_enums::FrmPreferredFlowTypes, } @@ -108,98 +106,6 @@ pub struct FrmFulfillmentSignifydApiRequest { pub fulfillments: Vec, } -#[derive(Debug, Deserialize, Serialize, Clone, ToSchema)] -#[serde(deny_unknown_fields)] -#[serde_with::skip_serializing_none] -#[serde(rename_all = "snake_case")] -pub struct FrmFulfillmentRequest { - ///unique payment_id for the transaction - #[schema(max_length = 255, example = "pay_qiYfHcDou1ycIaxVXKHF")] - pub payment_id: String, - ///unique order_id for the order_details in the transaction - #[schema(max_length = 255, example = "pay_qiYfHcDou1ycIaxVXKHF")] - pub order_id: String, - ///denotes the status of the fulfillment... can be one of PARTIAL, COMPLETE, REPLACEMENT, CANCELED - #[schema(value_type = Option, example = "COMPLETE")] - pub fulfillment_status: Option, - ///contains details of the fulfillment - #[schema(value_type = Vec)] - pub fulfillments: Vec, - //name of the tracking Company - #[schema(max_length = 255, example = "fedex")] - pub tracking_company: Option, - //tracking ID of the product - #[schema(example = r#"["track_8327446667", "track_8327446668"]"#)] - pub tracking_numbers: Option>, - //tracking_url for tracking the product - pub tracking_urls: Option>, - // The name of the Shipper. - pub carrier: Option, - // Fulfillment method for the shipment. - pub fulfillment_method: Option, - // Statuses to indicate shipment state. - pub shipment_status: Option, - // The date and time items are ready to be shipped. - pub shipped_at: Option, -} - -#[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] -#[serde_with::skip_serializing_none] -#[serde(rename_all = "snake_case")] -pub struct Fulfillments { - ///shipment_id of the shipped items - #[schema(max_length = 255, example = "ship_101")] - pub shipment_id: String, - ///products sent in the shipment - #[schema(value_type = Option>)] - pub products: Option>, - ///destination address of the shipment - #[schema(value_type = Destination)] - pub destination: Destination, -} - -#[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] -#[serde(untagged)] -#[serde_with::skip_serializing_none] -#[serde(rename_all = "snake_case")] -pub enum FulfillmentStatus { - PARTIAL, - COMPLETE, - REPLACEMENT, - CANCELED, -} - -#[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] -#[serde_with::skip_serializing_none] -#[serde(rename_all = "snake_case")] -pub struct Product { - pub item_name: String, - pub item_quantity: i64, - pub item_id: String, -} - -#[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] -#[serde_with::skip_serializing_none] -#[serde(rename_all = "snake_case")] -pub struct Destination { - pub full_name: Secret, - pub organization: Option, - pub email: Option, - pub address: Address, -} - -#[derive(Debug, Serialize, Eq, PartialEq, Deserialize, Clone)] -#[serde_with::skip_serializing_none] -#[serde(rename_all = "snake_case")] -pub struct Address { - pub street_address: Secret, - pub unit: Option>, - pub postal_code: Secret, - pub city: String, - pub province_code: Secret, - pub country_code: common_enums::CountryAlpha2, -} - #[derive(Debug, ToSchema, Clone, Serialize)] pub struct FrmFulfillmentResponse { ///unique order_id for the transaction diff --git a/crates/router/src/core/mandate/helpers.rs b/crates/router/src/core/mandate/helpers.rs index f3b7b384e2d5..4e9d07b7f828 100644 --- a/crates/router/src/core/mandate/helpers.rs +++ b/crates/router/src/core/mandate/helpers.rs @@ -81,7 +81,8 @@ pub struct MandateGenericData { pub payment_method: Option, pub payment_method_type: Option, pub mandate_data: Option, - pub recurring_mandate_payment_data: Option, + pub recurring_mandate_payment_data: + Option, pub mandate_connector: Option, pub payment_method_info: Option, } diff --git a/crates/router/src/core/payment_link.rs b/crates/router/src/core/payment_link.rs index 11c53a77b1ef..9b8fd3cbe0ef 100644 --- a/crates/router/src/core/payment_link.rs +++ b/crates/router/src/core/payment_link.rs @@ -120,7 +120,7 @@ pub async fn initiate_payment_link_flow( payment_intent.client_secret.clone(), )?; let amount = currency - .to_currency_base_unit(payment_intent.amount) + .to_currency_base_unit(payment_intent.amount.get_amount_as_i64()) .change_context(errors::ApiErrorResponse::CurrencyConversionFailed)?; let order_details = validate_order_details(payment_intent.order_details.clone(), currency)?; @@ -564,7 +564,7 @@ pub async fn get_payment_link_status( })?; let amount = currency - .to_currency_base_unit(payment_attempt.net_amount) + .to_currency_base_unit(payment_attempt.net_amount.get_amount_as_i64()) .change_context(errors::ApiErrorResponse::CurrencyConversionFailed)?; // converting first letter of merchant name to upperCase diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs index 71f0bdac03a9..4b4eb34b7e2d 100644 --- a/crates/router/src/core/payment_methods.rs +++ b/crates/router/src/core/payment_methods.rs @@ -1,6 +1,7 @@ pub mod cards; pub mod surcharge_decision_configs; pub mod transformers; +pub mod utils; pub mod vault; pub use api_models::enums::Connector; diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index d3870a9e5981..379050ba73e3 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -5,7 +5,7 @@ use std::{ }; use api_models::{ - admin::{self, PaymentMethodsEnabled}, + admin::PaymentMethodsEnabled, enums::{self as api_enums}, payment_methods::{ BankAccountTokenData, Card, CardDetailUpdate, CardDetailsPaymentMethod, CardNetworkTypes, @@ -19,18 +19,22 @@ use api_models::{ pm_auth::PaymentMethodAuthConfig, surcharge_decision_configs as api_surcharge_decision_configs, }; +use cgraph::ConstraintGraph; use common_enums::enums::MerchantStorageScheme; use common_utils::{ consts, ext_traits::{AsyncExt, Encode, StringExt, ValueExt}, generate_id, }; -use diesel_models::{ - business_profile::BusinessProfile, encryption::Encryption, enums as storage_enums, - payment_method, -}; +use diesel_models::{business_profile::BusinessProfile, encryption::Encryption, payment_method}; use domain::CustomerUpdate; use error_stack::{report, ResultExt}; +use euclid::{ + dssa::graph::{AnalysisContext, CgraphExt}, + frontend::dir, +}; +use hyperswitch_constraint_graph as cgraph; +use kgraph_utils::transformers::IntoDirValue; use masking::Secret; use router_env::{instrument, tracing}; use strum::IntoEnumIterator; @@ -45,7 +49,11 @@ use crate::{ configs::settings, core::{ errors::{self, StorageErrorExt}, - payment_methods::{transformers as payment_methods, vault}, + payment_methods::{ + transformers as payment_methods, + utils::{get_merchant_pm_filter_graph, make_pm_graph, refresh_pm_filters_cache}, + vault, + }, payments::{ helpers, routing::{self, SessionFlowRoutingInput}, @@ -140,6 +148,7 @@ pub async fn create_payment_method( last_modified: current_time, last_used_at: current_time, payment_method_billing_address, + updated_by: None, }, storage_scheme, ) @@ -427,7 +436,7 @@ pub async fn add_payment_method_data( pm_resp.payment_method_id.clone_from(&pm_id); pm_resp.client_secret = Some(client_secret.clone()); - let card_isin = card.card_number.clone().get_card_isin(); + let card_isin = card.card_number.get_card_isin(); let card_info = db .get_card_info(card_isin.as_str()) @@ -439,7 +448,7 @@ pub async fn add_payment_method_data( issuer_country: card_info .as_ref() .and_then(|ci| ci.card_issuing_country.clone()), - last4_digits: Some(card.card_number.clone().get_last4()), + last4_digits: Some(card.card_number.get_last4()), expiry_month: Some(card.card_exp_month), expiry_year: Some(card.card_exp_year), nick_name: card.nick_name, @@ -644,7 +653,7 @@ pub async fn add_payment_method( let updated_card = Some(api::CardDetailFromLocker { scheme: None, - last4_digits: Some(card.card_number.clone().get_last4()), + last4_digits: Some(card.card_number.get_last4()), issuer_country: None, card_number: Some(card.card_number), expiry_month: Some(card.card_exp_month), @@ -894,7 +903,7 @@ pub async fn update_customer_payment_method( // Construct new updated card object. Consider a field if passed in request or else populate it with the existing value from existing_card_data let updated_card = Some(api::CardDetailFromLocker { scheme: existing_card_data.scheme, - last4_digits: Some(card_data_from_locker.card_number.clone().get_last4()), + last4_digits: Some(card_data_from_locker.card_number.get_last4()), issuer_country: existing_card_data.issuer_country, card_number: existing_card_data.card_number, expiry_month: card_update @@ -1875,31 +1884,94 @@ pub async fn list_payment_methods( .await?; // filter out connectors based on the business country - let filtered_mcas = helpers::filter_mca_based_on_business_profile(all_mcas, profile_id); + let filtered_mcas = + helpers::filter_mca_based_on_business_profile(all_mcas.clone(), profile_id.clone()); logger::debug!(mca_before_filtering=?filtered_mcas); let mut response: Vec = vec![]; - for mca in &filtered_mcas { - let payment_methods = match &mca.payment_methods_enabled { - Some(pm) => pm.clone(), - None => continue, - }; - filter_payment_methods( - payment_methods, - &mut req, - &mut response, - payment_intent.as_ref(), - payment_attempt.as_ref(), - billing_address.as_ref(), - mca.connector_name.clone(), - pm_config_mapping, - &state.conf.mandates.supported_payment_methods, - &state.conf.mandates.update_mandate_supported, - &state.conf.saved_payment_methods, + // Key creation for storing PM_FILTER_CGRAPH + #[cfg(feature = "business_profile_routing")] + let key = { + let profile_id = profile_id + .clone() + .get_required_value("profile_id") + .change_context(errors::ApiErrorResponse::GenericNotFoundError { + message: "Profile id not found".to_string(), + })?; + format!( + "pm_filters_cgraph_{}_{}", + &merchant_account.merchant_id, profile_id ) - .await?; + }; + + #[cfg(not(feature = "business_profile_routing"))] + let key = { format!("pm_filters_cgraph_{}", &merchant_account.merchant_id) }; + + if let Some(graph) = get_merchant_pm_filter_graph(&key).await { + // Derivation of PM_FILTER_CGRAPH from MokaCache successful + for mca in &filtered_mcas { + let payment_methods = match &mca.payment_methods_enabled { + Some(pm) => pm, + None => continue, + }; + filter_payment_methods( + &graph, + payment_methods, + &mut req, + &mut response, + payment_intent.as_ref(), + payment_attempt.as_ref(), + billing_address.as_ref(), + mca.connector_name.clone(), + &state.conf.saved_payment_methods, + ) + .await?; + } + } else { + // No PM_FILTER_CGRAPH Cache present in MokaCache + let mut builder = cgraph::ConstraintGraphBuilder::<'static, _>::new(); + for mca in &filtered_mcas { + let payment_methods = match &mca.payment_methods_enabled { + Some(pm) => pm, + None => continue, + }; + if let Err(e) = make_pm_graph( + &mut builder, + payment_methods, + mca.connector_name.clone(), + pm_config_mapping, + &state.conf.mandates.supported_payment_methods, + &state.conf.mandates.update_mandate_supported, + ) { + logger::error!( + "Failed to construct constraint graph for list payment methods {e:?}" + ); + } + } + + // Refreshing our CGraph cache + let graph = refresh_pm_filters_cache(&key, builder.build()).await; + + for mca in &filtered_mcas { + let payment_methods = match &mca.payment_methods_enabled { + Some(pm) => pm, + None => continue, + }; + filter_payment_methods( + &graph, + payment_methods, + &mut req, + &mut response, + payment_intent.as_ref(), + payment_attempt.as_ref(), + billing_address.as_ref(), + mca.connector_name.clone(), + &state.conf.saved_payment_methods, + ) + .await?; + } } // Filter out wallet payment method from mca if customer has already saved it @@ -2267,7 +2339,7 @@ pub async fn list_payment_methods( .as_ref() .and_then(|r| get_val(key.to_owned(), r)); if let Some(s) = temp { - val.value = Some(s) + val.value = Some(s.into()) }; } } @@ -2756,20 +2828,18 @@ pub async fn call_surcharge_decision_management_for_saved_card( #[allow(clippy::too_many_arguments)] pub async fn filter_payment_methods( - payment_methods: Vec, + graph: &ConstraintGraph<'_, dir::DirValue>, + payment_methods: &[serde_json::Value], req: &mut api::PaymentMethodListRequest, resp: &mut Vec, payment_intent: Option<&storage::PaymentIntent>, payment_attempt: Option<&storage::PaymentAttempt>, address: Option<&domain::Address>, connector: String, - config: &settings::ConnectorFilters, - supported_payment_methods_for_mandate: &settings::SupportedPaymentMethodsForMandate, - supported_payment_methods_for_update_mandate: &settings::SupportedPaymentMethodsForMandate, saved_payment_methods: &settings::EligiblePaymentMethods, ) -> errors::CustomResult<(), errors::ApiErrorResponse> { - for payment_method in payment_methods.into_iter() { - let parse_result = serde_json::from_value::(payment_method); + for payment_method in payment_methods.iter() { + let parse_result = serde_json::from_value::(payment_method.clone()); if let Ok(payment_methods_enabled) = parse_result { let payment_method = payment_methods_enabled.payment_method; @@ -2794,59 +2864,19 @@ pub async fn filter_payment_methods( &payment_method_type_info, req.installment_payment_enabled, ) - && filter_amount_based(&payment_method_type_info, req.amount) + && filter_amount_based( + &payment_method_type_info, + req.amount + .map(|minor_amount| minor_amount.get_amount_as_i64()), + ) { - let mut payment_method_object = payment_method_type_info; - - let filter; - ( - payment_method_object.accepted_countries, - req.accepted_countries, - filter, - ) = filter_pm_country_based( - &payment_method_object.accepted_countries, - &req.accepted_countries, - ); - let filter2; - ( - payment_method_object.accepted_currencies, - req.accepted_currencies, - filter2, - ) = filter_pm_currencies_based( - &payment_method_object.accepted_currencies, - &req.accepted_currencies, - ); + let payment_method_object = payment_method_type_info.clone(); - let filter4 = filter_pm_card_network_based( - payment_method_object.card_networks.as_ref(), - req.card_networks.as_ref(), - &payment_method_object.payment_method_type, - ); - - let filter3 = if let Some(payment_intent) = payment_intent { - filter_payment_country_based(&payment_method_object, address).await? - && filter_payment_currency_based(payment_intent, &payment_method_object) - && filter_payment_amount_based(payment_intent, &payment_method_object) - && filter_payment_mandate_based(payment_attempt, &payment_method_object) - .await? - } else { - true - }; - - let filter5 = filter_pm_based_on_config( - config, - &connector, - &payment_method_object.payment_method_type, - payment_attempt, - &mut payment_method_object.card_networks, - &address.and_then(|inner| inner.country), - payment_attempt.and_then(|value| value.currency), - ); - - let filter6 = filter_pm_based_on_allowed_types( - allowed_payment_method_types.as_ref(), - &payment_method_object.payment_method_type, - ); + let pm_dir_value: dir::DirValue = + (payment_method_type_info.payment_method_type, payment_method) + .into_dir_value() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("pm_value_node not created")?; let connector_variant = api_enums::Connector::from_str(connector.as_str()) .change_context(errors::ConnectorError::InvalidConnectorName) @@ -2856,35 +2886,98 @@ pub async fn filter_payment_methods( .attach_printable_lazy(|| { format!("unable to parse connector name {connector:?}") })?; - let filter7 = payment_attempt - .and_then(|attempt| attempt.mandate_details.as_ref()) - .map(|_mandate_details| { - filter_pm_based_on_supported_payments_for_mandate( - supported_payment_methods_for_mandate, - &payment_method, - &payment_method_object.payment_method_type, - connector_variant, - ) + + let mut context_values: Vec = Vec::new(); + context_values.push(pm_dir_value.clone()); + + payment_intent.map(|intent| { + intent.currency.map(|currency| { + context_values.push(dir::DirValue::PaymentCurrency(currency)) }) - .unwrap_or(true); + }); + address.map(|address| { + address.country.map(|country| { + context_values.push(dir::DirValue::BillingCountry( + common_enums::Country::from_alpha2(country), + )) + }) + }); + + let filter_pm_based_on_allowed_types = filter_pm_based_on_allowed_types( + allowed_payment_method_types.as_ref(), + &payment_method_object.payment_method_type, + ); - let filter8 = payment_attempt + if payment_attempt + .and_then(|attempt| attempt.mandate_details.as_ref()) + .is_some() + { + context_values.push(dir::DirValue::PaymentType( + euclid::enums::PaymentType::NewMandate, + )); + if let Ok(connector) = api_enums::RoutableConnectors::from_str( + connector_variant.to_string().as_str(), + ) { + context_values.push(dir::DirValue::Connector(Box::new( + api_models::routing::ast::ConnectorChoice { + connector, + #[cfg(not(feature = "connector_choice_mca_id"))] + sub_label: None, + }, + ))); + }; + }; + + payment_attempt .and_then(|attempt| attempt.mandate_data.as_ref()) .map(|mandate_detail| { if mandate_detail.update_mandate_id.is_some() { - filter_pm_based_on_update_mandate_support_for_connector( - supported_payment_methods_for_update_mandate, - &payment_method, - &payment_method_object.payment_method_type, - connector_variant, - ) - } else { - true + context_values.push(dir::DirValue::PaymentType( + euclid::enums::PaymentType::UpdateMandate, + )); + if let Ok(connector) = api_enums::RoutableConnectors::from_str( + connector_variant.to_string().as_str(), + ) { + context_values.push(dir::DirValue::Connector(Box::new( + api_models::routing::ast::ConnectorChoice { + connector, + #[cfg(not(feature = "connector_choice_mca_id"))] + sub_label: None, + }, + ))); + }; } + }); + + payment_attempt + .map(|attempt| { + attempt.mandate_data.is_none() && attempt.mandate_details.is_none() }) - .unwrap_or(true); + .and_then(|res| { + res.then(|| { + context_values.push(dir::DirValue::PaymentType( + euclid::enums::PaymentType::NonMandate, + )) + }) + }); + + payment_attempt + .and_then(|inner| inner.capture_method) + .and_then(|capture_method| { + (capture_method == common_enums::CaptureMethod::Manual).then(|| { + context_values.push(dir::DirValue::CaptureMethod( + common_enums::CaptureMethod::Manual, + )); + }) + }); - let filter9 = req + let filter_pm_card_network_based = filter_pm_card_network_based( + payment_method_object.card_networks.as_ref(), + req.card_networks.as_ref(), + &payment_method_object.payment_method_type, + ); + + let saved_payment_methods_filter = req .client_secret .as_ref() .map(|cs| { @@ -2898,25 +2991,28 @@ pub async fn filter_payment_methods( }) .unwrap_or(true); - let connector = connector.clone(); + let context = AnalysisContext::from_dir_values(context_values.clone()); - let response_pm_type = ResponsePaymentMethodIntermediate::new( - payment_method_object, - connector, - payment_method, + let result = graph.key_value_analysis( + pm_dir_value.clone(), + &context, + &mut cgraph::Memoization::new(), + &mut cgraph::CycleCheck::new(), + None, ); - - if filter - && filter2 - && filter3 - && filter4 - && filter5 - && filter6 - && filter7 - && filter8 - && filter9 + if filter_pm_based_on_allowed_types + && filter_pm_card_network_based + && saved_payment_methods_filter + && matches!(result, Ok(())) { + let response_pm_type = ResponsePaymentMethodIntermediate::new( + payment_method_object, + connector.clone(), + payment_method, + ); resp.push(response_pm_type); + } else { + logger::error!("Filtering Payment Methods Failed"); } } } @@ -2924,287 +3020,12 @@ pub async fn filter_payment_methods( } Ok(()) } -pub fn filter_pm_based_on_update_mandate_support_for_connector( - supported_payment_methods_for_mandate: &settings::SupportedPaymentMethodsForMandate, - payment_method: &api_enums::PaymentMethod, - payment_method_type: &api_enums::PaymentMethodType, - connector: api_enums::Connector, -) -> bool { - if payment_method == &api_enums::PaymentMethod::Card { - supported_payment_methods_for_mandate - .0 - .get(payment_method) - .map(|payment_method_type_hm| { - let pm_credit = payment_method_type_hm - .0 - .get(&api_enums::PaymentMethodType::Credit) - .map(|conn| conn.connector_list.clone()) - .unwrap_or_default(); - let pm_debit = payment_method_type_hm - .0 - .get(&api_enums::PaymentMethodType::Debit) - .map(|conn| conn.connector_list.clone()) - .unwrap_or_default(); - &pm_credit | &pm_debit - }) - .map(|supported_connectors| supported_connectors.contains(&connector)) - .unwrap_or(false) - } else { - supported_payment_methods_for_mandate - .0 - .get(payment_method) - .and_then(|payment_method_type_hm| payment_method_type_hm.0.get(payment_method_type)) - .map(|supported_connectors| supported_connectors.connector_list.contains(&connector)) - .unwrap_or(false) - } -} -fn filter_pm_based_on_supported_payments_for_mandate( - supported_payment_methods_for_mandate: &settings::SupportedPaymentMethodsForMandate, - payment_method: &api_enums::PaymentMethod, - payment_method_type: &api_enums::PaymentMethodType, - connector: api_enums::Connector, -) -> bool { - supported_payment_methods_for_mandate - .0 - .get(payment_method) - .and_then(|payment_method_type_hm| payment_method_type_hm.0.get(payment_method_type)) - .map(|supported_connectors| supported_connectors.connector_list.contains(&connector)) - .unwrap_or(false) -} - -fn filter_pm_based_on_config<'a>( - config: &'a settings::ConnectorFilters, - connector: &'a str, - payment_method_type: &'a api_enums::PaymentMethodType, - payment_attempt: Option<&storage::PaymentAttempt>, - card_network: &mut Option>, - country: &Option, - currency: Option, -) -> bool { - config - .0 - .get(connector) - .or_else(|| config.0.get("default")) - .and_then(|inner| match payment_method_type { - api_enums::PaymentMethodType::Credit | api_enums::PaymentMethodType::Debit => { - let country_currency_filter = inner - .0 - .get(&settings::PaymentMethodFilterKey::PaymentMethodType( - *payment_method_type, - )) - .map(|value| global_country_currency_filter(value, country, currency)); - - card_network_filter(country, currency, card_network, inner); - - let capture_method_filter = payment_attempt - .and_then(|inner| inner.capture_method) - .and_then(|capture_method| { - (capture_method == storage_enums::CaptureMethod::Manual).then(|| { - filter_pm_based_on_capture_method_used(inner, payment_method_type) - }) - }); - - Some( - country_currency_filter.unwrap_or(true) - && capture_method_filter.unwrap_or(true), - ) - } - payment_method_type => inner - .0 - .get(&settings::PaymentMethodFilterKey::PaymentMethodType( - *payment_method_type, - )) - .map(|value| global_country_currency_filter(value, country, currency)), - }) - .unwrap_or(true) -} - -///Filters the payment method list on basis of Capture methods, checks whether the connector issues Manual payments using cards or not if not it won't be visible in payment methods list -fn filter_pm_based_on_capture_method_used( - payment_method_filters: &settings::PaymentMethodFilters, +fn filter_pm_based_on_allowed_types( + allowed_types: Option<&Vec>, payment_method_type: &api_enums::PaymentMethodType, ) -> bool { - payment_method_filters - .0 - .get(&settings::PaymentMethodFilterKey::PaymentMethodType( - *payment_method_type, - )) - .and_then(|v| v.not_available_flows) - .and_then(|v| v.capture_method) - .map(|v| !matches!(v, api_enums::CaptureMethod::Manual)) - .unwrap_or(true) -} - -fn card_network_filter( - country: &Option, - currency: Option, - card_network: &mut Option>, - payment_method_filters: &settings::PaymentMethodFilters, -) { - if let Some(value) = card_network.as_mut() { - let filtered_card_networks = value - .iter() - .filter(|&element| { - let key = settings::PaymentMethodFilterKey::CardNetwork(element.clone()); - payment_method_filters - .0 - .get(&key) - .map(|value| global_country_currency_filter(value, country, currency)) - .unwrap_or(true) - }) - .cloned() - .collect::>(); - *value = filtered_card_networks; - } -} - -fn global_country_currency_filter( - item: &settings::CurrencyCountryFlowFilter, - country: &Option, - currency: Option, -) -> bool { - let country_condition = item - .country - .as_ref() - .zip(country.as_ref()) - .map(|(lhs, rhs)| lhs.contains(rhs)); - let currency_condition = item - .currency - .as_ref() - .zip(currency) - .map(|(lhs, rhs)| lhs.contains(&rhs)); - country_condition.unwrap_or(true) && currency_condition.unwrap_or(true) -} - -fn filter_pm_card_network_based( - pm_card_networks: Option<&Vec>, - request_card_networks: Option<&Vec>, - pm_type: &api_enums::PaymentMethodType, -) -> bool { - match pm_type { - api_enums::PaymentMethodType::Credit | api_enums::PaymentMethodType::Debit => { - match (pm_card_networks, request_card_networks) { - (Some(pm_card_networks), Some(request_card_networks)) => request_card_networks - .iter() - .all(|card_network| pm_card_networks.contains(card_network)), - (None, Some(_)) => false, - _ => true, - } - } - _ => true, - } -} -fn filter_pm_country_based( - accepted_countries: &Option, - req_country_list: &Option>, -) -> ( - Option, - Option>, - bool, -) { - match (accepted_countries, req_country_list) { - (None, None) => (None, None, true), - (None, Some(ref r)) => ( - Some(admin::AcceptedCountries::EnableOnly(r.to_vec())), - Some(r.to_vec()), - true, - ), - (Some(l), None) => (Some(l.to_owned()), None, true), - (Some(l), Some(ref r)) => { - let updated = match l { - admin::AcceptedCountries::EnableOnly(acc) => { - filter_accepted_enum_based(&Some(acc.clone()), &Some(r.to_owned())) - .map(admin::AcceptedCountries::EnableOnly) - } - - admin::AcceptedCountries::DisableOnly(den) => { - filter_disabled_enum_based(&Some(den.clone()), &Some(r.to_owned())) - .map(admin::AcceptedCountries::DisableOnly) - } - - admin::AcceptedCountries::AllAccepted => { - Some(admin::AcceptedCountries::AllAccepted) - } - }; - - (updated, Some(r.to_vec()), true) - } - } -} - -fn filter_pm_currencies_based( - accepted_currency: &Option, - req_currency_list: &Option>, -) -> ( - Option, - Option>, - bool, -) { - match (accepted_currency, req_currency_list) { - (None, None) => (None, None, true), - (None, Some(ref r)) => ( - Some(admin::AcceptedCurrencies::EnableOnly(r.to_vec())), - Some(r.to_vec()), - true, - ), - (Some(l), None) => (Some(l.to_owned()), None, true), - (Some(l), Some(ref r)) => { - let updated = match l { - admin::AcceptedCurrencies::EnableOnly(acc) => { - filter_accepted_enum_based(&Some(acc.clone()), &Some(r.to_owned())) - .map(admin::AcceptedCurrencies::EnableOnly) - } - - admin::AcceptedCurrencies::DisableOnly(den) => { - filter_disabled_enum_based(&Some(den.clone()), &Some(r.to_owned())) - .map(admin::AcceptedCurrencies::DisableOnly) - } - - admin::AcceptedCurrencies::AllAccepted => { - Some(admin::AcceptedCurrencies::AllAccepted) - } - }; - - (updated, Some(r.to_vec()), true) - } - } -} - -fn filter_accepted_enum_based( - left: &Option>, - right: &Option>, -) -> Option> { - match (left, right) { - (Some(ref l), Some(ref r)) => { - let a: HashSet<&T> = HashSet::from_iter(l.iter()); - let b: HashSet<&T> = HashSet::from_iter(r.iter()); - - let y: Vec = a.intersection(&b).map(|&i| i.to_owned()).collect(); - Some(y) - } - (Some(ref l), None) => Some(l.to_vec()), - (_, _) => None, - } -} - -fn filter_disabled_enum_based( - left: &Option>, - right: &Option>, -) -> Option> { - match (left, right) { - (Some(ref l), Some(ref r)) => { - let mut enabled = Vec::new(); - for element in r { - if !l.contains(element) { - enabled.push(element.to_owned()); - } - } - Some(enabled) - } - (None, Some(r)) => Some(r.to_vec()), - (_, _) => None, - } + allowed_types.map_or(true, |pm| pm.contains(payment_method_type)) } fn filter_amount_based(payment_method: &RequestPaymentMethodTypes, amount: Option) -> bool { @@ -3222,24 +3043,9 @@ fn filter_amount_based(payment_method: &RequestPaymentMethodTypes, amount: Optio .map(|max_amt| amt <= max_amt.into()) }) .unwrap_or(true); - // let min_check = match (amount, payment_method.minimum_amount) { - // (Some(amt), Some(min_amt)) => amt >= min_amt, - // (_, _) => true, - // }; - // let max_check = match (amount, payment_method.maximum_amount) { - // (Some(amt), Some(max_amt)) => amt <= max_amt, - // (_, _) => true, - // }; (min_check && max_check) || amount == Some(0) } -fn filter_pm_based_on_allowed_types( - allowed_types: Option<&Vec>, - payment_method_type: &api_enums::PaymentMethodType, -) -> bool { - allowed_types.map_or(true, |pm| pm.contains(payment_method_type)) -} - fn filter_recurring_based( payment_method: &RequestPaymentMethodTypes, recurring_enabled: Option, @@ -3256,54 +3062,23 @@ fn filter_installment_based( }) } -async fn filter_payment_country_based( - pm: &RequestPaymentMethodTypes, - address: Option<&domain::Address>, -) -> errors::CustomResult { - Ok(address.map_or(true, |address| { - address.country.as_ref().map_or(true, |country| { - pm.accepted_countries.as_ref().map_or(true, |ac| match ac { - admin::AcceptedCountries::EnableOnly(acc) => acc.contains(country), - admin::AcceptedCountries::DisableOnly(den) => !den.contains(country), - admin::AcceptedCountries::AllAccepted => true, - }) - }) - })) -} - -fn filter_payment_currency_based( - payment_intent: &storage::PaymentIntent, - pm: &RequestPaymentMethodTypes, -) -> bool { - payment_intent.currency.map_or(true, |currency| { - pm.accepted_currencies.as_ref().map_or(true, |ac| match ac { - admin::AcceptedCurrencies::EnableOnly(acc) => acc.contains(¤cy), - admin::AcceptedCurrencies::DisableOnly(den) => !den.contains(¤cy), - admin::AcceptedCurrencies::AllAccepted => true, - }) - }) -} - -fn filter_payment_amount_based( - payment_intent: &storage::PaymentIntent, - pm: &RequestPaymentMethodTypes, +fn filter_pm_card_network_based( + pm_card_networks: Option<&Vec>, + request_card_networks: Option<&Vec>, + pm_type: &api_enums::PaymentMethodType, ) -> bool { - let amount = payment_intent.amount; - (pm.maximum_amount.map_or(true, |amt| amount <= amt.into()) - && pm.minimum_amount.map_or(true, |amt| amount >= amt.into())) - || payment_intent.amount == 0 -} - -async fn filter_payment_mandate_based( - payment_attempt: Option<&storage::PaymentAttempt>, - pm: &RequestPaymentMethodTypes, -) -> errors::CustomResult { - let recurring_filter = if !pm.recurring_enabled { - payment_attempt.map_or(true, |pa| pa.mandate_id.is_none()) - } else { - true - }; - Ok(recurring_filter) + match pm_type { + api_enums::PaymentMethodType::Credit | api_enums::PaymentMethodType::Debit => { + match (pm_card_networks, request_card_networks) { + (Some(pm_card_networks), Some(request_card_networks)) => request_card_networks + .iter() + .all(|card_network| pm_card_networks.contains(card_network)), + (None, Some(_)) => false, + _ => true, + } + } + _ => true, + } } pub async fn do_list_customer_pm_fetch_customer_if_not_passed( @@ -3520,7 +3295,22 @@ pub async fn list_customer_payment_method( ) .await .attach_printable("unable to decrypt payment method billing address details")?; - + let connector_mandate_details = pm + .connector_mandate_details + .clone() + .map(|val| { + val.parse_value::("PaymentsMandateReference") + }) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to deserialize to Payment Mandate Reference ")?; + let mca_enabled = get_mca_status( + state, + &key_store, + &merchant_account.merchant_id, + connector_mandate_details, + ) + .await?; // Need validation for enabled payment method ,querying MCA let pma = api::CustomerPaymentMethod { payment_token: parent_payment_method_token.to_owned(), @@ -3532,7 +3322,7 @@ pub async fn list_customer_payment_method( card: payment_method_retrieval_context.card_details, metadata: pm.metadata, payment_method_issuer_code: pm.payment_method_issuer_code, - recurring_enabled: false, + recurring_enabled: mca_enabled, installment_payment_enabled: false, payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), created: Some(pm.created_at), @@ -3662,7 +3452,43 @@ pub async fn list_customer_payment_method( Ok(services::ApplicationResponse::Json(response)) } +pub async fn get_mca_status( + state: &routes::AppState, + key_store: &domain::MerchantKeyStore, + merchant_id: &str, + connector_mandate_details: Option, +) -> errors::RouterResult { + if let Some(connector_mandate_details) = connector_mandate_details { + let mcas = state + .store + .find_merchant_connector_account_by_merchant_id_and_disabled_list( + merchant_id, + true, + key_store, + ) + .await + .change_context(errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: merchant_id.to_string(), + })?; + let mut mca_ids = HashSet::new(); + let mcas = mcas + .into_iter() + .filter(|mca| mca.disabled == Some(true)) + .collect::>(); + + for mca in mcas { + mca_ids.insert(mca.merchant_connector_id); + } + + for mca_id in connector_mandate_details.keys() { + if !mca_ids.contains(mca_id) { + return Ok(true); + } + } + } + Ok(false) +} pub async fn decrypt_generic_data( data: Option, key: &[u8], 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 e89fb22e8790..88ae82cad33c 100644 --- a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs +++ b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs @@ -367,7 +367,7 @@ fn get_surcharge_details_from_surcharge_output( .attach_printable("Failed to Calculate tax amount") }) .transpose()? - .unwrap_or(0); + .unwrap_or_default(); Ok(types::SurchargeDetails { original_amount: payment_attempt.amount, surcharge: match surcharge_details.surcharge { diff --git a/crates/router/src/core/payment_methods/transformers.rs b/crates/router/src/core/payment_methods/transformers.rs index 009e9e544676..08e87b47f91e 100644 --- a/crates/router/src/core/payment_methods/transformers.rs +++ b/crates/router/src/core/payment_methods/transformers.rs @@ -339,7 +339,7 @@ pub fn mk_add_card_response_hs( merchant_id: &str, ) -> api::PaymentMethodResponse { let card_number = card.card_number.clone(); - let last4_digits = card_number.clone().get_last4(); + let last4_digits = card_number.get_last4(); let card_isin = card_number.get_card_isin(); let card = api::CardDetailFromLocker { @@ -550,13 +550,13 @@ pub fn get_card_detail( response: Card, ) -> CustomResult { let card_number = response.card_number; - let mut last4_digits = card_number.peek().to_owned(); + let last4_digits = card_number.clone().get_last4(); //fetch form card bin let card_detail = api::CardDetailFromLocker { scheme: pm.scheme.to_owned(), issuer_country: pm.issuer_country.clone(), - last4_digits: Some(last4_digits.split_off(last4_digits.len() - 4)), + last4_digits: Some(last4_digits), card_number: Some(card_number), expiry_month: Some(response.card_exp_month), expiry_year: Some(response.card_exp_year), diff --git a/crates/router/src/core/payment_methods/utils.rs b/crates/router/src/core/payment_methods/utils.rs new file mode 100644 index 000000000000..fc7416bf7edf --- /dev/null +++ b/crates/router/src/core/payment_methods/utils.rs @@ -0,0 +1,828 @@ +use std::{str::FromStr, sync::Arc}; + +use api_models::{ + admin::{self, PaymentMethodsEnabled}, + enums as api_enums, + payment_methods::RequestPaymentMethodTypes, +}; +use common_enums::enums; +use euclid::frontend::dir; +use hyperswitch_constraint_graph as cgraph; +use kgraph_utils::{error::KgraphError, transformers::IntoDirValue}; +use storage_impl::redis::cache::PM_FILTERS_CGRAPH_CACHE; + +use crate::configs::settings; + +pub fn make_pm_graph( + builder: &mut cgraph::ConstraintGraphBuilder<'_, dir::DirValue>, + payment_methods: &[serde_json::value::Value], + connector: String, + pm_config_mapping: &settings::ConnectorFilters, + supported_payment_methods_for_mandate: &settings::SupportedPaymentMethodsForMandate, + supported_payment_methods_for_update_mandate: &settings::SupportedPaymentMethodsForMandate, +) -> Result<(), KgraphError> { + for payment_method in payment_methods.iter() { + let pm_enabled = serde_json::from_value::(payment_method.clone()); + if let Ok(payment_methods_enabled) = pm_enabled { + compile_pm_graph( + builder, + payment_methods_enabled.clone(), + connector.clone(), + pm_config_mapping, + supported_payment_methods_for_mandate, + supported_payment_methods_for_update_mandate, + )?; + }; + } + Ok(()) +} + +pub async fn get_merchant_pm_filter_graph<'a>( + key: &str, +) -> Option>> { + PM_FILTERS_CGRAPH_CACHE + .get_val::>>(key) + .await +} + +pub async fn refresh_pm_filters_cache( + key: &str, + graph: cgraph::ConstraintGraph<'static, dir::DirValue>, +) -> Arc> { + let pm_filter_graph = Arc::new(graph); + PM_FILTERS_CGRAPH_CACHE + .push(key.to_string(), pm_filter_graph.clone()) + .await; + pm_filter_graph +} + +fn compile_pm_graph( + builder: &mut cgraph::ConstraintGraphBuilder<'_, dir::DirValue>, + pm_enabled: PaymentMethodsEnabled, + connector: String, + config: &settings::ConnectorFilters, + supported_payment_methods_for_mandate: &settings::SupportedPaymentMethodsForMandate, + supported_payment_methods_for_update_mandate: &settings::SupportedPaymentMethodsForMandate, +) -> Result<(), KgraphError> { + if let Some(payment_method_types) = pm_enabled.payment_method_types { + for pmt in payment_method_types { + let mut agg_nodes: Vec<(cgraph::NodeId, cgraph::Relation, cgraph::Strength)> = + Vec::new(); + let mut agg_or_nodes_for_mandate_filters: Vec<( + cgraph::NodeId, + cgraph::Relation, + cgraph::Strength, + )> = Vec::new(); + + // Connector supported for Update mandate filter + let res = construct_supported_connectors_for_update_mandate_node( + builder, + supported_payment_methods_for_update_mandate, + pmt.clone(), + &pm_enabled.payment_method, + ); + if let Ok(Some(connector_eligible_for_update_mandates_node)) = res { + agg_or_nodes_for_mandate_filters.push(( + connector_eligible_for_update_mandates_node, + cgraph::Relation::Positive, + cgraph::Strength::Strong, + )) + } + + // Connector supported for mandates filter + if let Some(supported_pm_for_mandates) = supported_payment_methods_for_mandate + .0 + .get(&pm_enabled.payment_method) + { + if let Some(supported_connector_for_mandates) = + supported_pm_for_mandates.0.get(&pmt.payment_method_type) + { + let supported_connectors: Vec = + supported_connector_for_mandates + .connector_list + .clone() + .into_iter() + .collect(); + if let Ok(Some(connector_eligible_for_mandates_node)) = + construct_supported_connectors_for_mandate_node( + builder, + supported_connectors, + ) + { + agg_or_nodes_for_mandate_filters.push(( + connector_eligible_for_mandates_node, + cgraph::Relation::Positive, + cgraph::Strength::Strong, + )) + } + } + } + + // Non Prominent Mandate flows + let payment_type_non_mandate_value_node = builder.make_value_node( + cgraph::NodeValue::Value(dir::DirValue::PaymentType( + euclid::enums::PaymentType::NonMandate, + )), + None, + None::<()>, + ); + let payment_type_setup_mandate_value_node = builder.make_value_node( + cgraph::NodeValue::Value(dir::DirValue::PaymentType( + euclid::enums::PaymentType::SetupMandate, + )), + None, + None::<()>, + ); + + let non_major_mandate_any_node = builder + .make_any_aggregator( + &[ + ( + payment_type_non_mandate_value_node, + cgraph::Relation::Positive, + cgraph::Strength::Strong, + ), + ( + payment_type_setup_mandate_value_node, + cgraph::Relation::Positive, + cgraph::Strength::Strong, + ), + ], + None, + None::<()>, + None, + ) + .map_err(KgraphError::GraphConstructionError)?; + + agg_or_nodes_for_mandate_filters.push(( + non_major_mandate_any_node, + cgraph::Relation::Positive, + cgraph::Strength::Strong, + )); + + let agg_or_node = builder + .make_any_aggregator(&agg_or_nodes_for_mandate_filters, None, None::<()>, None) + .map_err(KgraphError::GraphConstructionError)?; + + agg_nodes.push(( + agg_or_node, + cgraph::Relation::Positive, + cgraph::Strength::Strong, + )); + + // Capture Method filter + config + .0 + .get(connector.as_str()) + .or_else(|| config.0.get("default")) + .map(|inner| { + if let Ok(Some(capture_method_filter)) = + construct_capture_method_node(builder, inner, &pmt.payment_method_type) + { + agg_nodes.push(( + capture_method_filter, + cgraph::Relation::Negative, + cgraph::Strength::Strong, + )) + } + }); + + // Country filter + if let Some(pm_object_countries) = pmt.accepted_countries { + if let Ok(Some(country_node)) = compile_accepted_countries_for_mca( + builder, + &pmt.payment_method_type, + pm_object_countries, + config, + connector.clone(), + ) { + agg_nodes.push(( + country_node, + cgraph::Relation::Positive, + cgraph::Strength::Strong, + )) + } + } + + // Currency filter + if let Some(pm_object_currencies) = pmt.accepted_currencies { + if let Ok(Some(currency_node)) = compile_accepted_currency_for_mca( + builder, + &pmt.payment_method_type, + pm_object_currencies, + config, + connector.clone(), + ) { + agg_nodes.push(( + currency_node, + cgraph::Relation::Positive, + cgraph::Strength::Strong, + )) + } + } + + let and_node_for_all_the_filters = builder + .make_all_aggregator(&agg_nodes, None, None::<()>, None) + .map_err(KgraphError::GraphConstructionError)?; + + // Making our output node + let pmt_info = "PaymentMethodType"; + let dir_node: cgraph::NodeValue = + (pmt.payment_method_type, pm_enabled.payment_method) + .into_dir_value() + .map(Into::into)?; + let payment_method_type_value_node = + builder.make_value_node(dir_node, Some(pmt_info), None::<()>); + + builder + .make_edge( + and_node_for_all_the_filters, + payment_method_type_value_node, + cgraph::Strength::Strong, + cgraph::Relation::Positive, + None::, + ) + .map_err(KgraphError::GraphConstructionError)?; + } + } + Ok(()) +} + +fn construct_capture_method_node( + builder: &mut cgraph::ConstraintGraphBuilder<'_, dir::DirValue>, + payment_method_filters: &settings::PaymentMethodFilters, + payment_method_type: &api_enums::PaymentMethodType, +) -> Result, KgraphError> { + if !payment_method_filters + .0 + .get(&settings::PaymentMethodFilterKey::PaymentMethodType( + *payment_method_type, + )) + .and_then(|v| v.not_available_flows) + .and_then(|v| v.capture_method) + .map(|v| !matches!(v, api_enums::CaptureMethod::Manual)) + .unwrap_or(true) + { + return Ok(Some(builder.make_value_node( + cgraph::NodeValue::Value(dir::DirValue::CaptureMethod( + common_enums::CaptureMethod::Manual, + )), + None, + None::<()>, + ))); + } + Ok(None) +} + +fn construct_supported_connectors_for_update_mandate_node( + builder: &mut cgraph::ConstraintGraphBuilder<'_, dir::DirValue>, + supported_payment_methods_for_update_mandate: &settings::SupportedPaymentMethodsForMandate, + pmt: RequestPaymentMethodTypes, + payment_method: &enums::PaymentMethod, +) -> Result, KgraphError> { + let card_value_node = builder.make_value_node( + cgraph::NodeValue::Value(dir::DirValue::PaymentMethod(enums::PaymentMethod::Card)), + None, + None::<()>, + ); + + let payment_type_value_node = builder.make_value_node( + cgraph::NodeValue::Value(dir::DirValue::PaymentType( + euclid::enums::PaymentType::UpdateMandate, + )), + None, + None::<()>, + ); + + let mut agg_nodes: Vec<(cgraph::NodeId, cgraph::Relation, cgraph::Strength)> = Vec::new(); + let mut card_dir_values = Vec::new(); + let mut non_card_dir_values = Vec::new(); + + if let Some(supported_pm_for_mandates) = supported_payment_methods_for_update_mandate + .0 + .get(payment_method) + { + if payment_method == &enums::PaymentMethod::Card { + if let Some(credit_connector_list) = supported_pm_for_mandates + .0 + .get(&api_enums::PaymentMethodType::Credit) + { + card_dir_values.extend( + credit_connector_list + .connector_list + .clone() + .into_iter() + .filter_map(|connector| { + api_enums::RoutableConnectors::from_str(connector.to_string().as_str()) + .ok() + .map(|connector| { + dir::DirValue::Connector(Box::new( + api_models::routing::ast::ConnectorChoice { + connector, + #[cfg(not(feature = "connector_choice_mca_id"))] + sub_label: None, + }, + )) + }) + }), + ); + } + + if let Some(debit_connector_list) = supported_pm_for_mandates + .0 + .get(&api_enums::PaymentMethodType::Debit) + { + card_dir_values.extend( + debit_connector_list + .connector_list + .clone() + .into_iter() + .filter_map(|connector| { + api_enums::RoutableConnectors::from_str(connector.to_string().as_str()) + .ok() + .map(|connector| { + dir::DirValue::Connector(Box::new( + api_models::routing::ast::ConnectorChoice { + connector, + #[cfg(not(feature = "connector_choice_mca_id"))] + sub_label: None, + }, + )) + }) + }), + ); + } + let card_in_node = builder + .make_in_aggregator(card_dir_values, None, None::<()>) + .map_err(KgraphError::GraphConstructionError)?; + + let card_and_node = builder + .make_all_aggregator( + &[ + ( + card_value_node, + cgraph::Relation::Positive, + cgraph::Strength::Strong, + ), + ( + payment_type_value_node, + cgraph::Relation::Positive, + cgraph::Strength::Strong, + ), + ( + card_in_node, + cgraph::Relation::Positive, + cgraph::Strength::Strong, + ), + ], + None, + None::<()>, + None, + ) + .map_err(KgraphError::GraphConstructionError)?; + + agg_nodes.push(( + card_and_node, + cgraph::Relation::Positive, + cgraph::Strength::Strong, + )); + } else if let Some(connector_list) = + supported_pm_for_mandates.0.get(&pmt.payment_method_type) + { + non_card_dir_values.extend( + connector_list + .connector_list + .clone() + .into_iter() + .filter_map(|connector| { + api_enums::RoutableConnectors::from_str(connector.to_string().as_str()) + .ok() + .map(|connector| { + dir::DirValue::Connector(Box::new( + api_models::routing::ast::ConnectorChoice { + connector, + #[cfg(not(feature = "connector_choice_mca_id"))] + sub_label: None, + }, + )) + }) + }), + ); + let non_card_mandate_in_node = builder + .make_in_aggregator(non_card_dir_values, None, None::<()>) + .map_err(KgraphError::GraphConstructionError)?; + + let non_card_and_node = builder + .make_all_aggregator( + &[ + ( + card_value_node, + cgraph::Relation::Negative, + cgraph::Strength::Strong, + ), + ( + payment_type_value_node, + cgraph::Relation::Positive, + cgraph::Strength::Strong, + ), + ( + non_card_mandate_in_node, + cgraph::Relation::Positive, + cgraph::Strength::Strong, + ), + ], + None, + None::<()>, + None, + ) + .map_err(KgraphError::GraphConstructionError)?; + + agg_nodes.push(( + non_card_and_node, + cgraph::Relation::Positive, + cgraph::Strength::Strong, + )); + } + } + + Ok(Some( + builder + .make_any_aggregator( + &agg_nodes, + Some("any node for card and non card pm"), + None::<()>, + None, + ) + .map_err(KgraphError::GraphConstructionError)?, + )) +} + +fn construct_supported_connectors_for_mandate_node( + builder: &mut cgraph::ConstraintGraphBuilder<'_, dir::DirValue>, + eligible_connectors: Vec, +) -> Result, KgraphError> { + let payment_type_value_node = builder.make_value_node( + cgraph::NodeValue::Value(dir::DirValue::PaymentType( + euclid::enums::PaymentType::NewMandate, + )), + None, + None::<()>, + ); + let connectors_from_config: Vec = eligible_connectors + .into_iter() + .filter_map(|connector| { + match api_enums::RoutableConnectors::from_str(connector.to_string().as_str()) { + Ok(connector) => Some(dir::DirValue::Connector(Box::new( + api_models::routing::ast::ConnectorChoice { + connector, + #[cfg(not(feature = "connector_choice_mca_id"))] + sub_label: None, + }, + ))), + Err(_) => None, + } + }) + .collect(); + + if connectors_from_config.is_empty() { + Ok(None) + } else { + let connector_in_aggregator = builder + .make_in_aggregator(connectors_from_config, None, None::<()>) + .map_err(KgraphError::GraphConstructionError)?; + Ok(Some( + builder + .make_all_aggregator( + &[ + ( + payment_type_value_node, + cgraph::Relation::Positive, + cgraph::Strength::Strong, + ), + ( + connector_in_aggregator, + cgraph::Relation::Positive, + cgraph::Strength::Strong, + ), + ], + None, + None::<()>, + None, + ) + .map_err(KgraphError::GraphConstructionError)?, + )) + } +} + +// fn construct_card_network_nodes( +// builder: &mut cgraph::ConstraintGraphBuilder<'_, dir::DirValue>, +// mca_card_networks: Vec, +// ) -> Result, KgraphError> { +// Ok(Some( +// builder +// .make_in_aggregator( +// mca_card_networks +// .into_iter() +// .map(dir::DirValue::CardNetwork) +// .collect(), +// None, +// None::<()>, +// ) +// .map_err(KgraphError::GraphConstructionError)?, +// )) +// } + +fn compile_accepted_countries_for_mca( + builder: &mut cgraph::ConstraintGraphBuilder<'_, dir::DirValue>, + payment_method_type: &enums::PaymentMethodType, + pm_obj_countries: admin::AcceptedCountries, + config: &settings::ConnectorFilters, + connector: String, +) -> Result, KgraphError> { + match pm_obj_countries { + admin::AcceptedCountries::EnableOnly(countries) => { + // Country from the MCA + let pm_object_country_value_node = builder + .make_in_aggregator( + countries + .into_iter() + .map(|country| { + dir::DirValue::BillingCountry(common_enums::Country::from_alpha2( + country, + )) + }) + .collect(), + None, + None::<()>, + ) + .map_err(KgraphError::GraphConstructionError)?; + if let Some(config) = config + .0 + .get(connector.as_str()) + .or_else(|| config.0.get("default")) + { + if let Some(value) = + config + .0 + .get(&settings::PaymentMethodFilterKey::PaymentMethodType( + *payment_method_type, + )) + { + // country from config + if let Some(config_countries) = value.country.as_ref() { + let config_countries: Vec = + Vec::from_iter(config_countries) + .into_iter() + .map(|country| common_enums::Country::from_alpha2(*country)) + .collect(); + let dir_countries: Vec = config_countries + .into_iter() + .map(dir::DirValue::BillingCountry) + .collect(); + + let config_country_agg_node = builder + .make_in_aggregator(dir_countries, None, None::<()>) + .map_err(KgraphError::GraphConstructionError)?; + + let node = builder + .make_all_aggregator( + &[ + ( + pm_object_country_value_node, + cgraph::Relation::Positive, + cgraph::Strength::Strong, + ), + ( + config_country_agg_node, + cgraph::Relation::Positive, + cgraph::Strength::Strong, + ), + ], + None, + None::<()>, + None, + ) + .map_err(KgraphError::GraphConstructionError)?; + return Ok(Some(node)); + } + } + } + } + admin::AcceptedCountries::DisableOnly(countries) => { + if let Some(config) = config + .0 + .get(connector.as_str()) + .or_else(|| config.0.get("default")) + { + // Country from the MCA + let pm_object_country_value_node = builder + .make_in_aggregator( + countries + .into_iter() + .map(|country| { + dir::DirValue::BillingCountry(common_enums::Country::from_alpha2( + country, + )) + }) + .collect(), + None, + None::<()>, + ) + .map_err(KgraphError::GraphConstructionError)?; + + if let Some(value) = + config + .0 + .get(&settings::PaymentMethodFilterKey::PaymentMethodType( + *payment_method_type, + )) + { + // country from config + if let Some(config_countries) = value.country.as_ref() { + let config_countries: Vec = + Vec::from_iter(config_countries) + .into_iter() + .map(|country| common_enums::Country::from_alpha2(*country)) + .collect(); + let dir_countries: Vec = config_countries + .into_iter() + .map(dir::DirValue::BillingCountry) + .collect(); + + let config_country_agg_node = builder + .make_in_aggregator(dir_countries, None, None::<()>) + .map_err(KgraphError::GraphConstructionError)?; + + let node = builder + .make_all_aggregator( + &[ + ( + pm_object_country_value_node, + cgraph::Relation::Negative, + cgraph::Strength::Strong, + ), + ( + config_country_agg_node, + cgraph::Relation::Positive, + cgraph::Strength::Strong, + ), + ], + None, + None::<()>, + None, + ) + .map_err(KgraphError::GraphConstructionError)?; + return Ok(Some(node)); + } + } + } + } + admin::AcceptedCountries::AllAccepted => return Ok(None), + } + Ok(None) +} + +fn compile_accepted_currency_for_mca( + builder: &mut cgraph::ConstraintGraphBuilder<'_, dir::DirValue>, + payment_method_type: &enums::PaymentMethodType, + pm_obj_currency: admin::AcceptedCurrencies, + config: &settings::ConnectorFilters, + connector: String, +) -> Result, KgraphError> { + match pm_obj_currency { + admin::AcceptedCurrencies::EnableOnly(currency) => { + // Currency from the MCA + let pm_object_currency_value_node = builder + .make_in_aggregator( + currency + .into_iter() + .map(dir::DirValue::PaymentCurrency) + .collect(), + None, + None::<()>, + ) + .map_err(KgraphError::GraphConstructionError)?; + + if let Some(config) = config + .0 + .get(connector.as_str()) + .or_else(|| config.0.get("default")) + { + if let Some(value) = + config + .0 + .get(&settings::PaymentMethodFilterKey::PaymentMethodType( + *payment_method_type, + )) + { + // Currency from config + if let Some(config_currencies) = value.currency.as_ref() { + let config_currency: Vec = + Vec::from_iter(config_currencies) + .into_iter() + .cloned() + .collect(); + + let dir_currencies: Vec = config_currency + .into_iter() + .map(dir::DirValue::PaymentCurrency) + .collect(); + + let config_currency_agg_node = builder + .make_in_aggregator(dir_currencies, None, None::<()>) + .map_err(KgraphError::GraphConstructionError)?; + + let node = builder + .make_all_aggregator( + &[ + ( + pm_object_currency_value_node, + cgraph::Relation::Positive, + cgraph::Strength::Strong, + ), + ( + config_currency_agg_node, + cgraph::Relation::Positive, + cgraph::Strength::Strong, + ), + ], + None, + None::<()>, + None, + ) + .map_err(KgraphError::GraphConstructionError)?; + return Ok(Some(node)); + } + } + } + } + admin::AcceptedCurrencies::DisableOnly(currency) => { + // Currency from the MCA + let pm_object_currency_value_node = builder + .make_in_aggregator( + currency + .into_iter() + .map(dir::DirValue::PaymentCurrency) + .collect(), + None, + None::<()>, + ) + .map_err(KgraphError::GraphConstructionError)?; + + if let Some(config) = config + .0 + .get(connector.as_str()) + .or_else(|| config.0.get("default")) + { + if let Some(value) = + config + .0 + .get(&settings::PaymentMethodFilterKey::PaymentMethodType( + *payment_method_type, + )) + { + // Currency from config + if let Some(config_currencies) = value.currency.as_ref() { + let config_currency: Vec = + Vec::from_iter(config_currencies) + .into_iter() + .cloned() + .collect(); + + let dir_currencies: Vec = config_currency + .into_iter() + .map(dir::DirValue::PaymentCurrency) + .collect(); + + let config_currency_agg_node = builder + .make_in_aggregator(dir_currencies, None, None::<()>) + .map_err(KgraphError::GraphConstructionError)?; + + let node = builder + .make_all_aggregator( + &[ + ( + pm_object_currency_value_node, + cgraph::Relation::Negative, + cgraph::Strength::Strong, + ), + ( + config_currency_agg_node, + cgraph::Relation::Positive, + cgraph::Strength::Strong, + ), + ], + None, + None::<()>, + None, + ) + .map_err(KgraphError::GraphConstructionError)?; + return Ok(Some(node)); + } + } + } + } + admin::AcceptedCurrencies::AllAccepted => return Ok(None), + } + Ok(None) +} diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 1d25db6a0baf..f760e907288a 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -27,16 +27,20 @@ use api_models::{ use common_utils::{ ext_traits::{AsyncExt, StringExt}, pii, - types::Surcharge, + types::{MinorUnit, Surcharge}, }; use diesel_models::{ephemeral_key, fraud_check::FraudCheck}; use error_stack::{report, ResultExt}; use events::EventInfo; use futures::future::join_all; use helpers::ApplePayData; -use hyperswitch_domain_models::mandates::{CustomerAcceptance, MandateData}; +pub use hyperswitch_domain_models::{ + mandates::{CustomerAcceptance, MandateData}, + payment_address::PaymentAddress, + router_data::RouterData, + router_request_types::CustomerDetails, +}; use masking::{ExposeInterface, Secret}; -pub use payment_address::PaymentAddress; use redis_interface::errors::RedisError; use router_env::{instrument, tracing}; #[cfg(feature = "olap")] @@ -117,7 +121,7 @@ where // To create connector flow specific interface data PaymentData: ConstructFlowSpecificData, - router_types::RouterData: Feature, + RouterData: Feature, // To construct connector flow specific api dyn api::Connector: @@ -291,7 +295,7 @@ where call_connector_action.clone(), &validate_result, schedule_time, - header_payload, + header_payload.clone(), #[cfg(feature = "frm")] frm_info.as_ref().and_then(|fi| fi.suggested_action), #[cfg(not(feature = "frm"))] @@ -361,7 +365,7 @@ where call_connector_action.clone(), &validate_result, schedule_time, - header_payload, + header_payload.clone(), #[cfg(feature = "frm")] frm_info.as_ref().and_then(|fi| fi.suggested_action), #[cfg(not(feature = "frm"))] @@ -494,7 +498,7 @@ where frm_info.and_then(|info| info.suggested_action), #[cfg(not(feature = "frm"))] None, - header_payload, + header_payload.clone(), ) .await?; } @@ -525,7 +529,7 @@ where None, &key_store, None, - header_payload, + header_payload.clone(), ) .await?; } @@ -691,7 +695,7 @@ 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(0); + 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; Ok(Some(api::SessionSurchargeDetails::PreDetermined( @@ -756,7 +760,7 @@ where Res: transformers::ToResponse, Op>, // To create connector flow specific interface data PaymentData: ConstructFlowSpecificData, - router_types::RouterData: Feature, + RouterData: Feature, // To construct connector flow specific api dyn api::Connector: @@ -782,7 +786,7 @@ where call_connector_action, auth_flow, eligible_routable_connectors, - header_payload, + header_payload.clone(), ) .await?; @@ -1207,7 +1211,7 @@ impl PaymentRedirectFlow for PaymentAuthenticateCompleteAuthorize { ); let response = if is_pull_mechanism_enabled || authentication.authentication_type - == Some(common_enums::DecoupledAuthenticationType::Frictionless) + != Some(common_enums::DecoupledAuthenticationType::Challenge) { let payment_confirm_req = api::PaymentsRequest { payment_id: Some(req.resource_id.clone()), @@ -1386,15 +1390,14 @@ pub async fn call_connector_service( header_payload: HeaderPayload, frm_suggestion: Option, business_profile: &storage::business_profile::BusinessProfile, -) -> RouterResult> +) -> RouterResult> where F: Send + Clone + Sync, RouterDReq: Send + Sync, // To create connector flow specific interface data PaymentData: ConstructFlowSpecificData, - router_types::RouterData: - Feature + Send, + RouterData: Feature + Send, // To construct connector flow specific api dyn api::Connector: services::api::ConnectorIntegration, @@ -1495,23 +1498,29 @@ where }; let apple_pay_predecrypt = apple_pay_data - .parse_value::("ApplePayPredecryptData") + .parse_value::( + "ApplePayPredecryptData", + ) .change_context(errors::ApiErrorResponse::InternalServerError)?; logger::debug!(?apple_pay_predecrypt); - router_data.payment_method_token = Some(router_types::PaymentMethodToken::ApplePayDecrypt( - Box::new(apple_pay_predecrypt), - )); + router_data.payment_method_token = Some( + hyperswitch_domain_models::router_data::PaymentMethodToken::ApplePayDecrypt(Box::new( + apple_pay_predecrypt, + )), + ); } let pm_token = router_data .add_payment_method_token(state, &connector, &tokenization_action) .await?; if let Some(payment_method_token) = pm_token.clone() { - router_data.payment_method_token = Some(router_types::PaymentMethodToken::Token( - payment_method_token, - )); + router_data.payment_method_token = Some( + hyperswitch_domain_models::router_data::PaymentMethodToken::Token(Secret::new( + payment_method_token, + )), + ); }; (router_data, should_continue_further) = complete_preprocessing_steps_if_required( @@ -1571,7 +1580,7 @@ where updated_customer, key_store, frm_suggestion, - header_payload, + header_payload.clone(), ) .await?; @@ -1657,7 +1666,7 @@ where // To create connector flow specific interface data PaymentData: ConstructFlowSpecificData, - router_types::RouterData: Feature, + RouterData: Feature, // To construct connector flow specific api dyn api::Connector: @@ -1771,7 +1780,7 @@ where // To create connector flow specific interface data PaymentData: ConstructFlowSpecificData, - router_types::RouterData: Feature + Send, + RouterData: Feature + Send, // To construct connector flow specific api dyn api::Connector: @@ -1863,17 +1872,14 @@ async fn complete_preprocessing_steps_if_required( state: &AppState, connector: &api::ConnectorData, payment_data: &PaymentData, - mut router_data: router_types::RouterData, + mut router_data: RouterData, operation: &BoxedOperation<'_, F, Q>, should_continue_payment: bool, -) -> RouterResult<( - router_types::RouterData, - bool, -)> +) -> RouterResult<(RouterData, bool)> where F: Send + Clone + Sync, Req: Send + Sync, - router_types::RouterData: Feature + Send, + RouterData: Feature + Send, dyn api::Connector: services::api::ConnectorIntegration, { @@ -2398,91 +2404,6 @@ pub enum CallConnectorAction { HandleResponse(Vec), } -pub mod payment_address { - use super::*; - - #[derive(Clone, Default, Debug)] - pub struct PaymentAddress { - shipping: Option, - billing: Option, - unified_payment_method_billing: Option, - payment_method_billing: Option, - } - - impl PaymentAddress { - pub fn new( - shipping: Option, - billing: Option, - payment_method_billing: Option, - should_unify_address: Option, - ) -> Self { - // billing -> .billing, this is the billing details passed in the root of payments request - // payment_method_billing -> payment_method_data.billing - - let unified_payment_method_billing = if should_unify_address.unwrap_or(true) { - // Merge the billing details field from both `payment.billing` and `payment.payment_method_data.billing` - // The unified payment_method_billing will be used as billing address and passed to the connector module - // This unification is required in order to provide backwards compatibility - // so that if `payment.billing` is passed it should be sent to the connector module - // Unify the billing details with `payment_method_data.billing` - payment_method_billing - .as_ref() - .map(|payment_method_billing| { - payment_method_billing - .clone() - .unify_address(billing.as_ref()) - }) - .or(billing.clone()) - } else { - payment_method_billing.clone() - }; - - Self { - shipping, - billing, - unified_payment_method_billing, - payment_method_billing, - } - } - - pub fn get_shipping(&self) -> Option<&api::Address> { - self.shipping.as_ref() - } - - pub fn get_payment_method_billing(&self) -> Option<&api::Address> { - self.unified_payment_method_billing.as_ref() - } - - /// Unify the billing details from `payment_method_data.[payment_method_data].billing details`. - pub fn unify_with_payment_method_data_billing( - self, - payment_method_data_billing: Option, - ) -> Self { - // Unify the billing details with `payment_method_data.billing_details` - let unified_payment_method_billing = payment_method_data_billing - .map(|payment_method_data_billing| { - payment_method_data_billing.unify_address(self.get_payment_method_billing()) - }) - .or(self.get_payment_method_billing().cloned()); - - Self { - shipping: self.shipping, - billing: self.billing, - unified_payment_method_billing, - payment_method_billing: self.payment_method_billing, - } - } - - pub fn get_request_payment_method_billing(&self) -> Option<&api::Address> { - self.payment_method_billing.as_ref() - } - - pub fn get_payment_billing(&self) -> Option<&api::Address> { - self.billing.as_ref() - } - } -} - #[derive(Clone)] pub struct MandateConnectorDetails { pub connector: String, @@ -2520,7 +2441,8 @@ where pub creds_identifier: Option, pub pm_token: Option, pub connector_customer_id: Option, - pub recurring_mandate_payment_data: Option, + pub recurring_mandate_payment_data: + Option, pub ephemeral_key: Option, pub redirect_response: Option, pub surcharge_details: Option, @@ -2529,7 +2451,6 @@ where pub incremental_authorization_details: Option, pub authorizations: Vec, pub authentication: Option, - pub frm_metadata: Option, pub recurring_details: Option, pub poll_config: Option, } @@ -2562,28 +2483,12 @@ impl EventInfo for PaymentEvent { #[derive(Debug, Default, Clone)] pub struct IncrementalAuthorizationDetails { - pub additional_amount: i64, - pub total_amount: i64, + pub additional_amount: MinorUnit, + pub total_amount: MinorUnit, pub reason: Option, pub authorization_id: Option, } -#[derive(Debug, Default, Clone)] -pub struct RecurringMandatePaymentData { - pub payment_method_type: Option, //required for making recurring payment using saved payment method through stripe - pub original_payment_authorized_amount: Option, - pub original_payment_authorized_currency: Option, -} - -#[derive(Debug, Default, Clone)] -pub struct CustomerDetails { - pub customer_id: Option, - pub name: Option>, - pub email: Option, - pub phone: Option>, - pub phone_country_code: Option, -} - pub trait CustomerDetailsExt { type Error; fn get_name(&self) -> Result, Self::Error>; @@ -3257,6 +3162,34 @@ where { routing_data.business_sub_label = choice.sub_label.clone(); } + + #[cfg(feature = "retry")] + let should_do_retry = + retry::config_should_call_gsm(&*state.store, &merchant_account.merchant_id).await; + + #[cfg(feature = "retry")] + if payment_data.payment_attempt.payment_method_type + == Some(storage_enums::PaymentMethodType::ApplePay) + && should_do_retry + { + let retryable_connector_data = helpers::get_apple_pay_retryable_connectors( + state, + merchant_account, + payment_data, + key_store, + connector_data.clone(), + #[cfg(feature = "connector_choice_mca_id")] + choice.merchant_connector_id.clone().as_ref(), + #[cfg(not(feature = "connector_choice_mca_id"))] + None, + ) + .await?; + + if let Some(connector_data_list) = retryable_connector_data { + return Ok(ConnectorCallType::Retryable(connector_data_list)); + } + } + return Ok(ConnectorCallType::PreDetermined(connector_data)); } } @@ -3279,7 +3212,6 @@ where connectors = routing::perform_eligibility_analysis_with_fallback( &state.clone(), key_store, - merchant_account.modified_at.assume_utc().unix_timestamp(), connectors, &TransactionData::Payment(payment_data), eligible_connectors, @@ -3337,7 +3269,6 @@ where connectors = routing::perform_eligibility_analysis_with_fallback( &state, key_store, - merchant_account.modified_at.assume_utc().unix_timestamp(), connectors, &TransactionData::Payment(payment_data), eligible_connectors, @@ -3506,7 +3437,7 @@ pub async fn decide_multiplex_connector_for_normal_or_recurring_payment { + metrics::ACCESS_TOKEN_CACHE_MISS.add( + &metrics::CONTEXT, + 1, + &[metrics::request::add_attributes( + "connector", + connector.connector_name.to_string(), + )], + ); + let cloned_router_data = router_data.clone(); let refresh_token_request_data = types::AccessTokenRequestData::try_from( router_data.connector_auth_type.clone(), @@ -123,15 +140,31 @@ pub async fn add_access_token< &refresh_token_router_data, ) .await? - .async_map(|access_token| async { - // Store the access token in redis with expiry - // The expiry should be adjusted for network delays from the connector + .async_map(|access_token| async move { let store = &*state.store; + + // The expiry should be adjusted for network delays from the connector + // The access token might not have been expired when request is sent + // But once it reaches the connector, it might expire because of the network delay + // Subtract few seconds from the expiry in order to account for these network delays + // This will reduce the expiry time by `REDUCE_ACCESS_TOKEN_EXPIRY_TIME` seconds + let modified_access_token_with_expiry = types::AccessToken { + expires: access_token + .expires + .saturating_sub(consts::REDUCE_ACCESS_TOKEN_EXPIRY_TIME.into()), + ..access_token + }; + + logger::debug!( + access_token_expiry_after_modification = + modified_access_token_with_expiry.expires + ); + if let Err(access_token_set_error) = store .set_access_token( merchant_id, &merchant_connector_id_or_connector_name, - access_token.clone(), + modified_access_token_with_expiry.clone(), ) .await .change_context(errors::ApiErrorResponse::InternalServerError) @@ -142,7 +175,7 @@ pub async fn add_access_token< // The next request will create new access token, if required logger::error!(access_token_set_error=?access_token_set_error); } - Some(access_token) + Some(modified_access_token_with_expiry) }) .await } diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index a8425896dd7b..5cb610e4b068 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -2568,6 +2568,7 @@ macro_rules! default_imp_for_connector_authentication { $( impl api::ExternalAuthentication for $path::$connector {} impl api::ConnectorAuthentication for $path::$connector {} impl api::ConnectorPreAuthentication for $path::$connector {} + impl api::ConnectorPreAuthenticationVersionCall for $path::$connector {} impl api::ConnectorPostAuthentication for $path::$connector {} impl services::ConnectorIntegration< @@ -2583,6 +2584,13 @@ macro_rules! default_imp_for_connector_authentication { types::authentication::AuthenticationResponseData, > for $path::$connector {} + impl + services::ConnectorIntegration< + api::PreAuthenticationVersionCall, + types::authentication::PreAuthNRequestData, + types::authentication::AuthenticationResponseData, + > for $path::$connector + {} impl services::ConnectorIntegration< api::PostAuthentication, @@ -2599,6 +2607,8 @@ impl api::ExternalAuthentication for connector::DummyConnector { #[cfg(feature = "dummy_connector")] impl api::ConnectorPreAuthentication for connector::DummyConnector {} #[cfg(feature = "dummy_connector")] +impl api::ConnectorPreAuthenticationVersionCall for connector::DummyConnector {} +#[cfg(feature = "dummy_connector")] impl api::ConnectorAuthentication for connector::DummyConnector {} #[cfg(feature = "dummy_connector")] impl api::ConnectorPostAuthentication for connector::DummyConnector {} @@ -2622,6 +2632,15 @@ impl { } #[cfg(feature = "dummy_connector")] +impl + services::ConnectorIntegration< + api::PreAuthenticationVersionCall, + types::authentication::PreAuthNRequestData, + types::authentication::AuthenticationResponseData, + > for connector::DummyConnector +{ +} +#[cfg(feature = "dummy_connector")] impl services::ConnectorIntegration< api::PostAuthentication, diff --git a/crates/router/src/core/payments/flows/approve_flow.rs b/crates/router/src/core/payments/flows/approve_flow.rs index 92f815f6b5f4..95ca0c3e31c7 100644 --- a/crates/router/src/core/payments/flows/approve_flow.rs +++ b/crates/router/src/core/payments/flows/approve_flow.rs @@ -3,7 +3,7 @@ use async_trait::async_trait; use super::{ConstructFlowSpecificData, Feature}; use crate::{ core::{ - errors::{api_error_response::NotImplementedMessage, ApiErrorResponse, RouterResult}, + errors::{ApiErrorResponse, NotImplementedMessage, RouterResult}, payments::{self, access_token, helpers, transformers, PaymentData}, }, routes::AppState, diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index a770454b2046..75bbb5bda8c3 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -212,7 +212,14 @@ impl Feature for types::PaymentsAu } } -impl types::PaymentsAuthorizeRouterData { +pub trait RouterDataAuthorize { + fn decide_authentication_type(&mut self); + + /// to decide if we need to proceed with authorize or not, Eg: If any of the pretask returns `redirection_response` then we should not proceed with authorize call + fn should_proceed_with_authorize(&self) -> bool; +} + +impl RouterDataAuthorize for types::PaymentsAuthorizeRouterData { fn decide_authentication_type(&mut self) { if self.auth_type == diesel_models::enums::AuthenticationType::ThreeDs && !self.request.enrolled_for_3ds diff --git a/crates/router/src/core/payments/flows/psync_flow.rs b/crates/router/src/core/payments/flows/psync_flow.rs index d1e2e4b0abef..51627e2d68b7 100644 --- a/crates/router/src/core/payments/flows/psync_flow.rs +++ b/crates/router/src/core/payments/flows/psync_flow.rs @@ -145,9 +145,31 @@ impl Feature } } -impl types::RouterData { +#[async_trait] +pub trait RouterDataPSync +where + Self: Sized, +{ async fn execute_connector_processing_step_for_each_capture( - mut self, + &self, + _state: &AppState, + _pending_connector_capture_id_list: Vec, + _call_connector_action: payments::CallConnectorAction, + _connector_integration: services::BoxedConnectorIntegration< + '_, + api::PSync, + types::PaymentsSyncData, + types::PaymentsResponseData, + >, + ) -> RouterResult; +} + +#[async_trait] +impl RouterDataPSync + for types::RouterData +{ + async fn execute_connector_processing_step_for_each_capture( + &self, state: &AppState, pending_connector_capture_id_list: Vec, call_connector_action: payments::CallConnectorAction, @@ -164,7 +186,7 @@ impl types::RouterData Err(ApiErrorResponse::PreconditionFailed { message: "Response type must be PaymentsResponseData::MultipleCaptureResponse for payment sync".into() })?, }; } - self.response = Ok(types::PaymentsResponseData::MultipleCaptureResponse { - capture_sync_response_list: capture_sync_response_map, - }); - Ok(self) + let mut cloned_router_data = self.clone(); + cloned_router_data.response = + Ok(types::PaymentsResponseData::MultipleCaptureResponse { + capture_sync_response_list: capture_sync_response_map, + }); + Ok(cloned_router_data) } } } diff --git a/crates/router/src/core/payments/flows/reject_flow.rs b/crates/router/src/core/payments/flows/reject_flow.rs index 726993aa7fae..638efa054eb8 100644 --- a/crates/router/src/core/payments/flows/reject_flow.rs +++ b/crates/router/src/core/payments/flows/reject_flow.rs @@ -3,7 +3,7 @@ use async_trait::async_trait; use super::{ConstructFlowSpecificData, Feature}; use crate::{ core::{ - errors::{api_error_response::NotImplementedMessage, ApiErrorResponse, RouterResult}, + errors::{ApiErrorResponse, NotImplementedMessage, RouterResult}, payments::{self, access_token, helpers, transformers, PaymentData}, }, routes::AppState, diff --git a/crates/router/src/core/payments/flows/session_flow.rs b/crates/router/src/core/payments/flows/session_flow.rs index e0837be25120..76ff96c70215 100644 --- a/crates/router/src/core/payments/flows/session_flow.rs +++ b/crates/router/src/core/payments/flows/session_flow.rs @@ -122,36 +122,6 @@ fn is_dynamic_fields_required( .unwrap_or(false) } -fn get_applepay_metadata( - connector_metadata: Option, -) -> RouterResult { - connector_metadata - .clone() - .parse_value::( - "ApplepayCombinedSessionTokenData", - ) - .map(|combined_metadata| { - api_models::payments::ApplepaySessionTokenMetadata::ApplePayCombined( - combined_metadata.apple_pay_combined, - ) - }) - .or_else(|_| { - connector_metadata - .parse_value::( - "ApplepaySessionTokenData", - ) - .map(|old_metadata| { - api_models::payments::ApplepaySessionTokenMetadata::ApplePay( - old_metadata.apple_pay, - ) - }) - }) - .change_context(errors::ApiErrorResponse::InvalidDataFormat { - field_name: "connector_metadata".to_string(), - expected_format: "applepay_metadata_format".to_string(), - }) -} - fn build_apple_pay_session_request( state: &routes::AppState, request: payment_types::ApplepaySessionRequest, @@ -196,7 +166,8 @@ async fn create_applepay_session_token( ) } else { // Get the apple pay metadata - let apple_pay_metadata = get_applepay_metadata(router_data.connector_meta_data.clone())?; + let apple_pay_metadata = + helpers::get_applepay_metadata(router_data.connector_meta_data.clone())?; // Get payment request data , apple pay session request and merchant keys let ( @@ -213,6 +184,8 @@ async fn create_applepay_session_token( payment_request_data, session_token_data, } => { + logger::info!("Apple pay simplified flow"); + let merchant_identifier = state .conf .applepay_merchant_configs @@ -254,6 +227,8 @@ async fn create_applepay_session_token( payment_request_data, session_token_data, } => { + logger::info!("Apple pay manual flow"); + let apple_pay_session_request = get_session_request_for_manual_apple_pay(session_token_data.clone()); @@ -269,6 +244,8 @@ async fn create_applepay_session_token( } }, payment_types::ApplepaySessionTokenMetadata::ApplePay(apple_pay_metadata) => { + logger::info!("Apple pay manual flow"); + let apple_pay_session_request = get_session_request_for_manual_apple_pay( apple_pay_metadata.session_token_data.clone(), ); @@ -642,8 +619,24 @@ fn log_session_response_if_error( .map(|res| res.as_ref().map_err(|error| logger::error!(?error))); } -impl types::PaymentsSessionRouterData { - pub async fn decide_flow<'a, 'b>( +#[async_trait] +pub trait RouterDataSession +where + Self: Sized, +{ + async fn decide_flow<'a, 'b>( + &'b self, + state: &'a routes::AppState, + connector: &api::ConnectorData, + _confirm: Option, + call_connector_action: payments::CallConnectorAction, + business_profile: &storage::business_profile::BusinessProfile, + ) -> RouterResult; +} + +#[async_trait] +impl RouterDataSession for types::PaymentsSessionRouterData { + async fn decide_flow<'a, 'b>( &'b self, state: &'a routes::AppState, connector: &api::ConnectorData, diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index b4b1b09c3fcf..973ca4b22661 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -8,6 +8,7 @@ use base64::Engine; use common_utils::{ ext_traits::{AsyncExt, ByteSliceExt, Encode, ValueExt}, fp_utils, generate_id, pii, + types::MinorUnit, }; use diesel_models::enums; // TODO : Evaluate all the helper functions () @@ -48,7 +49,6 @@ use crate::{ routes::{metrics, payment_methods as payment_methods_handler, AppState}, services, types::{ - self as core_types, api::{self, admin, enums as api_enums, MandateValidationFieldsExt}, domain::{ self, @@ -58,7 +58,8 @@ use crate::{ self, enums as storage_enums, ephemeral_key, CardTokenData, CustomerUpdate::Update, }, transformers::{ForeignFrom, ForeignTryFrom}, - ErrorResponse, MandateReference, RouterData, + AdditionalPaymentMethodConnectorResponse, ErrorResponse, MandateReference, + RecurringMandatePaymentData, RouterData, }, utils::{ self, @@ -599,7 +600,9 @@ pub async fn get_token_for_recurring_mandate( .await .flatten(); - let original_payment_authorized_amount = original_payment_intent.clone().map(|pi| pi.amount); + let original_payment_authorized_amount = original_payment_intent + .clone() + .map(|pi| pi.amount.get_amount_as_i64()); let original_payment_authorized_currency = original_payment_intent.clone().and_then(|pi| pi.currency); @@ -665,7 +668,7 @@ pub async fn get_token_for_recurring_mandate( Ok(MandateGenericData { token: Some(token), payment_method: payment_method.payment_method, - recurring_mandate_payment_data: Some(payments::RecurringMandatePaymentData { + recurring_mandate_payment_data: Some(RecurringMandatePaymentData { payment_method_type, original_payment_authorized_amount, original_payment_authorized_currency, @@ -679,7 +682,7 @@ pub async fn get_token_for_recurring_mandate( Ok(MandateGenericData { token: None, payment_method: payment_method.payment_method, - recurring_mandate_payment_data: Some(payments::RecurringMandatePaymentData { + recurring_mandate_payment_data: Some(RecurringMandatePaymentData { payment_method_type, original_payment_authorized_amount, original_payment_authorized_currency, @@ -716,7 +719,7 @@ pub fn validate_merchant_id( #[instrument(skip_all)] pub fn validate_request_amount_and_amount_to_capture( op_amount: Option, - op_amount_to_capture: Option, + op_amount_to_capture: Option, surcharge_details: Option, ) -> CustomResult<(), errors::ApiErrorResponse> { match (op_amount, op_amount_to_capture) { @@ -727,10 +730,10 @@ pub fn validate_request_amount_and_amount_to_capture( api::Amount::Value(amount_inner) => { // If both amount and amount to capture is present // then amount to be capture should be less than or equal to request amount - let total_capturable_amount = amount_inner.get() + let total_capturable_amount = MinorUnit::new(amount_inner.get()) + surcharge_details .map(|surcharge_details| surcharge_details.get_total_surcharge_amount()) - .unwrap_or(0); + .unwrap_or_default(); utils::when(!amount_to_capture.le(&total_capturable_amount), || { Err(report!(errors::ApiErrorResponse::PreconditionFailed { message: format!( @@ -765,23 +768,25 @@ pub fn validate_amount_to_capture_and_capture_method( if capture_method == api_enums::CaptureMethod::Automatic { let original_amount = request .amount - .map(|amount| amount.into()) + .map(MinorUnit::from) .or(payment_attempt.map(|payment_attempt| payment_attempt.amount)); let surcharge_amount = request .surcharge_details .map(|surcharge_details| surcharge_details.get_total_surcharge_amount()) .or_else(|| { payment_attempt.map(|payment_attempt| { - payment_attempt.surcharge_amount.unwrap_or(0) - + payment_attempt.tax_amount.unwrap_or(0) + payment_attempt.surcharge_amount.unwrap_or_default() + + payment_attempt.tax_amount.unwrap_or_default() }) }) - .unwrap_or(0); + .unwrap_or_default(); let total_capturable_amount = original_amount.map(|original_amount| original_amount + surcharge_amount); + let amount_to_capture = request .amount_to_capture .or(payment_attempt.and_then(|pa| pa.amount_to_capture)); + if let Some((total_capturable_amount, amount_to_capture)) = total_capturable_amount.zip(amount_to_capture) { @@ -1097,7 +1102,7 @@ fn validate_recurring_mandate(req: api::MandateValidationFields) -> RouterResult } pub fn verify_mandate_details( - request_amount: i64, + request_amount: MinorUnit, request_currency: api_enums::Currency, mandate: storage::Mandate, ) -> RouterResult<()> { @@ -1105,7 +1110,7 @@ pub fn verify_mandate_details( storage_enums::MandateType::SingleUse => utils::when( mandate .mandate_amount - .map(|mandate_amount| request_amount > mandate_amount) + .map(|mandate_amount| request_amount.get_amount_as_i64() > mandate_amount) .unwrap_or(true), || { Err(report!(errors::ApiErrorResponse::MandateValidationFailed { @@ -1117,7 +1122,8 @@ pub fn verify_mandate_details( mandate .mandate_amount .map(|mandate_amount| { - (mandate.amount_captured.unwrap_or(0) + request_amount) > mandate_amount + (mandate.amount_captured.unwrap_or(0) + request_amount.get_amount_as_i64()) + > mandate_amount }) .unwrap_or(false), || { @@ -1370,6 +1376,24 @@ fn validate_options_for_inequality( ) } +pub fn validate_max_amount( + amount: api_models::payments::Amount, +) -> CustomResult<(), errors::ApiErrorResponse> { + match amount { + api_models::payments::Amount::Value(value) => { + utils::when(value.get() > consts::MAX_ALLOWED_AMOUNT, || { + Err(report!(errors::ApiErrorResponse::PreconditionFailed { + message: format!( + "amount should not be more than {}", + consts::MAX_ALLOWED_AMOUNT + ) + })) + }) + } + api_models::payments::Amount::Zero => Ok(()), + } +} + // Checks if the customer details are passed in both places // If so, raise an error pub fn validate_customer_details_in_request( @@ -1590,6 +1614,7 @@ pub async fn create_customer_if_not_exist<'a, F: Clone, R>( connector_customer: None, address_id: None, default_payment_method_id: None, + updated_by: None, }, ) } @@ -2680,7 +2705,7 @@ pub fn generate_mandate( match data.mandate_type.get_required_value("mandate_type")? { hyperswitch_domain_models::mandates::MandateDataType::SingleUse(data) => { new_mandate - .set_mandate_amount(Some(data.amount)) + .set_mandate_amount(Some(data.amount.get_amount_as_i64())) .set_mandate_currency(Some(data.currency)) .set_mandate_type(storage_enums::MandateType::SingleUse) .to_owned() @@ -2689,7 +2714,7 @@ pub fn generate_mandate( hyperswitch_domain_models::mandates::MandateDataType::MultiUse(op_data) => { match op_data { Some(data) => new_mandate - .set_mandate_amount(Some(data.amount)) + .set_mandate_amount(Some(data.amount.get_amount_as_i64())) .set_mandate_currency(Some(data.currency)) .set_start_date(data.start_date) .set_end_date(data.end_date), @@ -2902,7 +2927,7 @@ mod tests { payment_id: "23".to_string(), merchant_id: "22".to_string(), status: storage_enums::IntentStatus::RequiresCapture, - amount: 200, + amount: MinorUnit::new(200), currency: None, amount_captured: None, customer_id: None, @@ -2947,6 +2972,8 @@ mod tests { .saturating_add(time::Duration::seconds(consts::DEFAULT_SESSION_EXPIRY)), ), request_external_three_ds_authentication: None, + charges: None, + frm_metadata: None, }; let req_cs = Some("1".to_string()); assert!(authenticate_client_secret(req_cs.as_ref(), &payment_intent).is_ok()); @@ -2960,7 +2987,7 @@ mod tests { payment_id: "23".to_string(), merchant_id: "22".to_string(), status: storage_enums::IntentStatus::RequiresCapture, - amount: 200, + amount: MinorUnit::new(200), currency: None, amount_captured: None, customer_id: None, @@ -3005,6 +3032,8 @@ mod tests { .saturating_add(time::Duration::seconds(consts::DEFAULT_SESSION_EXPIRY)), ), request_external_three_ds_authentication: None, + charges: None, + frm_metadata: None, }; let req_cs = Some("1".to_string()); assert!(authenticate_client_secret(req_cs.as_ref(), &payment_intent,).is_err()) @@ -3017,7 +3046,7 @@ mod tests { payment_id: "23".to_string(), merchant_id: "22".to_string(), status: storage_enums::IntentStatus::RequiresCapture, - amount: 200, + amount: MinorUnit::new(200), currency: None, amount_captured: None, customer_id: None, @@ -3062,6 +3091,8 @@ mod tests { .saturating_add(time::Duration::seconds(consts::DEFAULT_SESSION_EXPIRY)), ), request_external_three_ds_authentication: None, + charges: None, + frm_metadata: None, }; let req_cs = Some("1".to_string()); assert!(authenticate_client_secret(req_cs.as_ref(), &payment_intent).is_err()) @@ -3090,7 +3121,7 @@ pub async fn insert_merchant_connector_creds_to_config( .serialize_and_set_key_with_expiry( key.as_str(), &encoded_data.peek(), - crate::consts::CONNECTOR_CREDS_TOKEN_TTL, + consts::CONNECTOR_CREDS_TOKEN_TTL, ) .await .map_or_else( @@ -3533,6 +3564,9 @@ impl AttemptType { // New payment method billing address can be passed for a retry payment_method_billing_address_id: None, fingerprint_id: None, + charge_id: None, + client_source: None, + client_version: None, } } @@ -3692,7 +3726,7 @@ pub async fn get_additional_payment_data( ) -> api_models::payments::AdditionalPaymentData { match pm_data { api_models::payments::PaymentMethodData::Card(card_data) => { - let card_isin = Some(card_data.card_number.clone().get_card_isin()); + let card_isin = Some(card_data.card_number.get_card_isin()); let enable_extended_bin =db .find_config_by_key_unwrap_or( format!("{}_enable_extended_card_bin", profile_id).as_str(), @@ -3701,11 +3735,11 @@ pub async fn get_additional_payment_data( let card_extended_bin = match enable_extended_bin { Some(config) if config.config == "true" => { - Some(card_data.card_number.clone().get_card_extended_bin()) + Some(card_data.card_number.get_extended_card_bin()) } _ => None, }; - let last4 = Some(card_data.card_number.clone().get_last4()); + let last4 = Some(card_data.card_number.get_last4()); if card_data.card_issuer.is_some() && card_data.card_network.is_some() && card_data.card_type.is_some() @@ -3858,6 +3892,138 @@ pub fn validate_customer_access( Ok(()) } +pub fn is_apple_pay_simplified_flow( + connector_metadata: Option, + connector_name: Option<&String>, +) -> CustomResult { + let option_apple_pay_metadata = get_applepay_metadata(connector_metadata) + .map_err(|error| { + logger::info!( + "Apple pay metadata parsing for {:?} in is_apple_pay_simplified_flow {:?}", + connector_name, + error + ) + }) + .ok(); + + // return true only if the apple flow type is simplified + Ok(matches!( + option_apple_pay_metadata, + Some( + api_models::payments::ApplepaySessionTokenMetadata::ApplePayCombined( + api_models::payments::ApplePayCombinedMetadata::Simplified { .. } + ) + ) + )) +} + +pub fn get_applepay_metadata( + connector_metadata: Option, +) -> RouterResult { + connector_metadata + .clone() + .parse_value::( + "ApplepayCombinedSessionTokenData", + ) + .map(|combined_metadata| { + api_models::payments::ApplepaySessionTokenMetadata::ApplePayCombined( + combined_metadata.apple_pay_combined, + ) + }) + .or_else(|_| { + connector_metadata + .parse_value::( + "ApplepaySessionTokenData", + ) + .map(|old_metadata| { + api_models::payments::ApplepaySessionTokenMetadata::ApplePay( + old_metadata.apple_pay, + ) + }) + }) + .change_context(errors::ApiErrorResponse::InvalidDataFormat { + field_name: "connector_metadata".to_string(), + expected_format: "applepay_metadata_format".to_string(), + }) +} + +pub async fn get_apple_pay_retryable_connectors( + state: AppState, + merchant_account: &domain::MerchantAccount, + payment_data: &mut PaymentData, + key_store: &domain::MerchantKeyStore, + decided_connector_data: api::ConnectorData, + merchant_connector_id: Option<&String>, +) -> CustomResult>, errors::ApiErrorResponse> +where + F: Send + Clone, +{ + let profile_id = &payment_data + .payment_intent + .profile_id + .clone() + .get_required_value("profile_id") + .change_context(errors::ApiErrorResponse::MissingRequiredField { + field_name: "profile_id", + })?; + + let merchant_connector_account_type = get_merchant_connector_account( + &state, + merchant_account.merchant_id.as_str(), + payment_data.creds_identifier.to_owned(), + key_store, + profile_id, // need to fix this + &decided_connector_data.connector_name.to_string(), + merchant_connector_id, + ) + .await?; + + let connector_data_list = if is_apple_pay_simplified_flow( + merchant_connector_account_type.get_metadata(), + merchant_connector_account_type + .get_connector_name() + .as_ref(), + )? { + let merchant_connector_account_list = state + .store + .find_merchant_connector_account_by_merchant_id_and_disabled_list( + merchant_account.merchant_id.as_str(), + false, + key_store, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InternalServerError)?; + + let mut connector_data_list = vec![decided_connector_data.clone()]; + + for merchant_connector_account in merchant_connector_account_list { + if is_apple_pay_simplified_flow( + merchant_connector_account.metadata, + Some(&merchant_connector_account.connector_name), + )? { + let connector_data = api::ConnectorData::get_connector_by_name( + &state.conf.connectors, + &merchant_connector_account.connector_name.to_string(), + api::GetToken::Connector, + Some(merchant_connector_account.merchant_connector_id), + ) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Invalid connector name received")?; + + if !connector_data_list.iter().any(|connector_details| { + connector_details.merchant_connector_id == connector_data.merchant_connector_id + }) { + connector_data_list.push(connector_data) + } + } + } + Some(connector_data_list) + } else { + None + }; + Ok(connector_data_list) +} + #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct ApplePayData { version: masking::Secret, @@ -4010,6 +4176,8 @@ impl ApplePayData { &self, symmetric_key: &[u8], ) -> CustomResult { + logger::info!("Decrypt apple pay token"); + let data = BASE64_ENGINE .decode(self.data.peek().as_bytes()) .change_context(errors::ApplePayDecryptionError::Base64DecodingFailed)?; @@ -4198,7 +4366,7 @@ pub fn validate_session_expiry(session_expiry: u32) -> Result<(), errors::ApiErr pub fn add_connector_response_to_additional_payment_data( additional_payment_data: api_models::payments::AdditionalPaymentData, - connector_response_payment_method_data: core_types::AdditionalPaymentMethodConnectorResponse, + connector_response_payment_method_data: AdditionalPaymentMethodConnectorResponse, ) -> api_models::payments::AdditionalPaymentData { match ( &additional_payment_data, @@ -4206,7 +4374,7 @@ pub fn add_connector_response_to_additional_payment_data( ) { ( api_models::payments::AdditionalPaymentData::Card(additional_card_data), - core_types::AdditionalPaymentMethodConnectorResponse::Card { + AdditionalPaymentMethodConnectorResponse::Card { authentication_data, payment_checks, }, @@ -4223,7 +4391,7 @@ pub fn add_connector_response_to_additional_payment_data( pub fn update_additional_payment_data_with_connector_response_pm_data( additional_payment_data: Option, - connector_response_pm_data: Option, + connector_response_pm_data: Option, ) -> RouterResult> { let parsed_additional_payment_method_data = additional_payment_data .as_ref() diff --git a/crates/router/src/core/payments/operations/payment_approve.rs b/crates/router/src/core/payments/operations/payment_approve.rs index 1a92ab0c528c..52325db753a6 100644 --- a/crates/router/src/core/payments/operations/payment_approve.rs +++ b/crates/router/src/core/payments/operations/payment_approve.rs @@ -172,7 +172,6 @@ impl GetTracker, api::PaymentsCaptureRequest> payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], - frm_metadata: None, authentication: None, recurring_details: None, poll_config: None, diff --git a/crates/router/src/core/payments/operations/payment_cancel.rs b/crates/router/src/core/payments/operations/payment_cancel.rs index 73fecd202591..64114fc40e65 100644 --- a/crates/router/src/core/payments/operations/payment_cancel.rs +++ b/crates/router/src/core/payments/operations/payment_cancel.rs @@ -185,7 +185,6 @@ impl GetTracker, api::PaymentsCancelRequest> payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], - frm_metadata: None, authentication: None, recurring_details: None, poll_config: None, diff --git a/crates/router/src/core/payments/operations/payment_capture.rs b/crates/router/src/core/payments/operations/payment_capture.rs index 29f9c14e549f..ec86d1d17245 100644 --- a/crates/router/src/core/payments/operations/payment_capture.rs +++ b/crates/router/src/core/payments/operations/payment_capture.rs @@ -77,8 +77,10 @@ impl GetTracker, api::PaymentsCaptu helpers::validate_status_with_capture_method(payment_intent.status, capture_method)?; helpers::validate_amount_to_capture( - payment_attempt.amount_capturable, - request.amount_to_capture, + payment_attempt.amount_capturable.get_amount_as_i64(), + request + .amount_to_capture + .map(|capture_amount| capture_amount.get_amount_as_i64()), )?; helpers::validate_capture_method(capture_method)?; @@ -89,8 +91,8 @@ impl GetTracker, api::PaymentsCaptu .get_required_value("amount_to_capture")?; helpers::validate_amount_to_capture( - payment_attempt.amount_capturable, - Some(amount_to_capture), + payment_attempt.amount_capturable.get_amount_as_i64(), + Some(amount_to_capture.get_amount_as_i64()), )?; let previous_captures = db @@ -228,7 +230,6 @@ impl GetTracker, api::PaymentsCaptu payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], - frm_metadata: None, authentication: None, recurring_details: None, poll_config: None, 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 06627920d711..3f57ba2830fb 100644 --- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs +++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs @@ -59,6 +59,8 @@ impl GetTracker, api::PaymentsRequest> for Co .setup_future_usage .or(payment_intent.setup_future_usage); + helpers::authenticate_client_secret(request.client_secret.as_ref(), &payment_intent)?; + helpers::validate_payment_status_against_not_allowed_statuses( &payment_intent.status, &[ @@ -173,16 +175,22 @@ impl GetTracker, api::PaymentsRequest> for Co .or_else(|| request.customer_id.clone()), )?; - let shipping_address = helpers::get_address_by_id( + let shipping_address = helpers::create_or_update_address_for_payment_by_request( db, - payment_intent.shipping_address_id.clone(), + request.shipping.as_ref(), + payment_intent.shipping_address_id.clone().as_deref(), + merchant_id.as_ref(), + payment_intent.customer_id.as_ref(), key_store, - &payment_intent.payment_id, - merchant_id, - merchant_account.storage_scheme, + payment_id.as_ref(), + storage_scheme, ) .await?; + payment_intent.shipping_address_id = shipping_address + .as_ref() + .map(|shipping_address| shipping_address.address_id.clone()); + let billing_address = helpers::get_address_by_id( db, payment_intent.billing_address_id.clone(), @@ -303,7 +311,6 @@ impl GetTracker, api::PaymentsRequest> for Co incremental_authorization_details: None, authorizations: vec![], authentication: None, - frm_metadata: None, recurring_details, poll_config: None, }; @@ -422,11 +429,11 @@ impl UpdateTracker, api::PaymentsRequest> for Comple #[instrument(skip_all)] async fn update_trackers<'b>( &'b self, - _state: &'b AppState, + state: &'b AppState, _req_state: ReqState, - payment_data: PaymentData, + mut payment_data: PaymentData, _customer: Option, - _storage_scheme: storage_enums::MerchantStorageScheme, + storage_scheme: storage_enums::MerchantStorageScheme, _updated_customer: Option, _merchant_key_store: &domain::MerchantKeyStore, _frm_suggestion: Option, @@ -435,6 +442,19 @@ impl UpdateTracker, api::PaymentsRequest> for Comple where F: 'b + Send, { + let payment_intent_update = hyperswitch_domain_models::payments::payment_intent::PaymentIntentUpdate::CompleteAuthorizeUpdate { + shipping_address_id: payment_data.payment_intent.shipping_address_id.clone() + }; + + let db = &*state.store; + let payment_intent = payment_data.payment_intent.clone(); + + let updated_payment_intent = db + .update_payment_intent(payment_intent, payment_intent_update, storage_scheme) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + + payment_data.payment_intent = updated_payment_intent; Ok((Box::new(self), payment_data)) } } diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index f5ab92911e24..a3ed16def0c7 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -1,6 +1,11 @@ use std::marker::PhantomData; -use api_models::{admin::ExtendedCardInfoConfig, enums::FrmSuggestion, payments::ExtendedCardInfo}; +use api_models::{ + admin::ExtendedCardInfoConfig, + enums::FrmSuggestion, + payment_methods::PaymentMethodsData, + payments::{AdditionalPaymentData, ExtendedCardInfo}, +}; use async_trait::async_trait; use common_utils::ext_traits::{AsyncExt, Encode, StringExt, ValueExt}; use error_stack::{report, ResultExt}; @@ -29,7 +34,7 @@ use crate::{ types::{ self, api::{self, ConnectorCallType, PaymentIdTypeExt}, - domain, + domain::{self, types::decrypt}, storage::{self, enums as storage_enums}, }, utils::{self, OptionExt}, @@ -76,7 +81,7 @@ impl GetTracker, api::PaymentsRequest> for Pa if let Some(order_details) = &request.order_details { helpers::validate_order_details_amount( order_details.to_owned(), - payment_intent.amount, + payment_intent.amount.get_amount_as_i64(), false, )?; } @@ -393,6 +398,7 @@ impl GetTracker, api::PaymentsRequest> for Pa .attach_printable("Error converting feature_metadata to Value")? .or(payment_intent.feature_metadata); payment_intent.metadata = request.metadata.clone().or(payment_intent.metadata); + payment_intent.frm_metadata = request.frm_metadata.clone().or(payment_intent.frm_metadata); payment_intent.request_incremental_authorization = request .request_incremental_authorization .map(|request_incremental_authorization| { @@ -632,7 +638,6 @@ impl GetTracker, api::PaymentsRequest> for Pa payment_link_data: None, incremental_authorization_details: None, authorizations: vec![], - frm_metadata: request.frm_metadata.clone(), authentication: None, recurring_details, poll_config: None, @@ -1035,6 +1040,39 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to encode additional pm data")?; + let encode_additional_pm_to_value = if let Some(ref pm) = payment_data.payment_method_info { + let key = key_store.key.get_inner().peek(); + + let card_detail_from_locker: Option = + decrypt::( + pm.payment_method_data.clone(), + key, + ) + .await + .change_context(errors::StorageError::DecryptionError) + .attach_printable("unable to decrypt card details") + .ok() + .flatten() + .map(|x| x.into_inner().expose()) + .and_then(|v| serde_json::from_value::(v).ok()) + .and_then(|pmd| match pmd { + PaymentMethodsData::Card(crd) => Some(api::CardDetailFromLocker::from(crd)), + _ => None, + }); + + card_detail_from_locker.and_then(|card_details| { + let additional_data = card_details.into(); + let additional_data_payment = + AdditionalPaymentData::Card(Box::new(additional_data)); + additional_data_payment + .encode_to_value() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to encode additional pm data") + .ok() + }) + } else { + None + }; let business_sub_label = payment_data.payment_attempt.business_sub_label.clone(); let authentication_type = payment_data.payment_attempt.authentication_type; @@ -1062,19 +1100,37 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen .take(); let order_details = payment_data.payment_intent.order_details.clone(); let metadata = payment_data.payment_intent.metadata.clone(); + let frm_metadata = payment_data.payment_intent.frm_metadata.clone(); let authorized_amount = payment_data .surcharge_details .as_ref() .map(|surcharge_details| surcharge_details.final_amount) .unwrap_or(payment_data.payment_attempt.amount); + let client_source = header_payload + .client_source + .clone() + .or(payment_data.payment_attempt.client_source.clone()); + let client_version = header_payload + .client_version + .clone() + .or(payment_data.payment_attempt.client_version.clone()); + let m_payment_data_payment_attempt = payment_data.payment_attempt.clone(); - let m_payment_method_id = payment_data.payment_attempt.payment_method_id.clone(); + let m_payment_method_id = + payment_data + .payment_attempt + .payment_method_id + .clone() + .or(payment_data + .payment_method_info + .as_ref() + .map(|payment_method| payment_method.payment_method_id.clone())); let m_browser_info = browser_info.clone(); let m_connector = connector.clone(); let m_capture_method = capture_method; let m_payment_token = payment_token.clone(); - let m_additional_pm_data = additional_pm_data.clone(); + let m_additional_pm_data = additional_pm_data.clone().or(encode_additional_pm_to_value); let m_business_sub_label = business_sub_label.clone(); let m_straight_through_algorithm = straight_through_algorithm.clone(); let m_error_code = error_code.clone(); @@ -1135,6 +1191,8 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen payment_method_billing_address_id, fingerprint_id: m_fingerprint_id, payment_method_id: m_payment_method_id, + client_source, + client_version, }, storage_scheme, ) @@ -1155,6 +1213,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen let m_statement_descriptor_suffix = statement_descriptor_suffix.clone(); let m_order_details = order_details.clone(); let m_metadata = metadata.clone(); + let m_frm_metadata = frm_metadata.clone(); let m_db = state.clone().store; let m_storage_scheme = storage_scheme.to_string(); let session_expiry = m_payment_data_payment_intent.session_expiry; @@ -1184,6 +1243,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen fingerprint_id: None, session_expiry, request_external_three_ds_authentication: None, + frm_metadata: m_frm_metadata, }, storage_scheme, ) @@ -1249,6 +1309,9 @@ impl ValidateRequest for PaymentConfir operations::ValidateResult<'a>, )> { helpers::validate_customer_details_in_request(request)?; + if let Some(amount) = request.amount { + helpers::validate_max_amount(amount)?; + } let request_merchant_id = request.merchant_id.as_deref(); helpers::validate_merchant_id(&merchant_account.merchant_id, request_merchant_id) diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index f8ea54382332..68f8b7056c0f 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -4,14 +4,17 @@ use api_models::{ enums::FrmSuggestion, mandates::RecurringDetails, payment_methods::PaymentMethodsData, }; use async_trait::async_trait; -use common_utils::ext_traits::{AsyncExt, Encode, ValueExt}; +use common_utils::{ + ext_traits::{AsyncExt, Encode, ValueExt}, + types::MinorUnit, +}; use diesel_models::{ephemeral_key, PaymentMethod}; use error_stack::{self, ResultExt}; use hyperswitch_domain_models::{ mandates::{MandateData, MandateDetails}, payments::payment_attempt::PaymentAttempt, }; -use masking::{ExposeInterface, PeekInterface}; +use masking::{ExposeInterface, PeekInterface, Secret}; use router_derive::PaymentOperation; use router_env::{instrument, logger, tracing}; use time::PrimitiveDateTime; @@ -287,7 +290,7 @@ impl GetTracker, api::PaymentsRequest> for Pa if let Some(order_details) = &request.order_details { helpers::validate_order_details_amount( order_details.to_owned(), - payment_intent.amount, + payment_intent.amount.get_amount_as_i64(), false, )?; } @@ -446,7 +449,6 @@ impl GetTracker, api::PaymentsRequest> for Pa incremental_authorization_details: None, authorizations: vec![], authentication: None, - frm_metadata: request.frm_metadata.clone(), recurring_details, poll_config: None, }; @@ -661,6 +663,9 @@ impl ValidateRequest for PaymentCreate operations::ValidateResult<'a>, )> { helpers::validate_customer_details_in_request(request)?; + if let Some(amount) = request.amount { + helpers::validate_max_amount(amount)?; + } if let Some(session_expiry) = &request.session_expiry { helpers::validate_session_expiry(session_expiry.to_owned())?; } @@ -876,7 +881,7 @@ impl PaymentCreate { attempt_id, status, currency, - amount: amount.into(), + amount: MinorUnit::from(amount), payment_method, capture_method: request.capture_method, capture_on: request.capture_on, @@ -902,7 +907,7 @@ impl PaymentCreate { external_three_ds_authentication_attempted: None, mandate_data, payment_method_billing_address_id, - net_amount: i64::default(), + net_amount: MinorUnit::new(i64::default()), save_to_locker: None, connector: None, error_message: None, @@ -918,7 +923,7 @@ impl PaymentCreate { error_reason: None, connector_response_reference_id: None, multiple_capture_count: None, - amount_capturable: i64::default(), + amount_capturable: MinorUnit::new(i64::default()), updated_by: String::default(), authentication_data: None, encoded_data: None, @@ -928,6 +933,9 @@ impl PaymentCreate { fingerprint_id: None, authentication_connector: None, authentication_id: None, + charge_id: None, + client_source: None, + client_version: None, }, additional_pm_data, )) @@ -990,11 +998,24 @@ impl PaymentCreate { request.capture_method, )?; + let charges = request + .charges + .as_ref() + .map(|charges| { + charges.encode_to_value().map_err(|err| { + logger::warn!("Failed to serialize PaymentCharges - {}", err); + err + }) + }) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError)? + .map(Secret::new); + Ok(storage::PaymentIntentNew { payment_id: payment_id.to_string(), merchant_id: merchant_account.merchant_id.to_string(), status, - amount: amount.into(), + amount: MinorUnit::from(amount), currency, description: request.description.clone(), created_at, @@ -1035,6 +1056,8 @@ impl PaymentCreate { session_expiry: Some(session_expiry), request_external_three_ds_authentication: request .request_external_three_ds_authentication, + charges, + frm_metadata: request.frm_metadata.clone(), }) } @@ -1106,7 +1129,7 @@ async fn create_payment_link( payment_id: payment_id.clone(), merchant_id: merchant_id.clone(), link_to_pay: payment_link.clone(), - amount: amount.into(), + amount: MinorUnit::from(amount), currency: request.currency, created_at, last_modified_at, diff --git a/crates/router/src/core/payments/operations/payment_reject.rs b/crates/router/src/core/payments/operations/payment_reject.rs index e03f3ce6cb15..60061d655c1c 100644 --- a/crates/router/src/core/payments/operations/payment_reject.rs +++ b/crates/router/src/core/payments/operations/payment_reject.rs @@ -170,7 +170,6 @@ impl GetTracker, PaymentsCancelRequest> for P incremental_authorization_details: None, authorizations: vec![], authentication: None, - frm_metadata: None, recurring_details: None, poll_config: None, }; diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 9867d59a69a0..047eb3503787 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use async_trait::async_trait; use common_enums::AuthorizationStatus; -use common_utils::ext_traits::Encode; +use common_utils::{ext_traits::Encode, types::MinorUnit}; use error_stack::{report, ResultExt}; use futures::FutureExt; use hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt; @@ -266,16 +266,19 @@ impl PostUpdateTracker, types::PaymentsIncrementalAu .. }) => { if status == AuthorizationStatus::Success { - (Some( - storage::PaymentAttemptUpdate::IncrementalAuthorizationAmountUpdate { - amount: incremental_authorization_details.total_amount, - amount_capturable: incremental_authorization_details.total_amount, - }, - ), Some( - storage::PaymentIntentUpdate::IncrementalAuthorizationAmountUpdate { - amount: incremental_authorization_details.total_amount, - }, - )) + ( + Some( + storage::PaymentAttemptUpdate::IncrementalAuthorizationAmountUpdate { + amount: incremental_authorization_details.total_amount, + amount_capturable: incremental_authorization_details.total_amount, + }, + ), + Some( + storage::PaymentIntentUpdate::IncrementalAuthorizationAmountUpdate { + amount: incremental_authorization_details.total_amount, + }, + ), + ) } else { (None, None) } @@ -793,7 +796,8 @@ async fn payment_response_update_tracker( error_reason: Some(err.reason), amount_capturable: router_data .request - .get_amount_capturable(&payment_data, status), + .get_amount_capturable(&payment_data, status) + .map(MinorUnit::new), updated_by: storage_scheme.to_string(), unified_code: option_gsm.clone().map(|gsm| gsm.unified_code), unified_message: option_gsm.map(|gsm| gsm.unified_message), @@ -866,6 +870,7 @@ async fn payment_response_update_tracker( connector_metadata, connector_response_reference_id, incremental_authorization_allowed, + charge_id, .. } => { payment_data @@ -915,7 +920,6 @@ async fn payment_response_update_tracker( payment_data.payment_attempt.connector.clone(), payment_data.payment_attempt.merchant_id.clone(), ); - let (capture_updates, payment_attempt_update) = match payment_data .multiple_capture_data { @@ -940,7 +944,8 @@ async fn payment_response_update_tracker( authentication_type: None, amount_capturable: router_data .request - .get_amount_capturable(&payment_data, updated_attempt_status), + .get_amount_capturable(&payment_data, updated_attempt_status) + .map(MinorUnit::new), payment_method_id, mandate_id: payment_data.payment_attempt.mandate_id.clone(), connector_metadata, @@ -955,6 +960,7 @@ async fn payment_response_update_tracker( authentication_data, encoded_data, payment_method_data: additional_payment_method_data, + charge_id, }), ), }; @@ -1098,7 +1104,7 @@ async fn payment_response_update_tracker( let amount_captured = get_total_amount_captured( &router_data.request, - router_data.amount_captured, + router_data.amount_captured.map(MinorUnit::new), router_data.status, &payment_data, ); @@ -1167,7 +1173,7 @@ async fn payment_response_update_tracker( tokenization::update_connector_mandate_details_in_payment_method( payment_method.clone(), payment_method.payment_method_type, - Some(payment_data.payment_attempt.amount), + Some(payment_data.payment_attempt.amount.get_amount_as_i64()), payment_data.payment_attempt.currency, payment_data.payment_attempt.merchant_connector_id.clone(), connector_mandate_id, @@ -1353,10 +1359,10 @@ fn get_capture_update_for_unmapped_capture_responses( fn get_total_amount_captured( request: &T, - amount_captured: Option, + amount_captured: Option, router_data_status: enums::AttemptStatus, payment_data: &PaymentData, -) -> Option { +) -> Option { match &payment_data.multiple_capture_data { Some(multiple_capture_data) => { //multiple capture @@ -1364,7 +1370,9 @@ fn get_total_amount_captured( } None => { //Non multiple capture - let amount = request.get_captured_amount(payment_data); + let amount = request + .get_captured_amount(payment_data) + .map(MinorUnit::new); amount_captured.or_else(|| { if router_data_status == enums::AttemptStatus::Charged { amount diff --git a/crates/router/src/core/payments/operations/payment_session.rs b/crates/router/src/core/payments/operations/payment_session.rs index db8b3131c588..e4d91d4a0681 100644 --- a/crates/router/src/core/payments/operations/payment_session.rs +++ b/crates/router/src/core/payments/operations/payment_session.rs @@ -197,7 +197,6 @@ impl GetTracker, api::PaymentsSessionRequest> incremental_authorization_details: None, authorizations: vec![], authentication: None, - frm_metadata: None, recurring_details: None, poll_config: None, }; diff --git a/crates/router/src/core/payments/operations/payment_start.rs b/crates/router/src/core/payments/operations/payment_start.rs index cf012aa09ebe..e5567e5b4748 100644 --- a/crates/router/src/core/payments/operations/payment_start.rs +++ b/crates/router/src/core/payments/operations/payment_start.rs @@ -182,7 +182,6 @@ impl GetTracker, api::PaymentsStartRequest> f incremental_authorization_details: None, authorizations: vec![], authentication: None, - frm_metadata: None, recurring_details: None, poll_config: None, }; diff --git a/crates/router/src/core/payments/operations/payment_status.rs b/crates/router/src/core/payments/operations/payment_status.rs index 4d5b87b671c1..0bedd70b921e 100644 --- a/crates/router/src/core/payments/operations/payment_status.rs +++ b/crates/router/src/core/payments/operations/payment_status.rs @@ -471,7 +471,6 @@ async fn get_tracker_for_sync< incremental_authorization_details: None, authorizations, authentication, - frm_metadata: None, recurring_details: None, poll_config: None, }; diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index b52611e6f911..d7351b3b5ba0 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -63,7 +63,7 @@ impl GetTracker, api::PaymentsRequest> for Pa if let Some(order_details) = &request.order_details { helpers::validate_order_details_amount( order_details.to_owned(), - payment_intent.amount, + payment_intent.amount.get_amount_as_i64(), false, )?; } @@ -250,6 +250,7 @@ impl GetTracker, api::PaymentsRequest> for Pa .attach_printable("Error converting feature_metadata to Value")? .or(payment_intent.feature_metadata); payment_intent.metadata = request.metadata.clone().or(payment_intent.metadata); + payment_intent.frm_metadata = request.frm_metadata.clone().or(payment_intent.frm_metadata); Self::populate_payment_intent_with_request(&mut payment_intent, request); let token = token.or_else(|| payment_attempt.payment_token.clone()); @@ -336,9 +337,9 @@ impl GetTracker, api::PaymentsRequest> for Pa .as_ref() .map(RequestSurchargeDetails::get_total_surcharge_amount) .or(payment_attempt.get_total_surcharge_amount()); - (amount + surcharge_amount.unwrap_or(0)).into() + amount + surcharge_amount.unwrap_or_default() }; - (Box::new(operations::PaymentConfirm), amount) + (Box::new(operations::PaymentConfirm), amount.into()) } else { (Box::new(self), amount) }; @@ -451,7 +452,6 @@ impl GetTracker, api::PaymentsRequest> for Pa incremental_authorization_details: None, authorizations: vec![], authentication: None, - frm_metadata: request.frm_metadata.clone(), recurring_details, poll_config: None, }; @@ -690,8 +690,8 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen .clone(); let order_details = payment_data.payment_intent.order_details.clone(); let metadata = payment_data.payment_intent.metadata.clone(); + let frm_metadata = payment_data.payment_intent.frm_metadata.clone(); let session_expiry = payment_data.payment_intent.session_expiry; - payment_data.payment_intent = state .store .update_payment_intent( @@ -719,6 +719,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen request_external_three_ds_authentication: payment_data .payment_intent .request_external_three_ds_authentication, + frm_metadata, }, storage_scheme, ) @@ -743,6 +744,9 @@ impl ValidateRequest for PaymentUpdate operations::ValidateResult<'a>, )> { helpers::validate_customer_details_in_request(request)?; + if let Some(amount) = request.amount { + helpers::validate_max_amount(amount)?; + } if let Some(session_expiry) = &request.session_expiry { helpers::validate_session_expiry(session_expiry.to_owned())?; } 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 c81773a7342d..56665d65a32d 100644 --- a/crates/router/src/core/payments/operations/payments_incremental_authorization.rs +++ b/crates/router/src/core/payments/operations/payments_incremental_authorization.rs @@ -75,7 +75,7 @@ impl })? } - if request.amount < payment_intent.amount { + if payment_intent.amount > request.amount { Err(errors::ApiErrorResponse::PreconditionFailed { message: "Amount should be greater than original authorized amount".to_owned(), })? @@ -151,7 +151,6 @@ impl }), authorizations: vec![], authentication: None, - frm_metadata: None, recurring_details: None, poll_config: None, }; diff --git a/crates/router/src/core/payments/retry.rs b/crates/router/src/core/payments/retry.rs index ebea9207b1ab..3bafd7774099 100644 --- a/crates/router/src/core/payments/retry.rs +++ b/crates/router/src/core/payments/retry.rs @@ -1,6 +1,6 @@ use std::{str::FromStr, vec::IntoIter}; -use common_utils::ext_traits::Encode; +use common_utils::{ext_traits::Encode, types::MinorUnit}; use diesel_models::enums as storage_enums; use error_stack::{report, ResultExt}; use router_env::{ @@ -150,7 +150,7 @@ where } api_models::gsm::GsmDecision::Requeue => { Err(report!(errors::ApiErrorResponse::NotImplemented { - message: errors::api_error_response::NotImplementedMessage::Reason( + message: errors::NotImplementedMessage::Reason( "Requeue not implemented".to_string(), ), }))? @@ -360,6 +360,7 @@ where resource_id, connector_metadata, redirection_data, + charge_id, .. }) => { let encoded_data = payment_data.payment_attempt.encoded_data.clone(); @@ -397,7 +398,7 @@ where error_message: None, error_reason: None, amount_capturable: if router_data.status.is_terminal_status() { - Some(0) + Some(MinorUnit::new(0)) } else { None }, @@ -407,6 +408,7 @@ where unified_code: None, unified_message: None, payment_method_data: additional_payment_method_data, + charge_id, }, storage_scheme, ) @@ -428,7 +430,7 @@ where error_message: Some(Some(error_response.message.clone())), status: storage_enums::AttemptStatus::Failure, error_reason: Some(error_response.reason.clone()), - amount_capturable: Some(0), + amount_capturable: Some(MinorUnit::new(0)), updated_by: storage_scheme.to_string(), unified_code: option_gsm.clone().map(|gsm| gsm.unified_code), unified_message: option_gsm.map(|gsm| gsm.unified_message), diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index 6967c977753d..d45f804268b3 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -1,8 +1,9 @@ mod transformers; use std::{ - collections::hash_map, + collections::{hash_map, HashMap}, hash::{Hash, Hasher}, + str::FromStr, sync::Arc, }; @@ -12,7 +13,6 @@ use api_models::{ payments::Address, routing::ConnectorSelection, }; -use common_utils::static_cache::StaticCache; use diesel_models::enums as storage_enums; use error_stack::ResultExt; use euclid::{ @@ -24,6 +24,7 @@ use euclid::{ use kgraph_utils::{ mca as mca_graph, transformers::{IntoContext, IntoDirValue}, + types::CountryCurrencyFilter, }; use masking::PeekInterface; use rand::{ @@ -31,6 +32,7 @@ use rand::{ SeedableRng, }; use rustc_hash::FxHashMap; +use storage_impl::redis::cache::{CGRAPH_CACHE, ROUTING_CACHE}; #[cfg(feature = "payouts")] use crate::core::payouts; @@ -43,14 +45,15 @@ use crate::{ }, logger, types::{ - api, api::routing as routing_types, domain, storage as oss_storage, - transformers::ForeignInto, + api::{self, routing as routing_types}, + domain, storage as oss_storage, + transformers::{ForeignFrom, ForeignInto}, }, utils::{OptionExt, ValueExt}, AppState, }; -pub(super) enum CachedAlgorithm { +pub enum CachedAlgorithm { Single(Box), Priority(Vec), VolumeSplit(Vec), @@ -70,7 +73,6 @@ pub struct SessionFlowRoutingInput<'a> { pub struct SessionRoutingPmTypeInput<'a> { state: &'a AppState, key_store: &'a domain::MerchantKeyStore, - merchant_last_modified: i64, attempt_id: &'a str, routing_algorithm: &'a MerchantAccountRoutingAlgorithm, backend_input: dsl_inputs::BackendInput, @@ -81,10 +83,6 @@ pub struct SessionRoutingPmTypeInput<'a> { ))] profile_id: Option, } -static ROUTING_CACHE: StaticCache = StaticCache::new(); -static KGRAPH_CACHE: StaticCache< - hyperswitch_constraint_graph::ConstraintGraph<'_, euclid_dir::DirValue>, -> = StaticCache::new(); type RoutingResult = oss_errors::CustomResult; @@ -104,7 +102,6 @@ impl Default for MerchantAccountRoutingAlgorithm { pub fn make_dsl_input_for_payouts( payout_data: &payouts::PayoutData, ) -> RoutingResult { - use crate::types::transformers::ForeignFrom; let mandate = dsl_inputs::MandateData { mandate_acceptance_type: None, mandate_type: None, @@ -213,7 +210,7 @@ where }; let payment_input = dsl_inputs::PaymentInput { - amount: payment_data.payment_intent.amount, + amount: payment_data.payment_intent.amount.get_amount_as_i64(), card_bin: payment_data .payment_method_data .as_ref() @@ -300,20 +297,15 @@ pub async fn perform_static_routing_v1( return Ok(fallback_config); }; - let key = ensure_algorithm_cached_v1( + let cached_algorithm = ensure_algorithm_cached_v1( state, merchant_id, - algorithm_ref.timestamp, &algorithm_id, #[cfg(feature = "business_profile_routing")] Some(profile_id).cloned(), &api_enums::TransactionType::from(transaction_data), ) .await?; - let cached_algorithm: Arc = ROUTING_CACHE - .retrieve(&key) - .change_context(errors::RoutingError::CacheMiss) - .attach_printable("Unable to retrieve cached routing algorithm even after refresh")?; Ok(match cached_algorithm.as_ref() { CachedAlgorithm::Single(conn) => vec![(**conn).clone()], @@ -340,11 +332,10 @@ pub async fn perform_static_routing_v1( async fn ensure_algorithm_cached_v1( state: &AppState, merchant_id: &str, - timestamp: i64, algorithm_id: &str, #[cfg(feature = "business_profile_routing")] profile_id: Option, transaction_type: &api_enums::TransactionType, -) -> RoutingResult { +) -> RoutingResult> { #[cfg(feature = "business_profile_routing")] let key = { let profile_id = profile_id @@ -374,29 +365,24 @@ async fn ensure_algorithm_cached_v1( } }; - let present = ROUTING_CACHE - .present(&key) - .change_context(errors::RoutingError::DslCachePoisoned) - .attach_printable("Error checking presence of DSL")?; + let cached_algorithm = ROUTING_CACHE + .get_val::>(key.as_str()) + .await; - let expired = ROUTING_CACHE - .expired(&key, timestamp) - .change_context(errors::RoutingError::DslCachePoisoned) - .attach_printable("Error checking expiry of DSL in cache")?; - - if !present || expired { + let algorithm = if let Some(algo) = cached_algorithm { + algo + } else { refresh_routing_cache_v1( state, key.clone(), algorithm_id, - timestamp, #[cfg(feature = "business_profile_routing")] profile_id, ) - .await?; + .await? }; - Ok(key) + Ok(algorithm) } pub fn perform_straight_through_routing( @@ -445,9 +431,8 @@ pub async fn refresh_routing_cache_v1( state: &AppState, key: String, algorithm_id: &str, - timestamp: i64, #[cfg(feature = "business_profile_routing")] profile_id: Option, -) -> RoutingResult<()> { +) -> RoutingResult> { #[cfg(feature = "business_profile_routing")] let algorithm = { let algorithm = state @@ -496,12 +481,11 @@ pub async fn refresh_routing_cache_v1( } }; - ROUTING_CACHE - .save(key, cached_algorithm, timestamp) - .change_context(errors::RoutingError::DslCachePoisoned) - .attach_printable("Error saving DSL to cache")?; + let arc_cached_algorithm = Arc::new(cached_algorithm); - Ok(()) + ROUTING_CACHE.push(key, arc_cached_algorithm.clone()).await; + + Ok(arc_cached_algorithm) } pub fn perform_volume_split( @@ -538,10 +522,9 @@ pub fn perform_volume_split( Ok(splits.into_iter().map(|sp| sp.connector).collect()) } -pub async fn get_merchant_kgraph<'a>( +pub async fn get_merchant_cgraph<'a>( state: &AppState, key_store: &domain::MerchantKeyStore, - merchant_last_modified: i64, #[cfg(feature = "business_profile_routing")] profile_id: Option, transaction_type: &api_enums::TransactionType, ) -> RoutingResult>> { @@ -554,10 +537,10 @@ pub async fn get_merchant_kgraph<'a>( .get_required_value("profile_id") .change_context(errors::RoutingError::ProfileIdMissing)?; match transaction_type { - api_enums::TransactionType::Payment => format!("kgraph_{}_{}", merchant_id, profile_id), + api_enums::TransactionType::Payment => format!("cgraph_{}_{}", merchant_id, profile_id), #[cfg(feature = "payouts")] api_enums::TransactionType::Payout => { - format!("kgraph_po_{}_{}", merchant_id, profile_id) + format!("cgraph_po_{}_{}", merchant_id, profile_id) } } }; @@ -569,45 +552,36 @@ pub async fn get_merchant_kgraph<'a>( api_enums::TransactionType::Payout => format!("kgraph_po_{}", merchant_id), }; - let kgraph_present = KGRAPH_CACHE - .present(&key) - .change_context(errors::RoutingError::KgraphCacheFailure) - .attach_printable("when checking kgraph presence")?; - - let kgraph_expired = KGRAPH_CACHE - .expired(&key, merchant_last_modified) - .change_context(errors::RoutingError::KgraphCacheFailure) - .attach_printable("when checking kgraph expiry")?; + let cached_cgraph = CGRAPH_CACHE + .get_val::>>( + key.as_str(), + ) + .await; - if !kgraph_present || kgraph_expired { - refresh_kgraph_cache( + let cgraph = if let Some(graph) = cached_cgraph { + graph + } else { + refresh_cgraph_cache( state, key_store, - merchant_last_modified, key.clone(), #[cfg(feature = "business_profile_routing")] profile_id, transaction_type, ) - .await?; - } - - let cached_kgraph = KGRAPH_CACHE - .retrieve(&key) - .change_context(errors::RoutingError::CacheMiss) - .attach_printable("when retrieving kgraph")?; + .await? + }; - Ok(cached_kgraph) + Ok(cgraph) } -pub async fn refresh_kgraph_cache( +pub async fn refresh_cgraph_cache<'a>( state: &AppState, key_store: &domain::MerchantKeyStore, - timestamp: i64, key: String, #[cfg(feature = "business_profile_routing")] profile_id: Option, transaction_type: &api_enums::TransactionType, -) -> RoutingResult<()> { +) -> RoutingResult>> { let mut merchant_connector_accounts = state .store .find_merchant_connector_account_by_merchant_id_and_disabled_list( @@ -645,24 +619,46 @@ pub async fn refresh_kgraph_cache( .map(admin_api::MerchantConnectorResponse::try_from) .collect::, _>>() .change_context(errors::RoutingError::KgraphCacheRefreshFailed)?; + let connector_configs = state + .conf + .pm_filters + .0 + .clone() + .into_iter() + .filter(|(key, _)| key != "default") + .map(|(key, value)| { + let key = api_enums::RoutableConnectors::from_str(&key) + .map_err(|_| errors::RoutingError::InvalidConnectorName(key))?; + + Ok((key, value.foreign_into())) + }) + .collect::, errors::RoutingError>>()?; + let default_configs = state + .conf + .pm_filters + .0 + .get("default") + .cloned() + .map(ForeignFrom::foreign_from); + let config_pm_filters = CountryCurrencyFilter { + connector_configs, + default_configs, + }; + let cgraph = Arc::new( + mca_graph::make_mca_graph(api_mcas, &config_pm_filters) + .change_context(errors::RoutingError::KgraphCacheRefreshFailed) + .attach_printable("when construction cgraph")?, + ); - let kgraph = mca_graph::make_mca_graph(api_mcas) - .change_context(errors::RoutingError::KgraphCacheRefreshFailed) - .attach_printable("when construction kgraph")?; - - KGRAPH_CACHE - .save(key, kgraph, timestamp) - .change_context(errors::RoutingError::KgraphCacheRefreshFailed) - .attach_printable("when saving kgraph to cache")?; + CGRAPH_CACHE.push(key, Arc::clone(&cgraph)).await; - Ok(()) + Ok(cgraph) } #[allow(clippy::too_many_arguments)] -async fn perform_kgraph_filtering( +async fn perform_cgraph_filtering( state: &AppState, key_store: &domain::MerchantKeyStore, - merchant_last_modified: i64, chosen: Vec, backend_input: dsl_inputs::BackendInput, eligible_connectors: Option<&Vec>, @@ -674,10 +670,9 @@ async fn perform_kgraph_filtering( .into_context() .change_context(errors::RoutingError::KgraphAnalysisError)?, ); - let cached_kgraph = get_merchant_kgraph( + let cached_cgraph = get_merchant_cgraph( state, key_store, - merchant_last_modified, #[cfg(feature = "business_profile_routing")] profile_id, transaction_type, @@ -691,7 +686,7 @@ async fn perform_kgraph_filtering( let dir_val = euclid_choice .into_dir_value() .change_context(errors::RoutingError::KgraphAnalysisError)?; - let kgraph_eligible = cached_kgraph + let cgraph_eligible = cached_cgraph .check_value_validity( dir_val, &context, @@ -704,7 +699,7 @@ async fn perform_kgraph_filtering( let filter_eligible = eligible_connectors.map_or(true, |list| list.contains(&routable_connector)); - if kgraph_eligible && filter_eligible { + if cgraph_eligible && filter_eligible { final_selection.push(choice); } } @@ -715,7 +710,6 @@ async fn perform_kgraph_filtering( pub async fn perform_eligibility_analysis( state: &AppState, key_store: &domain::MerchantKeyStore, - merchant_last_modified: i64, chosen: Vec, transaction_data: &routing::TransactionData<'_, F>, eligible_connectors: Option<&Vec>, @@ -727,10 +721,9 @@ pub async fn perform_eligibility_analysis( routing::TransactionData::Payout(payout_data) => make_dsl_input_for_payouts(payout_data)?, }; - perform_kgraph_filtering( + perform_cgraph_filtering( state, key_store, - merchant_last_modified, chosen, backend_input, eligible_connectors, @@ -744,7 +737,6 @@ pub async fn perform_eligibility_analysis( pub async fn perform_fallback_routing( state: &AppState, key_store: &domain::MerchantKeyStore, - merchant_last_modified: i64, transaction_data: &routing::TransactionData<'_, F>, eligible_connectors: Option<&Vec>, #[cfg(feature = "business_profile_routing")] profile_id: Option, @@ -775,10 +767,9 @@ pub async fn perform_fallback_routing( routing::TransactionData::Payout(payout_data) => make_dsl_input_for_payouts(payout_data)?, }; - perform_kgraph_filtering( + perform_cgraph_filtering( state, key_store, - merchant_last_modified, fallback_config, backend_input, eligible_connectors, @@ -792,7 +783,6 @@ pub async fn perform_fallback_routing( pub async fn perform_eligibility_analysis_with_fallback( state: &AppState, key_store: &domain::MerchantKeyStore, - merchant_last_modified: i64, chosen: Vec, transaction_data: &routing::TransactionData<'_, F>, eligible_connectors: Option>, @@ -801,7 +791,6 @@ pub async fn perform_eligibility_analysis_with_fallback( let mut final_selection = perform_eligibility_analysis( state, key_store, - merchant_last_modified, chosen, transaction_data, eligible_connectors.as_ref(), @@ -813,7 +802,6 @@ pub async fn perform_eligibility_analysis_with_fallback( let fallback_selection = perform_fallback_routing( state, key_store, - merchant_last_modified, transaction_data, eligible_connectors.as_ref(), #[cfg(feature = "business_profile_routing")] @@ -847,11 +835,6 @@ pub async fn perform_session_flow_routing( ) -> RoutingResult> { let mut pm_type_map: FxHashMap> = FxHashMap::default(); - let merchant_last_modified = session_input - .merchant_account - .modified_at - .assume_utc() - .unix_timestamp(); #[cfg(feature = "business_profile_routing")] let routing_algorithm: MerchantAccountRoutingAlgorithm = { @@ -897,7 +880,7 @@ pub async fn perform_session_flow_routing( }; let payment_input = dsl_inputs::PaymentInput { - amount: session_input.payment_intent.amount, + amount: session_input.payment_intent.amount.get_amount_as_i64(), currency: session_input .payment_intent .currency @@ -969,7 +952,6 @@ pub async fn perform_session_flow_routing( let session_pm_input = SessionRoutingPmTypeInput { state: session_input.state, key_store: session_input.key_store, - merchant_last_modified, attempt_id: &session_input.payment_attempt.attempt_id, routing_algorithm: &routing_algorithm, backend_input: backend_input.clone(), @@ -1009,10 +991,9 @@ async fn perform_session_routing_for_pm_type( let chosen_connectors = match session_pm_input.routing_algorithm { MerchantAccountRoutingAlgorithm::V1(algorithm_ref) => { if let Some(ref algorithm_id) = algorithm_ref.algorithm_id { - let key = ensure_algorithm_cached_v1( + let cached_algorithm = ensure_algorithm_cached_v1( &session_pm_input.state.clone(), merchant_id, - algorithm_ref.timestamp, algorithm_id, #[cfg(feature = "business_profile_routing")] session_pm_input.profile_id.clone(), @@ -1020,11 +1001,6 @@ async fn perform_session_routing_for_pm_type( ) .await?; - let cached_algorithm = ROUTING_CACHE - .retrieve(&key) - .change_context(errors::RoutingError::CacheMiss) - .attach_printable("unable to retrieve cached routing algorithm")?; - match cached_algorithm.as_ref() { CachedAlgorithm::Single(conn) => vec![(**conn).clone()], CachedAlgorithm::Priority(plist) => plist.clone(), @@ -1058,10 +1034,9 @@ async fn perform_session_routing_for_pm_type( } }; - let mut final_selection = perform_kgraph_filtering( + let mut final_selection = perform_cgraph_filtering( &session_pm_input.state.clone(), session_pm_input.key_store, - session_pm_input.merchant_last_modified, chosen_connectors, session_pm_input.backend_input.clone(), None, @@ -1089,10 +1064,9 @@ async fn perform_session_routing_for_pm_type( .await .change_context(errors::RoutingError::FallbackConfigFetchFailed)?; - final_selection = perform_kgraph_filtering( + final_selection = perform_cgraph_filtering( &session_pm_input.state.clone(), session_pm_input.key_store, - session_pm_input.merchant_last_modified, fallback, session_pm_input.backend_input, None, @@ -1142,7 +1116,7 @@ pub fn make_dsl_input_for_surcharge( payment_type: None, }; let payment_input = dsl_inputs::PaymentInput { - amount: payment_attempt.amount, + amount: payment_attempt.amount.get_amount_as_i64(), // currency is always populated in payment_attempt during payment create currency: payment_attempt .currency diff --git a/crates/router/src/core/payments/routing/transformers.rs b/crates/router/src/core/payments/routing/transformers.rs index b273f18f3fd8..ae779a6551f0 100644 --- a/crates/router/src/core/payments/routing/transformers.rs +++ b/crates/router/src/core/payments/routing/transformers.rs @@ -1,8 +1,14 @@ +use std::collections::HashMap; + use api_models::{self, routing as routing_types}; use diesel_models::enums as storage_enums; use euclid::{enums as dsl_enums, frontend::ast as dsl_ast}; +use kgraph_utils::types; -use crate::types::transformers::ForeignFrom; +use crate::{ + configs::settings, + types::transformers::{ForeignFrom, ForeignInto}, +}; impl ForeignFrom for dsl_ast::ConnectorChoice { fn foreign_from(from: routing_types::RoutableConnectorChoice) -> Self { @@ -52,3 +58,40 @@ impl ForeignFrom for dsl_enums::MandateType { } } } + +impl ForeignFrom for types::PaymentMethodFilterKey { + fn foreign_from(from: settings::PaymentMethodFilterKey) -> Self { + match from { + settings::PaymentMethodFilterKey::PaymentMethodType(pmt) => { + Self::PaymentMethodType(pmt) + } + settings::PaymentMethodFilterKey::CardNetwork(cn) => Self::CardNetwork(cn), + } + } +} +impl ForeignFrom for types::CurrencyCountryFlowFilter { + fn foreign_from(from: settings::CurrencyCountryFlowFilter) -> Self { + Self { + currency: from.currency, + country: from.country, + not_available_flows: from.not_available_flows.map(ForeignInto::foreign_into), + } + } +} +impl ForeignFrom for types::NotAvailableFlows { + fn foreign_from(from: settings::NotAvailableFlows) -> Self { + Self { + capture_method: from.capture_method, + } + } +} +impl ForeignFrom for types::PaymentMethodFilters { + fn foreign_from(from: settings::PaymentMethodFilters) -> Self { + let iter_map = from + .0 + .into_iter() + .map(|(key, val)| (key.foreign_into(), val.foreign_into())) + .collect::>(); + Self(iter_map) + } +} diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs index d41bfcf66d44..f5a4265a9fb3 100644 --- a/crates/router/src/core/payments/tokenization.rs +++ b/crates/router/src/core/payments/tokenization.rs @@ -111,7 +111,7 @@ where .to_owned() .get_required_value("payment_token")?; let token = match tokens { - types::PaymentMethodToken::Token(connector_token) => connector_token, + types::PaymentMethodToken::Token(connector_token) => connector_token.expose(), types::PaymentMethodToken::ApplePayDecrypt(_) => { Err(errors::ApiErrorResponse::NotSupported { message: "Apple Pay Decrypt token is not supported".to_string(), @@ -470,7 +470,7 @@ where let updated_card = Some(CardDetailFromLocker { scheme: None, - last4_digits: Some(card.card_number.clone().get_last4()), + last4_digits: Some(card.card_number.get_last4()), issuer_country: None, card_number: Some(card.card_number), expiry_month: Some(card.card_exp_month), diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 793c1cb9abdd..120f3e97fcb0 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -1,13 +1,16 @@ use std::{fmt::Debug, marker::PhantomData, str::FromStr}; -use api_models::payments::{FrmMessage, GetAddressFromPaymentMethodData, RequestSurchargeDetails}; +use api_models::payments::{ + FrmMessage, GetAddressFromPaymentMethodData, PaymentChargeRequest, PaymentChargeResponse, + RequestSurchargeDetails, +}; #[cfg(feature = "payouts")] use api_models::payouts::PayoutAttemptResponse; use common_enums::RequestIncrementalAuthorization; -use common_utils::{consts::X_HS_LATENCY, fp_utils}; +use common_utils::{consts::X_HS_LATENCY, fp_utils, types::MinorUnit}; use diesel_models::ephemeral_key; use error_stack::{report, ResultExt}; -use masking::Maskable; +use masking::{Maskable, PeekInterface, Secret}; use router_env::{instrument, tracing}; use super::{flows::Feature, types::AuthenticationData, PaymentData}; @@ -19,6 +22,7 @@ use crate::{ payments::{self, helpers}, utils as core_utils, }, + headers::X_PAYMENT_CONFIRM_SOURCE, routes::{metrics, AppState}, services::{self, RedirectForm}, types::{ @@ -85,6 +89,7 @@ where network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, + charge_id: None, }); let additional_data = PaymentAdditionalData { @@ -151,12 +156,17 @@ where connector_meta_data: merchant_connector_account.get_metadata(), request: T::try_from(additional_data)?, response, - amount_captured: payment_data.payment_intent.amount_captured, + amount_captured: payment_data + .payment_intent + .amount_captured + .map(|amt| amt.get_amount_as_i64()), access_token: None, session_token: None, reference_id: None, payment_method_status: payment_data.payment_method_info.map(|info| info.status), - payment_method_token: payment_data.pm_token.map(types::PaymentMethodToken::Token), + payment_method_token: payment_data + .pm_token + .map(|token| types::PaymentMethodToken::Token(Secret::new(token))), connector_customer: payment_data.connector_customer_id, recurring_mandate_payment_data: payment_data.recurring_mandate_payment_data, connector_request_reference_id: core_utils::get_connector_request_reference_id( @@ -317,7 +327,7 @@ where 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(masking::Secret::new), + client_secret: data.payment_intent.client_secret.map(Secret::new), customer_id: customer.as_ref().map(|x| x.customer_id.clone()), email: customer .as_ref() @@ -370,7 +380,7 @@ where .as_ref() .get_required_value("currency")?; let amount = currency - .to_currency_base_unit(payment_attempt.amount) + .to_currency_base_unit(payment_attempt.amount.get_amount_as_i64()) .change_context(errors::ApiErrorResponse::InvalidDataValue { field_name: "amount", })?; @@ -486,7 +496,7 @@ where .unwrap_or_default(); if let Some(payment_confirm_source) = payment_intent.payment_confirm_source { headers.push(( - "payment_confirm_source".to_string(), + X_PAYMENT_CONFIRM_SOURCE.to_string(), Maskable::new_normal(payment_confirm_source.to_string()), )) } @@ -597,6 +607,9 @@ where three_ds_method_url: None, }), poll_config: api_models::payments::PollConfigResponse {poll_id: request_poll_id, delay_in_secs: poll_config.delay_in_secs, frequency: poll_config.frequency}, + message_version: authentication.message_version.as_ref() + .map(|version| version.to_string()), + directory_server_id: authentication.directory_server_id.clone(), }, }) }else{ @@ -628,6 +641,28 @@ where ) }); + let charges_response = match payment_intent.charges { + None => None, + Some(charges) => { + let payment_charges: PaymentChargeRequest = charges + .peek() + .clone() + .parse_value("PaymentChargeRequest") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable(format!( + "Failed to parse PaymentChargeRequest for payment_intent {}", + payment_intent.payment_id + ))?; + + Some(PaymentChargeResponse { + charge_id: payment_attempt.charge_id, + charge_type: payment_charges.charge_type, + application_fees: payment_charges.fees, + transfer_account_id: payment_charges.transfer_account_id, + }) + } + }; + services::ApplicationResponse::JsonWithHeaders(( response .set_net_amount(payment_attempt.net_amount) @@ -639,7 +674,7 @@ where .set_amount_received(payment_intent.amount_captured) .set_surcharge_details(surcharge_details) .set_connector(routed_through) - .set_client_secret(payment_intent.client_secret.map(masking::Secret::new)) + .set_client_secret(payment_intent.client_secret.map(Secret::new)) .set_created(Some(payment_intent.created_at)) .set_currency(currency.to_string()) .set_customer_id(customer.as_ref().map(|cus| cus.clone().customer_id)) @@ -779,6 +814,8 @@ where .set_customer(customer_details_response.clone()) .set_browser_info(payment_attempt.browser_info) .set_updated(Some(payment_intent.modified_at)) + .set_charges(charges_response) + .set_frm_metadata(payment_intent.frm_metadata) .to_owned(), headers, )) @@ -1156,6 +1193,16 @@ impl TryFrom> for types::PaymentsAuthoriz .map(|customer| customer.clone().into_inner()) }); + let charges = match payment_data.payment_intent.charges { + Some(charges) => charges + .peek() + .clone() + .parse_value("PaymentCharges") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to parse charges in to PaymentCharges")?, + None => None, + }; + Ok(Self { payment_method_data: From::from( payment_method_data.get_required_value("payment_method_data")?, @@ -1168,7 +1215,7 @@ impl TryFrom> for types::PaymentsAuthoriz statement_descriptor_suffix: payment_data.payment_intent.statement_descriptor_suffix, statement_descriptor: payment_data.payment_intent.statement_descriptor_name, capture_method: payment_data.payment_attempt.capture_method, - amount, + amount: amount.get_amount_as_i64(), currency: payment_data.currency, browser_info, email: payment_data.email, @@ -1199,6 +1246,7 @@ impl TryFrom> for types::PaymentsAuthoriz .map(AuthenticationData::foreign_try_from) .transpose()?, customer_acceptance: payment_data.customer_acceptance, + charges, }) } } @@ -1244,25 +1292,25 @@ impl TryFrom> api::GetToken::Connector, payment_data.payment_attempt.merchant_connector_id.clone(), )?; + let total_amount = payment_data + .incremental_authorization_details + .clone() + .map(|details| details.total_amount) + .ok_or( + report!(errors::ApiErrorResponse::InternalServerError) + .attach_printable("missing incremental_authorization_details in payment_data"), + )?; + let additional_amount = payment_data + .incremental_authorization_details + .clone() + .map(|details| details.additional_amount) + .ok_or( + report!(errors::ApiErrorResponse::InternalServerError) + .attach_printable("missing incremental_authorization_details in payment_data"), + )?; Ok(Self { - total_amount: payment_data - .incremental_authorization_details - .clone() - .map(|details| details.total_amount) - .ok_or( - report!(errors::ApiErrorResponse::InternalServerError).attach_printable( - "missing incremental_authorization_details in payment_data", - ), - )?, - additional_amount: payment_data - .incremental_authorization_details - .clone() - .map(|details| details.additional_amount) - .ok_or( - report!(errors::ApiErrorResponse::InternalServerError).attach_printable( - "missing incremental_authorization_details in payment_data", - ), - )?, + total_amount: total_amount.get_amount_as_i64(), + additional_amount: additional_amount.get_amount_as_i64(), reason: payment_data .incremental_authorization_details .and_then(|details| details.reason), @@ -1311,7 +1359,7 @@ impl TryFrom> for types::PaymentsCaptureD api::GetToken::Connector, payment_data.payment_attempt.merchant_connector_id.clone(), )?; - let amount_to_capture: i64 = payment_data + let amount_to_capture = payment_data .payment_attempt .amount_to_capture .map_or(payment_data.amount.into(), |capture_amount| capture_amount); @@ -1324,15 +1372,15 @@ impl TryFrom> for types::PaymentsCaptureD .change_context(errors::ApiErrorResponse::InvalidDataValue { field_name: "browser_info", })?; - + let amount = MinorUnit::from(payment_data.amount); Ok(Self { - amount_to_capture, + amount_to_capture: amount_to_capture.get_amount_as_i64(), // This should be removed once we start moving to connector module currency: payment_data.currency, connector_transaction_id: connector .connector .connector_transaction_id(payment_data.payment_attempt.clone())? .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?, - payment_amount: payment_data.amount.into(), + payment_amount: amount.get_amount_as_i64(), // This should be removed once we start moving to connector module connector_meta: payment_data.payment_attempt.connector_metadata, multiple_capture_data: match payment_data.multiple_capture_data { Some(multiple_capture_data) => Some(MultipleCaptureRequestData { @@ -1370,8 +1418,9 @@ impl TryFrom> for types::PaymentsCancelDa .change_context(errors::ApiErrorResponse::InvalidDataValue { field_name: "browser_info", })?; + let amount = MinorUnit::from(payment_data.amount); Ok(Self { - amount: Some(payment_data.amount.into()), + amount: Some(amount.get_amount_as_i64()), // This should be removed once we start moving to connector module currency: Some(payment_data.currency), connector_transaction_id: connector .connector @@ -1390,8 +1439,9 @@ impl TryFrom> for types::PaymentsApproveD fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result { let payment_data = additional_data.payment_data; + let amount = MinorUnit::from(payment_data.amount); Ok(Self { - amount: Some(payment_data.amount.into()), + amount: Some(amount.get_amount_as_i64()), //need to change after we move to connector module currency: Some(payment_data.currency), }) } @@ -1402,8 +1452,9 @@ impl TryFrom> for types::PaymentsRejectDa fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result { let payment_data = additional_data.payment_data; + let amount = MinorUnit::from(payment_data.amount); Ok(Self { - amount: Some(payment_data.amount.into()), + amount: Some(amount.get_amount_as_i64()), //need to change after we move to connector module currency: Some(payment_data.currency), }) } @@ -1439,7 +1490,7 @@ impl TryFrom> for types::PaymentsSessionD .unwrap_or(payment_data.amount.into()); Ok(Self { - amount, + amount: amount.get_amount_as_i64(), //need to change once we move to connector module currency: payment_data.currency, country: payment_data.address.get_payment_method_billing().and_then( |billing_address| { @@ -1487,11 +1538,11 @@ impl TryFrom> for types::SetupMandateRequ .as_ref() .map(|customer| customer.clone().into_inner()) }); - + let amount = MinorUnit::from(payment_data.amount); Ok(Self { currency: payment_data.currency, confirm: true, - amount: Some(payment_data.amount.into()), + amount: Some(amount.get_amount_as_i64()), //need to change once we move to connector module payment_method_data: From::from( payment_data .payment_method_data @@ -1603,7 +1654,7 @@ impl TryFrom> for types::CompleteAuthoriz confirm: payment_data.payment_attempt.confirm, statement_descriptor_suffix: payment_data.payment_intent.statement_descriptor_suffix, capture_method: payment_data.payment_attempt.capture_method, - amount, + amount: amount.get_amount_as_i64(), // need to change once we move to connector module currency: payment_data.currency, browser_info, email: payment_data.email, @@ -1680,7 +1731,7 @@ impl TryFrom> for types::PaymentsPreProce payment_method_data: payment_method_data.map(From::from), email: payment_data.email, currency: Some(payment_data.currency), - amount: Some(amount), + amount: Some(amount.get_amount_as_i64()), // need to change this once we move to connector module payment_method_type: payment_data.payment_attempt.payment_method_type, setup_mandate_details: payment_data.setup_mandate, capture_method: payment_data.payment_attempt.capture_method, diff --git a/crates/router/src/core/payments/types.rs b/crates/router/src/core/payments/types.rs index b297470bce84..eb3a711b5b9d 100644 --- a/crates/router/src/core/payments/types.rs +++ b/crates/router/src/core/payments/types.rs @@ -1,8 +1,7 @@ use std::{collections::HashMap, num::TryFromIntError}; -use api_models::{payment_methods::SurchargeDetailsResponse, payments::RequestSurchargeDetails}; +use api_models::payment_methods::SurchargeDetailsResponse; use common_utils::{ - consts, errors::CustomResult, ext_traits::{Encode, OptionExt}, types as common_types, @@ -10,6 +9,7 @@ use common_utils::{ use diesel_models::business_profile::BusinessProfile; use error_stack::ResultExt; use hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt; +pub use hyperswitch_domain_models::router_request_types::{AuthenticationData, SurchargeDetails}; use redis_interface::errors::RedisError; use router_env::{instrument, tracing}; @@ -80,27 +80,31 @@ impl MultipleCaptureData { .and_modify(|capture| *capture = updated_capture.clone()); } } - pub fn get_total_blocked_amount(&self) -> i64 { - self.all_captures.iter().fold(0, |accumulator, capture| { - accumulator - + match capture.1.status { - storage_enums::CaptureStatus::Charged - | storage_enums::CaptureStatus::Pending => capture.1.amount, - storage_enums::CaptureStatus::Started - | storage_enums::CaptureStatus::Failed => 0, - } - }) + pub fn get_total_blocked_amount(&self) -> common_types::MinorUnit { + self.all_captures + .iter() + .fold(common_types::MinorUnit::new(0), |accumulator, capture| { + accumulator + + match capture.1.status { + storage_enums::CaptureStatus::Charged + | storage_enums::CaptureStatus::Pending => capture.1.amount, + storage_enums::CaptureStatus::Started + | storage_enums::CaptureStatus::Failed => common_types::MinorUnit::new(0), + } + }) } - pub fn get_total_charged_amount(&self) -> i64 { - self.all_captures.iter().fold(0, |accumulator, capture| { - accumulator - + match capture.1.status { - storage_enums::CaptureStatus::Charged => capture.1.amount, - storage_enums::CaptureStatus::Pending - | storage_enums::CaptureStatus::Started - | storage_enums::CaptureStatus::Failed => 0, - } - }) + pub fn get_total_charged_amount(&self) -> common_types::MinorUnit { + self.all_captures + .iter() + .fold(common_types::MinorUnit::new(0), |accumulator, capture| { + accumulator + + match capture.1.status { + storage_enums::CaptureStatus::Charged => capture.1.amount, + storage_enums::CaptureStatus::Pending + | storage_enums::CaptureStatus::Started + | storage_enums::CaptureStatus::Failed => common_types::MinorUnit::new(0), + } + }) } pub fn get_captures_count(&self) -> RouterResult { i16::try_from(self.all_captures.len()) @@ -123,7 +127,10 @@ impl MultipleCaptureData { accumulator }) } - pub fn get_attempt_status(&self, authorized_amount: i64) -> storage_enums::AttemptStatus { + pub fn get_attempt_status( + &self, + authorized_amount: common_types::MinorUnit, + ) -> storage_enums::AttemptStatus { let total_captured_amount = self.get_total_charged_amount(); if authorized_amount == total_captured_amount { return storage_enums::AttemptStatus::Charged; @@ -179,54 +186,24 @@ impl MultipleCaptureData { } } -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct SurchargeDetails { - /// original_amount - pub original_amount: i64, - /// surcharge value - pub surcharge: common_types::Surcharge, - /// tax on surcharge value - pub tax_on_surcharge: - Option>, - /// surcharge amount for this payment - pub surcharge_amount: i64, - /// tax on surcharge amount for this payment - pub tax_on_surcharge_amount: i64, - /// sum of original amount, - pub final_amount: i64, -} - -impl From<(&RequestSurchargeDetails, &PaymentAttempt)> for SurchargeDetails { - fn from( - (request_surcharge_details, payment_attempt): (&RequestSurchargeDetails, &PaymentAttempt), - ) -> Self { - let surcharge_amount = request_surcharge_details.surcharge_amount; - let tax_on_surcharge_amount = request_surcharge_details.tax_amount.unwrap_or(0); - Self { - original_amount: payment_attempt.amount, - surcharge: common_types::Surcharge::Fixed(request_surcharge_details.surcharge_amount), - tax_on_surcharge: None, - surcharge_amount, - tax_on_surcharge_amount, - final_amount: payment_attempt.amount + surcharge_amount + tax_on_surcharge_amount, - } - } -} - impl ForeignTryFrom<(&SurchargeDetails, &PaymentAttempt)> for SurchargeDetailsResponse { type Error = TryFromIntError; fn foreign_try_from( (surcharge_details, payment_attempt): (&SurchargeDetails, &PaymentAttempt), ) -> Result { let currency = payment_attempt.currency.unwrap_or_default(); - let display_surcharge_amount = - currency.to_currency_base_unit_asf64(surcharge_details.surcharge_amount)?; - let display_tax_on_surcharge_amount = - currency.to_currency_base_unit_asf64(surcharge_details.tax_on_surcharge_amount)?; - let display_final_amount = - currency.to_currency_base_unit_asf64(surcharge_details.final_amount)?; + let display_surcharge_amount = currency + .to_currency_base_unit_asf64(surcharge_details.surcharge_amount.get_amount_as_i64())?; + let display_tax_on_surcharge_amount = currency.to_currency_base_unit_asf64( + surcharge_details + .tax_on_surcharge_amount + .get_amount_as_i64(), + )?; + let display_final_amount = currency + .to_currency_base_unit_asf64(surcharge_details.final_amount.get_amount_as_i64())?; let display_total_surcharge_amount = currency.to_currency_base_unit_asf64( - surcharge_details.surcharge_amount + surcharge_details.tax_on_surcharge_amount, + (surcharge_details.surcharge_amount + surcharge_details.tax_on_surcharge_amount) + .get_amount_as_i64(), )?; Ok(Self { surcharge: surcharge_details.surcharge.clone().into(), @@ -239,19 +216,6 @@ impl ForeignTryFrom<(&SurchargeDetails, &PaymentAttempt)> for SurchargeDetailsRe } } -impl SurchargeDetails { - pub fn is_request_surcharge_matching( - &self, - request_surcharge_details: RequestSurchargeDetails, - ) -> bool { - request_surcharge_details.surcharge_amount == self.surcharge_amount - && request_surcharge_details.tax_amount.unwrap_or(0) == self.tax_on_surcharge_amount - } - pub fn get_total_surcharge_amount(&self) -> i64 { - self.surcharge_amount + self.tax_on_surcharge_amount - } -} - #[derive(Eq, Hash, PartialEq, Clone, Debug, strum::Display)] pub enum SurchargeKey { Token(String), @@ -375,14 +339,6 @@ impl SurchargeMetadata { } } -#[derive(Debug, Clone)] -pub struct AuthenticationData { - pub eci: Option, - pub cavv: String, - pub threeds_server_transaction_id: String, - pub message_version: String, -} - impl ForeignTryFrom<&storage::Authentication> for AuthenticationData { type Error = error_stack::Report; fn foreign_try_from(authentication: &storage::Authentication) -> Result { @@ -418,3 +374,10 @@ impl ForeignTryFrom<&storage::Authentication> for AuthenticationData { } } } + +#[derive(Debug, serde::Deserialize, Clone)] +pub struct PaymentCharges { + pub charge_type: api_models::enums::PaymentChargeType, + pub fees: i64, + pub transfer_account_id: String, +} diff --git a/crates/router/src/core/payouts.rs b/crates/router/src/core/payouts.rs index d0db097cfd7f..2e30cdac1c6a 100644 --- a/crates/router/src/core/payouts.rs +++ b/crates/router/src/core/payouts.rs @@ -6,7 +6,7 @@ pub mod validator; use std::vec::IntoIter; use api_models::enums as api_enums; -use common_utils::{consts, crypto::Encryptable, ext_traits::ValueExt, pii}; +use common_utils::{consts, crypto::Encryptable, ext_traits::ValueExt, pii, types::MinorUnit}; use diesel_models::enums as storage_enums; use error_stack::{report, ResultExt}; #[cfg(feature = "olap")] @@ -365,11 +365,12 @@ pub async fn payouts_update_core( ), })); } - // Update DB with new data let payouts = payout_data.payouts.to_owned(); + let amount = MinorUnit::from(req.amount.unwrap_or(MinorUnit::new(payouts.amount).into())) + .get_amount_as_i64(); let updated_payouts = storage::PayoutsUpdate::Update { - amount: req.amount.unwrap_or(payouts.amount.into()).into(), + amount, destination_currency: req.currency.unwrap_or(payouts.destination_currency), source_currency: req.currency.unwrap_or(payouts.source_currency), description: req.description.clone().or(payouts.description.clone()), @@ -1964,14 +1965,14 @@ pub async fn payout_create_db_entries( } else { None }; - + let amount = MinorUnit::from(req.amount.unwrap_or(api::Amount::Zero)).get_amount_as_i64(); let payouts_req = storage::PayoutsNew { payout_id: payout_id.to_string(), merchant_id: merchant_id.to_string(), customer_id: customer_id.to_owned(), address_id: address_id.to_owned(), payout_type, - amount: req.amount.unwrap_or(api::Amount::Zero).into(), + amount, destination_currency: currency, source_currency: currency, description: req.description.to_owned(), diff --git a/crates/router/src/core/payouts/helpers.rs b/crates/router/src/core/payouts/helpers.rs index c22f74bab4a8..dad0739879fb 100644 --- a/crates/router/src/core/payouts/helpers.rs +++ b/crates/router/src/core/payouts/helpers.rs @@ -371,9 +371,7 @@ pub async fn save_payout_data_to_locker( metadata_update.as_ref(), ) { // Fetch card info from db - let card_isin = card_details - .as_ref() - .map(|c| c.card_number.clone().get_card_isin()); + let card_isin = card_details.as_ref().map(|c| c.card_number.get_card_isin()); let mut payment_method = api::PaymentMethodCreate { payment_method: Some(api_enums::PaymentMethod::foreign_from( @@ -410,9 +408,7 @@ pub async fn save_payout_data_to_locker( card_info.card_network.clone().map(|cn| cn.to_string()); api::payment_methods::PaymentMethodsData::Card( api::payment_methods::CardDetailsPaymentMethod { - last4_digits: card_details - .as_ref() - .map(|c| c.card_number.clone().get_last4()), + last4_digits: card_details.as_ref().map(|c| c.card_number.get_last4()), issuer_country: card_info.card_issuing_country, expiry_month: card_details.as_ref().map(|c| c.card_exp_month.clone()), expiry_year: card_details.as_ref().map(|c| c.card_exp_year.clone()), @@ -432,9 +428,7 @@ pub async fn save_payout_data_to_locker( .unwrap_or_else(|| { api::payment_methods::PaymentMethodsData::Card( api::payment_methods::CardDetailsPaymentMethod { - last4_digits: card_details - .as_ref() - .map(|c| c.card_number.clone().get_last4()), + last4_digits: card_details.as_ref().map(|c| c.card_number.get_last4()), issuer_country: None, expiry_month: card_details.as_ref().map(|c| c.card_exp_month.clone()), expiry_year: card_details.as_ref().map(|c| c.card_exp_year.clone()), @@ -626,6 +620,7 @@ pub async fn get_or_create_customer_details( modified_at: common_utils::date_time::now(), address_id: None, default_payment_method_id: None, + updated_by: None, }; Ok(Some( @@ -674,7 +669,6 @@ pub async fn decide_payout_connector( connectors = routing::perform_eligibility_analysis_with_fallback( state, key_store, - merchant_account.modified_at.assume_utc().unix_timestamp(), connectors, &TransactionData::<()>::Payout(payout_data), eligible_connectors, @@ -733,7 +727,6 @@ pub async fn decide_payout_connector( connectors = routing::perform_eligibility_analysis_with_fallback( state, key_store, - merchant_account.modified_at.assume_utc().unix_timestamp(), connectors, &TransactionData::<()>::Payout(payout_data), eligible_connectors, diff --git a/crates/router/src/core/payouts/retry.rs b/crates/router/src/core/payouts/retry.rs index a7dcfa864a0a..7cb929db94a2 100644 --- a/crates/router/src/core/payouts/retry.rs +++ b/crates/router/src/core/payouts/retry.rs @@ -81,7 +81,7 @@ pub async fn do_gsm_multiple_connector_actions( } api_models::gsm::GsmDecision::Requeue => { Err(report!(errors::ApiErrorResponse::NotImplemented { - message: errors::api_error_response::NotImplementedMessage::Reason( + message: errors::NotImplementedMessage::Reason( "Requeue not implemented".to_string(), ), }))? @@ -145,7 +145,7 @@ pub async fn do_gsm_single_connector_actions( } api_models::gsm::GsmDecision::Requeue => { Err(report!(errors::ApiErrorResponse::NotImplemented { - message: errors::api_error_response::NotImplementedMessage::Reason( + message: errors::NotImplementedMessage::Reason( "Requeue not implemented".to_string(), ), }))? diff --git a/crates/router/src/core/pm_auth.rs b/crates/router/src/core/pm_auth.rs index 8d30a79df19e..f184e166e22d 100644 --- a/crates/router/src/core/pm_auth.rs +++ b/crates/router/src/core/pm_auth.rs @@ -467,6 +467,7 @@ async fn store_bank_details_in_payment_methods( network_transaction_id: None, client_secret: None, payment_method_billing_address: None, + updated_by: None, }; new_entries.push(pm_new); diff --git a/crates/router/src/core/refunds.rs b/crates/router/src/core/refunds.rs index fa6b06ba3a5d..b9861ea1f23a 100644 --- a/crates/router/src/core/refunds.rs +++ b/crates/router/src/core/refunds.rs @@ -5,8 +5,9 @@ use std::collections::HashMap; #[cfg(feature = "olap")] use api_models::admin::MerchantConnectorInfo; -use common_utils::ext_traits::AsyncExt; +use common_utils::ext_traits::{AsyncExt, ValueExt}; use error_stack::{report, ResultExt}; +use masking::PeekInterface; use router_env::{instrument, tracing}; use scheduler::{consumer::types::process_data, utils as process_tracker_utils}; #[cfg(feature = "olap")] @@ -16,7 +17,7 @@ use crate::{ consts, core::{ errors::{self, ConnectorErrorExt, RouterResponse, RouterResult, StorageErrorExt}, - payments::{self, access_token}, + payments::{self, access_token, types::PaymentCharges}, utils as core_utils, }, db, logger, @@ -28,6 +29,7 @@ use crate::{ domain, storage::{self, enums}, transformers::{ForeignFrom, ForeignInto}, + ChargeRefunds, }, utils::{self, OptionExt}, workflows::payment_sync, @@ -73,7 +75,9 @@ pub async fn refund_create_core( // Amount is not passed in request refer from payment intent. amount = req .amount - .or(payment_intent.amount_captured) + .or(payment_intent + .amount_captured + .map(|capture_amount| capture_amount.get_amount_as_i64())) .ok_or(errors::ApiErrorResponse::InternalServerError) .attach_printable("amount captured is none in a successful payment")?; @@ -126,6 +130,7 @@ pub async fn refund_create_core( .map(services::ApplicationResponse::Json) } +#[allow(clippy::too_many_arguments)] #[instrument(skip_all)] pub async fn trigger_refund_to_gateway( state: &AppState, @@ -135,6 +140,7 @@ pub async fn trigger_refund_to_gateway( payment_attempt: &storage::PaymentAttempt, payment_intent: &storage::PaymentIntent, creds_identifier: Option, + charges: Option, ) -> RouterResult { let routed_through = payment_attempt .connector @@ -172,11 +178,12 @@ pub async fn trigger_refund_to_gateway( &routed_through, merchant_account, key_store, - (payment_attempt.amount, currency), + (payment_attempt.amount.get_amount_as_i64(), currency), payment_intent, payment_attempt, refund, creds_identifier, + charges, ) .await?; @@ -451,11 +458,12 @@ pub async fn sync_refund_with_gateway( &connector_id, merchant_account, key_store, - (payment_attempt.amount, currency), + (payment_attempt.amount.get_amount_as_i64(), currency), payment_intent, payment_attempt, refund, creds_identifier, + None, ) .await?; @@ -586,6 +594,39 @@ pub async fn validate_and_create_refund( ) -> RouterResult { let db = &*state.store; + // Validate charge_id and refund options + let charges = match ( + payment_intent.charges.as_ref(), + payment_attempt.charge_id.as_ref(), + ) { + (Some(charges), Some(charge_id)) => { + let refund_charge_request = req.charges.clone().get_required_value("charges")?; + utils::when(*charge_id != refund_charge_request.charge_id, || { + Err(report!(errors::ApiErrorResponse::InvalidDataValue { + field_name: "charges.charge_id" + })) + .attach_printable("charge_id sent in request mismatches with original charge_id") + })?; + let payment_charges: PaymentCharges = charges + .peek() + .clone() + .parse_value("PaymentCharges") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to parse charges in to PaymentCharges")?; + let options = validator::validate_charge_refund( + &refund_charge_request, + &payment_charges.charge_type, + )?; + Some(ChargeRefunds { + charge_id: charge_id.to_string(), + charge_type: payment_charges.charge_type, + transfer_account_id: payment_charges.transfer_account_id, + options, + }) + } + _ => None, + }; + // Only for initial dev and testing let refund_type = req.refund_type.unwrap_or_default(); @@ -636,8 +677,12 @@ pub async fn validate_and_create_refund( .amount_captured .unwrap_or(payment_attempt.amount); - validator::validate_refund_amount(total_amount_captured, &all_refunds, refund_amount) - .change_context(errors::ApiErrorResponse::RefundAmountExceedsPaymentAmount)?; + validator::validate_refund_amount( + total_amount_captured.get_amount_as_i64(), + &all_refunds, + refund_amount, + ) + .change_context(errors::ApiErrorResponse::RefundAmountExceedsPaymentAmount)?; validator::validate_maximum_refund_against_payment_attempt( &all_refunds, @@ -660,7 +705,7 @@ pub async fn validate_and_create_refund( .set_connector_transaction_id(connecter_transaction_id.to_string()) .set_connector(connector) .set_refund_type(req.refund_type.unwrap_or_default().foreign_into()) - .set_total_amount(payment_attempt.amount) + .set_total_amount(payment_attempt.amount.get_amount_as_i64()) .set_refund_amount(refund_amount) .set_currency(currency) .set_created_at(Some(common_utils::date_time::now())) @@ -672,6 +717,7 @@ pub async fn validate_and_create_refund( .set_refund_reason(req.reason) .set_profile_id(payment_intent.profile_id.clone()) .set_merchant_connector_id(payment_attempt.merchant_connector_id.clone()) + .set_charges(req.charges) .to_owned(); let refund = match db @@ -688,6 +734,7 @@ pub async fn validate_and_create_refund( payment_attempt, payment_intent, creds_identifier, + charges, ) .await? } @@ -826,6 +873,7 @@ pub async fn get_filters_for_refunds( impl ForeignFrom for api::RefundResponse { fn foreign_from(refund: storage::Refund) -> Self { let refund = refund; + Self { payment_id: refund.payment_id, refund_id: refund.refund_id, @@ -841,6 +889,7 @@ impl ForeignFrom for api::RefundResponse { updated_at: Some(refund.updated_at), connector: refund.connector, merchant_connector_id: refund.merchant_connector_id, + charges: refund.charges, } } } @@ -858,6 +907,7 @@ pub async fn schedule_refund_execution( payment_attempt: &storage::PaymentAttempt, payment_intent: &storage::PaymentIntent, creds_identifier: Option, + charges: Option, ) -> RouterResult { // refunds::RefundResponse> { let db = &*state.store; @@ -894,6 +944,7 @@ pub async fn schedule_refund_execution( payment_attempt, payment_intent, creds_identifier, + charges, ) .await } @@ -1073,6 +1124,41 @@ pub async fn trigger_refund_execute_workflow( .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + let charges = match ( + payment_intent.charges.as_ref(), + payment_attempt.charge_id.as_ref(), + ) { + (Some(charges), Some(charge_id)) => { + let refund_charge_request = + refund.charges.clone().get_required_value("charges")?; + utils::when(*charge_id != refund_charge_request.charge_id, || { + Err(report!(errors::ApiErrorResponse::InvalidDataValue { + field_name: "charges.charge_id" + })) + .attach_printable( + "charge_id sent in request mismatches with original charge_id", + ) + })?; + let payment_charges: PaymentCharges = charges + .peek() + .clone() + .parse_value("PaymentCharges") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to parse charges in to PaymentCharges")?; + let options = validator::validate_charge_refund( + &refund_charge_request, + &payment_charges.charge_type, + )?; + Some(ChargeRefunds { + charge_id: charge_id.to_string(), + charge_type: payment_charges.charge_type, + transfer_account_id: payment_charges.transfer_account_id, + options, + }) + } + _ => None, + }; + //trigger refund request to gateway let updated_refund = trigger_refund_to_gateway( state, @@ -1082,6 +1168,7 @@ pub async fn trigger_refund_execute_workflow( &payment_attempt, &payment_intent, None, + charges, ) .await?; add_refund_sync_task( diff --git a/crates/router/src/core/refunds/validator.rs b/crates/router/src/core/refunds/validator.rs index 49248ae4fea5..3788b8ac26d1 100644 --- a/crates/router/src/core/refunds/validator.rs +++ b/crates/router/src/core/refunds/validator.rs @@ -4,7 +4,11 @@ use time::PrimitiveDateTime; use crate::{ core::errors::{self, CustomResult, RouterResult}, - types::storage::{self, enums}, + types::{ + self, + api::enums as api_enums, + storage::{self, enums}, + }, utils::{self, OptionExt}, }; @@ -144,3 +148,28 @@ pub fn validate_for_valid_refunds( _ => Ok(()), } } + +pub fn validate_charge_refund( + charges: &common_utils::types::ChargeRefunds, + charge_type: &api_enums::PaymentChargeType, +) -> RouterResult { + match charge_type { + api_enums::PaymentChargeType::Stripe(api_enums::StripeChargeType::Direct) => Ok( + types::ChargeRefundsOptions::Direct(types::DirectChargeRefund { + revert_platform_fee: charges + .revert_platform_fee + .get_required_value("revert_platform_fee")?, + }), + ), + api_enums::PaymentChargeType::Stripe(api_enums::StripeChargeType::Destination) => Ok( + types::ChargeRefundsOptions::Destination(types::DestinationChargeRefund { + revert_platform_fee: charges + .revert_platform_fee + .get_required_value("revert_platform_fee")?, + revert_transfer: charges + .revert_transfer + .get_required_value("revert_transfer")?, + }), + ), + } +} diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index 894e09ab60e4..1d46e54bccf3 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -10,10 +10,11 @@ use diesel_models::{ }; use error_stack::ResultExt; use rustc_hash::FxHashSet; +use storage_impl::redis::cache as redis_cache; use crate::{ core::errors::{self, RouterResult}, - db::StorageInterface, + db::{cache, StorageInterface}, types::{domain, storage}, utils::StringExt, }; @@ -239,6 +240,17 @@ pub async fn update_business_profile_active_algorithm_ref( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to convert routing ref to value")?; + let merchant_id = current_business_profile.merchant_id.clone(); + + #[cfg(feature = "business_profile_routing")] + let profile_id = current_business_profile.profile_id.clone(); + #[cfg(feature = "business_profile_routing")] + let routing_cache_key = redis_cache::CacheKind::Routing( + format!("routing_config_{merchant_id}_{profile_id}").into(), + ); + + #[cfg(not(feature = "business_profile_routing"))] + let routing_cache_key = redis_cache::CacheKind::Routing(format!("dsl_{merchant_id}").into()); let (routing_algorithm, payout_routing_algorithm) = match transaction_type { storage::enums::TransactionType::Payment => (Some(ref_val), None), #[cfg(feature = "payouts")] @@ -272,6 +284,11 @@ pub async fn update_business_profile_active_algorithm_ref( .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to update routing algorithm ref in business profile")?; + + cache::publish_into_redact_channel(db, [routing_cache_key]) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to invalidate routing cache")?; Ok(()) } diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index 5d1eaeaeb8de..4fd9fa865cd4 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -24,8 +24,9 @@ use crate::{ routes::{app::ReqState, AppState}, services::{authentication as auth, authorization::roles, ApplicationResponse}, types::{domain, transformers::ForeignInto}, - utils, + utils::{self, user::two_factor_auth as tfa_utils}, }; + pub mod dashboard_metadata; #[cfg(feature = "dummy_connector")] pub mod sample_data; @@ -178,7 +179,7 @@ pub async fn signin( })? .into(); - user_from_db.compare_password(request.password)?; + user_from_db.compare_password(&request.password)?; let signin_strategy = if let Some(preferred_merchant_id) = user_from_db.get_preferred_merchant_id() { @@ -216,7 +217,7 @@ pub async fn signin_token_only_flow( .to_not_found_response(UserErrors::InvalidCredentials)? .into(); - user_from_db.compare_password(request.password)?; + user_from_db.compare_password(&request.password)?; let next_flow = domain::NextFlow::from_origin(domain::Origin::SignIn, user_from_db.clone(), &state).await?; @@ -340,7 +341,7 @@ pub async fn change_password( .change_context(UserErrors::InternalServerError)? .into(); - user.compare_password(request.old_password.to_owned()) + user.compare_password(&request.old_password) .change_context(UserErrors::InvalidOldPassword)?; if request.old_password == request.new_password { @@ -438,7 +439,7 @@ pub async fn rotate_password( let password = domain::UserPassword::new(request.password.to_owned())?; let hash_password = utils::user::password::generate_password_hash(password.get_secret())?; - if user.compare_password(request.password).is_ok() { + if user.compare_password(&request.password).is_ok() { return Err(UserErrors::ChangePasswordError.into()); } @@ -1631,7 +1632,7 @@ pub async fn begin_totp( })); } - let totp = utils::user::generate_default_totp(user_from_db.get_email(), None)?; + let totp = tfa_utils::generate_default_totp(user_from_db.get_email(), None)?; let recovery_codes = domain::RecoveryCodes::generate_new(); let key_store = user_from_db.get_or_create_key_store(&state).await?; @@ -1694,7 +1695,7 @@ pub async fn verify_totp( .ok_or(UserErrors::InternalServerError)?; let totp = - utils::user::generate_default_totp(user_from_db.get_email(), Some(user_totp_secret))?; + tfa_utils::generate_default_totp(user_from_db.get_email(), Some(user_totp_secret))?; if totp .generate_current() @@ -1732,3 +1733,132 @@ pub async fn verify_totp( token, ) } + +pub async fn generate_recovery_codes( + state: AppState, + user_token: auth::UserFromSinglePurposeToken, +) -> UserResponse { + if !tfa_utils::check_totp_in_redis(&state, &user_token.user_id).await? { + return Err(UserErrors::TotpRequired.into()); + } + + let recovery_codes = domain::RecoveryCodes::generate_new(); + + state + .store + .update_user_by_user_id( + &user_token.user_id, + storage_user::UserUpdate::TotpUpdate { + totp_status: None, + totp_secret: None, + totp_recovery_codes: Some( + recovery_codes + .get_hashed() + .change_context(UserErrors::InternalServerError)?, + ), + }, + ) + .await + .change_context(UserErrors::InternalServerError)?; + + Ok(ApplicationResponse::Json(user_api::RecoveryCodes { + recovery_codes: recovery_codes.into_inner(), + })) +} + +pub async fn verify_recovery_code( + state: AppState, + user_token: auth::UserFromSinglePurposeToken, + req: user_api::VerifyRecoveryCodeRequest, +) -> UserResponse { + let user_from_db: domain::UserFromStorage = state + .store + .find_user_by_id(&user_token.user_id) + .await + .change_context(UserErrors::InternalServerError)? + .into(); + + if user_from_db.get_totp_status() != TotpStatus::Set { + return Err(UserErrors::TwoFactorAuthNotSetup.into()); + } + + let mut recovery_codes = user_from_db + .get_recovery_codes() + .ok_or(UserErrors::InternalServerError)?; + + let matching_index = utils::user::password::get_index_for_correct_recovery_code( + &req.recovery_code, + &recovery_codes, + )? + .ok_or(UserErrors::InvalidRecoveryCode)?; + + tfa_utils::insert_recovery_code_in_redis(&state, user_from_db.get_user_id()).await?; + let _ = recovery_codes.remove(matching_index); + + state + .store + .update_user_by_user_id( + user_from_db.get_user_id(), + storage_user::UserUpdate::TotpUpdate { + totp_status: None, + totp_secret: None, + totp_recovery_codes: Some(recovery_codes), + }, + ) + .await + .change_context(UserErrors::InternalServerError)?; + + Ok(ApplicationResponse::StatusOk) +} + +pub async fn terminate_two_factor_auth( + state: AppState, + user_token: auth::UserFromSinglePurposeToken, + skip_two_factor_auth: bool, +) -> UserResponse { + let user_from_db: domain::UserFromStorage = state + .store + .find_user_by_id(&user_token.user_id) + .await + .change_context(UserErrors::InternalServerError)? + .into(); + + if !skip_two_factor_auth { + if !tfa_utils::check_totp_in_redis(&state, &user_token.user_id).await? + && !tfa_utils::check_recovery_code_in_redis(&state, &user_token.user_id).await? + { + return Err(UserErrors::TwoFactorAuthRequired.into()); + } + + if user_from_db.get_recovery_codes().is_none() { + return Err(UserErrors::TwoFactorAuthNotSetup.into()); + } + + if user_from_db.get_totp_status() != TotpStatus::Set { + state + .store + .update_user_by_user_id( + user_from_db.get_user_id(), + storage_user::UserUpdate::TotpUpdate { + totp_status: Some(TotpStatus::Set), + totp_secret: None, + totp_recovery_codes: None, + }, + ) + .await + .change_context(UserErrors::InternalServerError)?; + } + } + + let current_flow = domain::CurrentFlow::new(user_token.origin, domain::SPTFlow::TOTP.into())?; + let next_flow = current_flow.next(user_from_db, &state).await?; + let token = next_flow.get_token(&state).await?; + + auth::cookies::set_cookie_response( + user_api::TokenResponse { + token: token.clone(), + token_type: next_flow.get_flow().into(), + }, + token, + ) +} diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index 91d567e62988..28eb19135cb2 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -8,13 +8,14 @@ use common_enums::{IntentStatus, RequestIncrementalAuthorization}; use common_utils::{crypto::Encryptable, pii::Email}; use common_utils::{errors::CustomResult, ext_traits::AsyncExt}; use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::{payment_address::PaymentAddress, router_data::ErrorResponse}; #[cfg(feature = "payouts")] use masking::PeekInterface; use maud::{html, PreEscaped}; use router_env::{instrument, tracing}; use uuid::Uuid; -use super::payments::{helpers, PaymentAddress}; +use super::payments::helpers; #[cfg(feature = "payouts")] use super::payouts::PayoutData; #[cfg(feature = "payouts")] @@ -28,7 +29,7 @@ use crate::{ types::{ self, domain, storage::{self, enums}, - ErrorResponse, PollConfig, + PollConfig, }, utils::{generate_id, generate_uuid, OptionExt, ValueExt}, }; @@ -221,6 +222,7 @@ pub async fn construct_refund_router_data<'a, F>( payment_attempt: &storage::PaymentAttempt, refund: &'a storage::Refund, creds_identifier: Option, + charges: Option, ) -> RouterResult> { let profile_id = get_profile_id_from_business_details( payment_intent.business_country, @@ -313,7 +315,9 @@ pub async fn construct_refund_router_data<'a, F>( address: PaymentAddress::default(), auth_type: payment_attempt.authentication_type.unwrap_or_default(), connector_meta_data: merchant_connector_account.get_metadata(), - amount_captured: payment_intent.amount_captured, + amount_captured: payment_intent + .amount_captured + .map(|amt| amt.get_amount_as_i64()), payment_method_status: None, request: types::RefundsData { refund_id: refund.refund_id.clone(), @@ -326,6 +330,7 @@ pub async fn construct_refund_router_data<'a, F>( reason: refund.refund_reason.clone(), connector_refund_id: refund.connector_refund_id.clone(), browser_info, + charges, }, response: Ok(types::RefundsResponseData { @@ -557,7 +562,9 @@ pub async fn construct_accept_dispute_router_data<'a>( address: PaymentAddress::default(), auth_type: payment_attempt.authentication_type.unwrap_or_default(), connector_meta_data: merchant_connector_account.get_metadata(), - amount_captured: payment_intent.amount_captured, + amount_captured: payment_intent + .amount_captured + .map(|amt| amt.get_amount_as_i64()), payment_method_status: None, request: types::AcceptDisputeRequestData { dispute_id: dispute.dispute_id.clone(), @@ -651,7 +658,9 @@ pub async fn construct_submit_evidence_router_data<'a>( address: PaymentAddress::default(), auth_type: payment_attempt.authentication_type.unwrap_or_default(), connector_meta_data: merchant_connector_account.get_metadata(), - amount_captured: payment_intent.amount_captured, + amount_captured: payment_intent + .amount_captured + .map(|amt| amt.get_amount_as_i64()), request: submit_evidence_request_data, response: Err(ErrorResponse::default()), access_token: None, @@ -743,7 +752,9 @@ pub async fn construct_upload_file_router_data<'a>( address: PaymentAddress::default(), auth_type: payment_attempt.authentication_type.unwrap_or_default(), connector_meta_data: merchant_connector_account.get_metadata(), - amount_captured: payment_intent.amount_captured, + amount_captured: payment_intent + .amount_captured + .map(|amt| amt.get_amount_as_i64()), payment_method_status: None, request: types::UploadFileRequestData { file_key, @@ -839,7 +850,9 @@ pub async fn construct_defend_dispute_router_data<'a>( address: PaymentAddress::default(), auth_type: payment_attempt.authentication_type.unwrap_or_default(), connector_meta_data: merchant_connector_account.get_metadata(), - amount_captured: payment_intent.amount_captured, + amount_captured: payment_intent + .amount_captured + .map(|amt| amt.get_amount_as_i64()), payment_method_status: None, request: types::DefendDisputeRequestData { dispute_id: dispute.dispute_id.clone(), diff --git a/crates/router/src/core/verification.rs b/crates/router/src/core/verification.rs index fe3d752497a7..802a4ec3c638 100644 --- a/crates/router/src/core/verification.rs +++ b/crates/router/src/core/verification.rs @@ -4,7 +4,7 @@ use common_utils::{errors::CustomResult, request::RequestContent}; use error_stack::ResultExt; use masking::ExposeInterface; -use crate::{core::errors::api_error_response, headers, logger, routes::AppState, services}; +use crate::{core::errors, headers, logger, routes::AppState, services}; const APPLEPAY_INTERNAL_MERCHANT_NAME: &str = "Applepay_merchant"; @@ -12,10 +12,8 @@ pub async fn verify_merchant_creds_for_applepay( state: AppState, body: verifications::ApplepayMerchantVerificationRequest, merchant_id: String, -) -> CustomResult< - services::ApplicationResponse, - api_error_response::ApiErrorResponse, -> { +) -> CustomResult, errors::ApiErrorResponse> +{ let applepay_merchant_configs = state.conf.applepay_merchant_configs.get_inner(); let applepay_internal_merchant_identifier = applepay_merchant_configs @@ -55,7 +53,7 @@ pub async fn verify_merchant_creds_for_applepay( utils::log_applepay_verification_response_if_error(&response); let applepay_response = - response.change_context(api_error_response::ApiErrorResponse::InternalServerError)?; + response.change_context(errors::ApiErrorResponse::InternalServerError)?; // Error is already logged match applepay_response { @@ -67,7 +65,7 @@ pub async fn verify_merchant_creds_for_applepay( body.domain_names.clone(), ) .await - .change_context(api_error_response::ApiErrorResponse::InternalServerError)?; + .change_context(errors::ApiErrorResponse::InternalServerError)?; Ok(services::api::ApplicationResponse::Json( ApplepayMerchantResponse { status_message: "Applepay verification Completed".to_string(), @@ -76,7 +74,7 @@ pub async fn verify_merchant_creds_for_applepay( } Err(error) => { logger::error!(?error); - Err(api_error_response::ApiErrorResponse::InvalidRequestData { + Err(errors::ApiErrorResponse::InvalidRequestData { message: "Applepay verification Failed".to_string(), } .into()) @@ -90,13 +88,13 @@ pub async fn get_verified_apple_domains_with_mid_mca_id( merchant_connector_id: String, ) -> CustomResult< services::ApplicationResponse, - api_error_response::ApiErrorResponse, + errors::ApiErrorResponse, > { let db = state.store.as_ref(); let key_store = db .get_merchant_key_store_by_merchant_id(&merchant_id, &db.get_master_key().to_vec().into()) .await - .change_context(api_error_response::ApiErrorResponse::MerchantAccountNotFound)?; + .change_context(errors::ApiErrorResponse::MerchantAccountNotFound)?; let verified_domains = db .find_by_merchant_connector_account_merchant_id_merchant_connector_id( @@ -105,7 +103,7 @@ pub async fn get_verified_apple_domains_with_mid_mca_id( &key_store, ) .await - .change_context(api_error_response::ApiErrorResponse::ResourceIdNotFound)? + .change_context(errors::ApiErrorResponse::ResourceIdNotFound)? .applepay_verified_domains .unwrap_or_default(); diff --git a/crates/router/src/core/verify_connector.rs b/crates/router/src/core/verify_connector.rs index 17cedd1e2d02..8a8b0a14f7db 100644 --- a/crates/router/src/core/verify_connector.rs +++ b/crates/router/src/core/verify_connector.rs @@ -6,8 +6,11 @@ use crate::{ core::errors, services, types::{ - api, - api::verify_connector::{self as types, VerifyConnector}, + api::{ + self, + verify_connector::{self as types, VerifyConnector}, + }, + transformers::ForeignInto, }, utils::verify_connector as utils, AppState, @@ -38,7 +41,7 @@ pub async fn verify_connector_credentials( &state, types::VerifyConnectorData { connector: *boxed_connector.connector, - connector_auth: req.connector_account_details.into(), + connector_auth: req.connector_account_details.foreign_into(), card_details, }, ) @@ -48,7 +51,7 @@ pub async fn verify_connector_credentials( &state, types::VerifyConnectorData { connector: *boxed_connector.connector, - connector_auth: req.connector_account_details.into(), + connector_auth: req.connector_account_details.foreign_into(), card_details, }, ) diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index 3312f21296f3..f78f48d7db6d 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -807,6 +807,118 @@ pub async fn mandates_incoming_webhook_flow( } } +#[allow(clippy::too_many_arguments)] +#[instrument(skip_all)] +pub(crate) async fn frm_incoming_webhook_flow( + state: AppState, + req_state: ReqState, + merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, + source_verified: bool, + event_type: webhooks::IncomingWebhookEvent, + object_ref_id: api::ObjectReferenceId, + business_profile: diesel_models::business_profile::BusinessProfile, +) -> CustomResult { + if source_verified { + let payment_attempt = + get_payment_attempt_from_object_reference_id(&state, object_ref_id, &merchant_account) + .await?; + let payment_response = match event_type { + webhooks::IncomingWebhookEvent::FrmApproved => { + Box::pin(payments::payments_core::< + api::Capture, + api::PaymentsResponse, + _, + _, + _, + >( + state.clone(), + req_state, + merchant_account.clone(), + key_store.clone(), + payments::PaymentApprove, + api::PaymentsCaptureRequest { + payment_id: payment_attempt.payment_id, + amount_to_capture: payment_attempt.amount_to_capture, + ..Default::default() + }, + services::api::AuthFlow::Merchant, + payments::CallConnectorAction::Trigger, + None, + HeaderPayload::default(), + )) + .await? + } + webhooks::IncomingWebhookEvent::FrmRejected => { + Box::pin(payments::payments_core::< + api::Void, + api::PaymentsResponse, + _, + _, + _, + >( + state.clone(), + req_state, + merchant_account.clone(), + key_store.clone(), + payments::PaymentReject, + api::PaymentsCancelRequest { + payment_id: payment_attempt.payment_id.clone(), + cancellation_reason: Some( + "Rejected by merchant based on FRM decision".to_string(), + ), + ..Default::default() + }, + services::api::AuthFlow::Merchant, + payments::CallConnectorAction::Trigger, + None, + HeaderPayload::default(), + )) + .await? + } + _ => Err(errors::ApiErrorResponse::EventNotFound)?, + }; + match payment_response { + services::ApplicationResponse::JsonWithHeaders((payments_response, _)) => { + let payment_id = payments_response + .payment_id + .clone() + .get_required_value("payment_id") + .change_context(errors::ApiErrorResponse::WebhookProcessingFailure) + .attach_printable("payment id not received from payments core")?; + let status = payments_response.status; + let event_type: Option = payments_response.status.foreign_into(); + if let Some(outgoing_event_type) = event_type { + let primary_object_created_at = payments_response.created; + create_event_and_trigger_outgoing_webhook( + state, + merchant_account, + business_profile, + &key_store, + outgoing_event_type, + enums::EventClass::Payments, + payment_id.clone(), + enums::EventObjectType::PaymentDetails, + api::OutgoingWebhookContent::PaymentDetails(payments_response), + primary_object_created_at, + ) + .await?; + }; + let response = WebhookResponseTracker::Payment { payment_id, status }; + Ok(response) + } + _ => Err(errors::ApiErrorResponse::WebhookProcessingFailure).attach_printable( + "Did not get payment id as object reference id in webhook payments flow", + )?, + } + } else { + logger::error!("Webhook source verification failed for frm webhooks flow"); + Err(report!( + errors::ApiErrorResponse::WebhookAuthenticationFailed + )) + } +} + #[allow(clippy::too_many_arguments)] #[instrument(skip_all)] pub async fn disputes_incoming_webhook_flow( @@ -1958,6 +2070,18 @@ pub async fn webhooks_core( .await .attach_printable("Incoming webhook flow for external authentication failed")? } + api::WebhookFlow::FraudCheck => Box::pin(frm_incoming_webhook_flow( + state.clone(), + req_state, + merchant_account, + key_store, + source_verified, + event_type, + object_ref_id, + business_profile, + )) + .await + .attach_printable("Incoming webhook flow for fraud check failed")?, #[cfg(feature = "payouts")] api::WebhookFlow::Payout => Box::pin(payouts_incoming_webhook_flow( @@ -2044,6 +2168,19 @@ fn get_connector_by_connector_name( ) -> CustomResult<(&'static (dyn api::Connector + Sync), String), errors::ApiErrorResponse> { let authentication_connector = api_models::enums::convert_authentication_connector(connector_name); + #[cfg(feature = "frm")] + { + let frm_connector = api_models::enums::convert_frm_connector(connector_name); + if frm_connector.is_some() { + let frm_connector_data = + api::FraudCheckConnectorData::get_connector_by_name(connector_name)?; + return Ok(( + *frm_connector_data.connector, + frm_connector_data.connector_name.to_string(), + )); + } + } + let (connector, connector_name) = if authentication_connector.is_some() { let authentication_connector_data = api::AuthenticationConnectorData::get_connector_by_name(connector_name)?; diff --git a/crates/router/src/db/address.rs b/crates/router/src/db/address.rs index c93e33b14718..5a6b2606d0c1 100644 --- a/crates/router/src/db/address.rs +++ b/crates/router/src/db/address.rs @@ -275,7 +275,9 @@ mod storage { use error_stack::{report, ResultExt}; use redis_interface::HsetnxReply; use router_env::{instrument, tracing}; - use storage_impl::redis::kv_store::{kv_wrapper, KvOperation, PartitionKey}; + use storage_impl::redis::kv_store::{ + decide_storage_scheme, kv_wrapper, KvOperation, Op, PartitionKey, + }; use super::AddressInterface; use crate::{ @@ -332,6 +334,9 @@ 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 address = match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, MerchantStorageScheme::RedisKv => { @@ -394,6 +399,18 @@ mod storage { let address = Conversion::convert(this) .await .change_context(errors::StorageError::EncryptionError)?; + let merchant_id = address.merchant_id.clone(); + let key = PartitionKey::MerchantIdPaymentId { + merchant_id: &merchant_id, + payment_id: &payment_id, + }; + let field = format!("add_{}", address.address_id); + let storage_scheme = 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 => { address @@ -409,12 +426,6 @@ mod storage { .await } MerchantStorageScheme::RedisKv => { - let merchant_id = address.merchant_id.clone(); - let key = PartitionKey::MerchantIdPaymentId { - merchant_id: &merchant_id, - payment_id: &payment_id, - }; - let field = format!("add_{}", address.address_id); let updated_address = AddressUpdateInternal::from(address_update.clone()) .create_address(address.clone()); let redis_value = serde_json::to_string(&updated_address) @@ -466,6 +477,12 @@ 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>( + self, + storage_scheme, + Op::Insert, + ) + .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { let conn = connection::pg_connection_write(self).await?; diff --git a/crates/router/src/db/authentication.rs b/crates/router/src/db/authentication.rs index 398af72f8bd8..4eff3dfdbfde 100644 --- a/crates/router/src/db/authentication.rs +++ b/crates/router/src/db/authentication.rs @@ -147,6 +147,7 @@ impl AuthenticationInterface for MockDb { profile_id: authentication.profile_id, payment_id: authentication.payment_id, merchant_connector_id: authentication.merchant_connector_id, + directory_server_id: authentication.directory_server_id, }; authentications.push(authentication.clone()); Ok(authentication) diff --git a/crates/router/src/db/cache.rs b/crates/router/src/db/cache.rs index d9db13dde80a..ba0aae55e894 100644 --- a/crates/router/src/db/cache.rs +++ b/crates/router/src/db/cache.rs @@ -88,7 +88,7 @@ where Fut: futures::Future> + Send, { let data = fun().await?; - in_memory.async_map(|cache| cache.invalidate(key)).await; + in_memory.async_map(|cache| cache.remove(key)).await; let redis_conn = store .get_redis_conn() diff --git a/crates/router/src/db/customers.rs b/crates/router/src/db/customers.rs index 00fb7e4a08f7..5f8dbe69f803 100644 --- a/crates/router/src/db/customers.rs +++ b/crates/router/src/db/customers.rs @@ -75,7 +75,9 @@ mod storage { use futures::future::try_join_all; use masking::PeekInterface; use router_env::{instrument, tracing}; - use storage_impl::redis::kv_store::{kv_wrapper, KvOperation, PartitionKey}; + use storage_impl::redis::kv_store::{ + decide_storage_scheme, kv_wrapper, KvOperation, Op, PartitionKey, + }; use super::CustomerInterface; use crate::{ @@ -116,7 +118,9 @@ 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 maybe_customer = match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, MerchantStorageScheme::RedisKv => { @@ -184,15 +188,20 @@ mod storage { .await .map_err(|error| report!(errors::StorageError::from(error))) }; - + let key = PartitionKey::MerchantIdCustomerId { + merchant_id: merchant_id.as_str(), + customer_id: customer_id.as_str(), + }; + let field = format!("cust_{}", customer_id); + let storage_scheme = 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, MerchantStorageScheme::RedisKv => { - let key = PartitionKey::MerchantIdCustomerId { - merchant_id: merchant_id.as_str(), - customer_id: customer_id.as_str(), - }; - let field = format!("cust_{}", customer_id); let updated_customer = diesel_models::CustomerUpdateInternal::from(customer_update.clone()) .apply_changeset(customer.clone()); @@ -250,7 +259,9 @@ 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 customer = match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, MerchantStorageScheme::RedisKv => { @@ -324,11 +335,17 @@ mod storage { ) -> CustomResult { let customer_id = customer_data.customer_id.clone(); let merchant_id = customer_data.merchant_id.clone(); - let new_customer = customer_data + let mut new_customer = customer_data .construct_new() .await .change_context(errors::StorageError::EncryptionError)?; - + let storage_scheme = 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 { MerchantStorageScheme::PostgresOnly => { let conn = connection::pg_connection_write(self).await?; diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index a3c39d3c086e..28ebcedcd8d4 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -76,7 +76,7 @@ use crate::{ }, }; -#[derive(Clone, Serialize)] +#[derive(Debug, Clone, Serialize)] pub struct TenantID(pub String); #[derive(Clone)] @@ -413,7 +413,11 @@ impl DisputeInterface for KafkaStore { ) -> CustomResult { let dispute = self.diesel_store.insert_dispute(dispute_new).await?; - if let Err(er) = self.kafka_producer.log_dispute(&dispute, None).await { + if let Err(er) = self + .kafka_producer + .log_dispute(&dispute, None, self.tenant_id.clone()) + .await + { logger::error!(message="Failed to add analytics entry for Dispute {dispute:?}", error_message=?er); }; @@ -466,7 +470,7 @@ impl DisputeInterface for KafkaStore { .await?; if let Err(er) = self .kafka_producer - .log_dispute(&dispute_new, Some(this)) + .log_dispute(&dispute_new, Some(this), self.tenant_id.clone()) .await { logger::error!(message="Failed to add analytics entry for Dispute {dispute_new:?}", error_message=?er); @@ -824,6 +828,15 @@ impl MerchantAccountInterface for KafkaStore { .await } + async fn update_all_merchant_account( + &self, + merchant_account: storage::MerchantAccountUpdate, + ) -> CustomResult { + self.diesel_store + .update_all_merchant_account(merchant_account) + .await + } + async fn find_merchant_account_by_publishable_key( &self, publishable_key: &str, @@ -1109,7 +1122,7 @@ impl PaymentAttemptInterface for KafkaStore { if let Err(er) = self .kafka_producer - .log_payment_attempt(&attempt, None) + .log_payment_attempt(&attempt, None, self.tenant_id.clone()) .await { logger::error!(message="Failed to log analytics event for payment attempt {attempt:?}", error_message=?er) @@ -1131,7 +1144,7 @@ impl PaymentAttemptInterface for KafkaStore { if let Err(er) = self .kafka_producer - .log_payment_attempt(&attempt, Some(this)) + .log_payment_attempt(&attempt, Some(this), self.tenant_id.clone()) .await { logger::error!(message="Failed to log analytics event for payment attempt {attempt:?}", error_message=?er) @@ -1311,7 +1324,7 @@ impl PaymentIntentInterface for KafkaStore { if let Err(er) = self .kafka_producer - .log_payment_intent(&intent, Some(this)) + .log_payment_intent(&intent, Some(this), self.tenant_id.clone()) .await { logger::error!(message="Failed to add analytics entry for Payment Intent {intent:?}", error_message=?er); @@ -1331,7 +1344,11 @@ impl PaymentIntentInterface for KafkaStore { .insert_payment_intent(new, storage_scheme) .await?; - if let Err(er) = self.kafka_producer.log_payment_intent(&intent, None).await { + if let Err(er) = self + .kafka_producer + .log_payment_intent(&intent, None, self.tenant_id.clone()) + .await + { logger::error!(message="Failed to add analytics entry for Payment Intent {intent:?}", error_message=?er); }; @@ -1573,6 +1590,7 @@ impl PayoutAttemptInterface for KafkaStore { .log_payout( &KafkaPayout::from_storage(payouts, &updated_payout_attempt), Some(KafkaPayout::from_storage(payouts, this)), + self.tenant_id.clone(), ) .await { @@ -1597,6 +1615,7 @@ impl PayoutAttemptInterface for KafkaStore { .log_payout( &KafkaPayout::from_storage(payouts, &payout_attempt_new), None, + self.tenant_id.clone(), ) .await { @@ -1654,6 +1673,7 @@ impl PayoutsInterface for KafkaStore { .log_payout( &KafkaPayout::from_storage(&payout, payout_attempt), Some(KafkaPayout::from_storage(this, payout_attempt)), + self.tenant_id.clone(), ) .await { @@ -1918,7 +1938,11 @@ impl RefundInterface for KafkaStore { .update_refund(this.clone(), refund, storage_scheme) .await?; - if let Err(er) = self.kafka_producer.log_refund(&refund, Some(this)).await { + if let Err(er) = self + .kafka_producer + .log_refund(&refund, Some(this), self.tenant_id.clone()) + .await + { logger::error!(message="Failed to insert analytics event for Refund Update {refund?}", error_message=?er); } Ok(refund) @@ -1946,7 +1970,11 @@ impl RefundInterface for KafkaStore { ) -> CustomResult { let refund = self.diesel_store.insert_refund(new, storage_scheme).await?; - if let Err(er) = self.kafka_producer.log_refund(&refund, None).await { + if let Err(er) = self + .kafka_producer + .log_refund(&refund, None, self.tenant_id.clone()) + .await + { logger::error!(message="Failed to insert analytics event for Refund Create {refund?}", error_message=?er); } Ok(refund) @@ -2519,7 +2547,7 @@ impl BatchSampleDataInterface for KafkaStore { for payment_intent in payment_intents_list.iter() { let _ = self .kafka_producer - .log_payment_intent(payment_intent, None) + .log_payment_intent(payment_intent, None, self.tenant_id.clone()) .await; } Ok(payment_intents_list) @@ -2540,7 +2568,7 @@ impl BatchSampleDataInterface for KafkaStore { for payment_attempt in payment_attempts_list.iter() { let _ = self .kafka_producer - .log_payment_attempt(payment_attempt, None) + .log_payment_attempt(payment_attempt, None, self.tenant_id.clone()) .await; } Ok(payment_attempts_list) @@ -2557,7 +2585,10 @@ impl BatchSampleDataInterface for KafkaStore { .await?; for refund in refunds_list.iter() { - let _ = self.kafka_producer.log_refund(refund, None).await; + let _ = self + .kafka_producer + .log_refund(refund, None, self.tenant_id.clone()) + .await; } Ok(refunds_list) } @@ -2577,7 +2608,7 @@ impl BatchSampleDataInterface for KafkaStore { for payment_intent in payment_intents_list.iter() { let _ = self .kafka_producer - .log_payment_intent_delete(payment_intent) + .log_payment_intent_delete(payment_intent, self.tenant_id.clone()) .await; } Ok(payment_intents_list) @@ -2598,7 +2629,7 @@ impl BatchSampleDataInterface for KafkaStore { for payment_attempt in payment_attempts_list.iter() { let _ = self .kafka_producer - .log_payment_attempt_delete(payment_attempt) + .log_payment_attempt_delete(payment_attempt, self.tenant_id.clone()) .await; } @@ -2616,7 +2647,10 @@ impl BatchSampleDataInterface for KafkaStore { .await?; for refund in refunds_list.iter() { - let _ = self.kafka_producer.log_refund_delete(refund).await; + let _ = self + .kafka_producer + .log_refund_delete(refund, self.tenant_id.clone()) + .await; } Ok(refunds_list) diff --git a/crates/router/src/db/mandate.rs b/crates/router/src/db/mandate.rs index 1751657e3f9c..c9995d502c0d 100644 --- a/crates/router/src/db/mandate.rs +++ b/crates/router/src/db/mandate.rs @@ -57,7 +57,9 @@ mod storage { use error_stack::{report, ResultExt}; use redis_interface::HsetnxReply; use router_env::{instrument, tracing}; - use storage_impl::redis::kv_store::{kv_wrapper, KvOperation, PartitionKey}; + use storage_impl::redis::kv_store::{ + decide_storage_scheme, kv_wrapper, KvOperation, Op, PartitionKey, + }; use super::MandateInterface; use crate::{ @@ -88,7 +90,9 @@ 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; match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, MerchantStorageScheme::RedisKv => { @@ -132,7 +136,9 @@ 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; match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, MerchantStorageScheme::RedisKv => { @@ -187,24 +193,29 @@ mod storage { storage_scheme: MerchantStorageScheme, ) -> CustomResult { let conn = connection::pg_connection_write(self).await?; - + let key = PartitionKey::MerchantIdMandateId { + merchant_id, + mandate_id, + }; + let field = format!("mandate_{}", mandate_id); + let storage_scheme = 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 => { storage_types::Mandate::update_by_merchant_id_mandate_id( &conn, merchant_id, mandate_id, - storage_types::MandateUpdateInternal::from(mandate_update), + mandate_update.convert_to_mandate_update(storage_scheme), ) .await .map_err(|error| report!(errors::StorageError::from(error))) } MerchantStorageScheme::RedisKv => { - let key = PartitionKey::MerchantIdMandateId { - merchant_id, - mandate_id, - }; - let field = format!("mandate_{}", mandate_id); let key_str = key.to_string(); if let diesel_models::MandateUpdate::ConnectorMandateIdUpdate { @@ -223,7 +234,7 @@ mod storage { .await?; } - let m_update = diesel_models::MandateUpdateInternal::from(mandate_update); + let m_update = mandate_update.convert_to_mandate_update(storage_scheme); let updated_mandate = m_update.clone().apply_changeset(mandate.clone()); let redis_value = serde_json::to_string(&updated_mandate) @@ -271,11 +282,17 @@ mod storage { #[instrument(skip_all)] async fn insert_mandate( &self, - mandate: storage_types::MandateNew, + mut mandate: storage_types::MandateNew, storage_scheme: MerchantStorageScheme, ) -> CustomResult { let conn = connection::pg_connection_write(self).await?; - + let storage_scheme = decide_storage_scheme::<_, diesel_models::Mandate>( + self, + storage_scheme, + Op::Insert, + ) + .await; + mandate.update_storage_scheme(storage_scheme); match storage_scheme { MerchantStorageScheme::PostgresOnly => mandate .insert(&conn) @@ -516,32 +533,9 @@ impl MandateInterface for MockDb { .find(|mandate| mandate.merchant_id == merchant_id && mandate.mandate_id == mandate_id) { Some(mandate) => { - match mandate_update { - storage_types::MandateUpdate::StatusUpdate { mandate_status } => { - mandate.mandate_status = mandate_status; - } - storage_types::MandateUpdate::CaptureAmountUpdate { amount_captured } => { - mandate.amount_captured = amount_captured; - } - storage_types::MandateUpdate::ConnectorReferenceUpdate { - connector_mandate_ids, - } => { - mandate.connector_mandate_ids = connector_mandate_ids; - } - - diesel_models::MandateUpdate::ConnectorMandateIdUpdate { - connector_mandate_id, - connector_mandate_ids, - payment_method_id, - original_payment_id, - } => { - mandate.connector_mandate_ids = connector_mandate_ids; - mandate.connector_mandate_id = connector_mandate_id; - mandate.payment_method_id = payment_method_id; - mandate.original_payment_id = original_payment_id - } - } - Ok(mandate.clone()) + let m_update = diesel_models::MandateUpdateInternal::from(mandate_update); + let updated_mandate = m_update.clone().apply_changeset(mandate.clone()); + Ok(updated_mandate) } None => { Err(errors::StorageError::ValueNotFound("mandate not found".to_string()).into()) @@ -634,6 +628,7 @@ impl MandateInterface for MockDb { metadata: mandate_new.metadata, connector_mandate_ids: mandate_new.connector_mandate_ids, merchant_connector_id: mandate_new.merchant_connector_id, + updated_by: mandate_new.updated_by, }; mandates.push(mandate.clone()); Ok(mandate) diff --git a/crates/router/src/db/merchant_account.rs b/crates/router/src/db/merchant_account.rs index 08d0c2790473..0cecb5e8d769 100644 --- a/crates/router/src/db/merchant_account.rs +++ b/crates/router/src/db/merchant_account.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use common_utils::ext_traits::AsyncExt; +use diesel_models::MerchantAccountUpdateInternal; use error_stack::{report, ResultExt}; use router_env::{instrument, tracing}; #[cfg(feature = "accounts_cache")] @@ -40,6 +41,11 @@ where merchant_key_store: &domain::MerchantKeyStore, ) -> CustomResult; + async fn update_all_merchant_account( + &self, + merchant_account: storage::MerchantAccountUpdate, + ) -> CustomResult; + async fn update_merchant( &self, this: domain::MerchantAccount, @@ -354,6 +360,38 @@ impl MerchantAccountInterface for Store { Ok(merchant_accounts) } + + async fn update_all_merchant_account( + &self, + merchant_account: storage::MerchantAccountUpdate, + ) -> CustomResult { + let conn = connection::pg_connection_read(self).await?; + + let db_func = || async { + storage::MerchantAccount::update_all_merchant_accounts( + &conn, + MerchantAccountUpdateInternal::from(merchant_account), + ) + .await + .map_err(|error| report!(errors::StorageError::from(error))) + }; + + let total; + #[cfg(not(feature = "accounts_cache"))] + { + let ma = db_func().await?; + total = ma.len(); + } + + #[cfg(feature = "accounts_cache")] + { + let ma = db_func().await?; + publish_and_redact_all_merchant_account_cache(self, &ma).await?; + total = ma.len(); + } + + Ok(total) + } } #[async_trait::async_trait] @@ -433,6 +471,13 @@ impl MerchantAccountInterface for MockDb { Err(errors::StorageError::MockDbError)? } + async fn update_all_merchant_account( + &self, + _merchant_account_update: storage::MerchantAccountUpdate, + ) -> CustomResult { + Err(errors::StorageError::MockDbError)? + } + async fn delete_merchant_account_by_merchant_id( &self, _merchant_id: &str, @@ -468,11 +513,48 @@ async fn publish_and_redact_merchant_account_cache( .as_ref() .map(|publishable_key| CacheKind::Accounts(publishable_key.into())); + #[cfg(feature = "business_profile_routing")] + let kgraph_key = merchant_account.default_profile.as_ref().map(|profile_id| { + CacheKind::CGraph( + format!( + "kgraph_{}_{}", + merchant_account.merchant_id.clone(), + profile_id, + ) + .into(), + ) + }); + + #[cfg(not(feature = "business_profile_routing"))] + let kgraph_key = Some(CacheKind::CGraph( + format!("kgraph_{}", merchant_account.merchant_id.clone()).into(), + )); + let mut cache_keys = vec![CacheKind::Accounts( merchant_account.merchant_id.as_str().into(), )]; cache_keys.extend(publishable_key.into_iter()); + cache_keys.extend(kgraph_key.into_iter()); + + super::cache::publish_into_redact_channel(store, cache_keys).await?; + Ok(()) +} + +#[cfg(feature = "accounts_cache")] +async fn publish_and_redact_all_merchant_account_cache( + store: &dyn super::StorageInterface, + merchant_accounts: &[storage::MerchantAccount], +) -> CustomResult<(), errors::StorageError> { + let merchant_ids = merchant_accounts.iter().map(|m| m.merchant_id.clone()); + let publishable_keys = merchant_accounts + .iter() + .filter_map(|m| m.publishable_key.clone()); + + let cache_keys: Vec> = merchant_ids + .chain(publishable_keys) + .map(|s| CacheKind::Accounts(s.into())) + .collect(); super::cache::publish_into_redact_channel(store, cache_keys).await?; Ok(()) diff --git a/crates/router/src/db/merchant_connector_account.rs b/crates/router/src/db/merchant_connector_account.rs index 8f50f5f54269..72199c20e086 100644 --- a/crates/router/src/db/merchant_connector_account.rs +++ b/crates/router/src/db/merchant_connector_account.rs @@ -427,6 +427,12 @@ impl MerchantConnectorAccountInterface for Store { cache::CacheKind::Accounts( format!("{}_{}", _merchant_id, _merchant_connector_id).into(), ), + cache::CacheKind::CGraph( + format!("cgraph_{}_{_profile_id}", _merchant_id).into(), + ), + cache::CacheKind::PmFiltersCGraph( + format!("pm_filters_cgraph_{}_{_profile_id}", _merchant_id).into(), + ), ], update_call, ) @@ -475,9 +481,19 @@ impl MerchantConnectorAccountInterface for Store { "profile_id".to_string(), ))?; - super::cache::publish_and_redact( + super::cache::publish_and_redact_multiple( self, - cache::CacheKind::Accounts(format!("{}_{}", mca.merchant_id, _profile_id).into()), + [ + cache::CacheKind::Accounts( + format!("{}_{}", mca.merchant_id, _profile_id).into(), + ), + cache::CacheKind::CGraph( + format!("cgraph_{}_{_profile_id}", mca.merchant_id).into(), + ), + cache::CacheKind::PmFiltersCGraph( + format!("pm_filters_cgraph_{}_{_profile_id}", mca.merchant_id).into(), + ), + ], delete_call, ) .await diff --git a/crates/router/src/db/payment_method.rs b/crates/router/src/db/payment_method.rs index 7c987c38d5b3..29b3379a2919 100644 --- a/crates/router/src/db/payment_method.rs +++ b/crates/router/src/db/payment_method.rs @@ -71,7 +71,9 @@ mod storage { use error_stack::{report, ResultExt}; use redis_interface::HsetnxReply; use router_env::{instrument, tracing}; - use storage_impl::redis::kv_store::{kv_wrapper, KvOperation, PartitionKey}; + use storage_impl::redis::kv_store::{ + decide_storage_scheme, kv_wrapper, KvOperation, Op, PartitionKey, + }; use super::PaymentMethodInterface; use crate::{ @@ -97,7 +99,12 @@ 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; match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, MerchantStorageScheme::RedisKv => { @@ -141,7 +148,12 @@ 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; match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, MerchantStorageScheme::RedisKv => { @@ -194,9 +206,16 @@ mod storage { #[instrument(skip_all)] async fn insert_payment_method( &self, - payment_method_new: storage_types::PaymentMethodNew, + mut payment_method_new: storage_types::PaymentMethodNew, storage_scheme: MerchantStorageScheme, ) -> CustomResult { + let storage_scheme = decide_storage_scheme::<_, storage_types::PaymentMethod>( + self, + storage_scheme, + Op::Insert, + ) + .await; + payment_method_new.update_storage_scheme(storage_scheme); match storage_scheme { MerchantStorageScheme::PostgresOnly => { let conn = connection::pg_connection_write(self).await?; @@ -278,25 +297,35 @@ mod storage { payment_method_update: storage_types::PaymentMethodUpdate, storage_scheme: MerchantStorageScheme, ) -> CustomResult { + let merchant_id = payment_method.merchant_id.clone(); + let customer_id = payment_method.customer_id.clone(); + let key = PartitionKey::MerchantIdCustomerId { + merchant_id: &merchant_id, + customer_id: &customer_id, + }; + let field = format!("payment_method_id_{}", payment_method.payment_method_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; match storage_scheme { MerchantStorageScheme::PostgresOnly => { let conn = connection::pg_connection_write(self).await?; payment_method - .update_with_payment_method_id(&conn, payment_method_update.into()) + .update_with_payment_method_id( + &conn, + payment_method_update.convert_to_payment_method_update(storage_scheme), + ) .await .map_err(|error| report!(errors::StorageError::from(error))) } MerchantStorageScheme::RedisKv => { - let merchant_id = payment_method.merchant_id.clone(); - let customer_id = payment_method.customer_id.clone(); - let key = PartitionKey::MerchantIdCustomerId { - merchant_id: &merchant_id, - customer_id: &customer_id, - }; let key_str = key.to_string(); - let field = format!("payment_method_id_{}", payment_method.payment_method_id); - let p_update: PaymentMethodUpdateInternal = payment_method_update.into(); + let p_update: PaymentMethodUpdateInternal = + payment_method_update.convert_to_payment_method_update(storage_scheme); let updated_payment_method = p_update.clone().apply_changeset(payment_method.clone()); @@ -662,6 +691,7 @@ impl PaymentMethodInterface for MockDb { status: payment_method_new.status, client_secret: payment_method_new.client_secret, network_transaction_id: payment_method_new.network_transaction_id, + updated_by: payment_method_new.updated_by, payment_method_billing_address: payment_method_new.payment_method_billing_address, }; payment_methods.push(payment_method.clone()); diff --git a/crates/router/src/db/refund.rs b/crates/router/src/db/refund.rs index df1130b7af67..1b1bfed38714 100644 --- a/crates/router/src/db/refund.rs +++ b/crates/router/src/db/refund.rs @@ -275,7 +275,9 @@ mod storage { use error_stack::{report, ResultExt}; use redis_interface::HsetnxReply; use router_env::{instrument, tracing}; - use storage_impl::redis::kv_store::{kv_wrapper, KvOperation, PartitionKey}; + use storage_impl::redis::kv_store::{ + decide_storage_scheme, kv_wrapper, KvOperation, Op, PartitionKey, + }; use super::RefundInterface; use crate::{ @@ -305,6 +307,9 @@ 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; match storage_scheme { enums::MerchantStorageScheme::PostgresOnly => database_call().await, enums::MerchantStorageScheme::RedisKv => { @@ -341,6 +346,9 @@ 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; match storage_scheme { enums::MerchantStorageScheme::PostgresOnly => { let conn = connection::pg_connection_write(self).await?; @@ -386,6 +394,7 @@ mod storage { profile_id: new.profile_id.clone(), updated_by: new.updated_by.clone(), merchant_connector_id: new.merchant_connector_id.clone(), + charges: new.charges.clone(), }; let field = format!( @@ -485,6 +494,9 @@ 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; match storage_scheme { enums::MerchantStorageScheme::PostgresOnly => database_call().await, enums::MerchantStorageScheme::RedisKv => { @@ -526,6 +538,19 @@ mod storage { refund: storage_types::RefundUpdate, storage_scheme: enums::MerchantStorageScheme, ) -> CustomResult { + let merchant_id = this.merchant_id.clone(); + let payment_id = this.payment_id.clone(); + let key = PartitionKey::MerchantIdPaymentId { + merchant_id: &merchant_id, + payment_id: &payment_id, + }; + let field = format!("pa_{}_ref_{}", &this.attempt_id, &this.refund_id); + let storage_scheme = 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 => { let conn = connection::pg_connection_write(self).await?; @@ -534,14 +559,7 @@ mod storage { .map_err(|error| report!(errors::StorageError::from(error))) } enums::MerchantStorageScheme::RedisKv => { - let merchant_id = this.merchant_id.clone(); - let payment_id = this.payment_id.clone(); - let key = PartitionKey::MerchantIdPaymentId { - merchant_id: &merchant_id, - payment_id: &payment_id, - }; let key_str = key.to_string(); - let field = format!("pa_{}_ref_{}", &this.attempt_id, &this.refund_id); let updated_refund = refund.clone().apply_changeset(this.clone()); let redis_value = updated_refund @@ -588,6 +606,9 @@ 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; match storage_scheme { enums::MerchantStorageScheme::PostgresOnly => database_call().await, enums::MerchantStorageScheme::RedisKv => { @@ -637,6 +658,9 @@ 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; match storage_scheme { enums::MerchantStorageScheme::PostgresOnly => database_call().await, enums::MerchantStorageScheme::RedisKv => { @@ -685,6 +709,9 @@ 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; match storage_scheme { enums::MerchantStorageScheme::PostgresOnly => database_call().await, enums::MerchantStorageScheme::RedisKv => { @@ -822,6 +849,7 @@ impl RefundInterface for MockDb { profile_id: new.profile_id, updated_by: new.updated_by, merchant_connector_id: new.merchant_connector_id, + charges: new.charges, }; refunds.push(refund.clone()); Ok(refund) diff --git a/crates/router/src/db/reverse_lookup.rs b/crates/router/src/db/reverse_lookup.rs index 121ec06ec1f9..fcf38ca420d0 100644 --- a/crates/router/src/db/reverse_lookup.rs +++ b/crates/router/src/db/reverse_lookup.rs @@ -69,7 +69,9 @@ mod storage { use error_stack::{report, ResultExt}; use redis_interface::SetnxReply; use router_env::{instrument, tracing}; - use storage_impl::redis::kv_store::{kv_wrapper, KvOperation, PartitionKey}; + use storage_impl::redis::kv_store::{ + decide_storage_scheme, kv_wrapper, KvOperation, Op, PartitionKey, + }; use super::{ReverseLookupInterface, Store}; use crate::{ @@ -91,6 +93,8 @@ mod storage { new: ReverseLookupNew, storage_scheme: enums::MerchantStorageScheme, ) -> CustomResult { + let storage_scheme = + decide_storage_scheme::<_, ReverseLookup>(self, storage_scheme, Op::Insert).await; match storage_scheme { enums::MerchantStorageScheme::PostgresOnly => { let conn = connection::pg_connection_write(self).await?; @@ -150,7 +154,8 @@ mod storage { .await .map_err(|error| report!(errors::StorageError::from(error))) }; - + let storage_scheme = + 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 d11e5cf997ac..2e9ecc13dcb1 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -72,6 +72,10 @@ pub mod headers { pub const X_REQUEST_ID: &str = "X-Request-Id"; pub const STRIPE_COMPATIBLE_WEBHOOK_SIGNATURE: &str = "Stripe-Signature"; pub const STRIPE_COMPATIBLE_CONNECT_ACCOUNT: &str = "Stripe-Account"; + pub const X_CLIENT_VERSION: &str = "X-Client-Version"; + pub const X_CLIENT_SOURCE: &str = "X-Client-Source"; + pub const X_PAYMENT_CONFIRM_SOURCE: &str = "X-Payment-Confirm-Source"; + pub const CONTENT_LENGTH: &str = "Content-Length"; } pub mod pii { diff --git a/crates/router/src/middleware.rs b/crates/router/src/middleware.rs index c7ceacc716d0..e0a4f80908dc 100644 --- a/crates/router/src/middleware.rs +++ b/crates/router/src/middleware.rs @@ -3,6 +3,8 @@ use router_env::{ logger, tracing::{field::Empty, Instrument}, }; + +use crate::headers; /// Middleware to include request ID in response header. pub struct RequestId; @@ -253,15 +255,35 @@ where .into_iter() .collect::, actix_web::error::PayloadError>>()?; let bytes = payload.clone().concat().to_vec(); + let bytes_length = bytes.len(); // we are creating h1 payload manually from bytes, currently there's no way to create http2 payload with actix let (_, mut new_payload) = actix_http::h1::Payload::create(true); new_payload.unread_data(bytes.to_vec().clone().into()); let new_req = actix_web::dev::ServiceRequest::from_parts(http_req, new_payload.into()); + + let content_length_header = new_req + .headers() + .get(headers::CONTENT_LENGTH) + .map(ToOwned::to_owned); let response_fut = svc.call(new_req); let response = response_fut.await?; // Log the request_details when we receive 400 status from the application if response.status() == 400 { let request_id = request_id_fut.await?.as_hyphenated().to_string(); + let content_length_header_string = content_length_header + .map(|content_length_header| { + content_length_header.to_str().map(ToOwned::to_owned) + }) + .transpose() + .map_err(|error| { + logger::warn!("Could not convert content length to string {error:?}"); + error + }) + .ok() + .flatten(); + + logger::info!("Content length from header: {content_length_header_string:?}, Bytes length: {bytes_length}"); + if !bytes.is_empty() { let value_result: Result = serde_json::from_slice(&bytes); diff --git a/crates/router/src/routes/admin.rs b/crates/router/src/routes/admin.rs index d9cadf002c1b..2d1b77460a5f 100644 --- a/crates/router/src/routes/admin.rs +++ b/crates/router/src/routes/admin.rs @@ -458,6 +458,31 @@ pub async fn merchant_account_toggle_kv( ) .await } + +/// Merchant Account - Toggle KV +/// +/// Toggle KV mode for all Merchant Accounts +#[instrument(skip_all)] +pub async fn merchant_account_toggle_all_kv( + state: web::Data, + req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::ConfigKeyUpdate; + let payload = json_payload.into_inner(); + + api::server_wrap( + flow, + state, + &req, + payload, + |state, _, payload, _| toggle_kv_for_all_merchants(state, payload.kv_enabled), + &auth::AdminApiAuth, + api_locking::LockAction::NotApplicable, + ) + .await +} + #[instrument(skip_all, fields(flow = ?Flow::BusinessProfileCreate))] pub async fn business_profile_create( state: web::Data, diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 21cc994381eb..bad5ab5a9268 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -386,7 +386,11 @@ impl Payments { ) .service( web::resource("/{payment_id}/{merchant_id}/redirect/complete/{connector}") - .route(web::get().to(payments_complete_authorize)) + .route(web::get().to(payments_complete_authorize_redirect)) + .route(web::post().to(payments_complete_authorize_redirect)), + ) + .service( + web::resource("/{payment_id}/complete_authorize") .route(web::post().to(payments_complete_authorize)), ) .service( @@ -866,6 +870,7 @@ impl MerchantAccount { .route(web::post().to(merchant_account_toggle_kv)) .route(web::get().to(merchant_account_kv_status)), ) + .service(web::resource("/kv").route(web::post().to(merchant_account_toggle_all_kv))) .service( web::resource("/{id}") .route(web::get().to(retrieve_merchant_account)) @@ -1206,7 +1211,16 @@ impl User { .route(web::post().to(set_dashboard_metadata)), ) .service(web::resource("/totp/begin").route(web::get().to(totp_begin))) - .service(web::resource("/totp/verify").route(web::post().to(totp_verify))); + .service(web::resource("/totp/verify").route(web::post().to(totp_verify))) + .service( + web::resource("/2fa/terminate").route(web::get().to(terminate_two_factor_auth)), + ); + + route = route.service( + web::scope("/recovery_code") + .service(web::resource("/verify").route(web::post().to(verify_recovery_code))) + .service(web::resource("/generate").route(web::post().to(generate_recovery_codes))), + ); #[cfg(feature = "email")] { diff --git a/crates/router/src/routes/currency.rs b/crates/router/src/routes/currency.rs index e80bd53d8df9..15684e6ae88a 100644 --- a/crates/router/src/routes/currency.rs +++ b/crates/router/src/routes/currency.rs @@ -31,7 +31,7 @@ pub async fn convert_forex( params: web::Query, ) -> HttpResponse { let flow = Flow::RetrieveForexFlow; - let amount = ¶ms.amount; + let amount = params.amount; let to_currency = ¶ms.to_currency; let from_currency = ¶ms.from_currency; Box::pin(api::server_wrap( @@ -42,7 +42,7 @@ pub async fn convert_forex( |state, _, _, _| { currency::convert_forex( state, - *amount, + amount.get_amount_as_i64(), to_currency.to_string(), from_currency.to_string(), ) diff --git a/crates/router/src/routes/fraud_check.rs b/crates/router/src/routes/fraud_check.rs index f0b73015f3cb..70bd55b71056 100644 --- a/crates/router/src/routes/fraud_check.rs +++ b/crates/router/src/routes/fraud_check.rs @@ -34,9 +34,3 @@ impl ApiEventMetric for FraudCheckResponseData { Some(ApiEventsType::FraudCheck) } } - -impl ApiEventMetric for frm_core::types::FrmFulfillmentRequest { - fn get_api_event_type(&self) -> Option { - Some(ApiEventsType::FraudCheck) - } -} diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index 30b582079e32..75821bbd2ba2 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -121,7 +121,8 @@ impl From for ApiIdentifier { | Flow::PaymentsIncrementalAuthorization | Flow::PaymentsExternalAuthentication | Flow::PaymentsAuthorize - | Flow::GetExtendedCardInfo => Self::Payments, + | Flow::GetExtendedCardInfo + | Flow::PaymentsCompleteAuthorize => Self::Payments, Flow::PayoutsCreate | Flow::PayoutsRetrieve @@ -213,8 +214,10 @@ impl From for ApiIdentifier { | Flow::VerifyEmailRequest | Flow::UpdateUserAccountDetails | Flow::TotpBegin - | Flow::TotpVerify => Self::User, - + | Flow::TotpVerify + | Flow::RecoveryCodeVerify + | Flow::RecoveryCodesGenerate + | Flow::TerminateTwoFactorAuth => Self::User, Flow::ListRoles | Flow::GetRole | Flow::GetRoleFromToken diff --git a/crates/router/src/routes/metrics.rs b/crates/router/src/routes/metrics.rs index 780dd51bbde1..1123be1a8748 100644 --- a/crates/router/src/routes/metrics.rs +++ b/crates/router/src/routes/metrics.rs @@ -52,7 +52,6 @@ counter_metric!(MCA_CREATE, GLOBAL_METER); // Flow Specific Metrics -counter_metric!(ACCESS_TOKEN_CREATION, GLOBAL_METER); histogram_metric!(CONNECTOR_REQUEST_TIME, GLOBAL_METER); counter_metric!(SESSION_TOKEN_CREATED, GLOBAL_METER); @@ -123,5 +122,16 @@ counter_metric!(TASKS_ADDED_COUNT, GLOBAL_METER); // Tasks added to process trac counter_metric!(TASK_ADDITION_FAILURES_COUNT, GLOBAL_METER); // Failures in task addition to process tracker counter_metric!(TASKS_RESET_COUNT, GLOBAL_METER); // Tasks reset in process tracker for requeue flow +// Access token metrics +// +// A counter to indicate the number of new access tokens created +counter_metric!(ACCESS_TOKEN_CREATION, GLOBAL_METER); + +// A counter to indicate the access token cache hits +counter_metric!(ACCESS_TOKEN_CACHE_HIT, GLOBAL_METER); + +// A counter to indicate the access token cache miss +counter_metric!(ACCESS_TOKEN_CACHE_MISS, GLOBAL_METER); + pub mod request; pub mod utils; diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index 6a1b424a3300..f28cad89004f 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -7,6 +7,7 @@ pub mod helpers; use actix_web::{web, Responder}; use api_models::payments::HeaderPayload; use error_stack::report; +use masking::PeekInterface; use router_env::{env, instrument, tracing, types, Flow}; use super::app::ReqState; @@ -497,7 +498,7 @@ pub async fn payments_confirm( req_state, auth.merchant_account, auth.key_store, - header_payload, + header_payload.clone(), req, auth_flow, ) @@ -749,7 +750,7 @@ pub async fn payments_redirect_response_with_creds_identifier( .await } #[instrument(skip_all, fields(flow =? Flow::PaymentsRedirect, payment_id))] -pub async fn payments_complete_authorize( +pub async fn payments_complete_authorize_redirect( state: web::Data, req: actix_web::HttpRequest, json_payload: Option>, @@ -792,6 +793,70 @@ pub async fn payments_complete_authorize( ) .await } + +/// Payments - Complete Authorize +#[instrument(skip_all, fields(flow =? Flow::PaymentsCompleteAuthorize, payment_id))] +pub async fn payments_complete_authorize( + state: web::Data, + req: actix_web::HttpRequest, + json_payload: web::Json, + path: web::Path, +) -> impl Responder { + let flow = Flow::PaymentsCompleteAuthorize; + let mut payload = json_payload.into_inner(); + + let payment_id = path.into_inner(); + payload.payment_id.clone_from(&payment_id); + + tracing::Span::current().record("payment_id", &payment_id); + + let payment_confirm_req = payment_types::PaymentsRequest { + payment_id: Some(payment_types::PaymentIdType::PaymentIntentId( + payment_id.clone(), + )), + shipping: payload.shipping.clone(), + client_secret: Some(payload.client_secret.peek().clone()), + ..Default::default() + }; + + let (auth_type, auth_flow) = + match auth::check_client_secret_and_get_auth(req.headers(), &payment_confirm_req) { + Ok(auth) => auth, + Err(err) => return api::log_and_return_error_response(report!(err)), + }; + + let locking_action = payload.get_locking_input(flow.clone()); + Box::pin(api::server_wrap( + flow, + state, + &req, + payload, + |state, auth, _req, req_state| { + payments::payments_core::< + api_types::CompleteAuthorize, + payment_types::PaymentsResponse, + _, + _, + _, + >( + state.clone(), + req_state, + auth.merchant_account, + auth.key_store, + payments::operations::payment_complete_authorize::CompleteAuthorize, + payment_confirm_req.clone(), + auth_flow, + payments::CallConnectorAction::Trigger, + None, + HeaderPayload::default(), + ) + }, + &*auth_type, + locking_action, + )) + .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 @@ -1465,6 +1530,22 @@ impl GetLockingInput for payments::PaymentsRedirectResponseData { } } +impl GetLockingInput for payment_types::PaymentsCompleteAuthorizeRequest { + 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.to_owned(), + api_identifier: lock_utils::ApiIdentifier::from(flow), + override_lock_retries: None, + }, + } + } +} + impl GetLockingInput for payment_types::PaymentsCancelRequest { fn get_locking_input(&self, flow: F) -> api_locking::LockAction where diff --git a/crates/router/src/routes/payments/helpers.rs b/crates/router/src/routes/payments/helpers.rs index 256767f2aea7..c881ec7790c9 100644 --- a/crates/router/src/routes/payments/helpers.rs +++ b/crates/router/src/routes/payments/helpers.rs @@ -2,7 +2,7 @@ use error_stack::ResultExt; use crate::{ core::errors::{self, RouterResult}, - headers, logger, + logger, types::{self, api}, utils::{Encode, ValueExt}, }; @@ -32,26 +32,22 @@ pub fn populate_ip_into_browser_info( ip_address: None, }); - // Parse the IP Address from the "X-Forwarded-For" header - // This header will contain multiple IP addresses for each ALB hop which has - // a comma separated list of IP addresses: 'X.X.X.X, Y.Y.Y.Y, Z.Z.Z.Z' - // The first one here will be the client IP which we want to retrieve - let ip_address_from_header = req.headers() - .get(headers::X_FORWARDED_FOR) - .map(|val| val.to_str()) - .transpose() - .unwrap_or_else(|e| { - logger::error!(error=?e, message="failed to retrieve ip address from X-Forwarded-For header"); - None - }) - .and_then(|ips| ips.split(',').next()); + let ip_address = req + .connection_info() + .realip_remote_addr() + .map(ToOwned::to_owned); + + if ip_address.is_some() { + logger::debug!("Extracted ip address from request"); + } browser_info.ip_address = browser_info.ip_address.or_else(|| { - ip_address_from_header + ip_address + .as_ref() .map(|ip| ip.parse()) .transpose() .unwrap_or_else(|e| { - logger::error!(error=?e, message="failed to parse ip address from X-Forwarded-For"); + logger::error!(error=?e, message="failed to parse ip address which is extracted from the request"); None }) }); @@ -70,7 +66,7 @@ pub fn populate_ip_into_browser_info( { *req_ip = req_ip .clone() - .or_else(|| ip_address_from_header.map(|ip| masking::Secret::new(ip.to_string()))); + .or_else(|| ip_address.map(|ip| masking::Secret::new(ip.to_string()))); } let encoded = browser_info diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs index 6de38bd55a5c..6b8015ac3e58 100644 --- a/crates/router/src/routes/user.rs +++ b/crates/router/src/routes/user.rs @@ -507,7 +507,7 @@ pub async fn accept_invite_from_email( |state, user, req_payload, _| { user_core::accept_invite_from_email_token_only_flow(state, user, req_payload) }, - &auth::SinglePurposeJWTAuth(common_enums::TokenPurpose::AcceptInvitationFromEmail), + &auth::SinglePurposeJWTAuth(TokenPurpose::AcceptInvitationFromEmail), api_locking::LockAction::NotApplicable, )) .await @@ -545,7 +545,7 @@ pub async fn verify_email( |state, user, req_payload, _| { user_core::verify_email_token_only_flow(state, user, req_payload) }, - &auth::SinglePurposeJWTAuth(common_enums::TokenPurpose::VerifyEmail), + &auth::SinglePurposeJWTAuth(TokenPurpose::VerifyEmail), api_locking::LockAction::NotApplicable, )) .await @@ -642,7 +642,7 @@ pub async fn totp_begin(state: web::Data, req: HttpRequest) -> HttpRes &req, (), |state, user, _, _| user_core::begin_totp(state, user), - &auth::SinglePurposeJWTAuth(common_enums::TokenPurpose::TOTP), + &auth::SinglePurposeJWTAuth(TokenPurpose::TOTP), api_locking::LockAction::NotApplicable, )) .await @@ -660,7 +660,59 @@ pub async fn totp_verify( &req, json_payload.into_inner(), |state, user, req_body, _| user_core::verify_totp(state, user, req_body), - &auth::SinglePurposeJWTAuth(common_enums::TokenPurpose::TOTP), + &auth::SinglePurposeJWTAuth(TokenPurpose::TOTP), + api_locking::LockAction::NotApplicable, + )) + .await +} + +pub async fn verify_recovery_code( + state: web::Data, + req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::RecoveryCodeVerify; + Box::pin(api::server_wrap( + flow, + state.clone(), + &req, + json_payload.into_inner(), + |state, user, req_body, _| user_core::verify_recovery_code(state, user, req_body), + &auth::SinglePurposeJWTAuth(TokenPurpose::TOTP), + api_locking::LockAction::NotApplicable, + )) + .await +} + +pub async fn generate_recovery_codes(state: web::Data, req: HttpRequest) -> HttpResponse { + let flow = Flow::RecoveryCodesGenerate; + Box::pin(api::server_wrap( + flow, + state.clone(), + &req, + (), + |state, user, _, _| user_core::generate_recovery_codes(state, user), + &auth::SinglePurposeJWTAuth(TokenPurpose::TOTP), + api_locking::LockAction::NotApplicable, + )) + .await +} + +pub async fn terminate_two_factor_auth( + state: web::Data, + req: HttpRequest, + query: web::Query, +) -> HttpResponse { + let flow = Flow::TerminateTwoFactorAuth; + let skip_two_factor_auth = query.into_inner().skip_two_factor_auth.unwrap_or(false); + + Box::pin(api::server_wrap( + flow, + state.clone(), + &req, + (), + |state, user, _, _| user_core::terminate_two_factor_auth(state, user, skip_two_factor_auth), + &auth::SinglePurposeJWTAuth(TokenPurpose::TOTP), api_locking::LockAction::NotApplicable, )) .await diff --git a/crates/router/src/services.rs b/crates/router/src/services.rs index 5af325a46f76..6d9d657e1085 100644 --- a/crates/router/src/services.rs +++ b/crates/router/src/services.rs @@ -79,6 +79,7 @@ pub async fn get_store( config.drainer.stream_name.clone(), config.drainer.num_partitions, config.kv_config.ttl, + config.kv_config.soft_kill, ); Ok(store) diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index 71c74099ded4..287f98cdb792 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -1328,6 +1328,11 @@ impl EmbedError for Report { } } +impl EmbedError + for Report +{ +} + pub fn http_response_json(response: T) -> HttpResponse { HttpResponse::Ok() .content_type(mime::APPLICATION_JSON) diff --git a/crates/router/src/services/kafka.rs b/crates/router/src/services/kafka.rs index ba9806b29d19..6f2aa9fcd100 100644 --- a/crates/router/src/services/kafka.rs +++ b/crates/router/src/services/kafka.rs @@ -30,6 +30,7 @@ use crate::types::storage::Dispute; // Using message queue result here to avoid confusion with Kafka result provided by library pub type MQResult = CustomResult; +use crate::db::kafka_store::TenantID; pub trait KafkaMessage where @@ -54,19 +55,22 @@ struct KafkaEvent<'a, T: KafkaMessage> { #[serde(flatten)] event: &'a T, sign_flag: i32, + tenant_id: TenantID, } impl<'a, T: KafkaMessage> KafkaEvent<'a, T> { - fn new(event: &'a T) -> Self { + fn new(event: &'a T, tenant_id: TenantID) -> Self { Self { event, sign_flag: 1, + tenant_id, } } - fn old(event: &'a T) -> Self { + fn old(event: &'a T, tenant_id: TenantID) -> Self { Self { event, sign_flag: -1, + tenant_id, } } } @@ -255,7 +259,7 @@ impl KafkaProducer { .timestamp( event .creation_timestamp() - .unwrap_or_else(|| OffsetDateTime::now_utc().unix_timestamp()), + .unwrap_or_else(|| OffsetDateTime::now_utc().unix_timestamp() * 1_000), ), ) .map_err(|(error, record)| report!(error).attach_printable(format!("{record:?}"))) @@ -266,28 +270,33 @@ impl KafkaProducer { &self, attempt: &PaymentAttempt, old_attempt: Option, + tenant_id: TenantID, ) -> MQResult<()> { if let Some(negative_event) = old_attempt { - self.log_event(&KafkaEvent::old(&KafkaPaymentAttempt::from_storage( - &negative_event, - ))) + self.log_event(&KafkaEvent::old( + &KafkaPaymentAttempt::from_storage(&negative_event), + tenant_id.clone(), + )) .attach_printable_lazy(|| { format!("Failed to add negative attempt event {negative_event:?}") })?; }; - self.log_event(&KafkaEvent::new(&KafkaPaymentAttempt::from_storage( - attempt, - ))) + self.log_event(&KafkaEvent::new( + &KafkaPaymentAttempt::from_storage(attempt), + tenant_id.clone(), + )) .attach_printable_lazy(|| format!("Failed to add positive attempt event {attempt:?}")) } pub async fn log_payment_attempt_delete( &self, delete_old_attempt: &PaymentAttempt, + tenant_id: TenantID, ) -> MQResult<()> { - self.log_event(&KafkaEvent::old(&KafkaPaymentAttempt::from_storage( - delete_old_attempt, - ))) + self.log_event(&KafkaEvent::old( + &KafkaPaymentAttempt::from_storage(delete_old_attempt), + tenant_id.clone(), + )) .attach_printable_lazy(|| { format!("Failed to add negative attempt event {delete_old_attempt:?}") }) @@ -297,48 +306,69 @@ impl KafkaProducer { &self, intent: &PaymentIntent, old_intent: Option, + tenant_id: TenantID, ) -> MQResult<()> { if let Some(negative_event) = old_intent { - self.log_event(&KafkaEvent::old(&KafkaPaymentIntent::from_storage( - &negative_event, - ))) + self.log_event(&KafkaEvent::old( + &KafkaPaymentIntent::from_storage(&negative_event), + tenant_id.clone(), + )) .attach_printable_lazy(|| { format!("Failed to add negative intent event {negative_event:?}") })?; }; - self.log_event(&KafkaEvent::new(&KafkaPaymentIntent::from_storage(intent))) - .attach_printable_lazy(|| format!("Failed to add positive intent event {intent:?}")) + self.log_event(&KafkaEvent::new( + &KafkaPaymentIntent::from_storage(intent), + tenant_id.clone(), + )) + .attach_printable_lazy(|| format!("Failed to add positive intent event {intent:?}")) } pub async fn log_payment_intent_delete( &self, delete_old_intent: &PaymentIntent, + tenant_id: TenantID, ) -> MQResult<()> { - self.log_event(&KafkaEvent::old(&KafkaPaymentIntent::from_storage( - delete_old_intent, - ))) + self.log_event(&KafkaEvent::old( + &KafkaPaymentIntent::from_storage(delete_old_intent), + tenant_id.clone(), + )) .attach_printable_lazy(|| { format!("Failed to add negative intent event {delete_old_intent:?}") }) } - pub async fn log_refund(&self, refund: &Refund, old_refund: Option) -> MQResult<()> { + pub async fn log_refund( + &self, + refund: &Refund, + old_refund: Option, + tenant_id: TenantID, + ) -> MQResult<()> { if let Some(negative_event) = old_refund { - self.log_event(&KafkaEvent::old(&KafkaRefund::from_storage( - &negative_event, - ))) + self.log_event(&KafkaEvent::old( + &KafkaRefund::from_storage(&negative_event), + tenant_id.clone(), + )) .attach_printable_lazy(|| { format!("Failed to add negative refund event {negative_event:?}") })?; }; - self.log_event(&KafkaEvent::new(&KafkaRefund::from_storage(refund))) - .attach_printable_lazy(|| format!("Failed to add positive refund event {refund:?}")) + self.log_event(&KafkaEvent::new( + &KafkaRefund::from_storage(refund), + tenant_id.clone(), + )) + .attach_printable_lazy(|| format!("Failed to add positive refund event {refund:?}")) } - pub async fn log_refund_delete(&self, delete_old_refund: &Refund) -> MQResult<()> { - self.log_event(&KafkaEvent::old(&KafkaRefund::from_storage( - delete_old_refund, - ))) + pub async fn log_refund_delete( + &self, + delete_old_refund: &Refund, + tenant_id: TenantID, + ) -> MQResult<()> { + self.log_event(&KafkaEvent::old( + &KafkaRefund::from_storage(delete_old_refund), + tenant_id.clone(), + )) .attach_printable_lazy(|| { format!("Failed to add negative refund event {delete_old_refund:?}") }) @@ -348,17 +378,22 @@ impl KafkaProducer { &self, dispute: &Dispute, old_dispute: Option, + tenant_id: TenantID, ) -> MQResult<()> { if let Some(negative_event) = old_dispute { - self.log_event(&KafkaEvent::old(&KafkaDispute::from_storage( - &negative_event, - ))) + self.log_event(&KafkaEvent::old( + &KafkaDispute::from_storage(&negative_event), + tenant_id.clone(), + )) .attach_printable_lazy(|| { format!("Failed to add negative dispute event {negative_event:?}") })?; }; - self.log_event(&KafkaEvent::new(&KafkaDispute::from_storage(dispute))) - .attach_printable_lazy(|| format!("Failed to add positive dispute event {dispute:?}")) + self.log_event(&KafkaEvent::new( + &KafkaDispute::from_storage(dispute), + tenant_id.clone(), + )) + .attach_printable_lazy(|| format!("Failed to add positive dispute event {dispute:?}")) } #[cfg(feature = "payouts")] @@ -366,20 +401,25 @@ impl KafkaProducer { &self, payout: &KafkaPayout<'_>, old_payout: Option>, + tenant_id: TenantID, ) -> MQResult<()> { if let Some(negative_event) = old_payout { - self.log_event(&KafkaEvent::old(&negative_event)) + self.log_event(&KafkaEvent::old(&negative_event, tenant_id.clone())) .attach_printable_lazy(|| { format!("Failed to add negative payout event {negative_event:?}") })?; }; - self.log_event(&KafkaEvent::new(payout)) + self.log_event(&KafkaEvent::new(payout, tenant_id.clone())) .attach_printable_lazy(|| format!("Failed to add positive payout event {payout:?}")) } #[cfg(feature = "payouts")] - pub async fn log_payout_delete(&self, delete_old_payout: &KafkaPayout<'_>) -> MQResult<()> { - self.log_event(&KafkaEvent::old(delete_old_payout)) + pub async fn log_payout_delete( + &self, + delete_old_payout: &KafkaPayout<'_>, + tenant_id: TenantID, + ) -> MQResult<()> { + self.log_event(&KafkaEvent::old(delete_old_payout, tenant_id.clone())) .attach_printable_lazy(|| { format!("Failed to add negative payout event {delete_old_payout:?}") }) @@ -436,7 +476,7 @@ impl MessagingInterface for KafkaProducer { .key(&data.identifier()) .payload(&json_data) .timestamp( - (timestamp.assume_utc().unix_timestamp_nanos() / 1_000) + (timestamp.assume_utc().unix_timestamp_nanos() / 1_000_000) .to_i64() .unwrap_or_else(|| { // kafka producer accepts milliseconds diff --git a/crates/router/src/services/kafka/dispute.rs b/crates/router/src/services/kafka/dispute.rs index c01089855c65..1950fb253a79 100644 --- a/crates/router/src/services/kafka/dispute.rs +++ b/crates/router/src/services/kafka/dispute.rs @@ -70,10 +70,6 @@ impl<'a> super::KafkaMessage for KafkaDispute<'a> { ) } - fn creation_timestamp(&self) -> Option { - Some(self.modified_at.unix_timestamp()) - } - fn event_type(&self) -> crate::events::EventType { crate::events::EventType::Dispute } diff --git a/crates/router/src/services/kafka/payment_attempt.rs b/crates/router/src/services/kafka/payment_attempt.rs index 9f442876fab1..c321093776ca 100644 --- a/crates/router/src/services/kafka/payment_attempt.rs +++ b/crates/router/src/services/kafka/payment_attempt.rs @@ -1,4 +1,5 @@ // use diesel_models::enums::MandateDetails; +use common_utils::types::MinorUnit; use diesel_models::enums as storage_enums; use hyperswitch_domain_models::{ mandates::MandateDetails, payments::payment_attempt::PaymentAttempt, @@ -11,14 +12,14 @@ pub struct KafkaPaymentAttempt<'a> { pub merchant_id: &'a String, pub attempt_id: &'a String, pub status: storage_enums::AttemptStatus, - pub amount: i64, + pub amount: MinorUnit, pub currency: Option, pub save_to_locker: Option, pub connector: Option<&'a String>, pub error_message: Option<&'a String>, - pub offer_amount: Option, - pub surcharge_amount: Option, - pub tax_amount: Option, + pub offer_amount: Option, + pub surcharge_amount: Option, + pub tax_amount: Option, pub payment_method_id: Option<&'a String>, pub payment_method: Option, pub connector_transaction_id: Option<&'a String>, @@ -34,7 +35,7 @@ pub struct KafkaPaymentAttempt<'a> { #[serde(default, with = "time::serde::timestamp::option")] pub last_synced: Option, pub cancellation_reason: Option<&'a String>, - pub amount_to_capture: Option, + pub amount_to_capture: Option, pub mandate_id: Option<&'a String>, pub browser_info: Option, pub error_code: Option<&'a String>, @@ -45,12 +46,14 @@ pub struct KafkaPaymentAttempt<'a> { pub payment_method_data: Option, pub error_reason: Option<&'a String>, pub multiple_capture_count: Option, - pub amount_capturable: i64, + pub amount_capturable: MinorUnit, pub merchant_connector_id: Option<&'a String>, - pub net_amount: i64, + pub net_amount: MinorUnit, pub unified_code: Option<&'a String>, pub unified_message: Option<&'a String>, pub mandate_data: Option<&'a MandateDetails>, + pub client_source: Option<&'a String>, + pub client_version: Option<&'a String>, } impl<'a> KafkaPaymentAttempt<'a> { @@ -95,6 +98,8 @@ impl<'a> KafkaPaymentAttempt<'a> { unified_code: attempt.unified_code.as_ref(), unified_message: attempt.unified_message.as_ref(), mandate_data: attempt.mandate_data.as_ref(), + client_source: attempt.client_source.as_ref(), + client_version: attempt.client_version.as_ref(), } } } @@ -107,10 +112,6 @@ impl<'a> super::KafkaMessage for KafkaPaymentAttempt<'a> { ) } - fn creation_timestamp(&self) -> Option { - Some(self.modified_at.unix_timestamp()) - } - fn event_type(&self) -> crate::events::EventType { crate::events::EventType::PaymentAttempt } diff --git a/crates/router/src/services/kafka/payment_intent.rs b/crates/router/src/services/kafka/payment_intent.rs index 2edd0d49cccf..81ac6454c91f 100644 --- a/crates/router/src/services/kafka/payment_intent.rs +++ b/crates/router/src/services/kafka/payment_intent.rs @@ -1,3 +1,4 @@ +use common_utils::types::MinorUnit; use diesel_models::enums as storage_enums; use hyperswitch_domain_models::payments::PaymentIntent; use time::OffsetDateTime; @@ -7,9 +8,9 @@ pub struct KafkaPaymentIntent<'a> { pub payment_id: &'a String, pub merchant_id: &'a String, pub status: storage_enums::IntentStatus, - pub amount: i64, + pub amount: MinorUnit, pub currency: Option, - pub amount_captured: Option, + pub amount_captured: Option, pub customer_id: Option<&'a String>, pub description: Option<&'a String>, pub return_url: Option<&'a String>, @@ -29,6 +30,7 @@ pub struct KafkaPaymentIntent<'a> { pub business_country: Option, pub business_label: Option<&'a String>, pub attempt_count: i16, + pub payment_confirm_source: Option, } impl<'a> KafkaPaymentIntent<'a> { @@ -56,6 +58,7 @@ impl<'a> KafkaPaymentIntent<'a> { business_country: intent.business_country, business_label: intent.business_label.as_ref(), attempt_count: intent.attempt_count, + payment_confirm_source: intent.payment_confirm_source, } } } @@ -65,10 +68,6 @@ impl<'a> super::KafkaMessage for KafkaPaymentIntent<'a> { format!("{}_{}", self.merchant_id, self.payment_id) } - fn creation_timestamp(&self) -> Option { - Some(self.modified_at.unix_timestamp()) - } - fn event_type(&self) -> crate::events::EventType { crate::events::EventType::PaymentIntent } diff --git a/crates/router/src/services/kafka/payout.rs b/crates/router/src/services/kafka/payout.rs index 505e9eec7d8a..624d4d3782b5 100644 --- a/crates/router/src/services/kafka/payout.rs +++ b/crates/router/src/services/kafka/payout.rs @@ -80,10 +80,6 @@ impl<'a> super::KafkaMessage for KafkaPayout<'a> { format!("{}_{}", self.merchant_id, self.payout_attempt_id) } - fn creation_timestamp(&self) -> Option { - Some(self.last_modified_at.unix_timestamp()) - } - fn event_type(&self) -> crate::events::EventType { crate::events::EventType::Payout } diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index 9163c4815902..9dcb7f629c86 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -23,14 +23,28 @@ pub use api_models::{enums::Connector, mandates}; pub use api_models::{enums::PayoutConnectors, payouts as payout_types}; use common_enums::MandateStatus; pub use common_utils::request::RequestContent; -use common_utils::{pii, pii::Email}; -use error_stack::ResultExt; +use common_utils::{pii, pii::Email, types::MinorUnit}; use hyperswitch_domain_models::mandates::{CustomerAcceptance, MandateData}; +#[cfg(feature = "payouts")] +pub use hyperswitch_domain_models::router_request_types::PayoutsData; +pub use hyperswitch_domain_models::{ + payment_address::PaymentAddress, + router_data::{ + AccessToken, AdditionalPaymentMethodConnectorResponse, ApplePayCryptogramData, + ApplePayPredecryptData, ConnectorAuthType, ConnectorResponseData, ErrorResponse, + PaymentMethodBalance, PaymentMethodToken, RecurringMandatePaymentData, RouterData, + }, + router_request_types::{ + AcceptDisputeRequestData, AccessTokenRequestData, BrowserInformation, ChargeRefunds, + ChargeRefundsOptions, DefendDisputeRequestData, DestinationChargeRefund, + DirectChargeRefund, RefundsData, ResponseId, RetrieveFileRequestData, + SubmitEvidenceRequestData, UploadFileRequestData, VerifyWebhookSourceRequestData, + }, +}; use masking::Secret; -use serde::Serialize; use self::storage::enums as storage_enums; -pub use crate::core::payments::{payment_address::PaymentAddress, CustomerDetails}; +pub use crate::core::payments::CustomerDetails; #[cfg(feature = "payouts")] use crate::{ connector::utils::missing_field_err, @@ -40,10 +54,13 @@ use crate::{ consts, core::{ errors::{self}, - payments::{types, PaymentData, RecurringMandatePaymentData}, + payments::{types, PaymentData}, }, services, - types::{transformers::ForeignFrom, types::AuthenticationData}, + types::{ + transformers::{ForeignFrom, ForeignTryFrom}, + types::AuthenticationData, + }, }; pub type PaymentsAuthorizeRouterData = RouterData; @@ -267,139 +284,6 @@ pub type PayoutsRouterData = RouterData; pub type PayoutsResponseRouterData = ResponseRouterData; -#[derive(Debug, Clone)] -pub struct RouterData { - pub flow: PhantomData, - pub merchant_id: String, - pub customer_id: Option, - pub connector_customer: Option, - pub connector: String, - pub payment_id: String, - pub attempt_id: String, - pub status: storage_enums::AttemptStatus, - pub payment_method: storage_enums::PaymentMethod, - pub connector_auth_type: ConnectorAuthType, - pub description: Option, - pub return_url: Option, - pub address: PaymentAddress, - pub auth_type: storage_enums::AuthenticationType, - pub connector_meta_data: Option, - pub amount_captured: Option, - pub access_token: Option, - pub session_token: Option, - pub reference_id: Option, - pub payment_method_token: Option, - pub recurring_mandate_payment_data: Option, - pub preprocessing_id: Option, - /// This is the balance amount for gift cards or voucher - pub payment_method_balance: Option, - - ///for switching between two different versions of the same connector - pub connector_api_version: Option, - - /// Contains flow-specific data required to construct a request and send it to the connector. - pub request: Request, - - /// Contains flow-specific data that the connector responds with. - pub response: Result, - - /// Contains a reference ID that should be sent in the connector request - pub connector_request_reference_id: String, - - #[cfg(feature = "payouts")] - /// Contains payout method data - pub payout_method_data: Option, - - #[cfg(feature = "payouts")] - /// Contains payout's quote ID - pub quote_id: Option, - - pub test_mode: Option, - pub connector_http_status_code: Option, - pub external_latency: Option, - /// Contains apple pay flow type simplified or manual - pub apple_pay_flow: Option, - - pub frm_metadata: Option, - - pub dispute_id: Option, - pub refund_id: Option, - - /// This field is used to store various data regarding the response from connector - pub connector_response: Option, - pub payment_method_status: Option, -} - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub enum AdditionalPaymentMethodConnectorResponse { - Card { - /// Details regarding the authentication details of the connector, if this is a 3ds payment. - authentication_data: Option, - /// Various payment checks that are done for a payment - payment_checks: Option, - }, -} - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct ConnectorResponseData { - pub additional_payment_method_data: Option, -} - -impl ConnectorResponseData { - pub fn with_additional_payment_method_data( - additional_payment_method_data: AdditionalPaymentMethodConnectorResponse, - ) -> Self { - Self { - additional_payment_method_data: Some(additional_payment_method_data), - } - } -} - -#[derive(Debug, Clone, serde::Deserialize)] -pub enum PaymentMethodToken { - Token(String), - ApplePayDecrypt(Box), -} - -#[derive(Debug, Clone, serde::Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ApplePayPredecryptData { - pub application_primary_account_number: Secret, - pub application_expiration_date: String, - pub currency_code: String, - pub transaction_amount: i64, - pub device_manufacturer_identifier: Secret, - pub payment_data_type: Secret, - pub payment_data: ApplePayCryptogramData, -} - -#[derive(Debug, Clone, serde::Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ApplePayCryptogramData { - pub online_payment_cryptogram: Secret, - pub eci_indicator: Option, -} - -#[derive(Debug, Clone)] -pub struct PaymentMethodBalance { - pub amount: i64, - pub currency: storage_enums::Currency, -} - -#[cfg(feature = "payouts")] -#[derive(Debug, Clone)] -pub struct PayoutsData { - pub payout_id: String, - pub amount: i64, - pub connector_payout_id: Option, - pub destination_currency: storage_enums::Currency, - pub source_currency: storage_enums::Currency, - pub payout_type: storage_enums::PayoutType, - pub entity_type: storage_enums::PayoutEntityType, - pub customer_details: Option, - pub vendor_details: Option, -} - #[cfg(feature = "payouts")] pub trait PayoutIndividualDetailsExt { type Error; @@ -466,6 +350,7 @@ pub struct PaymentsAuthorizeData { pub request_incremental_authorization: bool, pub metadata: Option, pub authentication_data: Option, + pub charges: Option, } #[derive(Debug, Clone, Default)] @@ -645,13 +530,6 @@ pub struct SetupMandateRequestData { pub metadata: Option, } -#[derive(Debug, Clone)] -pub struct AccessTokenRequestData { - pub app_id: Secret, - pub id: Option>, - // Add more keys if required -} - pub trait Capturable { fn get_captured_amount(&self, _payment_data: &PaymentData) -> Option where @@ -679,7 +557,7 @@ impl Capturable for PaymentsAuthorizeData { let final_amount = self .surcharge_details .as_ref() - .map(|surcharge_details| surcharge_details.final_amount); + .map(|surcharge_details| surcharge_details.final_amount.get_amount_as_i64()); final_amount.or(Some(self.amount)) } @@ -712,7 +590,7 @@ impl Capturable for PaymentsAuthorizeData { | common_enums::IntentStatus::PartiallyCapturedAndCapturable => None, } }, - common_enums::CaptureMethod::Manual => Some(payment_data.payment_attempt.get_total_amount()), + common_enums::CaptureMethod::Manual => Some(payment_data.payment_attempt.get_total_amount().get_amount_as_i64()), // In case of manual multiple, amount capturable must be inferred from all captures. common_enums::CaptureMethod::ManualMultiple | // Scheduled capture is not supported as of now @@ -789,7 +667,7 @@ impl Capturable for CompleteAuthorizeData { | common_enums::IntentStatus::PartiallyCapturedAndCapturable => None, } }, - common_enums::CaptureMethod::Manual => Some(payment_data.payment_attempt.get_total_amount()), + common_enums::CaptureMethod::Manual => Some(payment_data.payment_attempt.get_total_amount().get_amount_as_i64()), // In case of manual multiple, amount capturable must be inferred from all captures. common_enums::CaptureMethod::ManualMultiple | // Scheduled capture is not supported as of now @@ -804,7 +682,10 @@ impl Capturable for PaymentsCancelData { F: Clone, { // return previously captured amount - payment_data.payment_intent.amount_captured + payment_data + .payment_intent + .amount_captured + .map(|amt| amt.get_amount_as_i64()) } fn get_amount_capturable( &self, @@ -854,6 +735,7 @@ impl Capturable for PaymentsSyncData { .payment_attempt .amount_to_capture .or_else(|| Some(payment_data.payment_attempt.get_total_amount())) + .map(|amt| amt.get_amount_as_i64()) } fn get_amount_capturable( &self, @@ -876,12 +758,6 @@ pub struct AddAccessTokenResult { pub connector_supports_access_token: bool, } -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)] -pub struct AccessToken { - pub token: Secret, - pub expires: i64, -} - #[derive(serde::Serialize, Debug, Clone)] pub struct MandateReference { pub connector_mandate_id: Option, @@ -894,19 +770,19 @@ pub enum CaptureSyncResponse { resource_id: ResponseId, status: storage_enums::AttemptStatus, connector_response_reference_id: Option, - amount: Option, + amount: Option, }, Error { code: String, message: String, reason: Option, status_code: u16, - amount: Option, + amount: Option, }, } impl CaptureSyncResponse { - pub fn get_amount_captured(&self) -> Option { + pub fn get_amount_captured(&self) -> Option { match self { Self::Success { amount, .. } | Self::Error { amount, .. } => *amount, } @@ -932,6 +808,7 @@ pub enum PaymentsResponseData { network_txn_id: Option, connector_response_reference_id: Option, incremental_authorization_allowed: Option, + charge_id: Option, }, MultipleCaptureResponse { // pending_capture_id_list: Vec, @@ -981,60 +858,6 @@ pub enum PreprocessingResponseId { ConnectorTransactionId(String), } -#[derive(Debug, Clone, Default, Serialize)] -pub enum ResponseId { - ConnectorTransactionId(String), - EncodedData(String), - #[default] - NoResponseId, -} - -impl ResponseId { - pub fn get_connector_transaction_id( - &self, - ) -> errors::CustomResult { - match self { - Self::ConnectorTransactionId(txn_id) => Ok(txn_id.to_string()), - _ => Err(errors::ValidationError::IncorrectValueProvided { - field_name: "connector_transaction_id", - }) - .attach_printable("Expected connector transaction ID not found"), - } - } -} - -#[derive(Debug, Clone)] -pub struct RefundsData { - pub refund_id: String, - pub connector_transaction_id: String, - - pub connector_refund_id: Option, - pub currency: storage_enums::Currency, - /// Amount for the payment against which this refund is issued - pub payment_amount: i64, - pub reason: Option, - pub webhook_url: Option, - /// Amount to be refunded - pub refund_amount: i64, - /// Arbitrary metadata required for refund - pub connector_metadata: Option, - pub browser_info: Option, -} - -#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] -pub struct BrowserInformation { - pub color_depth: Option, - pub java_enabled: Option, - pub java_script_enabled: Option, - pub language: Option, - pub screen_height: Option, - pub screen_width: Option, - pub time_zone: Option, - pub ip_address: Option, - pub accept_header: Option, - pub user_agent: Option, -} - #[derive(Debug, Clone)] pub struct RefundsResponseData { pub connector_refund_id: String, @@ -1048,13 +871,6 @@ pub enum Redirection { NoRedirect, } -#[derive(Debug, Clone)] -pub struct VerifyWebhookSourceRequestData { - pub webhook_headers: actix_web::http::header::HeaderMap, - pub webhook_body: Vec, - pub merchant_secret: api_models::webhooks::ConnectorWebhookSecrets, -} - #[derive(Debug, Clone)] pub struct VerifyWebhookSourceResponseData { pub verify_webhook_status: VerifyWebhookStatus, @@ -1066,98 +882,28 @@ pub enum VerifyWebhookStatus { SourceNotVerified, } -#[derive(Default, Debug, Clone)] -pub struct AcceptDisputeRequestData { - pub dispute_id: String, - pub connector_dispute_id: String, -} - #[derive(Default, Clone, Debug)] pub struct AcceptDisputeResponse { pub dispute_status: api_models::enums::DisputeStatus, pub connector_status: Option, } -#[derive(Default, Debug, Clone)] -pub struct SubmitEvidenceRequestData { - pub dispute_id: String, - pub connector_dispute_id: String, - pub access_activity_log: Option, - pub billing_address: Option, - pub cancellation_policy: Option>, - pub cancellation_policy_provider_file_id: Option, - pub cancellation_policy_disclosure: Option, - pub cancellation_rebuttal: Option, - pub customer_communication: Option>, - pub customer_communication_provider_file_id: Option, - pub customer_email_address: Option, - pub customer_name: Option, - pub customer_purchase_ip: Option, - pub customer_signature: Option>, - pub customer_signature_provider_file_id: Option, - pub product_description: Option, - pub receipt: Option>, - pub receipt_provider_file_id: Option, - pub refund_policy: Option>, - pub refund_policy_provider_file_id: Option, - pub refund_policy_disclosure: Option, - pub refund_refusal_explanation: Option, - pub service_date: Option, - pub service_documentation: Option>, - pub service_documentation_provider_file_id: Option, - pub shipping_address: Option, - pub shipping_carrier: Option, - pub shipping_date: Option, - pub shipping_documentation: Option>, - pub shipping_documentation_provider_file_id: Option, - pub shipping_tracking_number: Option, - pub invoice_showing_distinct_transactions: Option>, - pub invoice_showing_distinct_transactions_provider_file_id: Option, - pub recurring_transaction_agreement: Option>, - pub recurring_transaction_agreement_provider_file_id: Option, - pub uncategorized_file: Option>, - pub uncategorized_file_provider_file_id: Option, - pub uncategorized_text: Option, -} - #[derive(Default, Clone, Debug)] pub struct SubmitEvidenceResponse { pub dispute_status: api_models::enums::DisputeStatus, pub connector_status: Option, } -#[derive(Default, Debug, Clone)] -pub struct DefendDisputeRequestData { - pub dispute_id: String, - pub connector_dispute_id: String, -} - #[derive(Default, Debug, Clone)] pub struct DefendDisputeResponse { pub dispute_status: api_models::enums::DisputeStatus, pub connector_status: Option, } -#[derive(Clone, Debug, serde::Serialize)] -pub struct UploadFileRequestData { - pub file_key: String, - #[serde(skip)] - pub file: Vec, - #[serde(serialize_with = "crate::utils::custom_serde::display_serialize")] - pub file_type: mime::Mime, - pub file_size: i32, -} - #[derive(Default, Clone, Debug)] pub struct UploadFileResponse { pub provider_file_id: String, } - -#[derive(Clone, Debug)] -pub struct RetrieveFileRequestData { - pub provider_file_id: String, -} - #[derive(Clone, Debug)] pub struct RetrieveFileResponse { pub file_data: Vec, @@ -1225,42 +971,8 @@ pub struct MandateRevokeResponseData { pub mandate_status: MandateStatus, } -// Different patterns of authentication. -#[derive(Default, Debug, Clone, serde::Deserialize, serde::Serialize)] -#[serde(tag = "auth_type")] -pub enum ConnectorAuthType { - TemporaryAuth, - HeaderKey { - api_key: Secret, - }, - BodyKey { - api_key: Secret, - key1: Secret, - }, - SignatureKey { - api_key: Secret, - key1: Secret, - api_secret: Secret, - }, - MultiAuthKey { - api_key: Secret, - key1: Secret, - api_secret: Secret, - key2: Secret, - }, - CurrencyAuthKey { - auth_key_map: HashMap, - }, - CertificateAuth { - certificate: Secret, - private_key: Secret, - }, - #[default] - NoKey, -} - -impl From for ConnectorAuthType { - fn from(value: api_models::admin::ConnectorAuthType) -> Self { +impl ForeignFrom for ConnectorAuthType { + fn foreign_from(value: api_models::admin::ConnectorAuthType) -> Self { match value { api_models::admin::ConnectorAuthType::TemporaryAuth => Self::TemporaryAuth, api_models::admin::ConnectorAuthType::HeaderKey { api_key } => { @@ -1357,38 +1069,9 @@ pub struct Response { pub status_code: u16, } -#[derive(Clone, Debug, serde::Serialize)] -pub struct ErrorResponse { - pub code: String, - pub message: String, - pub reason: Option, - pub status_code: u16, - pub attempt_status: Option, - pub connector_transaction_id: Option, -} - -impl ErrorResponse { - pub fn get_not_implemented() -> Self { - Self { - code: errors::ApiErrorResponse::NotImplemented { - message: errors::api_error_response::NotImplementedMessage::Default, - } - .error_code(), - message: errors::ApiErrorResponse::NotImplemented { - message: errors::api_error_response::NotImplementedMessage::Default, - } - .error_message(), - reason: None, - status_code: http::StatusCode::INTERNAL_SERVER_ERROR.as_u16(), - attempt_status: None, - connector_transaction_id: None, - } - } -} - -impl TryFrom for AccessTokenRequestData { +impl ForeignTryFrom for AccessTokenRequestData { type Error = errors::ApiErrorResponse; - fn try_from(connector_auth: ConnectorAuthType) -> Result { + fn foreign_try_from(connector_auth: ConnectorAuthType) -> Result { match connector_auth { ConnectorAuthType::HeaderKey { api_key } => Ok(Self { app_id: api_key, @@ -1414,28 +1097,6 @@ impl TryFrom for AccessTokenRequestData { } } -impl From for ErrorResponse { - fn from(error: errors::ApiErrorResponse) -> Self { - Self { - code: error.error_code(), - message: error.error_message(), - reason: None, - status_code: match error { - errors::ApiErrorResponse::ExternalConnectorError { status_code, .. } => status_code, - _ => 500, - }, - attempt_status: None, - connector_transaction_id: None, - } - } -} - -impl Default for ErrorResponse { - fn default() -> Self { - Self::from(errors::ApiErrorResponse::InternalServerError) - } -} - impl From<&&mut PaymentsAuthorizeRouterData> for AuthorizeSessionTokenData { fn from(data: &&mut PaymentsAuthorizeRouterData) -> Self { Self { @@ -1511,14 +1172,15 @@ impl From<&SetupMandateRouterData> for PaymentsAuthorizeData { metadata: None, authentication_data: None, customer_acceptance: data.request.customer_acceptance.clone(), + charges: None, // TODO: allow charges on mandates? } } } -impl From<(&RouterData, T2)> +impl ForeignFrom<(&RouterData, T2)> for RouterData { - fn from(item: (&RouterData, T2)) -> Self { + fn foreign_from(item: (&RouterData, T2)) -> Self { let data = item.0; let request = item.1; Self { @@ -1568,12 +1230,12 @@ impl From<(&RouterData, T2)> #[cfg(feature = "payouts")] impl - From<( + ForeignFrom<( &&mut RouterData, PayoutsData, )> for RouterData { - fn from( + fn foreign_from( item: ( &&mut RouterData, PayoutsData, diff --git a/crates/router/src/types/api/admin.rs b/crates/router/src/types/api/admin.rs index fcbd97d5cb13..8fc3ebb721f2 100644 --- a/crates/router/src/types/api/admin.rs +++ b/crates/router/src/types/api/admin.rs @@ -3,7 +3,8 @@ pub use api_models::admin::{ MerchantAccountDeleteResponse, MerchantAccountResponse, MerchantAccountUpdate, MerchantConnectorCreate, MerchantConnectorDeleteResponse, MerchantConnectorDetails, MerchantConnectorDetailsWrap, MerchantConnectorId, MerchantConnectorResponse, MerchantDetails, - MerchantId, PaymentMethodsEnabled, ToggleKVRequest, ToggleKVResponse, WebhookDetails, + MerchantId, PaymentMethodsEnabled, ToggleAllKVRequest, ToggleAllKVResponse, ToggleKVRequest, + ToggleKVResponse, WebhookDetails, }; use common_utils::ext_traits::{Encode, ValueExt}; use error_stack::ResultExt; diff --git a/crates/router/src/types/api/authentication.rs b/crates/router/src/types/api/authentication.rs index 5b8e3238fb7f..9837059dc012 100644 --- a/crates/router/src/types/api/authentication.rs +++ b/crates/router/src/types/api/authentication.rs @@ -3,6 +3,7 @@ use std::str::FromStr; use api_models::enums; use common_utils::errors::CustomResult; use error_stack::ResultExt; +pub use hyperswitch_domain_models::router_request_types::authentication::MessageCategory; use super::BoxedConnector; use crate::core::errors; @@ -10,6 +11,9 @@ use crate::core::errors; #[derive(Debug, Clone)] pub struct PreAuthentication; +#[derive(Debug, Clone)] +pub struct PreAuthenticationVersionCall; + #[derive(Debug, Clone)] pub struct Authentication; @@ -63,12 +67,6 @@ pub struct PostAuthenticationResponse { pub eci: Option, } -#[derive(Clone, serde::Deserialize, Debug, serde::Serialize, PartialEq, Eq)] -pub enum MessageCategory { - Payment, - NonPayment, -} - #[derive(Clone, serde::Deserialize, Debug, serde::Serialize, PartialEq, Eq)] pub struct ExternalAuthenticationPayload { pub trans_status: common_enums::TransactionStatus, @@ -94,6 +92,15 @@ pub trait ConnectorPreAuthentication: { } +pub trait ConnectorPreAuthenticationVersionCall: + services::ConnectorIntegration< + PreAuthenticationVersionCall, + types::authentication::PreAuthNRequestData, + types::authentication::AuthenticationResponseData, +> +{ +} + pub trait ConnectorPostAuthentication: services::ConnectorIntegration< PostAuthentication, @@ -107,6 +114,7 @@ pub trait ExternalAuthentication: super::ConnectorCommon + ConnectorAuthentication + ConnectorPreAuthentication + + ConnectorPreAuthenticationVersionCall + ConnectorPostAuthentication { } diff --git a/crates/router/src/types/api/files.rs b/crates/router/src/types/api/files.rs index 873516ee71bc..688962bd8367 100644 --- a/crates/router/src/types/api/files.rs +++ b/crates/router/src/types/api/files.rs @@ -1,5 +1,6 @@ use api_models::enums::FileUploadProvider; use masking::{Deserialize, Serialize}; +use serde_with::serde_as; use super::ConnectorCommon; use crate::{ @@ -47,12 +48,13 @@ impl ForeignTryFrom<&types::Connector> for FileUploadProvider { } } +#[serde_as] #[derive(Debug, Clone, serde::Serialize)] pub struct CreateFileRequest { pub file: Vec, pub file_name: Option, pub file_size: i32, - #[serde(serialize_with = "crate::utils::custom_serde::display_serialize")] + #[serde_as(as = "serde_with::DisplayFromStr")] pub file_type: mime::Mime, pub purpose: FilePurpose, pub dispute_id: Option, diff --git a/crates/router/src/types/api/payments.rs b/crates/router/src/types/api/payments.rs index 9f68cac98c9f..923f4a552685 100644 --- a/crates/router/src/types/api/payments.rs +++ b/crates/router/src/types/api/payments.rs @@ -6,12 +6,13 @@ pub use api_models::payments::{ PaymentListFilters, PaymentListFiltersV2, PaymentListResponse, PaymentListResponseV2, PaymentMethodData, PaymentMethodDataRequest, PaymentMethodDataResponse, PaymentOp, PaymentRetrieveBody, PaymentRetrieveBodyWithCredentials, PaymentsApproveRequest, - PaymentsCancelRequest, PaymentsCaptureRequest, PaymentsExternalAuthenticationRequest, - PaymentsIncrementalAuthorizationRequest, PaymentsRedirectRequest, PaymentsRedirectionResponse, - PaymentsRejectRequest, PaymentsRequest, PaymentsResponse, PaymentsResponseForm, - PaymentsRetrieveRequest, PaymentsSessionRequest, PaymentsSessionResponse, PaymentsStartRequest, - PgRedirectResponse, PhoneDetails, RedirectionResponse, SessionToken, TimeRange, UrlDetails, - VerifyRequest, VerifyResponse, WalletData, + PaymentsCancelRequest, PaymentsCaptureRequest, PaymentsCompleteAuthorizeRequest, + PaymentsExternalAuthenticationRequest, PaymentsIncrementalAuthorizationRequest, + PaymentsRedirectRequest, PaymentsRedirectionResponse, PaymentsRejectRequest, PaymentsRequest, + PaymentsResponse, PaymentsResponseForm, PaymentsRetrieveRequest, PaymentsSessionRequest, + PaymentsSessionResponse, PaymentsStartRequest, PgRedirectResponse, PhoneDetails, + RedirectionResponse, SessionToken, TimeRange, UrlDetails, VerifyRequest, VerifyResponse, + WalletData, }; use error_stack::ResultExt; @@ -245,7 +246,7 @@ mod payments_test { #[allow(dead_code)] fn payments_request() -> PaymentsRequest { PaymentsRequest { - amount: Some(Amount::from(200)), + amount: Some(Amount::from(common_utils::types::MinorUnit::new(200))), payment_method_data: Some(PaymentMethodDataRequest { payment_method_data: Some(PaymentMethodData::Card(card())), billing: None, diff --git a/crates/router/src/types/api/verify_connector.rs b/crates/router/src/types/api/verify_connector.rs index d35abd233f1b..cf7ba2382a33 100644 --- a/crates/router/src/types/api/verify_connector.rs +++ b/crates/router/src/types/api/verify_connector.rs @@ -52,6 +52,7 @@ impl VerifyConnectorData { request_incremental_authorization: false, authentication_data: None, customer_acceptance: None, + charges: None, } } diff --git a/crates/router/src/types/authentication.rs b/crates/router/src/types/authentication.rs index 7ea937b9ffd8..97ad8506d4fe 100644 --- a/crates/router/src/types/authentication.rs +++ b/crates/router/src/types/authentication.rs @@ -18,6 +18,7 @@ pub enum AuthenticationResponseData { three_ds_method_url: Option, message_version: common_utils::types::SemanticVersion, connector_metadata: Option, + directory_server_id: Option, }, AuthNResponse { authn_flow_type: AuthNFlowType, @@ -125,6 +126,9 @@ pub struct ConnectorPostAuthenticationRequestData { pub type PreAuthNRouterData = RouterData; +pub type PreAuthNVersionCallRouterData = + RouterData; + pub type ConnectorAuthenticationRouterData = RouterData; diff --git a/crates/router/src/types/domain/address.rs b/crates/router/src/types/domain/address.rs index 147c523e0b2b..f2b110deb191 100644 --- a/crates/router/src/types/domain/address.rs +++ b/crates/router/src/types/domain/address.rs @@ -80,7 +80,7 @@ impl behaviour::Conversion for CustomerAddress { .customer_id .clone() .ok_or(ValidationError::MissingRequiredField { - field_name: "cutomer_id".to_string(), + field_name: "customer_id".to_string(), })?; let address = Address::convert_back(other, key).await?; diff --git a/crates/router/src/types/domain/customer.rs b/crates/router/src/types/domain/customer.rs index 139cd1057790..0e8ac32b4af3 100644 --- a/crates/router/src/types/domain/customer.rs +++ b/crates/router/src/types/domain/customer.rs @@ -23,6 +23,7 @@ pub struct Customer { pub connector_customer: Option, pub address_id: Option, pub default_payment_method_id: Option, + pub updated_by: Option, } #[async_trait::async_trait] @@ -47,6 +48,7 @@ impl super::behaviour::Conversion for Customer { connector_customer: self.connector_customer, address_id: self.address_id, default_payment_method_id: self.default_payment_method_id, + updated_by: self.updated_by, }) } @@ -75,6 +77,7 @@ impl super::behaviour::Conversion for Customer { connector_customer: item.connector_customer, address_id: item.address_id, default_payment_method_id: item.default_payment_method_id, + updated_by: item.updated_by, }) } .await @@ -98,6 +101,7 @@ impl super::behaviour::Conversion for Customer { modified_at: now, connector_customer: self.connector_customer, address_id: self.address_id, + updated_by: self.updated_by, }) } } diff --git a/crates/router/src/types/domain/merchant_account.rs b/crates/router/src/types/domain/merchant_account.rs index 99553e26157f..5818ead38680 100644 --- a/crates/router/src/types/domain/merchant_account.rs +++ b/crates/router/src/types/domain/merchant_account.rs @@ -134,7 +134,7 @@ impl From for MerchantAccountUpdateInternal { ..Default::default() }, MerchantAccountUpdate::ReconUpdate { recon_status } => Self { - recon_status, + recon_status: Some(recon_status), ..Default::default() }, MerchantAccountUpdate::UnsetDefaultProfile => Self { diff --git a/crates/router/src/types/domain/payments.rs b/crates/router/src/types/domain/payments.rs index 1ea9c673fa5e..7b1f33654902 100644 --- a/crates/router/src/types/domain/payments.rs +++ b/crates/router/src/types/domain/payments.rs @@ -1,906 +1,10 @@ -use common_utils::pii::{self, Email}; -use masking::Secret; -use serde::{Deserialize, Serialize}; - -// We need to derive Serialize and Deserialize because some parts of payment method data are being -// stored in the database as serde_json::Value -#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] -pub enum PaymentMethodData { - Card(Card), - CardRedirect(CardRedirectData), - Wallet(WalletData), - PayLater(PayLaterData), - BankRedirect(BankRedirectData), - BankDebit(BankDebitData), - BankTransfer(Box), - Crypto(CryptoData), - MandatePayment, - Reward, - Upi(UpiData), - Voucher(VoucherData), - GiftCard(Box), - CardToken(CardToken), -} - -impl PaymentMethodData { - pub fn get_payment_method(&self) -> Option { - match self { - Self::Card(_) => Some(common_enums::PaymentMethod::Card), - Self::CardRedirect(_) => Some(common_enums::PaymentMethod::CardRedirect), - Self::Wallet(_) => Some(common_enums::PaymentMethod::Wallet), - Self::PayLater(_) => Some(common_enums::PaymentMethod::PayLater), - Self::BankRedirect(_) => Some(common_enums::PaymentMethod::BankRedirect), - Self::BankDebit(_) => Some(common_enums::PaymentMethod::BankDebit), - Self::BankTransfer(_) => Some(common_enums::PaymentMethod::BankTransfer), - Self::Crypto(_) => Some(common_enums::PaymentMethod::Crypto), - Self::Reward => Some(common_enums::PaymentMethod::Reward), - Self::Upi(_) => Some(common_enums::PaymentMethod::Upi), - Self::Voucher(_) => Some(common_enums::PaymentMethod::Voucher), - Self::GiftCard(_) => Some(common_enums::PaymentMethod::GiftCard), - Self::CardToken(_) | Self::MandatePayment => None, - } - } -} - -#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Default)] -pub struct Card { - pub card_number: cards::CardNumber, - pub card_exp_month: Secret, - pub card_exp_year: Secret, - pub card_cvc: Secret, - pub card_issuer: Option, - pub card_network: Option, - pub card_type: Option, - pub card_issuing_country: Option, - pub bank_code: Option, - pub nick_name: Option>, -} - -#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] -pub enum CardRedirectData { - Knet {}, - Benefit {}, - MomoAtm {}, - CardRedirect {}, -} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub enum PayLaterData { - KlarnaRedirect {}, - KlarnaSdk { token: String }, - AffirmRedirect {}, - AfterpayClearpayRedirect {}, - PayBrightRedirect {}, - WalleyRedirect {}, - AlmaRedirect {}, - AtomeRedirect {}, -} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] - -pub enum WalletData { - AliPayQr(Box), - AliPayRedirect(AliPayRedirection), - AliPayHkRedirect(AliPayHkRedirection), - MomoRedirect(MomoRedirection), - KakaoPayRedirect(KakaoPayRedirection), - GoPayRedirect(GoPayRedirection), - GcashRedirect(GcashRedirection), - ApplePay(ApplePayWalletData), - ApplePayRedirect(Box), - ApplePayThirdPartySdk(Box), - DanaRedirect {}, - GooglePay(GooglePayWalletData), - GooglePayRedirect(Box), - GooglePayThirdPartySdk(Box), - MbWayRedirect(Box), - MobilePayRedirect(Box), - PaypalRedirect(PaypalRedirection), - PaypalSdk(PayPalWalletData), - SamsungPay(Box), - TwintRedirect {}, - VippsRedirect {}, - TouchNGoRedirect(Box), - WeChatPayRedirect(Box), - WeChatPayQr(Box), - CashappQr(Box), - SwishQr(SwishQrData), -} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] - -pub struct SamsungPayWalletData { - /// The encrypted payment token from Samsung - pub token: Secret, -} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] - -pub struct GooglePayWalletData { - /// The type of payment method - pub pm_type: String, - /// User-facing message to describe the payment method that funds this transaction. - pub description: String, - /// The information of the payment method - pub info: GooglePayPaymentMethodInfo, - /// The tokenization data of Google pay - pub tokenization_data: GpayTokenizationData, -} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct ApplePayRedirectData {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct GooglePayRedirectData {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct GooglePayThirdPartySdkData {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct ApplePayThirdPartySdkData {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct WeChatPayRedirection {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct WeChatPay {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct WeChatPayQr {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct CashappQr {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct PaypalRedirection { - /// paypal's email address - pub email: Option, -} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct AliPayQr {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct AliPayRedirection {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct AliPayHkRedirection {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct MomoRedirection {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct KakaoPayRedirection {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct GoPayRedirection {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct GcashRedirection {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct MobilePayRedirection {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct MbWayRedirection {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] - -pub struct GooglePayPaymentMethodInfo { - /// The name of the card network - pub card_network: String, - /// The details of the card - pub card_details: String, -} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct PayPalWalletData { - /// Token generated for the Apple pay - pub token: String, -} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct TouchNGoRedirection {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct SwishQrData {} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct GpayTokenizationData { - /// The type of the token - pub token_type: String, - /// Token generated for the wallet - pub token: String, -} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct ApplePayWalletData { - /// The payment data of Apple pay - pub payment_data: String, - /// The payment method of Apple pay - pub payment_method: ApplepayPaymentMethod, - /// The unique identifier for the transaction - pub transaction_identifier: String, -} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct ApplepayPaymentMethod { - pub display_name: String, - pub network: String, - pub pm_type: String, -} - -#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] - -pub enum BankRedirectData { - BancontactCard { - card_number: Option, - card_exp_month: Option>, - card_exp_year: Option>, - card_holder_name: Option>, - billing_details: Option, - }, - Bizum {}, - Blik { - blik_code: Option, - }, - Eps { - billing_details: Option, - bank_name: Option, - country: Option, - }, - Giropay { - billing_details: Option, - bank_account_bic: Option>, - bank_account_iban: Option>, - country: Option, - }, - Ideal { - billing_details: Option, - bank_name: Option, - country: Option, - }, - Interac { - country: common_enums::CountryAlpha2, - email: Email, - }, - OnlineBankingCzechRepublic { - issuer: common_enums::BankNames, - }, - OnlineBankingFinland { - email: Option, - }, - OnlineBankingPoland { - issuer: common_enums::BankNames, - }, - OnlineBankingSlovakia { - issuer: common_enums::BankNames, - }, - OpenBankingUk { - issuer: Option, - country: Option, - }, - Przelewy24 { - bank_name: Option, - billing_details: BankRedirectBilling, - }, - Sofort { - billing_details: Option, - country: Option, - preferred_language: Option, - }, - Trustly { - country: common_enums::CountryAlpha2, - }, - OnlineBankingFpx { - issuer: common_enums::BankNames, - }, - OnlineBankingThailand { - issuer: common_enums::BankNames, - }, -} - -#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -pub struct BankRedirectBilling { - pub billing_name: Option>, - pub email: Option, -} - -#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "snake_case")] -pub struct CryptoData { - pub pay_currency: Option, -} - -#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "snake_case")] -pub struct UpiData { - pub vpa_id: Option>, -} - -#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum VoucherData { - Boleto(Box), - Efecty, - PagoEfectivo, - RedCompra, - RedPagos, - Alfamart(Box), - Indomaret(Box), - Oxxo, - SevenEleven(Box), - Lawson(Box), - MiniStop(Box), - FamilyMart(Box), - Seicomart(Box), - PayEasy(Box), -} - -#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct BoletoVoucherData { - /// The shopper's social security number - pub social_security_number: Option>, -} - -#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct AlfamartVoucherData {} - -#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct IndomaretVoucherData {} - -#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct JCSVoucherData {} - -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Eq, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum GiftCardData { - Givex(GiftCardDetails), - PaySafeCard {}, -} - -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Eq, PartialEq)] -#[serde(rename_all = "snake_case")] -pub struct GiftCardDetails { - /// The gift card number - pub number: Secret, - /// The card verification code. - pub cvc: Secret, -} - -#[derive(Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone, Default)] -#[serde(rename_all = "snake_case")] -pub struct CardToken { - /// The card holder's name - pub card_holder_name: Option>, - - /// The CVC number for the card - pub card_cvc: Option>, -} - -#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Eq, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum BankDebitData { - AchBankDebit { - account_number: Secret, - routing_number: Secret, - bank_name: Option, - bank_type: Option, - bank_holder_type: Option, - }, - SepaBankDebit { - iban: Secret, - }, - BecsBankDebit { - account_number: Secret, - bsb_number: Secret, - }, - BacsBankDebit { - account_number: Secret, - sort_code: Secret, - }, -} - -#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] -#[serde(rename_all = "snake_case")] -pub enum BankTransferData { - AchBankTransfer {}, - SepaBankTransfer {}, - BacsBankTransfer {}, - MultibancoBankTransfer {}, - PermataBankTransfer {}, - BcaBankTransfer {}, - BniVaBankTransfer {}, - BriVaBankTransfer {}, - CimbVaBankTransfer {}, - DanamonVaBankTransfer {}, - MandiriVaBankTransfer {}, - Pix {}, - Pse {}, - LocalBankTransfer { bank_code: Option }, -} - -#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -pub struct SepaAndBacsBillingDetails { - /// The Email ID for SEPA and BACS billing - pub email: Email, - /// The billing name for SEPA and BACS billing - pub name: Secret, -} - -impl From for PaymentMethodData { - fn from(api_model_payment_method_data: api_models::payments::PaymentMethodData) -> Self { - match api_model_payment_method_data { - api_models::payments::PaymentMethodData::Card(card_data) => { - Self::Card(Card::from(card_data)) - } - api_models::payments::PaymentMethodData::CardRedirect(card_redirect) => { - Self::CardRedirect(From::from(card_redirect)) - } - api_models::payments::PaymentMethodData::Wallet(wallet_data) => { - Self::Wallet(From::from(wallet_data)) - } - api_models::payments::PaymentMethodData::PayLater(pay_later_data) => { - Self::PayLater(From::from(pay_later_data)) - } - api_models::payments::PaymentMethodData::BankRedirect(bank_redirect_data) => { - Self::BankRedirect(From::from(bank_redirect_data)) - } - api_models::payments::PaymentMethodData::BankDebit(bank_debit_data) => { - Self::BankDebit(From::from(bank_debit_data)) - } - api_models::payments::PaymentMethodData::BankTransfer(bank_transfer_data) => { - Self::BankTransfer(Box::new(From::from(*bank_transfer_data))) - } - api_models::payments::PaymentMethodData::Crypto(crypto_data) => { - Self::Crypto(From::from(crypto_data)) - } - api_models::payments::PaymentMethodData::MandatePayment => Self::MandatePayment, - api_models::payments::PaymentMethodData::Reward => Self::Reward, - api_models::payments::PaymentMethodData::Upi(upi_data) => { - Self::Upi(From::from(upi_data)) - } - api_models::payments::PaymentMethodData::Voucher(voucher_data) => { - Self::Voucher(From::from(voucher_data)) - } - api_models::payments::PaymentMethodData::GiftCard(gift_card) => { - Self::GiftCard(Box::new(From::from(*gift_card))) - } - api_models::payments::PaymentMethodData::CardToken(card_token) => { - Self::CardToken(From::from(card_token)) - } - } - } -} - -impl From for Card { - fn from(value: api_models::payments::Card) -> Self { - let api_models::payments::Card { - card_number, - card_exp_month, - card_exp_year, - card_holder_name: _, - card_cvc, - card_issuer, - card_network, - card_type, - card_issuing_country, - bank_code, - nick_name, - } = value; - - Self { - card_number, - card_exp_month, - card_exp_year, - card_cvc, - card_issuer, - card_network, - card_type, - card_issuing_country, - bank_code, - nick_name, - } - } -} - -impl From for CardRedirectData { - fn from(value: api_models::payments::CardRedirectData) -> Self { - match value { - api_models::payments::CardRedirectData::Knet {} => Self::Knet {}, - api_models::payments::CardRedirectData::Benefit {} => Self::Benefit {}, - api_models::payments::CardRedirectData::MomoAtm {} => Self::MomoAtm {}, - api_models::payments::CardRedirectData::CardRedirect {} => Self::CardRedirect {}, - } - } -} - -impl From for WalletData { - fn from(value: api_models::payments::WalletData) -> Self { - match value { - api_models::payments::WalletData::AliPayQr(_) => Self::AliPayQr(Box::new(AliPayQr {})), - api_models::payments::WalletData::AliPayRedirect(_) => { - Self::AliPayRedirect(AliPayRedirection {}) - } - api_models::payments::WalletData::AliPayHkRedirect(_) => { - Self::AliPayHkRedirect(AliPayHkRedirection {}) - } - api_models::payments::WalletData::MomoRedirect(_) => { - Self::MomoRedirect(MomoRedirection {}) - } - api_models::payments::WalletData::KakaoPayRedirect(_) => { - Self::KakaoPayRedirect(KakaoPayRedirection {}) - } - api_models::payments::WalletData::GoPayRedirect(_) => { - Self::GoPayRedirect(GoPayRedirection {}) - } - api_models::payments::WalletData::GcashRedirect(_) => { - Self::GcashRedirect(GcashRedirection {}) - } - api_models::payments::WalletData::ApplePay(apple_pay_data) => { - Self::ApplePay(ApplePayWalletData::from(apple_pay_data)) - } - api_models::payments::WalletData::ApplePayRedirect(_) => { - Self::ApplePayRedirect(Box::new(ApplePayRedirectData {})) - } - api_models::payments::WalletData::ApplePayThirdPartySdk(_) => { - Self::ApplePayThirdPartySdk(Box::new(ApplePayThirdPartySdkData {})) - } - api_models::payments::WalletData::DanaRedirect {} => Self::DanaRedirect {}, - api_models::payments::WalletData::GooglePay(google_pay_data) => { - Self::GooglePay(GooglePayWalletData::from(google_pay_data)) - } - api_models::payments::WalletData::GooglePayRedirect(_) => { - Self::GooglePayRedirect(Box::new(GooglePayRedirectData {})) - } - api_models::payments::WalletData::GooglePayThirdPartySdk(_) => { - Self::GooglePayThirdPartySdk(Box::new(GooglePayThirdPartySdkData {})) - } - api_models::payments::WalletData::MbWayRedirect(..) => { - Self::MbWayRedirect(Box::new(MbWayRedirection {})) - } - api_models::payments::WalletData::MobilePayRedirect(_) => { - Self::MobilePayRedirect(Box::new(MobilePayRedirection {})) - } - api_models::payments::WalletData::PaypalRedirect(paypal_redirect_data) => { - Self::PaypalRedirect(PaypalRedirection { - email: paypal_redirect_data.email, - }) - } - api_models::payments::WalletData::PaypalSdk(paypal_sdk_data) => { - Self::PaypalSdk(PayPalWalletData { - token: paypal_sdk_data.token, - }) - } - api_models::payments::WalletData::SamsungPay(samsung_pay_data) => { - Self::SamsungPay(Box::new(SamsungPayWalletData { - token: samsung_pay_data.token, - })) - } - api_models::payments::WalletData::TwintRedirect {} => Self::TwintRedirect {}, - api_models::payments::WalletData::VippsRedirect {} => Self::VippsRedirect {}, - api_models::payments::WalletData::TouchNGoRedirect(_) => { - Self::TouchNGoRedirect(Box::new(TouchNGoRedirection {})) - } - api_models::payments::WalletData::WeChatPayRedirect(_) => { - Self::WeChatPayRedirect(Box::new(WeChatPayRedirection {})) - } - api_models::payments::WalletData::WeChatPayQr(_) => { - Self::WeChatPayQr(Box::new(WeChatPayQr {})) - } - api_models::payments::WalletData::CashappQr(_) => { - Self::CashappQr(Box::new(CashappQr {})) - } - api_models::payments::WalletData::SwishQr(_) => Self::SwishQr(SwishQrData {}), - } - } -} - -impl From for GooglePayWalletData { - fn from(value: api_models::payments::GooglePayWalletData) -> Self { - Self { - pm_type: value.pm_type, - description: value.description, - info: GooglePayPaymentMethodInfo { - card_network: value.info.card_network, - card_details: value.info.card_details, - }, - tokenization_data: GpayTokenizationData { - token_type: value.tokenization_data.token_type, - token: value.tokenization_data.token, - }, - } - } -} - -impl From for ApplePayWalletData { - fn from(value: api_models::payments::ApplePayWalletData) -> Self { - Self { - payment_data: value.payment_data, - payment_method: ApplepayPaymentMethod { - display_name: value.payment_method.display_name, - network: value.payment_method.network, - pm_type: value.payment_method.pm_type, - }, - transaction_identifier: value.transaction_identifier, - } - } -} - -impl From for PayLaterData { - fn from(value: api_models::payments::PayLaterData) -> Self { - match value { - api_models::payments::PayLaterData::KlarnaRedirect { .. } => Self::KlarnaRedirect {}, - api_models::payments::PayLaterData::KlarnaSdk { token } => Self::KlarnaSdk { token }, - api_models::payments::PayLaterData::AffirmRedirect {} => Self::AffirmRedirect {}, - api_models::payments::PayLaterData::AfterpayClearpayRedirect { .. } => { - Self::AfterpayClearpayRedirect {} - } - api_models::payments::PayLaterData::PayBrightRedirect {} => Self::PayBrightRedirect {}, - api_models::payments::PayLaterData::WalleyRedirect {} => Self::WalleyRedirect {}, - api_models::payments::PayLaterData::AlmaRedirect {} => Self::AlmaRedirect {}, - api_models::payments::PayLaterData::AtomeRedirect {} => Self::AtomeRedirect {}, - } - } -} - -impl From for BankRedirectData { - fn from(value: api_models::payments::BankRedirectData) -> Self { - match value { - api_models::payments::BankRedirectData::BancontactCard { - card_number, - card_exp_month, - card_exp_year, - card_holder_name, - billing_details, - } => Self::BancontactCard { - card_number, - card_exp_month, - card_exp_year, - card_holder_name, - billing_details: billing_details.map(BankRedirectBilling::from), - }, - api_models::payments::BankRedirectData::Bizum {} => Self::Bizum {}, - api_models::payments::BankRedirectData::Blik { blik_code } => Self::Blik { blik_code }, - api_models::payments::BankRedirectData::Eps { - billing_details, - bank_name, - country, - } => Self::Eps { - billing_details: billing_details.map(BankRedirectBilling::from), - bank_name, - country, - }, - api_models::payments::BankRedirectData::Giropay { - billing_details, - bank_account_bic, - bank_account_iban, - country, - } => Self::Giropay { - billing_details: billing_details.map(BankRedirectBilling::from), - bank_account_bic, - bank_account_iban, - country, - }, - api_models::payments::BankRedirectData::Ideal { - billing_details, - bank_name, - country, - } => Self::Ideal { - billing_details: billing_details.map(BankRedirectBilling::from), - bank_name, - country, - }, - 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 { email } => { - Self::OnlineBankingFinland { email } - } - api_models::payments::BankRedirectData::OnlineBankingPoland { issuer } => { - Self::OnlineBankingPoland { issuer } - } - api_models::payments::BankRedirectData::OnlineBankingSlovakia { issuer } => { - Self::OnlineBankingSlovakia { issuer } - } - api_models::payments::BankRedirectData::OpenBankingUk { issuer, country } => { - Self::OpenBankingUk { issuer, country } - } - api_models::payments::BankRedirectData::Przelewy24 { - bank_name, - billing_details, - } => Self::Przelewy24 { - bank_name, - billing_details: BankRedirectBilling { - billing_name: billing_details.billing_name, - email: billing_details.email, - }, - }, - api_models::payments::BankRedirectData::Sofort { - billing_details, - country, - preferred_language, - } => Self::Sofort { - billing_details: billing_details.map(BankRedirectBilling::from), - country, - preferred_language, - }, - api_models::payments::BankRedirectData::Trustly { country } => { - Self::Trustly { country } - } - api_models::payments::BankRedirectData::OnlineBankingFpx { issuer } => { - Self::OnlineBankingFpx { issuer } - } - api_models::payments::BankRedirectData::OnlineBankingThailand { issuer } => { - Self::OnlineBankingThailand { issuer } - } - } - } -} - -impl From for BankRedirectBilling { - fn from(billing: api_models::payments::BankRedirectBilling) -> Self { - Self { - billing_name: billing.billing_name, - email: billing.email, - } - } -} - -impl From for CryptoData { - fn from(value: api_models::payments::CryptoData) -> Self { - let api_models::payments::CryptoData { pay_currency } = value; - Self { pay_currency } - } -} - -impl From for UpiData { - fn from(value: api_models::payments::UpiData) -> Self { - let api_models::payments::UpiData { vpa_id } = value; - Self { vpa_id } - } -} - -impl From for VoucherData { - fn from(value: api_models::payments::VoucherData) -> Self { - match value { - api_models::payments::VoucherData::Boleto(boleto_data) => { - Self::Boleto(Box::new(BoletoVoucherData { - social_security_number: boleto_data.social_security_number, - })) - } - api_models::payments::VoucherData::Alfamart(_) => { - Self::Alfamart(Box::new(AlfamartVoucherData {})) - } - api_models::payments::VoucherData::Indomaret(_) => { - Self::Indomaret(Box::new(IndomaretVoucherData {})) - } - api_models::payments::VoucherData::SevenEleven(_) - | api_models::payments::VoucherData::Lawson(_) - | api_models::payments::VoucherData::MiniStop(_) - | api_models::payments::VoucherData::FamilyMart(_) - | api_models::payments::VoucherData::Seicomart(_) - | api_models::payments::VoucherData::PayEasy(_) => { - Self::SevenEleven(Box::new(JCSVoucherData {})) - } - api_models::payments::VoucherData::Efecty => Self::Efecty, - api_models::payments::VoucherData::PagoEfectivo => Self::PagoEfectivo, - api_models::payments::VoucherData::RedCompra => Self::RedCompra, - api_models::payments::VoucherData::RedPagos => Self::RedPagos, - api_models::payments::VoucherData::Oxxo => Self::Oxxo, - } - } -} - -impl From for GiftCardData { - fn from(value: api_models::payments::GiftCardData) -> Self { - match value { - api_models::payments::GiftCardData::Givex(details) => Self::Givex(GiftCardDetails { - number: details.number, - cvc: details.cvc, - }), - api_models::payments::GiftCardData::PaySafeCard {} => Self::PaySafeCard {}, - } - } -} - -impl From for CardToken { - fn from(value: api_models::payments::CardToken) -> Self { - let api_models::payments::CardToken { - card_holder_name, - card_cvc, - } = value; - Self { - card_holder_name, - card_cvc, - } - } -} - -impl From for BankDebitData { - fn from(value: api_models::payments::BankDebitData) -> Self { - match value { - api_models::payments::BankDebitData::AchBankDebit { - account_number, - routing_number, - bank_name, - bank_type, - bank_holder_type, - .. - } => Self::AchBankDebit { - account_number, - routing_number, - bank_name, - bank_type, - bank_holder_type, - }, - api_models::payments::BankDebitData::SepaBankDebit { iban, .. } => { - Self::SepaBankDebit { iban } - } - api_models::payments::BankDebitData::BecsBankDebit { - account_number, - bsb_number, - .. - } => Self::BecsBankDebit { - account_number, - bsb_number, - }, - api_models::payments::BankDebitData::BacsBankDebit { - account_number, - sort_code, - .. - } => Self::BacsBankDebit { - account_number, - sort_code, - }, - } - } -} - -impl From for BankTransferData { - fn from(value: api_models::payments::BankTransferData) -> Self { - match value { - api_models::payments::BankTransferData::AchBankTransfer { .. } => { - Self::AchBankTransfer {} - } - api_models::payments::BankTransferData::SepaBankTransfer { .. } => { - Self::SepaBankTransfer {} - } - api_models::payments::BankTransferData::BacsBankTransfer { .. } => { - Self::BacsBankTransfer {} - } - api_models::payments::BankTransferData::MultibancoBankTransfer { .. } => { - Self::MultibancoBankTransfer {} - } - api_models::payments::BankTransferData::PermataBankTransfer { .. } => { - Self::PermataBankTransfer {} - } - api_models::payments::BankTransferData::BcaBankTransfer { .. } => { - Self::BcaBankTransfer {} - } - api_models::payments::BankTransferData::BniVaBankTransfer { .. } => { - Self::BniVaBankTransfer {} - } - api_models::payments::BankTransferData::BriVaBankTransfer { .. } => { - Self::BriVaBankTransfer {} - } - api_models::payments::BankTransferData::CimbVaBankTransfer { .. } => { - Self::CimbVaBankTransfer {} - } - api_models::payments::BankTransferData::DanamonVaBankTransfer { .. } => { - Self::DanamonVaBankTransfer {} - } - api_models::payments::BankTransferData::MandiriVaBankTransfer { .. } => { - Self::MandiriVaBankTransfer {} - } - api_models::payments::BankTransferData::Pix {} => Self::Pix {}, - api_models::payments::BankTransferData::Pse {} => Self::Pse {}, - api_models::payments::BankTransferData::LocalBankTransfer { bank_code } => { - Self::LocalBankTransfer { bank_code } - } - } - } -} +pub use hyperswitch_domain_models::payment_method_data::{ + AliPayQr, ApplePayThirdPartySdkData, ApplePayWalletData, ApplepayPaymentMethod, BankDebitData, + BankRedirectData, BankTransferData, BoletoVoucherData, Card, CardRedirectData, CardToken, + CashappQr, CryptoData, GcashRedirection, GiftCardData, GiftCardDetails, GoPayRedirection, + GooglePayPaymentMethodInfo, GooglePayRedirectData, GooglePayThirdPartySdkData, + GooglePayWalletData, GpayTokenizationData, IndomaretVoucherData, KakaoPayRedirection, + MbWayRedirection, PayLaterData, PaymentMethodData, SamsungPayWalletData, + SepaAndBacsBillingDetails, SwishQrData, TouchNGoRedirection, VoucherData, WalletData, + WeChatPayQr, +}; diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs index 051e6ccf38ed..91323b0c16d3 100644 --- a/crates/router/src/types/domain/user.rs +++ b/crates/router/src/types/domain/user.rs @@ -774,8 +774,8 @@ impl UserFromStorage { self.0.user_id.as_str() } - pub fn compare_password(&self, candidate: Secret) -> UserResult<()> { - match password::is_correct_password(candidate, self.0.password.clone()) { + pub fn compare_password(&self, candidate: &Secret) -> UserResult<()> { + match password::is_correct_password(candidate, &self.0.password) { Ok(true) => Ok(()), Ok(false) => Err(UserErrors::InvalidCredentials.into()), Err(e) => Err(e), @@ -930,6 +930,10 @@ impl UserFromStorage { self.0.totp_status } + pub fn get_recovery_codes(&self) -> Option>> { + self.0.totp_recovery_codes.clone() + } + pub async fn decrypt_and_get_totp_secret( &self, state: &AppState, diff --git a/crates/router/src/types/fraud_check.rs b/crates/router/src/types/fraud_check.rs index 12ab7e4a067a..59be546b1c5d 100644 --- a/crates/router/src/types/fraud_check.rs +++ b/crates/router/src/types/fraud_check.rs @@ -1,11 +1,12 @@ -use common_utils::pii::Email; +pub use hyperswitch_domain_models::router_request_types::fraud_check::{ + FraudCheckCheckoutData, FraudCheckFulfillmentData, FraudCheckRecordReturnData, + FraudCheckSaleData, FraudCheckTransactionData, RefundMethod, +}; use crate::{ - connector::signifyd::transformers::RefundMethod, - core::fraud_check::types::FrmFulfillmentRequest, pii::Serialize, services, - types::{self, api, storage_enums, ErrorResponse, ResponseId, RouterData}, + types::{api, storage_enums, ErrorResponse, ResponseId, RouterData}, }; pub type FrmSaleRouterData = RouterData; @@ -13,13 +14,6 @@ pub type FrmSaleRouterData = RouterData; -#[derive(Debug, Clone)] -pub struct FraudCheckSaleData { - pub amount: i64, - pub order_details: Option>, - pub currency: Option, - pub email: Option, -} #[derive(Debug, Clone)] pub struct FrmRouterData { pub merchant_id: String, @@ -76,17 +70,6 @@ pub type FrmCheckoutType = dyn services::ConnectorIntegration< FraudCheckResponseData, >; -#[derive(Debug, Clone)] -pub struct FraudCheckCheckoutData { - pub amount: i64, - pub order_details: Option>, - pub currency: Option, - pub browser_info: Option, - pub payment_method_data: Option, - pub email: Option, - pub gateway: Option, -} - pub type FrmTransactionRouterData = RouterData; @@ -96,19 +79,6 @@ pub type FrmTransactionType = dyn services::ConnectorIntegration< FraudCheckResponseData, >; -#[derive(Debug, Clone)] -pub struct FraudCheckTransactionData { - pub amount: i64, - pub order_details: Option>, - pub currency: Option, - pub payment_method: Option, - pub error_code: Option, - pub error_message: Option, - pub connector_transaction_id: Option, - //The name of the payment gateway or financial institution that processed the transaction. - pub connector: Option, -} - pub type FrmFulfillmentRouterData = RouterData; @@ -125,18 +95,3 @@ pub type FrmRecordReturnType = dyn services::ConnectorIntegration< FraudCheckRecordReturnData, FraudCheckResponseData, >; - -#[derive(Debug, Clone)] -pub struct FraudCheckFulfillmentData { - pub amount: i64, - pub order_details: Option>>, - pub fulfillment_req: FrmFulfillmentRequest, -} - -#[derive(Debug, Clone)] -pub struct FraudCheckRecordReturnData { - pub amount: i64, - pub currency: Option, - pub refund_method: RefundMethod, - pub refund_transaction_id: Option, -} diff --git a/crates/router/src/types/storage/payment_attempt.rs b/crates/router/src/types/storage/payment_attempt.rs index 41e1aae635d8..4ad45f554ce7 100644 --- a/crates/router/src/types/storage/payment_attempt.rs +++ b/crates/router/src/types/storage/payment_attempt.rs @@ -1,3 +1,4 @@ +use common_utils::types::MinorUnit; use diesel_models::{capture::CaptureNew, enums}; use error_stack::ResultExt; pub use hyperswitch_domain_models::payments::payment_attempt::{ @@ -10,19 +11,19 @@ use crate::{ pub trait PaymentAttemptExt { fn make_new_capture( &self, - capture_amount: i64, + capture_amount: MinorUnit, capture_status: enums::CaptureStatus, ) -> RouterResult; fn get_next_capture_id(&self) -> String; - fn get_total_amount(&self) -> i64; + fn get_total_amount(&self) -> MinorUnit; fn get_surcharge_details(&self) -> Option; } impl PaymentAttemptExt for PaymentAttempt { fn make_new_capture( &self, - capture_amount: i64, + capture_amount: MinorUnit, capture_status: enums::CaptureStatus, ) -> RouterResult { let capture_sequence = self.multiple_capture_count.unwrap_or_default() + 1; @@ -66,8 +67,10 @@ impl PaymentAttemptExt for PaymentAttempt { } }) } - fn get_total_amount(&self) -> i64 { - self.amount + self.surcharge_amount.unwrap_or(0) + self.tax_amount.unwrap_or(0) + fn get_total_amount(&self) -> MinorUnit { + self.amount + + self.surcharge_amount.unwrap_or_default() + + self.tax_amount.unwrap_or_default() } } diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 8e4c914aae86..b6d5c6d9c8d3 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -10,6 +10,7 @@ use common_utils::{ ext_traits::{StringExt, ValueExt}, fp_utils::when, pii, + types::MinorUnit, }; use diesel_models::enums as storage_enums; use error_stack::{report, ResultExt}; @@ -18,6 +19,7 @@ use masking::{ExposeInterface, PeekInterface}; use super::domain; use crate::{ core::errors, + headers::{X_CLIENT_SOURCE, X_CLIENT_VERSION, X_PAYMENT_CONFIRM_SOURCE}, services::authentication::get_header_value_by_key, types::{ api::{self as api_types, routing as routing_types}, @@ -310,7 +312,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { impl ForeignFrom for payments::MandateAmountData { fn foreign_from(from: storage_enums::MandateAmountData) -> Self { Self { - amount: from.amount, + amount: MinorUnit::new(from.amount), currency: from.currency, start_date: from.start_date, end_date: from.end_date, @@ -377,7 +379,7 @@ impl ForeignFrom for hyperswitch_domain_models::mandates: impl ForeignFrom for storage_enums::MandateAmountData { fn foreign_from(from: payments::MandateAmountData) -> Self { Self { - amount: from.amount, + amount: from.amount.get_amount_as_i64(), currency: from.currency, start_date: from.start_date, end_date: from.end_date, @@ -1047,7 +1049,7 @@ impl ForeignTryFrom<&HeaderMap> for payments::HeaderPayload { type Error = error_stack::Report; fn foreign_try_from(headers: &HeaderMap) -> Result { let payment_confirm_source: Option = - get_header_value_by_key("payment_confirm_source".into(), headers)? + get_header_value_by_key(X_PAYMENT_CONFIRM_SOURCE.into(), headers)? .map(|source| { source .to_owned() @@ -1074,8 +1076,17 @@ impl ForeignTryFrom<&HeaderMap> for payments::HeaderPayload { let x_hs_latency = get_header_value_by_key(X_HS_LATENCY.into(), headers) .map(|value| value == Some("true")) .unwrap_or(false); + + let client_source = + get_header_value_by_key(X_CLIENT_SOURCE.into(), headers)?.map(|val| val.to_string()); + + let client_version = + get_header_value_by_key(X_CLIENT_VERSION.into(), headers)?.map(|val| val.to_string()); + Ok(Self { payment_confirm_source, + client_source, + client_version, x_hs_latency: Some(x_hs_latency), }) } diff --git a/crates/router/src/utils.rs b/crates/router/src/utils.rs index f693fc695191..2eda514aa6d8 100644 --- a/crates/router/src/utils.rs +++ b/crates/router/src/utils.rs @@ -1,7 +1,6 @@ #[cfg(feature = "olap")] pub mod connector_onboarding; pub mod currency; -pub mod custom_serde; pub mod db_utils; pub mod ext_traits; #[cfg(feature = "kv_store")] diff --git a/crates/router/src/utils/connector_onboarding.rs b/crates/router/src/utils/connector_onboarding.rs index 03735e61cc70..aeea146df292 100644 --- a/crates/router/src/utils/connector_onboarding.rs +++ b/crates/router/src/utils/connector_onboarding.rs @@ -4,7 +4,7 @@ use error_stack::ResultExt; use super::errors::StorageErrorExt; use crate::{ consts, - core::errors::{api_error_response::NotImplementedMessage, ApiErrorResponse, RouterResult}, + core::errors::{ApiErrorResponse, NotImplementedMessage, RouterResult}, routes::{app::settings, AppState}, types::{self, api::enums}, }; diff --git a/crates/router/src/utils/custom_serde.rs b/crates/router/src/utils/custom_serde.rs deleted file mode 100644 index dcdad3092b2f..000000000000 --- a/crates/router/src/utils/custom_serde.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub fn display_serialize(value: &T, serializer: S) -> Result -where - T: std::fmt::Display, - S: serde::ser::Serializer, -{ - serializer.serialize_str(&format!("{}", value)) -} diff --git a/crates/router/src/utils/user.rs b/crates/router/src/utils/user.rs index 4980958c9acb..9c67dbfcab59 100644 --- a/crates/router/src/utils/user.rs +++ b/crates/router/src/utils/user.rs @@ -1,14 +1,11 @@ use std::collections::HashMap; use api_models::user as user_api; -use common_utils::{errors::CustomResult, pii}; +use common_utils::errors::CustomResult; use diesel_models::{enums::UserStatus, user_role::UserRole}; use error_stack::ResultExt; -use masking::ExposeInterface; -use totp_rs::{Algorithm, TOTP}; use crate::{ - consts, core::errors::{StorageError, UserErrors, UserResult}, routes::AppState, services::{ @@ -22,6 +19,7 @@ pub mod dashboard_metadata; pub mod password; #[cfg(feature = "dummy_connector")] pub mod sample_data; +pub mod two_factor_auth; impl UserFromToken { pub async fn get_merchant_account_from_db( @@ -193,25 +191,3 @@ pub fn get_token_from_signin_response(resp: &user_api::SignInResponse) -> maskin user_api::SignInResponse::MerchantSelect(data) => data.token.clone(), } } - -pub fn generate_default_totp( - email: pii::Email, - secret: Option>, -) -> UserResult { - let secret = secret - .map(|sec| totp_rs::Secret::Encoded(sec.expose())) - .unwrap_or_else(totp_rs::Secret::generate_secret) - .to_bytes() - .change_context(UserErrors::InternalServerError)?; - - TOTP::new( - Algorithm::SHA1, - consts::user::TOTP_DIGITS, - consts::user::TOTP_TOLERANCE, - consts::user::TOTP_VALIDITY_DURATION_IN_SECONDS, - secret, - Some(consts::user::TOTP_ISSUER_NAME.to_string()), - email.expose().expose(), - ) - .change_context(UserErrors::InternalServerError) -} diff --git a/crates/router/src/utils/user/password.rs b/crates/router/src/utils/user/password.rs index beb87c325b62..e01181acb9d0 100644 --- a/crates/router/src/utils/user/password.rs +++ b/crates/router/src/utils/user/password.rs @@ -7,7 +7,7 @@ use argon2::{ }; use common_utils::errors::CustomResult; use error_stack::ResultExt; -use masking::{ExposeInterface, Secret}; +use masking::{ExposeInterface, PeekInterface, Secret}; use rand::{seq::SliceRandom, Rng}; use crate::core::errors::UserErrors; @@ -25,13 +25,13 @@ pub fn generate_password_hash( } pub fn is_correct_password( - candidate: Secret, - password: Secret, + candidate: &Secret, + password: &Secret, ) -> CustomResult { - let password = password.expose(); + let password = password.peek(); let parsed_hash = - PasswordHash::new(&password).change_context(UserErrors::InternalServerError)?; - let result = Argon2::default().verify_password(candidate.expose().as_bytes(), &parsed_hash); + PasswordHash::new(password).change_context(UserErrors::InternalServerError)?; + let result = Argon2::default().verify_password(candidate.peek().as_bytes(), &parsed_hash); match result { Ok(_) => Ok(true), Err(argon2Err::Password) => Ok(false), @@ -40,6 +40,19 @@ pub fn is_correct_password( .change_context(UserErrors::InternalServerError) } +pub fn get_index_for_correct_recovery_code( + candidate: &Secret, + recovery_codes: &[Secret], +) -> CustomResult, UserErrors> { + for (index, recovery_code) in recovery_codes.iter().enumerate() { + let is_match = is_correct_password(candidate, recovery_code)?; + if is_match { + return Ok(Some(index)); + } + } + Ok(None) +} + pub fn get_temp_password() -> Secret { let uuid_pass = uuid::Uuid::new_v4().to_string(); let mut rng = rand::thread_rng(); diff --git a/crates/router/src/utils/user/sample_data.rs b/crates/router/src/utils/user/sample_data.rs index 2216cb105548..8936b2af67fe 100644 --- a/crates/router/src/utils/user/sample_data.rs +++ b/crates/router/src/utils/user/sample_data.rs @@ -174,7 +174,7 @@ pub async fn generate_sample_data( true => common_enums::IntentStatus::Failed, _ => common_enums::IntentStatus::Succeeded, }, - amount: amount * 100, + amount: common_utils::types::MinorUnit::new(amount * 100), currency: Some( *currency_vec .get((num - 1) % currency_vec_len) @@ -192,7 +192,7 @@ pub async fn generate_sample_data( ), attempt_count: 1, customer_id: Some("hs-dashboard-user".to_string()), - amount_captured: Some(amount * 100), + amount_captured: Some(common_utils::types::MinorUnit::new(amount * 100)), profile_id: Some(profile_id.clone()), return_url: Default::default(), metadata: Default::default(), @@ -218,6 +218,8 @@ pub async fn generate_sample_data( fingerprint_id: None, session_expiry: Some(session_expiry), request_external_three_ds_authentication: None, + charges: None, + frm_metadata: Default::default(), }; let payment_attempt = PaymentAttemptBatchNew { attempt_id: attempt_id.clone(), @@ -294,6 +296,7 @@ pub async fn generate_sample_data( profile_id: payment_intent.profile_id.clone(), updated_by: merchant_from_db.storage_scheme.to_string(), merchant_connector_id: payment_attempt.merchant_connector_id.clone(), + charges: None, }) } else { None diff --git a/crates/router/src/utils/user/two_factor_auth.rs b/crates/router/src/utils/user/two_factor_auth.rs new file mode 100644 index 000000000000..e9e3f66005cf --- /dev/null +++ b/crates/router/src/utils/user/two_factor_auth.rs @@ -0,0 +1,74 @@ +use std::sync::Arc; + +use common_utils::pii; +use error_stack::ResultExt; +use masking::ExposeInterface; +use redis_interface::RedisConnectionPool; +use totp_rs::{Algorithm, TOTP}; + +use crate::{ + consts, + core::errors::{UserErrors, UserResult}, + routes::AppState, +}; + +pub fn generate_default_totp( + email: pii::Email, + secret: Option>, +) -> UserResult { + let secret = secret + .map(|sec| totp_rs::Secret::Encoded(sec.expose())) + .unwrap_or_else(totp_rs::Secret::generate_secret) + .to_bytes() + .change_context(UserErrors::InternalServerError)?; + + TOTP::new( + Algorithm::SHA1, + consts::user::TOTP_DIGITS, + consts::user::TOTP_TOLERANCE, + consts::user::TOTP_VALIDITY_DURATION_IN_SECONDS, + secret, + Some(consts::user::TOTP_ISSUER_NAME.to_string()), + email.expose().expose(), + ) + .change_context(UserErrors::InternalServerError) +} + +pub async fn check_totp_in_redis(state: &AppState, user_id: &str) -> UserResult { + let redis_conn = get_redis_connection(state)?; + let key = format!("{}{}", consts::user::TOTP_PREFIX, user_id); + redis_conn + .exists::<()>(&key) + .await + .change_context(UserErrors::InternalServerError) +} + +pub async fn check_recovery_code_in_redis(state: &AppState, user_id: &str) -> UserResult { + let redis_conn = get_redis_connection(state)?; + let key = format!("{}{}", consts::user::REDIS_RECOVERY_CODE_PREFIX, user_id); + redis_conn + .exists::<()>(&key) + .await + .change_context(UserErrors::InternalServerError) +} + +fn get_redis_connection(state: &AppState) -> UserResult> { + state + .store + .get_redis_conn() + .change_context(UserErrors::InternalServerError) + .attach_printable("Failed to get redis connection") +} + +pub async fn insert_recovery_code_in_redis(state: &AppState, user_id: &str) -> UserResult<()> { + let redis_conn = get_redis_connection(state)?; + let key = format!("{}{}", consts::user::REDIS_RECOVERY_CODE_PREFIX, user_id); + redis_conn + .set_key_with_expiry( + key.as_str(), + common_utils::date_time::now_unix_timestamp(), + state.conf.user.two_factor_auth_expiry_in_secs, + ) + .await + .change_context(UserErrors::InternalServerError) +} diff --git a/crates/router/src/workflows/payment_sync.rs b/crates/router/src/workflows/payment_sync.rs index 482b076a6778..14f372789ed3 100644 --- a/crates/router/src/workflows/payment_sync.rs +++ b/crates/router/src/workflows/payment_sync.rs @@ -127,7 +127,7 @@ impl ProcessTrackerWorkflow for PaymentsSyncWorkflow { error_reason: Some(Some( consts::REQUEST_TIMEOUT_ERROR_MESSAGE_FROM_PSYNC.to_string(), )), - amount_capturable: Some(0), + amount_capturable: Some(common_utils::types::MinorUnit::new(0)), updated_by: merchant_account.storage_scheme.to_string(), unified_code: None, unified_message: None, diff --git a/crates/router/tests/cache.rs b/crates/router/tests/cache.rs index 040e0dddf97c..c6eef06db7b1 100644 --- a/crates/router/tests/cache.rs +++ b/crates/router/tests/cache.rs @@ -50,8 +50,14 @@ async fn invalidate_existing_cache_success() { let response_body = response.body().await; println!("invalidate Cache: {response:?} : {response_body:?}"); assert_eq!(response.status(), awc::http::StatusCode::OK); - assert!(cache::CONFIG_CACHE.get(&cache_key).await.is_none()); - assert!(cache::ACCOUNTS_CACHE.get(&cache_key).await.is_none()); + assert!(cache::CONFIG_CACHE + .get_val::(&cache_key) + .await + .is_none()); + assert!(cache::ACCOUNTS_CACHE + .get_val::(&cache_key) + .await + .is_none()); } #[actix_web::test] diff --git a/crates/router/tests/connectors/aci.rs b/crates/router/tests/connectors/aci.rs index 3548bc5e5bdb..b4eafee4c832 100644 --- a/crates/router/tests/connectors/aci.rs +++ b/crates/router/tests/connectors/aci.rs @@ -75,6 +75,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData { metadata: None, authentication_data: None, customer_acceptance: None, + ..utils::PaymentAuthorizeType::default().0 }, response: Err(types::ErrorResponse::default()), address: PaymentAddress::new( @@ -152,6 +153,7 @@ fn construct_refund_router_data() -> types::RefundsRouterData { reason: None, connector_refund_id: None, browser_info: None, + ..utils::PaymentRefundType::default().0 }, response: Err(types::ErrorResponse::default()), address: PaymentAddress::default(), diff --git a/crates/router/tests/connectors/adyen.rs b/crates/router/tests/connectors/adyen.rs index 4d4318064e22..ff703dd84ef7 100644 --- a/crates/router/tests/connectors/adyen.rs +++ b/crates/router/tests/connectors/adyen.rs @@ -182,6 +182,7 @@ impl AdyenTest { metadata: None, authentication_data: None, customer_acceptance: None, + ..utils::PaymentAuthorizeType::default().0 }) } } diff --git a/crates/router/tests/connectors/bitpay.rs b/crates/router/tests/connectors/bitpay.rs index 2dd90e704b9c..85026c9c447d 100644 --- a/crates/router/tests/connectors/bitpay.rs +++ b/crates/router/tests/connectors/bitpay.rs @@ -99,6 +99,7 @@ fn payment_method_details() -> Option { metadata: None, authentication_data: None, customer_acceptance: None, + ..utils::PaymentAuthorizeType::default().0 }) } diff --git a/crates/router/tests/connectors/cashtocode.rs b/crates/router/tests/connectors/cashtocode.rs index 60620d1b4c41..76887fa0441b 100644 --- a/crates/router/tests/connectors/cashtocode.rs +++ b/crates/router/tests/connectors/cashtocode.rs @@ -72,6 +72,7 @@ impl CashtocodeTest { metadata: None, authentication_data: None, customer_acceptance: None, + ..utils::PaymentAuthorizeType::default().0 }) } diff --git a/crates/router/tests/connectors/coinbase.rs b/crates/router/tests/connectors/coinbase.rs index 5dd42a2c97c0..306255c94c54 100644 --- a/crates/router/tests/connectors/coinbase.rs +++ b/crates/router/tests/connectors/coinbase.rs @@ -101,6 +101,7 @@ fn payment_method_details() -> Option { metadata: None, authentication_data: None, customer_acceptance: None, + ..utils::PaymentAuthorizeType::default().0 }) } diff --git a/crates/router/tests/connectors/cryptopay.rs b/crates/router/tests/connectors/cryptopay.rs index c26bad8c5671..6d52a174b587 100644 --- a/crates/router/tests/connectors/cryptopay.rs +++ b/crates/router/tests/connectors/cryptopay.rs @@ -99,6 +99,7 @@ fn payment_method_details() -> Option { metadata: None, authentication_data: None, customer_acceptance: None, + ..utils::PaymentAuthorizeType::default().0 }) } diff --git a/crates/router/tests/connectors/gpayments.rs b/crates/router/tests/connectors/gpayments.rs index 8e133b3ede79..d7ab2e4e4da9 100644 --- a/crates/router/tests/connectors/gpayments.rs +++ b/crates/router/tests/connectors/gpayments.rs @@ -4,6 +4,7 @@ use test_utils::connector_auth; use crate::utils::{self, ConnectorActions}; #[derive(Clone, Copy)] +#[allow(dead_code)] struct GpaymentsTest; impl ConnectorActions for GpaymentsTest {} impl utils::Connector for GpaymentsTest { diff --git a/crates/router/tests/connectors/mifinity.rs b/crates/router/tests/connectors/mifinity.rs index f0b58342684c..a2535f71a57c 100644 --- a/crates/router/tests/connectors/mifinity.rs +++ b/crates/router/tests/connectors/mifinity.rs @@ -93,7 +93,7 @@ async fn should_sync_authorized_payment() { .psync_retry_till_status_matches( enums::AttemptStatus::Authorized, Some(types::PaymentsSyncData { - connector_transaction_id: router::types::ResponseId::ConnectorTransactionId( + connector_transaction_id: types::ResponseId::ConnectorTransactionId( txn_id.unwrap(), ), ..Default::default() @@ -213,7 +213,7 @@ async fn should_sync_auto_captured_payment() { .psync_retry_till_status_matches( enums::AttemptStatus::Charged, Some(types::PaymentsSyncData { - connector_transaction_id: router::types::ResponseId::ConnectorTransactionId( + connector_transaction_id: types::ResponseId::ConnectorTransactionId( txn_id.unwrap(), ), capture_method: Some(enums::CaptureMethod::Automatic), @@ -303,7 +303,7 @@ async fn should_fail_payment_for_incorrect_cvc() { let response = CONNECTOR .make_payment( Some(types::PaymentsAuthorizeData { - payment_method_data: types::domain::PaymentMethodData::Card(domain::Card { + payment_method_data: domain::PaymentMethodData::Card(domain::Card { card_cvc: Secret::new("12345".to_string()), ..utils::CCardType::default().0 }), @@ -325,7 +325,7 @@ async fn should_fail_payment_for_invalid_exp_month() { let response = CONNECTOR .make_payment( Some(types::PaymentsAuthorizeData { - payment_method_data: types::domain::PaymentMethodData::Card(domain::Card { + payment_method_data: domain::PaymentMethodData::Card(domain::Card { card_exp_month: Secret::new("20".to_string()), ..utils::CCardType::default().0 }), @@ -347,7 +347,7 @@ async fn should_fail_payment_for_incorrect_expiry_year() { let response = CONNECTOR .make_payment( Some(types::PaymentsAuthorizeData { - payment_method_data: types::domain::PaymentMethodData::Card(domain::Card { + payment_method_data: domain::PaymentMethodData::Card(domain::Card { card_exp_year: Secret::new("2000".to_string()), ..utils::CCardType::default().0 }), diff --git a/crates/router/tests/connectors/opennode.rs b/crates/router/tests/connectors/opennode.rs index 1e2cea554e90..91162b829e2f 100644 --- a/crates/router/tests/connectors/opennode.rs +++ b/crates/router/tests/connectors/opennode.rs @@ -100,6 +100,7 @@ fn payment_method_details() -> Option { metadata: None, authentication_data: None, customer_acceptance: None, + ..utils::PaymentAuthorizeType::default().0 }) } diff --git a/crates/router/tests/connectors/payone.rs b/crates/router/tests/connectors/payone.rs index d1aab45f4887..1f659c9092d5 100644 --- a/crates/router/tests/connectors/payone.rs +++ b/crates/router/tests/connectors/payone.rs @@ -93,7 +93,7 @@ async fn should_sync_authorized_payment() { .psync_retry_till_status_matches( enums::AttemptStatus::Authorized, Some(types::PaymentsSyncData { - connector_transaction_id: router::types::ResponseId::ConnectorTransactionId( + connector_transaction_id: types::ResponseId::ConnectorTransactionId( txn_id.unwrap(), ), ..Default::default() @@ -213,7 +213,7 @@ async fn should_sync_auto_captured_payment() { .psync_retry_till_status_matches( enums::AttemptStatus::Charged, Some(types::PaymentsSyncData { - connector_transaction_id: router::types::ResponseId::ConnectorTransactionId( + connector_transaction_id: types::ResponseId::ConnectorTransactionId( txn_id.unwrap(), ), capture_method: Some(enums::CaptureMethod::Automatic), @@ -303,7 +303,7 @@ async fn should_fail_payment_for_incorrect_cvc() { let response = CONNECTOR .make_payment( Some(types::PaymentsAuthorizeData { - payment_method_data: types::domain::PaymentMethodData::Card(domain::Card { + payment_method_data: domain::PaymentMethodData::Card(domain::Card { card_cvc: Secret::new("12345".to_string()), ..utils::CCardType::default().0 }), @@ -325,7 +325,7 @@ async fn should_fail_payment_for_invalid_exp_month() { let response = CONNECTOR .make_payment( Some(types::PaymentsAuthorizeData { - payment_method_data: types::domain::PaymentMethodData::Card(domain::Card { + payment_method_data: domain::PaymentMethodData::Card(domain::Card { card_exp_month: Secret::new("20".to_string()), ..utils::CCardType::default().0 }), @@ -347,7 +347,7 @@ async fn should_fail_payment_for_incorrect_expiry_year() { let response = CONNECTOR .make_payment( Some(types::PaymentsAuthorizeData { - payment_method_data: types::domain::PaymentMethodData::Card(domain::Card { + payment_method_data: domain::PaymentMethodData::Card(domain::Card { card_exp_year: Secret::new("2000".to_string()), ..utils::CCardType::default().0 }), diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index 09a34d26c4ce..3d35c34cc1b2 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -423,6 +423,7 @@ pub trait ConnectorActions: Connector { reason: None, connector_refund_id: Some(refund_id), browser_info: None, + charges: None, }), payment_info, ); @@ -529,9 +530,10 @@ pub trait ConnectorActions: Connector { access_token: info.clone().and_then(|a| a.access_token), session_token: None, reference_id: None, - payment_method_token: info - .clone() - .and_then(|a| a.payment_method_token.map(types::PaymentMethodToken::Token)), + payment_method_token: info.clone().and_then(|a| { + a.payment_method_token + .map(|token| types::PaymentMethodToken::Token(Secret::new(token))) + }), connector_customer: info.clone().and_then(|a| a.connector_customer), recurring_mandate_payment_data: None, @@ -945,6 +947,7 @@ impl Default for PaymentAuthorizeType { metadata: None, authentication_data: None, customer_acceptance: None, + charges: None, }; Self(data) } @@ -1021,6 +1024,7 @@ impl Default for PaymentRefundType { reason: Some("Customer returned product".to_string()), connector_refund_id: None, browser_info: None, + charges: None, }; Self(data) } @@ -1084,6 +1088,7 @@ pub fn get_connector_metadata( network_txn_id: _, connector_response_reference_id: _, incremental_authorization_allowed: _, + charge_id: _, }) => connector_metadata, _ => None, } diff --git a/crates/router/tests/connectors/worldline.rs b/crates/router/tests/connectors/worldline.rs index 36d0bba6c468..e26ba15eb25e 100644 --- a/crates/router/tests/connectors/worldline.rs +++ b/crates/router/tests/connectors/worldline.rs @@ -111,6 +111,7 @@ impl WorldlineTest { metadata: None, authentication_data: None, customer_acceptance: None, + ..utils::PaymentAuthorizeType::default().0 }) } } diff --git a/crates/router/tests/payments.rs b/crates/router/tests/payments.rs index 45d3b29d66a4..6566004c4cf6 100644 --- a/crates/router/tests/payments.rs +++ b/crates/router/tests/payments.rs @@ -2,6 +2,7 @@ mod utils; +use common_utils::types::MinorUnit; use router::{ configs, core::payments, @@ -303,10 +304,10 @@ async fn payments_create_core() { "pay_mbabizu24mvu3mela5njyhpit10".to_string(), )), merchant_id: Some("jarnura".to_string()), - amount: Some(6540.into()), + amount: Some(MinorUnit::new(6540).into()), currency: Some(api_enums::Currency::USD), capture_method: Some(api_enums::CaptureMethod::Automatic), - amount_to_capture: Some(6540), + amount_to_capture: Some(MinorUnit::new(6540)), capture_on: Some(datetime!(2022-09-10 11:12)), confirm: Some(true), customer_id: None, @@ -351,7 +352,7 @@ async fn payments_create_core() { let expected_response = api::PaymentsResponse { payment_id: Some("pay_mbabizu24mvu3mela5njyhpit10".to_string()), status: api_enums::IntentStatus::Succeeded, - amount: 6540, + amount: MinorUnit::new(6540), amount_capturable: None, amount_received: None, client_secret: None, @@ -486,10 +487,10 @@ async fn payments_create_core_adyen_no_redirect() { let req = api::PaymentsRequest { payment_id: Some(api::PaymentIdType::PaymentIntentId(payment_id.clone())), merchant_id: Some(merchant_id.clone()), - amount: Some(6540.into()), + amount: Some(MinorUnit::new(6540).into()), currency: Some(api_enums::Currency::USD), capture_method: Some(api_enums::CaptureMethod::Automatic), - amount_to_capture: Some(6540), + amount_to_capture: Some(MinorUnit::new(6540)), capture_on: Some(datetime!(2022-09-10 10:11:12)), confirm: Some(true), customer_id: Some(customer_id), @@ -533,7 +534,7 @@ async fn payments_create_core_adyen_no_redirect() { api::PaymentsResponse { payment_id: Some(payment_id.clone()), status: api_enums::IntentStatus::Processing, - amount: 6540, + amount: MinorUnit::new(6540), amount_capturable: None, amount_received: None, client_secret: None, diff --git a/crates/router/tests/payments2.rs b/crates/router/tests/payments2.rs index 278ee80a92af..288a1af3006c 100644 --- a/crates/router/tests/payments2.rs +++ b/crates/router/tests/payments2.rs @@ -2,6 +2,7 @@ mod utils; +use common_utils::types::MinorUnit; use router::{ core::payments, db::StorageImpl, @@ -63,10 +64,10 @@ async fn payments_create_core() { "pay_mbabizu24mvu3mela5njyhpit10".to_string(), )), merchant_id: Some("jarnura".to_string()), - amount: Some(6540.into()), + amount: Some(MinorUnit::new(6540).into()), currency: Some(api_enums::Currency::USD), capture_method: Some(api_enums::CaptureMethod::Automatic), - amount_to_capture: Some(6540), + amount_to_capture: Some(MinorUnit::new(6540)), capture_on: Some(datetime!(2022-09-10 10:11:12)), confirm: Some(true), customer_id: None, @@ -111,7 +112,7 @@ async fn payments_create_core() { let expected_response = api::PaymentsResponse { payment_id: Some("pay_mbabizu24mvu3mela5njyhpit10".to_string()), status: api_enums::IntentStatus::Succeeded, - amount: 6540, + amount: MinorUnit::new(6540), amount_capturable: None, amount_received: None, client_secret: None, @@ -253,10 +254,10 @@ async fn payments_create_core_adyen_no_redirect() { let req = api::PaymentsRequest { payment_id: Some(api::PaymentIdType::PaymentIntentId(payment_id.clone())), merchant_id: Some(merchant_id.clone()), - amount: Some(6540.into()), + amount: Some(MinorUnit::new(6540).into()), currency: Some(api_enums::Currency::USD), capture_method: Some(api_enums::CaptureMethod::Automatic), - amount_to_capture: Some(6540), + amount_to_capture: Some(MinorUnit::new(6540)), capture_on: Some(datetime!(2022-09-10 10:11:12)), confirm: Some(true), customer_id: Some(customer_id), @@ -301,7 +302,7 @@ async fn payments_create_core_adyen_no_redirect() { api::PaymentsResponse { payment_id: Some(payment_id.clone()), status: api_enums::IntentStatus::Processing, - amount: 6540, + amount: MinorUnit::new(6540), amount_capturable: None, amount_received: None, client_secret: None, diff --git a/crates/router_env/Cargo.toml b/crates/router_env/Cargo.toml index 3b95d5f22fd5..b9b1184fedf9 100644 --- a/crates/router_env/Cargo.toml +++ b/crates/router_env/Cargo.toml @@ -22,7 +22,7 @@ serde_path_to_error = "0.1.16" strum = { version = "0.26.2", features = ["derive"] } time = { version = "0.3.35", default-features = false, features = ["formatting"] } tokio = { version = "1.37.0" } -tracing = { version = "0.1.40" } +tracing = { workspace = true } tracing-actix-web = { version = "0.7.10", features = ["opentelemetry_0_19", "uuid_v7"], optional = true } tracing-appender = { version = "0.2.3" } tracing-attributes = "0.1.27" diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index ffa492bc60d6..35b55b6fb979 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -180,6 +180,8 @@ pub enum Flow { PayoutsAccounts, /// Payments Redirect flow. PaymentsRedirect, + /// Payemnts Complete Authorize Flow + PaymentsCompleteAuthorize, /// Refunds create flow. RefundsCreate, /// Refunds retrieve flow. @@ -404,6 +406,12 @@ pub enum Flow { TotpBegin, /// Verify TOTP TotpVerify, + /// Verify Access Code + RecoveryCodeVerify, + /// Generate or Regenerate recovery codes + RecoveryCodesGenerate, + // Terminate two factor authentication + TerminateTwoFactorAuth, /// List initial webhook delivery attempts WebhookEventInitialDeliveryAttemptList, /// List delivery attempts for a webhook event diff --git a/crates/scheduler/Cargo.toml b/crates/scheduler/Cargo.toml index b98f212d6740..73832a1ad6a9 100644 --- a/crates/scheduler/Cargo.toml +++ b/crates/scheduler/Cargo.toml @@ -7,7 +7,7 @@ license.workspace = true [features] default = ["kv_store", "olap"] -olap = ["storage_impl/olap"] +olap = ["storage_impl/olap", "hyperswitch_domain_models/olap"] kv_store = [] email = ["external_services/email"] @@ -35,6 +35,7 @@ masking = { version = "0.1.0", path = "../masking" } redis_interface = { version = "0.1.0", path = "../redis_interface" } router_env = { version = "0.1.0", path = "../router_env", features = ["log_extra_implicit_fields", "log_custom_entries_to_extra"] } storage_impl = { version = "0.1.0", path = "../storage_impl", default-features = false } +hyperswitch_domain_models = {version = "0.1.0", path = "../hyperswitch_domain_models", default-features = false } # [[bin]] # name = "scheduler" diff --git a/crates/scheduler/src/errors.rs b/crates/scheduler/src/errors.rs index e4fd56ba8d29..e630287c316a 100644 --- a/crates/scheduler/src/errors.rs +++ b/crates/scheduler/src/errors.rs @@ -1,6 +1,7 @@ pub use common_utils::errors::{ParsingError, ValidationError}; #[cfg(feature = "email")] use external_services::email::EmailError; +use hyperswitch_domain_models::errors::api_error_response::ApiErrorResponse; pub use redis_interface::errors::RedisError; pub use storage_impl::errors::ApplicationError; use storage_impl::errors::StorageError; @@ -88,6 +89,12 @@ impl From for ProcessTrackerError { } } +impl PTError for ApiErrorResponse { + fn to_pt_error(&self) -> ProcessTrackerError { + ProcessTrackerError::EApiErrorResponse + } +} + impl From> for ProcessTrackerError { diff --git a/crates/storage_impl/src/lib.rs b/crates/storage_impl/src/lib.rs index 7a3ef30bef3a..0963cd2b41e2 100644 --- a/crates/storage_impl/src/lib.rs +++ b/crates/storage_impl/src/lib.rs @@ -151,6 +151,7 @@ pub struct KVRouterStore { drainer_num_partitions: u8, ttl_for_kv: u32, pub request_id: Option, + soft_kill_mode: bool, } #[async_trait::async_trait] @@ -159,14 +160,16 @@ where RouterStore: DatabaseStore, T: DatabaseStore, { - type Config = (RouterStore, String, u8, u32); + type Config = (RouterStore, String, u8, u32, Option); async fn new(config: Self::Config, _test_transaction: bool) -> StorageResult { - let (router_store, drainer_stream_name, drainer_num_partitions, ttl_for_kv) = config; + let (router_store, drainer_stream_name, drainer_num_partitions, ttl_for_kv, soft_kill_mode) = + config; Ok(Self::from_store( router_store, drainer_stream_name, drainer_num_partitions, ttl_for_kv, + soft_kill_mode, )) } fn get_master_pool(&self) -> &PgPool { @@ -191,6 +194,7 @@ impl KVRouterStore { drainer_stream_name: String, drainer_num_partitions: u8, ttl_for_kv: u32, + soft_kill: Option, ) -> Self { let request_id = store.request_id.clone(); @@ -200,6 +204,7 @@ impl KVRouterStore { drainer_num_partitions, ttl_for_kv, request_id, + soft_kill_mode: soft_kill.unwrap_or(false), } } diff --git a/crates/storage_impl/src/lookup.rs b/crates/storage_impl/src/lookup.rs index f8daabf2e9eb..67b8635aba4a 100644 --- a/crates/storage_impl/src/lookup.rs +++ b/crates/storage_impl/src/lookup.rs @@ -12,7 +12,7 @@ use redis_interface::SetnxReply; use crate::{ diesel_error_to_data_error, errors::RedisErrorExt, - redis::kv_store::{kv_wrapper, KvOperation, PartitionKey}, + redis::kv_store::{decide_storage_scheme, kv_wrapper, KvOperation, Op, PartitionKey}, utils::{self, try_redis_get_else_try_database_get}, DatabaseStore, KVRouterStore, RouterStore, }; @@ -71,6 +71,8 @@ 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; match storage_scheme { storage_enums::MerchantStorageScheme::PostgresOnly => { self.router_store @@ -124,6 +126,8 @@ 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; match storage_scheme { storage_enums::MerchantStorageScheme::PostgresOnly => database_call().await, storage_enums::MerchantStorageScheme::RedisKv => { diff --git a/crates/storage_impl/src/metrics.rs b/crates/storage_impl/src/metrics.rs index 29bca2a007b7..2f22d578133e 100644 --- a/crates/storage_impl/src/metrics.rs +++ b/crates/storage_impl/src/metrics.rs @@ -10,3 +10,4 @@ counter_metric!(KV_OPERATION_SUCCESSFUL, GLOBAL_METER); counter_metric!(KV_OPERATION_FAILED, GLOBAL_METER); counter_metric!(KV_PUSHED_TO_DRAINER, GLOBAL_METER); counter_metric!(KV_FAILED_TO_PUSH_TO_DRAINER, GLOBAL_METER); +counter_metric!(KV_SOFT_KILL_ACTIVE_UPDATE, GLOBAL_METER); diff --git a/crates/storage_impl/src/mock_db/payment_attempt.rs b/crates/storage_impl/src/mock_db/payment_attempt.rs index 33ef6d416f23..0e8df898f126 100644 --- a/crates/storage_impl/src/mock_db/payment_attempt.rs +++ b/crates/storage_impl/src/mock_db/payment_attempt.rs @@ -157,6 +157,9 @@ impl PaymentAttemptInterface for MockDb { mandate_data: payment_attempt.mandate_data, payment_method_billing_address_id: payment_attempt.payment_method_billing_address_id, fingerprint_id: payment_attempt.fingerprint_id, + charge_id: payment_attempt.charge_id, + client_source: payment_attempt.client_source, + client_version: payment_attempt.client_version, }; 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 1dfb7ac9d91e..a12b06b29144 100644 --- a/crates/storage_impl/src/mock_db/payment_intent.rs +++ b/crates/storage_impl/src/mock_db/payment_intent.rs @@ -108,6 +108,8 @@ impl PaymentIntentInterface for MockDb { fingerprint_id: new.fingerprint_id, session_expiry: new.session_expiry, request_external_three_ds_authentication: new.request_external_three_ds_authentication, + charges: new.charges, + frm_metadata: new.frm_metadata, }; payment_intents.push(payment_intent.clone()); Ok(payment_intent) diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index d090f646da32..1e60c3e02f22 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -1,5 +1,5 @@ use api_models::enums::{AuthenticationType, Connector, PaymentMethod, PaymentMethodType}; -use common_utils::{errors::CustomResult, fallback_reverse_lookup_not_found}; +use common_utils::{errors::CustomResult, fallback_reverse_lookup_not_found, types::MinorUnit}; use diesel_models::{ enums::{ MandateAmountData as DieselMandateAmountData, MandateDataType as DieselMandateType, @@ -31,7 +31,7 @@ use crate::{ diesel_error_to_data_error, errors::RedisErrorExt, lookup::ReverseLookupInterface, - redis::kv_store::{kv_wrapper, KvOperation, PartitionKey}, + redis::kv_store::{decide_storage_scheme, kv_wrapper, KvOperation, Op, PartitionKey}, utils::{pg_connection_read, pg_connection_write, try_redis_get_else_try_database_get}, DataModelExt, DatabaseStore, KVRouterStore, RouterStore, }; @@ -333,6 +333,9 @@ 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; match storage_scheme { MerchantStorageScheme::PostgresOnly => { self.router_store @@ -410,6 +413,9 @@ impl PaymentAttemptInterface for KVRouterStore { .payment_method_billing_address_id .clone(), fingerprint_id: payment_attempt.fingerprint_id.clone(), + charge_id: payment_attempt.charge_id.clone(), + client_source: payment_attempt.client_source.clone(), + client_version: payment_attempt.client_version.clone(), }; let field = format!("pa_{}", created_attempt.attempt_id); @@ -468,6 +474,17 @@ impl PaymentAttemptInterface for KVRouterStore { payment_attempt: PaymentAttemptUpdate, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { + let key = PartitionKey::MerchantIdPaymentId { + merchant_id: &this.merchant_id, + payment_id: &this.payment_id, + }; + let field = format!("pa_{}", this.attempt_id); + let storage_scheme = decide_storage_scheme::<_, DieselPaymentAttempt>( + self, + storage_scheme, + Op::Update(key.clone(), &field, Some(&this.updated_by)), + ) + .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { self.router_store @@ -475,10 +492,6 @@ impl PaymentAttemptInterface for KVRouterStore { .await } MerchantStorageScheme::RedisKv => { - let key = PartitionKey::MerchantIdPaymentId { - merchant_id: &this.merchant_id, - payment_id: &this.payment_id, - }; let key_str = key.to_string(); let old_connector_transaction_id = &this.connector_transaction_id; let old_preprocessing_id = &this.preprocessing_step_id; @@ -491,7 +504,6 @@ impl PaymentAttemptInterface for KVRouterStore { // Check for database presence as well Maybe use a read replica here ? let redis_value = serde_json::to_string(&updated_attempt) .change_context(errors::StorageError::KVError)?; - let field = format!("pa_{}", updated_attempt.attempt_id); let redis_entry = kv::TypedSql { op: kv::DBOperation::Update { @@ -586,6 +598,8 @@ impl PaymentAttemptInterface for KVRouterStore { merchant_id: &str, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { + let storage_scheme = + decide_storage_scheme::<_, DieselPaymentAttempt>(self, storage_scheme, Op::Find).await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { self.router_store @@ -643,6 +657,8 @@ impl PaymentAttemptInterface for KVRouterStore { storage_scheme, ) }; + let storage_scheme = + decide_storage_scheme::<_, DieselPaymentAttempt>(self, storage_scheme, Op::Find).await; match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, MerchantStorageScheme::RedisKv => { @@ -695,6 +711,8 @@ impl PaymentAttemptInterface for KVRouterStore { storage_scheme, ) }; + let storage_scheme = + decide_storage_scheme::<_, DieselPaymentAttempt>(self, storage_scheme, Op::Find).await; match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, MerchantStorageScheme::RedisKv => { @@ -742,6 +760,8 @@ 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; match storage_scheme { MerchantStorageScheme::PostgresOnly => { self.router_store @@ -802,6 +822,8 @@ 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; match storage_scheme { MerchantStorageScheme::PostgresOnly => { self.router_store @@ -848,6 +870,8 @@ impl PaymentAttemptInterface for KVRouterStore { merchant_id: &str, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { + let storage_scheme = + decide_storage_scheme::<_, DieselPaymentAttempt>(self, storage_scheme, Op::Find).await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { self.router_store @@ -907,6 +931,8 @@ impl PaymentAttemptInterface for KVRouterStore { merchant_id: &str, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { + let storage_scheme = + decide_storage_scheme::<_, DieselPaymentAttempt>(self, storage_scheme, Op::Find).await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { self.router_store @@ -966,6 +992,8 @@ impl PaymentAttemptInterface for KVRouterStore { payment_id: &str, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result, errors::StorageError> { + let storage_scheme = + decide_storage_scheme::<_, DieselPaymentAttempt>(self, storage_scheme, Op::Find).await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { self.router_store @@ -1046,7 +1074,7 @@ impl DataModelExt for MandateAmountData { fn to_storage_model(self) -> Self::StorageModel { DieselMandateAmountData { - amount: self.amount, + amount: self.amount.get_amount_as_i64(), currency: self.currency, start_date: self.start_date, end_date: self.end_date, @@ -1056,7 +1084,7 @@ impl DataModelExt for MandateAmountData { fn from_storage_model(storage_model: Self::StorageModel) -> Self { Self { - amount: storage_model.amount, + amount: MinorUnit::new(storage_model.amount), currency: storage_model.currency, start_date: storage_model.start_date, end_date: storage_model.end_date, @@ -1114,15 +1142,19 @@ impl DataModelExt for PaymentAttempt { merchant_id: self.merchant_id, attempt_id: self.attempt_id, status: self.status, - amount: self.amount, - net_amount: Some(self.net_amount), + amount: self.amount.get_amount_as_i64(), + net_amount: Some(self.net_amount.get_amount_as_i64()), currency: self.currency, save_to_locker: self.save_to_locker, connector: self.connector, error_message: self.error_message, - offer_amount: self.offer_amount, - surcharge_amount: self.surcharge_amount, - tax_amount: self.tax_amount, + offer_amount: self + .offer_amount + .map(|offer_amt| offer_amt.get_amount_as_i64()), + surcharge_amount: self + .surcharge_amount + .map(|surcharge_amt| surcharge_amt.get_amount_as_i64()), + tax_amount: self.tax_amount.map(|tax_amt| tax_amt.get_amount_as_i64()), payment_method_id: self.payment_method_id, payment_method: self.payment_method, connector_transaction_id: self.connector_transaction_id, @@ -1134,7 +1166,9 @@ impl DataModelExt for PaymentAttempt { modified_at: self.modified_at, last_synced: self.last_synced, cancellation_reason: self.cancellation_reason, - amount_to_capture: self.amount_to_capture, + amount_to_capture: self + .amount_to_capture + .map(|capture_amt| capture_amt.get_amount_as_i64()), mandate_id: self.mandate_id, browser_info: self.browser_info, error_code: self.error_code, @@ -1150,7 +1184,7 @@ impl DataModelExt for PaymentAttempt { error_reason: self.error_reason, multiple_capture_count: self.multiple_capture_count, connector_response_reference_id: self.connector_response_reference_id, - amount_capturable: self.amount_capturable, + amount_capturable: self.amount_capturable.get_amount_as_i64(), updated_by: self.updated_by, authentication_data: self.authentication_data, encoded_data: self.encoded_data, @@ -1164,25 +1198,28 @@ impl DataModelExt for PaymentAttempt { mandate_data: self.mandate_data.map(|d| d.to_storage_model()), payment_method_billing_address_id: self.payment_method_billing_address_id, fingerprint_id: self.fingerprint_id, + charge_id: self.charge_id, + client_source: self.client_source, + client_version: self.client_version, } } fn from_storage_model(storage_model: Self::StorageModel) -> Self { Self { - net_amount: storage_model.get_or_calculate_net_amount(), + net_amount: MinorUnit::new(storage_model.get_or_calculate_net_amount()), id: storage_model.id, payment_id: storage_model.payment_id, merchant_id: storage_model.merchant_id, attempt_id: storage_model.attempt_id, status: storage_model.status, - amount: storage_model.amount, + amount: MinorUnit::new(storage_model.amount), currency: storage_model.currency, save_to_locker: storage_model.save_to_locker, connector: storage_model.connector, error_message: storage_model.error_message, - offer_amount: storage_model.offer_amount, - surcharge_amount: storage_model.surcharge_amount, - tax_amount: storage_model.tax_amount, + offer_amount: storage_model.offer_amount.map(MinorUnit::new), + surcharge_amount: storage_model.surcharge_amount.map(MinorUnit::new), + tax_amount: storage_model.tax_amount.map(MinorUnit::new), payment_method_id: storage_model.payment_method_id, payment_method: storage_model.payment_method, connector_transaction_id: storage_model.connector_transaction_id, @@ -1194,7 +1231,7 @@ impl DataModelExt for PaymentAttempt { modified_at: storage_model.modified_at, last_synced: storage_model.last_synced, cancellation_reason: storage_model.cancellation_reason, - amount_to_capture: storage_model.amount_to_capture, + amount_to_capture: storage_model.amount_to_capture.map(MinorUnit::new), mandate_id: storage_model.mandate_id, browser_info: storage_model.browser_info, error_code: storage_model.error_code, @@ -1212,7 +1249,7 @@ impl DataModelExt for PaymentAttempt { error_reason: storage_model.error_reason, multiple_capture_count: storage_model.multiple_capture_count, connector_response_reference_id: storage_model.connector_response_reference_id, - amount_capturable: storage_model.amount_capturable, + amount_capturable: MinorUnit::new(storage_model.amount_capturable), updated_by: storage_model.updated_by, authentication_data: storage_model.authentication_data, encoded_data: storage_model.encoded_data, @@ -1228,6 +1265,9 @@ impl DataModelExt for PaymentAttempt { .map(MandateDetails::from_storage_model), payment_method_billing_address_id: storage_model.payment_method_billing_address_id, fingerprint_id: storage_model.fingerprint_id, + charge_id: storage_model.charge_id, + client_source: storage_model.client_source, + client_version: storage_model.client_version, } } } @@ -1237,19 +1277,23 @@ impl DataModelExt for PaymentAttemptNew { fn to_storage_model(self) -> Self::StorageModel { DieselPaymentAttemptNew { - net_amount: Some(self.net_amount), + net_amount: Some(self.net_amount.get_amount_as_i64()), payment_id: self.payment_id, merchant_id: self.merchant_id, attempt_id: self.attempt_id, status: self.status, - amount: self.amount, + amount: self.amount.get_amount_as_i64(), currency: self.currency, save_to_locker: self.save_to_locker, connector: self.connector, error_message: self.error_message, - offer_amount: self.offer_amount, - surcharge_amount: self.surcharge_amount, - tax_amount: self.tax_amount, + offer_amount: self + .offer_amount + .map(|offer_amt| offer_amt.get_amount_as_i64()), + surcharge_amount: self + .surcharge_amount + .map(|surcharge_amt| surcharge_amt.get_amount_as_i64()), + tax_amount: self.tax_amount.map(|tax_amt| tax_amt.get_amount_as_i64()), payment_method_id: self.payment_method_id, payment_method: self.payment_method, capture_method: self.capture_method, @@ -1260,7 +1304,9 @@ impl DataModelExt for PaymentAttemptNew { modified_at: self.modified_at, last_synced: self.last_synced, cancellation_reason: self.cancellation_reason, - amount_to_capture: self.amount_to_capture, + amount_to_capture: self + .amount_to_capture + .map(|capture_amt| capture_amt.get_amount_as_i64()), mandate_id: self.mandate_id, browser_info: self.browser_info, payment_token: self.payment_token, @@ -1276,7 +1322,7 @@ impl DataModelExt for PaymentAttemptNew { error_reason: self.error_reason, connector_response_reference_id: self.connector_response_reference_id, multiple_capture_count: self.multiple_capture_count, - amount_capturable: self.amount_capturable, + amount_capturable: self.amount_capturable.get_amount_as_i64(), updated_by: self.updated_by, authentication_data: self.authentication_data, encoded_data: self.encoded_data, @@ -1290,24 +1336,27 @@ impl DataModelExt for PaymentAttemptNew { mandate_data: self.mandate_data.map(|d| d.to_storage_model()), payment_method_billing_address_id: self.payment_method_billing_address_id, fingerprint_id: self.fingerprint_id, + charge_id: self.charge_id, + client_source: self.client_source, + client_version: self.client_version, } } fn from_storage_model(storage_model: Self::StorageModel) -> Self { Self { - net_amount: storage_model.get_or_calculate_net_amount(), + net_amount: MinorUnit::new(storage_model.get_or_calculate_net_amount()), payment_id: storage_model.payment_id, merchant_id: storage_model.merchant_id, attempt_id: storage_model.attempt_id, status: storage_model.status, - amount: storage_model.amount, + amount: MinorUnit::new(storage_model.amount), currency: storage_model.currency, save_to_locker: storage_model.save_to_locker, connector: storage_model.connector, error_message: storage_model.error_message, - offer_amount: storage_model.offer_amount, - surcharge_amount: storage_model.surcharge_amount, - tax_amount: storage_model.tax_amount, + offer_amount: storage_model.offer_amount.map(MinorUnit::new), + surcharge_amount: storage_model.surcharge_amount.map(MinorUnit::new), + tax_amount: storage_model.tax_amount.map(MinorUnit::new), payment_method_id: storage_model.payment_method_id, payment_method: storage_model.payment_method, capture_method: storage_model.capture_method, @@ -1318,7 +1367,7 @@ impl DataModelExt for PaymentAttemptNew { modified_at: storage_model.modified_at, last_synced: storage_model.last_synced, cancellation_reason: storage_model.cancellation_reason, - amount_to_capture: storage_model.amount_to_capture, + amount_to_capture: storage_model.amount_to_capture.map(MinorUnit::new), mandate_id: storage_model.mandate_id, browser_info: storage_model.browser_info, payment_token: storage_model.payment_token, @@ -1336,7 +1385,7 @@ impl DataModelExt for PaymentAttemptNew { error_reason: storage_model.error_reason, connector_response_reference_id: storage_model.connector_response_reference_id, multiple_capture_count: storage_model.multiple_capture_count, - amount_capturable: storage_model.amount_capturable, + amount_capturable: MinorUnit::new(storage_model.amount_capturable), updated_by: storage_model.updated_by, authentication_data: storage_model.authentication_data, encoded_data: storage_model.encoded_data, @@ -1352,6 +1401,9 @@ impl DataModelExt for PaymentAttemptNew { .map(MandateDetails::from_storage_model), payment_method_billing_address_id: storage_model.payment_method_billing_address_id, fingerprint_id: storage_model.fingerprint_id, + charge_id: storage_model.charge_id, + client_source: storage_model.client_source, + client_version: storage_model.client_version, } } } @@ -1380,7 +1432,7 @@ impl DataModelExt for PaymentAttemptUpdate { payment_method_billing_address_id, updated_by, } => DieselPaymentAttemptUpdate::Update { - amount, + amount: amount.get_amount_as_i64(), currency, status, authentication_type, @@ -1390,10 +1442,12 @@ impl DataModelExt for PaymentAttemptUpdate { payment_method_type, payment_experience, business_sub_label, - amount_to_capture, + amount_to_capture: amount_to_capture + .map(|capture_amt| capture_amt.get_amount_as_i64()), capture_method, - surcharge_amount, - tax_amount, + surcharge_amount: surcharge_amount + .map(|surcharge_amt| surcharge_amt.get_amount_as_i64()), + tax_amount: tax_amount.map(|tax_amt| tax_amt.get_amount_as_i64()), fingerprint_id, payment_method_billing_address_id, updated_by, @@ -1411,9 +1465,11 @@ impl DataModelExt for PaymentAttemptUpdate { payment_token, connector, straight_through_algorithm, - amount_capturable, - surcharge_amount, - tax_amount, + amount_capturable: amount_capturable + .map(|amount_capturable| amount_capturable.get_amount_as_i64()), + surcharge_amount: surcharge_amount + .map(|surcharge_amt| surcharge_amt.get_amount_as_i64()), + tax_amount: tax_amount.map(|tax_amt| tax_amt.get_amount_as_i64()), updated_by, merchant_connector_id, }, @@ -1470,8 +1526,10 @@ impl DataModelExt for PaymentAttemptUpdate { authentication_connector, authentication_id, payment_method_billing_address_id, + client_source, + client_version, } => DieselPaymentAttemptUpdate::ConfirmUpdate { - amount, + amount: amount.get_amount_as_i64(), currency, status, authentication_type, @@ -1487,9 +1545,11 @@ impl DataModelExt for PaymentAttemptUpdate { straight_through_algorithm, error_code, error_message, - amount_capturable, - surcharge_amount, - tax_amount, + amount_capturable: amount_capturable + .map(|capture_amt| capture_amt.get_amount_as_i64()), + surcharge_amount: surcharge_amount + .map(|surcharge_amt| surcharge_amt.get_amount_as_i64()), + tax_amount: tax_amount.map(|tax_amt| tax_amt.get_amount_as_i64()), fingerprint_id, updated_by, merchant_connector_id: connector_id, @@ -1498,6 +1558,8 @@ impl DataModelExt for PaymentAttemptUpdate { authentication_connector, authentication_id, payment_method_billing_address_id, + client_source, + client_version, }, Self::VoidUpdate { status, @@ -1528,6 +1590,7 @@ impl DataModelExt for PaymentAttemptUpdate { unified_code, unified_message, payment_method_data, + charge_id, } => DieselPaymentAttemptUpdate::ResponseUpdate { status, connector, @@ -1541,13 +1604,15 @@ impl DataModelExt for PaymentAttemptUpdate { error_message, error_reason, connector_response_reference_id, - amount_capturable, + amount_capturable: amount_capturable + .map(|capture_amt| capture_amt.get_amount_as_i64()), updated_by, authentication_data, encoded_data, unified_code, unified_message, payment_method_data, + charge_id, }, Self::UnresolvedResponseUpdate { status, @@ -1591,7 +1656,8 @@ impl DataModelExt for PaymentAttemptUpdate { error_code, error_message, error_reason, - amount_capturable, + amount_capturable: amount_capturable + .map(|capture_amt| capture_amt.get_amount_as_i64()), updated_by, unified_code, unified_message, @@ -1605,7 +1671,8 @@ impl DataModelExt for PaymentAttemptUpdate { } => DieselPaymentAttemptUpdate::CaptureUpdate { multiple_capture_count, updated_by, - amount_to_capture, + amount_to_capture: amount_to_capture + .map(|capture_amt| capture_amt.get_amount_as_i64()), }, Self::PreprocessingUpdate { status, @@ -1641,7 +1708,7 @@ impl DataModelExt for PaymentAttemptUpdate { updated_by, } => DieselPaymentAttemptUpdate::AmountToCaptureUpdate { status, - amount_capturable, + amount_capturable: amount_capturable.get_amount_as_i64(), updated_by, }, Self::ConnectorResponse { @@ -1649,20 +1716,22 @@ impl DataModelExt for PaymentAttemptUpdate { encoded_data, connector_transaction_id, connector, + charge_id, updated_by, } => DieselPaymentAttemptUpdate::ConnectorResponse { authentication_data, encoded_data, connector_transaction_id, connector, + charge_id, updated_by, }, Self::IncrementalAuthorizationAmountUpdate { amount, amount_capturable, } => DieselPaymentAttemptUpdate::IncrementalAuthorizationAmountUpdate { - amount, - amount_capturable, + amount: amount.get_amount_as_i64(), + amount_capturable: amount_capturable.get_amount_as_i64(), }, Self::AuthenticationUpdate { status, @@ -1701,7 +1770,7 @@ impl DataModelExt for PaymentAttemptUpdate { updated_by, payment_method_billing_address_id, } => Self::Update { - amount, + amount: MinorUnit::new(amount), currency, status, authentication_type, @@ -1711,10 +1780,10 @@ impl DataModelExt for PaymentAttemptUpdate { payment_method_type, payment_experience, business_sub_label, - amount_to_capture, + amount_to_capture: amount_to_capture.map(MinorUnit::new), capture_method, - surcharge_amount, - tax_amount, + surcharge_amount: surcharge_amount.map(MinorUnit::new), + tax_amount: tax_amount.map(MinorUnit::new), fingerprint_id, payment_method_billing_address_id, updated_by, @@ -1732,9 +1801,9 @@ impl DataModelExt for PaymentAttemptUpdate { payment_token, connector, straight_through_algorithm, - amount_capturable, - surcharge_amount, - tax_amount, + amount_capturable: amount_capturable.map(MinorUnit::new), + surcharge_amount: surcharge_amount.map(MinorUnit::new), + tax_amount: tax_amount.map(MinorUnit::new), updated_by, merchant_connector_id: connector_id, }, @@ -1773,8 +1842,10 @@ impl DataModelExt for PaymentAttemptUpdate { authentication_connector, authentication_id, payment_method_billing_address_id, + client_source, + client_version, } => Self::ConfirmUpdate { - amount, + amount: MinorUnit::new(amount), currency, status, authentication_type, @@ -1790,9 +1861,9 @@ impl DataModelExt for PaymentAttemptUpdate { straight_through_algorithm, error_code, error_message, - amount_capturable, - surcharge_amount, - tax_amount, + amount_capturable: amount_capturable.map(MinorUnit::new), + surcharge_amount: surcharge_amount.map(MinorUnit::new), + tax_amount: tax_amount.map(MinorUnit::new), fingerprint_id, updated_by, merchant_connector_id: connector_id, @@ -1801,6 +1872,8 @@ impl DataModelExt for PaymentAttemptUpdate { authentication_connector, authentication_id, payment_method_billing_address_id, + client_source, + client_version, }, DieselPaymentAttemptUpdate::VoidUpdate { status, @@ -1849,6 +1922,7 @@ impl DataModelExt for PaymentAttemptUpdate { unified_code, unified_message, payment_method_data, + charge_id, } => Self::ResponseUpdate { status, connector, @@ -1862,13 +1936,14 @@ impl DataModelExt for PaymentAttemptUpdate { error_message, error_reason, connector_response_reference_id, - amount_capturable, + amount_capturable: amount_capturable.map(MinorUnit::new), updated_by, authentication_data, encoded_data, unified_code, unified_message, payment_method_data, + charge_id, }, DieselPaymentAttemptUpdate::UnresolvedResponseUpdate { status, @@ -1912,7 +1987,7 @@ impl DataModelExt for PaymentAttemptUpdate { error_code, error_message, error_reason, - amount_capturable, + amount_capturable: amount_capturable.map(MinorUnit::new), updated_by, unified_code, unified_message, @@ -1924,7 +1999,7 @@ impl DataModelExt for PaymentAttemptUpdate { multiple_capture_count, updated_by, } => Self::CaptureUpdate { - amount_to_capture, + amount_to_capture: amount_to_capture.map(MinorUnit::new), multiple_capture_count, updated_by, }, @@ -1962,7 +2037,7 @@ impl DataModelExt for PaymentAttemptUpdate { updated_by, } => Self::AmountToCaptureUpdate { status, - amount_capturable, + amount_capturable: MinorUnit::new(amount_capturable), updated_by, }, DieselPaymentAttemptUpdate::ConnectorResponse { @@ -1970,20 +2045,22 @@ impl DataModelExt for PaymentAttemptUpdate { encoded_data, connector_transaction_id, connector, + charge_id, updated_by, } => Self::ConnectorResponse { authentication_data, encoded_data, connector_transaction_id, connector, + charge_id, updated_by, }, DieselPaymentAttemptUpdate::IncrementalAuthorizationAmountUpdate { amount, amount_capturable, } => Self::IncrementalAuthorizationAmountUpdate { - amount, - amount_capturable, + amount: MinorUnit::new(amount), + amount_capturable: MinorUnit::new(amount_capturable), }, DieselPaymentAttemptUpdate::AuthenticationUpdate { status, diff --git a/crates/storage_impl/src/payments/payment_intent.rs b/crates/storage_impl/src/payments/payment_intent.rs index 2cbcbaaf01e6..f40a9c3e2d4d 100644 --- a/crates/storage_impl/src/payments/payment_intent.rs +++ b/crates/storage_impl/src/payments/payment_intent.rs @@ -43,7 +43,7 @@ use crate::connection; use crate::{ diesel_error_to_data_error, errors::RedisErrorExt, - redis::kv_store::{kv_wrapper, KvOperation, PartitionKey}, + redis::kv_store::{decide_storage_scheme, kv_wrapper, KvOperation, Op, PartitionKey}, utils::{self, pg_connection_read, pg_connection_write}, DataModelExt, DatabaseStore, KVRouterStore, }; @@ -55,6 +55,15 @@ impl PaymentIntentInterface for KVRouterStore { new: PaymentIntentNew, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { + let merchant_id = new.merchant_id.clone(); + let payment_id = new.payment_id.clone(); + let field = format!("pi_{}", new.payment_id); + 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; match storage_scheme { MerchantStorageScheme::PostgresOnly => { self.router_store @@ -63,14 +72,7 @@ impl PaymentIntentInterface for KVRouterStore { } MerchantStorageScheme::RedisKv => { - let merchant_id = new.merchant_id.clone(); - let payment_id = new.payment_id.clone(); - let key = PartitionKey::MerchantIdPaymentId { - merchant_id: &merchant_id, - payment_id: &payment_id, - }; let key_str = key.to_string(); - let field = format!("pi_{}", new.payment_id); let created_intent = PaymentIntent { id: 0i32, payment_id: new.payment_id.clone(), @@ -83,6 +85,7 @@ impl PaymentIntentInterface for KVRouterStore { description: new.description.clone(), return_url: new.return_url.clone(), metadata: new.metadata.clone(), + frm_metadata: new.frm_metadata.clone(), connector_id: new.connector_id.clone(), shipping_address_id: new.shipping_address_id.clone(), billing_address_id: new.billing_address_id.clone(), @@ -115,6 +118,7 @@ impl PaymentIntentInterface for KVRouterStore { session_expiry: new.session_expiry, request_external_three_ds_authentication: new .request_external_three_ds_authentication, + charges: new.charges.clone(), }; let redis_entry = kv::TypedSql { op: kv::DBOperation::Insert { @@ -154,6 +158,19 @@ impl PaymentIntentInterface for KVRouterStore { payment_intent_update: PaymentIntentUpdate, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { + let merchant_id = this.merchant_id.clone(); + let payment_id = this.payment_id.clone(); + let key = PartitionKey::MerchantIdPaymentId { + merchant_id: &merchant_id, + payment_id: &payment_id, + }; + let field = format!("pi_{}", this.payment_id); + let storage_scheme = decide_storage_scheme::<_, DieselPaymentIntent>( + self, + storage_scheme, + Op::Update(key.clone(), &field, Some(&this.updated_by)), + ) + .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { self.router_store @@ -161,14 +178,7 @@ impl PaymentIntentInterface for KVRouterStore { .await } MerchantStorageScheme::RedisKv => { - let merchant_id = this.merchant_id.clone(); - let payment_id = this.payment_id.clone(); - let key = PartitionKey::MerchantIdPaymentId { - merchant_id: &merchant_id, - payment_id: &payment_id, - }; let key_str = key.to_string(); - let field = format!("pi_{}", this.payment_id); let diesel_intent_update = payment_intent_update.to_storage_model(); let origin_diesel_intent = this.to_storage_model(); @@ -224,6 +234,8 @@ impl PaymentIntentInterface for KVRouterStore { er.change_context(new_err) }) }; + let storage_scheme = + decide_storage_scheme::<_, DieselPaymentIntent>(self, storage_scheme, Op::Find).await; match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, @@ -806,6 +818,7 @@ impl DataModelExt for PaymentIntentNew { description: self.description, return_url: self.return_url, metadata: self.metadata, + frm_metadata: self.frm_metadata, connector_id: self.connector_id, shipping_address_id: self.shipping_address_id, billing_address_id: self.billing_address_id, @@ -837,6 +850,7 @@ impl DataModelExt for PaymentIntentNew { fingerprint_id: self.fingerprint_id, session_expiry: self.session_expiry, request_external_three_ds_authentication: self.request_external_three_ds_authentication, + charges: self.charges, } } @@ -852,6 +866,7 @@ impl DataModelExt for PaymentIntentNew { description: storage_model.description, return_url: storage_model.return_url, metadata: storage_model.metadata, + frm_metadata: storage_model.frm_metadata, connector_id: storage_model.connector_id, shipping_address_id: storage_model.shipping_address_id, billing_address_id: storage_model.billing_address_id, @@ -884,6 +899,7 @@ impl DataModelExt for PaymentIntentNew { session_expiry: storage_model.session_expiry, request_external_three_ds_authentication: storage_model .request_external_three_ds_authentication, + charges: storage_model.charges, } } } @@ -935,6 +951,8 @@ impl DataModelExt for PaymentIntent { 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, } } @@ -983,6 +1001,8 @@ impl DataModelExt for PaymentIntent { session_expiry: storage_model.session_expiry, request_external_three_ds_authentication: storage_model .request_external_three_ds_authentication, + charges: storage_model.charges, + frm_metadata: storage_model.frm_metadata, } } } @@ -1070,6 +1090,7 @@ impl DataModelExt for PaymentIntentUpdate { fingerprint_id, session_expiry, request_external_three_ds_authentication, + frm_metadata, } => DieselPaymentIntentUpdate::Update { amount, currency, @@ -1091,6 +1112,7 @@ impl DataModelExt for PaymentIntentUpdate { fingerprint_id, session_expiry, request_external_three_ds_authentication, + frm_metadata, }, Self::PaymentAttemptAndAttemptCountUpdate { active_attempt_id, @@ -1145,6 +1167,11 @@ impl DataModelExt for PaymentIntentUpdate { } => DieselPaymentIntentUpdate::AuthorizationCountUpdate { authorization_count, }, + Self::CompleteAuthorizeUpdate { + shipping_address_id, + } => DieselPaymentIntentUpdate::CompleteAuthorizeUpdate { + shipping_address_id, + }, } } diff --git a/crates/storage_impl/src/payouts/payout_attempt.rs b/crates/storage_impl/src/payouts/payout_attempt.rs index cf1e9eeb20a2..5f04592596ae 100644 --- a/crates/storage_impl/src/payouts/payout_attempt.rs +++ b/crates/storage_impl/src/payouts/payout_attempt.rs @@ -30,7 +30,7 @@ use crate::{ diesel_error_to_data_error, errors::RedisErrorExt, lookup::ReverseLookupInterface, - redis::kv_store::{kv_wrapper, KvOperation, PartitionKey}, + redis::kv_store::{decide_storage_scheme, kv_wrapper, KvOperation, Op, PartitionKey}, utils::{self, pg_connection_read, pg_connection_write}, DataModelExt, DatabaseStore, KVRouterStore, }; @@ -44,6 +44,8 @@ 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; match storage_scheme { MerchantStorageScheme::PostgresOnly => { self.router_store @@ -137,6 +139,17 @@ impl PayoutAttemptInterface for KVRouterStore { payouts: &Payouts, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { + let key = PartitionKey::MerchantIdPayoutAttemptId { + merchant_id: &this.merchant_id, + payout_attempt_id: &this.payout_id, + }; + let field = format!("poa_{}", this.payout_attempt_id); + let storage_scheme = decide_storage_scheme::<_, DieselPayoutAttempt>( + self, + storage_scheme, + Op::Update(key.clone(), &field, None), + ) + .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { self.router_store @@ -144,12 +157,7 @@ impl PayoutAttemptInterface for KVRouterStore { .await } MerchantStorageScheme::RedisKv => { - let key = PartitionKey::MerchantIdPayoutAttemptId { - merchant_id: &this.merchant_id, - payout_attempt_id: &this.payout_id, - }; let key_str = key.to_string(); - let field = format!("poa_{}", this.payout_attempt_id); let diesel_payout_update = payout_update.clone().to_storage_model(); let origin_diesel_payout = this.clone().to_storage_model(); @@ -232,6 +240,8 @@ 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; 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 19921121a94d..92835dee59cf 100644 --- a/crates/storage_impl/src/payouts/payouts.rs +++ b/crates/storage_impl/src/payouts/payouts.rs @@ -38,7 +38,7 @@ use crate::connection; use crate::{ diesel_error_to_data_error, errors::RedisErrorExt, - redis::kv_store::{kv_wrapper, KvOperation, PartitionKey}, + redis::kv_store::{decide_storage_scheme, kv_wrapper, KvOperation, Op, PartitionKey}, utils::{self, pg_connection_read, pg_connection_write}, DataModelExt, DatabaseStore, KVRouterStore, }; @@ -51,6 +51,8 @@ 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; match storage_scheme { MerchantStorageScheme::PostgresOnly => { self.router_store.insert_payout(new, storage_scheme).await @@ -128,6 +130,17 @@ impl PayoutsInterface for KVRouterStore { payout_attempt: &PayoutAttempt, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { + let key = PartitionKey::MerchantIdPayoutId { + merchant_id: &this.merchant_id, + payout_id: &this.payout_id, + }; + let field = format!("po_{}", this.payout_id); + let storage_scheme = decide_storage_scheme::<_, DieselPayouts>( + self, + storage_scheme, + Op::Update(key.clone(), &field, None), + ) + .await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { self.router_store @@ -135,12 +148,7 @@ impl PayoutsInterface for KVRouterStore { .await } MerchantStorageScheme::RedisKv => { - let key = PartitionKey::MerchantIdPayoutId { - merchant_id: &this.merchant_id, - payout_id: &this.payout_id, - }; let key_str = key.to_string(); - let field = format!("po_{}", this.payout_id); let diesel_payout_update = payout_update.to_storage_model(); let origin_diesel_payout = this.clone().to_storage_model(); @@ -194,6 +202,8 @@ impl PayoutsInterface for KVRouterStore { er.change_context(new_err) }) }; + let storage_scheme = + decide_storage_scheme::<_, DieselPayouts>(self, storage_scheme, Op::Find).await; match storage_scheme { MerchantStorageScheme::PostgresOnly => database_call().await, MerchantStorageScheme::RedisKv => { @@ -236,6 +246,8 @@ impl PayoutsInterface for KVRouterStore { er.change_context(new_err) }) }; + let storage_scheme = + decide_storage_scheme::<_, DieselPayouts>(self, storage_scheme, Op::Find).await; match storage_scheme { MerchantStorageScheme::PostgresOnly => { let maybe_payouts = database_call().await?; diff --git a/crates/storage_impl/src/redis/cache.rs b/crates/storage_impl/src/redis/cache.rs index dc2a2225dc91..edcbed21ad6e 100644 --- a/crates/storage_impl/src/redis/cache.rs +++ b/crates/storage_impl/src/redis/cache.rs @@ -21,6 +21,15 @@ const CONFIG_CACHE_PREFIX: &str = "config"; /// Prefix for accounts cache key const ACCOUNTS_CACHE_PREFIX: &str = "accounts"; +/// Prefix for routing cache key +const ROUTING_CACHE_PREFIX: &str = "routing"; + +/// Prefix for kgraph cache key +const CGRAPH_CACHE_PREFIX: &str = "cgraph"; + +/// Prefix for PM Filter cgraph cache key +const PM_FILTERS_CGRAPH_CACHE_PREFIX: &str = "pm_filters_cgraph"; + /// Prefix for all kinds of cache key const ALL_CACHE_PREFIX: &str = "all_cache_kind"; @@ -40,6 +49,18 @@ pub static CONFIG_CACHE: Lazy = Lazy::new(|| Cache::new(CACHE_TTL, CACHE_ pub static ACCOUNTS_CACHE: Lazy = Lazy::new(|| Cache::new(CACHE_TTL, CACHE_TTI, Some(MAX_CAPACITY))); +/// Routing Cache +pub static ROUTING_CACHE: Lazy = + Lazy::new(|| Cache::new(CACHE_TTL, CACHE_TTI, Some(MAX_CAPACITY))); + +/// CGraph Cache +pub static CGRAPH_CACHE: Lazy = + Lazy::new(|| Cache::new(CACHE_TTL, CACHE_TTI, Some(MAX_CAPACITY))); + +/// PM Filter CGraph Cache +pub static PM_FILTERS_CGRAPH_CACHE: Lazy = + Lazy::new(|| Cache::new(CACHE_TTL, CACHE_TTI, Some(MAX_CAPACITY))); + /// Trait which defines the behaviour of types that's gonna be stored in Cache pub trait Cacheable: Any + Send + Sync + DynClone { fn as_any(&self) -> &dyn Any; @@ -48,6 +69,9 @@ pub trait Cacheable: Any + Send + Sync + DynClone { pub enum CacheKind<'a> { Config(Cow<'a, str>), Accounts(Cow<'a, str>), + Routing(Cow<'a, str>), + CGraph(Cow<'a, str>), + PmFiltersCGraph(Cow<'a, str>), All(Cow<'a, str>), } @@ -56,6 +80,9 @@ impl<'a> From> for RedisValue { let value = match kind { CacheKind::Config(s) => format!("{CONFIG_CACHE_PREFIX},{s}"), CacheKind::Accounts(s) => format!("{ACCOUNTS_CACHE_PREFIX},{s}"), + CacheKind::Routing(s) => format!("{ROUTING_CACHE_PREFIX},{s}"), + CacheKind::CGraph(s) => format!("{CGRAPH_CACHE_PREFIX},{s}"), + CacheKind::PmFiltersCGraph(s) => format!("{PM_FILTERS_CGRAPH_CACHE_PREFIX},{s}"), CacheKind::All(s) => format!("{ALL_CACHE_PREFIX},{s}"), }; Self::from_string(value) @@ -73,6 +100,11 @@ impl<'a> TryFrom for CacheKind<'a> { match split.0 { ACCOUNTS_CACHE_PREFIX => Ok(Self::Accounts(Cow::Owned(split.1.to_string()))), CONFIG_CACHE_PREFIX => Ok(Self::Config(Cow::Owned(split.1.to_string()))), + ROUTING_CACHE_PREFIX => Ok(Self::Routing(Cow::Owned(split.1.to_string()))), + CGRAPH_CACHE_PREFIX => Ok(Self::CGraph(Cow::Owned(split.1.to_string()))), + PM_FILTERS_CGRAPH_CACHE_PREFIX => { + Ok(Self::PmFiltersCGraph(Cow::Owned(split.1.to_string()))) + } ALL_CACHE_PREFIX => Ok(Self::All(Cow::Owned(split.1.to_string()))), _ => Err(validation_err.into()), } @@ -94,13 +126,6 @@ pub struct Cache { inner: MokaCache>, } -impl std::ops::Deref for Cache { - type Target = MokaCache>; - fn deref(&self) -> &Self::Target { - &self.inner - } -} - impl Cache { /// With given `time_to_live` and `time_to_idle` creates a moka cache. /// @@ -122,16 +147,21 @@ impl Cache { } pub async fn push(&self, key: String, val: T) { - self.insert(key, Arc::new(val)).await; + self.inner.insert(key, Arc::new(val)).await; } pub async fn get_val(&self, key: &str) -> Option { - let val = self.get(key).await?; + let val = self.inner.get(key).await?; (*val).as_any().downcast_ref::().cloned() } + /// Check if a key exists in cache + pub async fn exists(&self, key: &str) -> bool { + self.inner.contains_key(key) + } + pub async fn remove(&self, key: &str) { - self.invalidate(key).await; + self.inner.invalidate(key).await; } } @@ -208,7 +238,7 @@ where Fut: futures::Future> + Send, { let data = fun().await?; - in_memory.async_map(|cache| cache.invalidate(key)).await; + in_memory.async_map(|cache| cache.remove(key)).await; let redis_conn = store .get_redis_conn() diff --git a/crates/storage_impl/src/redis/kv_store.rs b/crates/storage_impl/src/redis/kv_store.rs index 7853763f32f9..c39e67eb841b 100644 --- a/crates/storage_impl/src/redis/kv_store.rs +++ b/crates/storage_impl/src/redis/kv_store.rs @@ -1,6 +1,7 @@ use std::{fmt::Debug, sync::Arc}; use common_utils::errors::CustomResult; +use diesel_models::enums::MerchantStorageScheme; use error_stack::report; use redis_interface::errors::RedisError; use router_derive::TryGetEnumVariant; @@ -20,6 +21,7 @@ pub trait KvStorePartition { } #[allow(unused)] +#[derive(Clone)] pub enum PartitionKey<'a> { MerchantIdPaymentId { merchant_id: &'a str, @@ -235,3 +237,65 @@ where err }) } + +pub enum Op<'a> { + Insert, + Update(PartitionKey<'a>, &'a str, Option<&'a str>), + Find, +} + +impl<'a> std::fmt::Display for Op<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Op::Insert => f.write_str("insert"), + Op::Find => f.write_str("find"), + Op::Update(p_key, _, updated_by) => { + f.write_str(&format!("update_{} for updated_by_{:?}", p_key, updated_by)) + } + } + } +} + +pub async fn decide_storage_scheme<'a, T, D>( + store: &KVRouterStore, + storage_scheme: MerchantStorageScheme, + operation: Op<'a>, +) -> MerchantStorageScheme +where + D: de::DeserializeOwned + + serde::Serialize + + Debug + + KvStorePartition + + UniqueConstraints + + Sync, + T: crate::database::store::DatabaseStore, +{ + if store.soft_kill_mode { + let ops = operation.to_string(); + let updated_scheme = match operation { + Op::Insert => MerchantStorageScheme::PostgresOnly, + Op::Find => MerchantStorageScheme::RedisKv, + Op::Update(_, _, Some("postgres_only")) => MerchantStorageScheme::PostgresOnly, + Op::Update(partition_key, field, Some(_updated_by)) => { + match kv_wrapper::(store, KvOperation::::HGet(field), partition_key) + .await + { + Ok(_) => { + metrics::KV_SOFT_KILL_ACTIVE_UPDATE.add(&metrics::CONTEXT, 1, &[]); + MerchantStorageScheme::RedisKv + } + Err(_) => MerchantStorageScheme::PostgresOnly, + } + } + + Op::Update(_, _, None) => MerchantStorageScheme::PostgresOnly, + }; + + let type_name = std::any::type_name::(); + logger::info!(soft_kill_mode = "decide_storage_scheme", decided_scheme = %updated_scheme, configured_scheme = %storage_scheme,entity = %type_name, operation = %ops); + + updated_scheme + } else { + storage_scheme + } +} diff --git a/crates/storage_impl/src/redis/pub_sub.rs b/crates/storage_impl/src/redis/pub_sub.rs index 349d1872d2ab..e4ebaf471d4a 100644 --- a/crates/storage_impl/src/redis/pub_sub.rs +++ b/crates/storage_impl/src/redis/pub_sub.rs @@ -2,7 +2,9 @@ use error_stack::ResultExt; use redis_interface::{errors as redis_errors, PubsubInterface, RedisValue}; use router_env::logger; -use crate::redis::cache::{CacheKind, ACCOUNTS_CACHE, CONFIG_CACHE}; +use crate::redis::cache::{ + CacheKind, ACCOUNTS_CACHE, CGRAPH_CACHE, CONFIG_CACHE, PM_FILTERS_CGRAPH_CACHE, ROUTING_CACHE, +}; #[async_trait::async_trait] pub trait PubSubInterface { @@ -60,16 +62,31 @@ impl PubSubInterface for redis_interface::RedisConnectionPool { let key = match key { CacheKind::Config(key) => { - CONFIG_CACHE.invalidate(key.as_ref()).await; + CONFIG_CACHE.remove(key.as_ref()).await; key } CacheKind::Accounts(key) => { - ACCOUNTS_CACHE.invalidate(key.as_ref()).await; + ACCOUNTS_CACHE.remove(key.as_ref()).await; + key + } + CacheKind::CGraph(key) => { + CGRAPH_CACHE.remove(key.as_ref()).await; + key + } + CacheKind::PmFiltersCGraph(key) => { + PM_FILTERS_CGRAPH_CACHE.remove(key.as_ref()).await; + key + } + CacheKind::Routing(key) => { + ROUTING_CACHE.remove(key.as_ref()).await; key } CacheKind::All(key) => { - CONFIG_CACHE.invalidate(key.as_ref()).await; - ACCOUNTS_CACHE.invalidate(key.as_ref()).await; + CONFIG_CACHE.remove(key.as_ref()).await; + ACCOUNTS_CACHE.remove(key.as_ref()).await; + CGRAPH_CACHE.remove(key.as_ref()).await; + PM_FILTERS_CGRAPH_CACHE.remove(key.as_ref()).await; + ROUTING_CACHE.remove(key.as_ref()).await; key } }; diff --git a/cypress-tests/cypress/e2e/ConnectorTest/00003-NoThreeDSAutoCapture.cy.js b/cypress-tests/cypress/e2e/ConnectorTest/00003-NoThreeDSAutoCapture.cy.js index e25b074bc1ed..4dc3f2a3e78f 100644 --- a/cypress-tests/cypress/e2e/ConnectorTest/00003-NoThreeDSAutoCapture.cy.js +++ b/cypress-tests/cypress/e2e/ConnectorTest/00003-NoThreeDSAutoCapture.cy.js @@ -3,6 +3,7 @@ import createConfirmPaymentBody from "../../fixtures/create-confirm-body.json"; import createPaymentBody from "../../fixtures/create-payment-body.json"; import State from "../../utils/State"; import getConnectorDetails from "../ConnectorUtils/utils"; +import * as utils from "../ConnectorUtils/utils"; let globalState; @@ -22,10 +23,20 @@ describe("Card - NoThreeDS payment flow test", () => { }) context("Card-NoThreeDS payment flow test Create and confirm", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); it("create-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.createPaymentIntentTest(createPaymentBody, det, "no_three_ds", "automatic", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PaymentIntent"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createPaymentIntentTest(createPaymentBody, req_data, res_data, "no_three_ds", "automatic", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("payment_methods-call-test", () => { @@ -33,8 +44,11 @@ describe("Card - NoThreeDS payment flow test", () => { }); it("Confirm No 3DS", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.confirmCallTest(confirmBody, det, true, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DSAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.confirmCallTest(confirmBody, req_data, res_data, true, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { @@ -44,11 +58,21 @@ describe("Card - NoThreeDS payment flow test", () => { }); context("Card-NoThreeDS payment flow test Create+Confirm", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); it("create+confirm-payment-call-test", () => { console.log("confirm -> " + globalState.get("connectorId")); - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.createConfirmPaymentTest(createConfirmPaymentBody, det, "no_three_ds", "automatic", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DSAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createConfirmPaymentTest(createConfirmPaymentBody, req_data, res_data, "no_three_ds", "automatic", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { @@ -57,4 +81,4 @@ describe("Card - NoThreeDS payment flow test", () => { }); -}); \ No newline at end of file +}); diff --git a/cypress-tests/cypress/e2e/ConnectorTest/00004-ThreeDSAutoCapture.cy.js b/cypress-tests/cypress/e2e/ConnectorTest/00004-ThreeDSAutoCapture.cy.js index 662739e51969..c970d9716eff 100644 --- a/cypress-tests/cypress/e2e/ConnectorTest/00004-ThreeDSAutoCapture.cy.js +++ b/cypress-tests/cypress/e2e/ConnectorTest/00004-ThreeDSAutoCapture.cy.js @@ -2,10 +2,18 @@ import confirmBody from "../../fixtures/confirm-body.json"; import createPaymentBody from "../../fixtures/create-payment-body.json"; import State from "../../utils/State"; import getConnectorDetails from "../ConnectorUtils/utils"; +import * as utils from "../ConnectorUtils/utils"; let globalState; describe("Card - ThreeDS payment flow test", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); before("seed global state", () => { @@ -25,8 +33,11 @@ describe("Card - ThreeDS payment flow test", () => { it("create-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.createPaymentIntentTest(createPaymentBody, det, "three_ds", "automatic", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PaymentIntent"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createPaymentIntentTest(createPaymentBody, req_data, res_data, "three_ds", "automatic", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("payment_methods-call-test", () => { @@ -35,9 +46,12 @@ describe("Card - ThreeDS payment flow test", () => { }); it("Confirm 3DS", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["3DS"]; + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["3DSAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; cy.task('cli_log', "GLOBAL STATE -> " + JSON.stringify(globalState.data)); - cy.confirmCallTest(confirmBody, det, true, globalState); + cy.confirmCallTest(confirmBody, req_data, res_data, true, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("Handle redirection", () => { @@ -45,4 +59,4 @@ describe("Card - ThreeDS payment flow test", () => { cy.handleRedirection(globalState, expected_redirection); }) -}); \ No newline at end of file +}); diff --git a/cypress-tests/cypress/e2e/ConnectorTest/00005-NoThreeDSManualCapture.cy.js b/cypress-tests/cypress/e2e/ConnectorTest/00005-NoThreeDSManualCapture.cy.js index a0af6897befb..48a22f5a5c03 100644 --- a/cypress-tests/cypress/e2e/ConnectorTest/00005-NoThreeDSManualCapture.cy.js +++ b/cypress-tests/cypress/e2e/ConnectorTest/00005-NoThreeDSManualCapture.cy.js @@ -4,6 +4,7 @@ import createConfirmPaymentBody from "../../fixtures/create-confirm-body.json"; import createPaymentBody from "../../fixtures/create-payment-body.json"; import State from "../../utils/State"; import getConnectorDetails from "../ConnectorUtils/utils"; +import * as utils from "../ConnectorUtils/utils"; let globalState; @@ -25,10 +26,20 @@ describe("Card - NoThreeDS Manual payment flow test", () => { context("Card - NoThreeDS Manual Full Capture payment flow test", () => { context("payment Create and Confirm", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); it("create-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.createPaymentIntentTest(createPaymentBody, det, "no_three_ds", "manual", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PaymentIntent"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createPaymentIntentTest(createPaymentBody, req_data, res_data, "no_three_ds", "manual", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("payment_methods-call-test", () => { @@ -37,9 +48,12 @@ describe("Card - NoThreeDS Manual payment flow test", () => { it("confirm-call-test", () => { console.log("confirm -> " + globalState.get("connectorId")); - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - console.log("det -> " + det.card); - cy.confirmCallTest(confirmBody, det, true, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DSManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.confirmCallTest(confirmBody, req_data, res_data, true, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { @@ -47,9 +61,12 @@ describe("Card - NoThreeDS Manual payment flow test", () => { }); it("capture-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - console.log("det -> " + det.card); - cy.captureCallTest(captureBody, 6500, det.paymentSuccessfulStatus, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Capture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.captureCallTest(captureBody, req_data, res_data, 6500, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { @@ -59,11 +76,22 @@ describe("Card - NoThreeDS Manual payment flow test", () => { }); context("Payment Create+Confirm", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); + it("create+confirm-payment-call-test", () => { console.log("confirm -> " + globalState.get("connectorId")); - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - console.log("det -> " + det.card); - cy.createConfirmPaymentTest(createConfirmPaymentBody, det, "no_three_ds", "manual", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DSManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.createConfirmPaymentTest(createConfirmPaymentBody, req_data, res_data, "no_three_ds", "manual", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { @@ -71,9 +99,12 @@ describe("Card - NoThreeDS Manual payment flow test", () => { }); it("capture-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - console.log("det -> " + det.card); - cy.captureCallTest(captureBody, 6540, det.paymentSuccessfulStatus, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Capture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.captureCallTest(captureBody, req_data, res_data, 6500, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { @@ -87,10 +118,20 @@ describe("Card - NoThreeDS Manual payment flow test", () => { context("Card - NoThreeDS Manual Partial Capture payment flow test - Create and Confirm", () => { context("payment Create and Payment Confirm", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); it("create-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.createPaymentIntentTest(createPaymentBody, det, "no_three_ds", "manual", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PaymentIntent"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createPaymentIntentTest(createPaymentBody, req_data, res_data, "no_three_ds", "manual", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("payment_methods-call-test", () => { @@ -99,9 +140,12 @@ describe("Card - NoThreeDS Manual payment flow test", () => { it("confirm-call-test", () => { console.log("confirm -> " + globalState.get("connectorId")); - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - console.log("det -> " + det.card); - cy.confirmCallTest(confirmBody, det, true, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DSManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.confirmCallTest(confirmBody, req_data, res_data, true, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { @@ -109,8 +153,11 @@ describe("Card - NoThreeDS Manual payment flow test", () => { }); it("capture-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.captureCallTest(captureBody, 100, det.paymentSuccessfulStatus, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PartialCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.captureCallTest(captureBody, req_data, res_data, 100, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { @@ -119,11 +166,22 @@ describe("Card - NoThreeDS Manual payment flow test", () => { }); context("payment + Confirm", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); + it("create+confirm-payment-call-test", () => { console.log("confirm -> " + globalState.get("connectorId")); - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - console.log("det -> " + det.card); - cy.createConfirmPaymentTest(createConfirmPaymentBody, det, "no_three_ds", "manual", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DSManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.createConfirmPaymentTest(createConfirmPaymentBody, req_data, res_data, "no_three_ds", "manual", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { @@ -131,9 +189,12 @@ describe("Card - NoThreeDS Manual payment flow test", () => { }); it("capture-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - console.log("det -> " + det.card); - cy.captureCallTest(captureBody, 5000, det.paymentSuccessfulStatus, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PartialCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.captureCallTest(captureBody, req_data, res_data, 100, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { @@ -144,4 +205,4 @@ describe("Card - NoThreeDS Manual payment flow test", () => { }); -}); \ No newline at end of file +}); diff --git a/cypress-tests/cypress/e2e/ConnectorTest/00006-VoidPayment.cy.js b/cypress-tests/cypress/e2e/ConnectorTest/00006-VoidPayment.cy.js index 61a27763134b..15f2e0e7930a 100644 --- a/cypress-tests/cypress/e2e/ConnectorTest/00006-VoidPayment.cy.js +++ b/cypress-tests/cypress/e2e/ConnectorTest/00006-VoidPayment.cy.js @@ -3,6 +3,7 @@ import createPaymentBody from "../../fixtures/create-payment-body.json"; import voidBody from "../../fixtures/void-payment-body.json"; import State from "../../utils/State"; import getConnectorDetails from "../ConnectorUtils/utils"; +import * as utils from "../ConnectorUtils/utils"; let globalState; @@ -22,9 +23,20 @@ describe("Card - NoThreeDS Manual payment flow test", () => { }) context("Card - void payment in Requires_capture state flow test", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); + it("create-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.createPaymentIntentTest(createPaymentBody, det, "no_three_ds", "manual", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PaymentIntent"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createPaymentIntentTest(createPaymentBody, req_data, res_data, "no_three_ds", "manual", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("payment_methods-call-test", () => { @@ -33,20 +45,38 @@ describe("Card - NoThreeDS Manual payment flow test", () => { it("confirm-call-test", () => { console.log("confirm -> " + globalState.get("connectorId")); - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - console.log("det -> " + det.card); - cy.confirmCallTest(confirmBody, det, true, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DSManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.confirmCallTest(confirmBody, req_data, res_data, true, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("void-call-test", () => { - cy.voidCallTest(voidBody, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Void"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.voidCallTest(voidBody, req_data, res_data, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); }); context("Card - void payment in Requires_payment_method state flow test", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); + it("create-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.createPaymentIntentTest(createPaymentBody, det, "no_three_ds", "manual", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PaymentIntent"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createPaymentIntentTest(createPaymentBody, req_data, res_data, "no_three_ds", "manual", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("payment_methods-call-test", () => { @@ -54,14 +84,28 @@ describe("Card - NoThreeDS Manual payment flow test", () => { }); it("void-call-test", () => { - cy.voidCallTest(voidBody, globalState); - }); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Void"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.voidCallTest(voidBody, req_data, res_data, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); }); context("Card - void payment in Requires_payment_method state flow test", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); + it("create-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.createPaymentIntentTest(createPaymentBody, det, "no_three_ds", "manual", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PaymentIntent"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createPaymentIntentTest(createPaymentBody, req_data, res_data, "no_three_ds", "manual", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("payment_methods-call-test", () => { @@ -70,13 +114,19 @@ describe("Card - NoThreeDS Manual payment flow test", () => { it("confirm-call-test", () => { console.log("confirm -> " + globalState.get("connectorId")); - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - console.log("det -> " + det.card); - cy.confirmCallTest(confirmBody, det, false, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DSManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.confirmCallTest(confirmBody, req_data, res_data, false, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("void-call-test", () => { - cy.voidCallTest(voidBody, globalState); - }); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Void"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.voidCallTest(voidBody, req_data, res_data, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); }); -}); \ No newline at end of file +}); diff --git a/cypress-tests/cypress/e2e/ConnectorTest/00007-SyncPayment.cy.js b/cypress-tests/cypress/e2e/ConnectorTest/00007-SyncPayment.cy.js index efdeeb8da2d4..10c16f7ff6e9 100644 --- a/cypress-tests/cypress/e2e/ConnectorTest/00007-SyncPayment.cy.js +++ b/cypress-tests/cypress/e2e/ConnectorTest/00007-SyncPayment.cy.js @@ -2,10 +2,18 @@ import confirmBody from "../../fixtures/confirm-body.json"; import createPaymentBody from "../../fixtures/create-payment-body.json"; import State from "../../utils/State"; import getConnectorDetails from "../ConnectorUtils/utils"; +import * as utils from "../ConnectorUtils/utils"; let globalState; describe("Card - Sync payment flow test", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); before("seed global state", () => { @@ -20,8 +28,11 @@ describe("Card - Sync payment flow test", () => { cy.task('setGlobalState', globalState.data); }) it("create-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.createPaymentIntentTest(createPaymentBody, det, "no_three_ds", "automatic", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PaymentIntent"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createPaymentIntentTest(createPaymentBody, req_data, res_data, "no_three_ds", "automatic", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("payment_methods-call-test", () => { @@ -30,13 +41,16 @@ describe("Card - Sync payment flow test", () => { it("confirm-call-test", () => { console.log("confirm -> " + globalState.get("connectorId")); - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - console.log("det -> " + det.card); - cy.confirmCallTest(confirmBody, det, true, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DSAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.confirmCallTest(confirmBody, req_data, res_data, true, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { cy.retrievePaymentCallTest(globalState); }); -}); \ No newline at end of file +}); diff --git a/cypress-tests/cypress/e2e/ConnectorTest/00008-RefundPayment.cy.js b/cypress-tests/cypress/e2e/ConnectorTest/00008-RefundPayment.cy.js index d45aa6dfec9b..dfdd0df21d17 100644 --- a/cypress-tests/cypress/e2e/ConnectorTest/00008-RefundPayment.cy.js +++ b/cypress-tests/cypress/e2e/ConnectorTest/00008-RefundPayment.cy.js @@ -8,6 +8,7 @@ import refundBody from "../../fixtures/refund-flow-body.json"; import listRefundCall from "../../fixtures/list-refund-call-body.json"; import State from "../../utils/State"; import getConnectorDetails from "../ConnectorUtils/utils"; +import * as utils from "../ConnectorUtils/utils"; let globalState; @@ -27,10 +28,20 @@ describe("Card - Refund flow test", () => { }) context("Card - Full Refund flow test for No-3DS", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); it("create-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.createPaymentIntentTest(createPaymentBody, det, "no_three_ds", "automatic", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PaymentIntent"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createPaymentIntentTest(createPaymentBody, req_data, res_data, "no_three_ds", "automatic", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("payment_methods-call-test", () => { @@ -39,26 +50,43 @@ describe("Card - Refund flow test", () => { it("confirm-call-test", () => { console.log("confirm -> " + globalState.get("connectorId")); - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - console.log("det -> " + det.card); - cy.confirmCallTest(confirmBody, det, true, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DSAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.confirmCallTest(confirmBody, req_data, res_data, true, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { + cy.retrievePaymentCallTest(globalState); }); it("refund-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.refundCallTest(refundBody, 6500, det, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Refund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.refundCallTest(refundBody, req_data, res_data, 6500, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); }); context("Card - Partial Refund flow test for No-3DS", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); it("create-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.createPaymentIntentTest(createPaymentBody, det, "no_three_ds", "automatic", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PaymentIntent"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createPaymentIntentTest(createPaymentBody, req_data, res_data, "no_three_ds", "automatic", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("payment_methods-call-test", () => { @@ -67,9 +95,12 @@ describe("Card - Refund flow test", () => { it("confirm-call-test", () => { console.log("confirm -> " + globalState.get("connectorId")); - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - console.log("det -> " + det.card); - cy.confirmCallTest(confirmBody, det, true, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DSAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.confirmCallTest(confirmBody, req_data, res_data, true, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { @@ -77,22 +108,38 @@ describe("Card - Refund flow test", () => { }); it("refund-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.refundCallTest(refundBody, 1200, det, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PartialRefund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.refundCallTest(refundBody, req_data, res_data, 1200, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("refund-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.refundCallTest(refundBody, 1200, det, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PartialRefund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.refundCallTest(refundBody, req_data, res_data, 1200, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); }); context("Fully Refund Card-NoThreeDS payment flow test Create+Confirm", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); it("create+confirm-payment-call-test", () => { console.log("confirm -> " + globalState.get("connectorId")); - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.createConfirmPaymentTest( createConfirmPaymentBody, det,"no_three_ds", "automatic", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DSAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createConfirmPaymentTest( createConfirmPaymentBody, req_data, res_data,"no_three_ds", "automatic", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { @@ -100,18 +147,31 @@ describe("Card - Refund flow test", () => { }); it("refund-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.refundCallTest(refundBody, 6540, det, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Refund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.refundCallTest(refundBody, req_data, res_data, 6500, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); }); context("Partially Refund Card-NoThreeDS payment flow test Create+Confirm", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); it("create+confirm-payment-call-test", () => { console.log("confirm -> " + globalState.get("connectorId")); - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.createConfirmPaymentTest( createConfirmPaymentBody, det,"no_three_ds", "automatic", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DSAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createConfirmPaymentTest( createConfirmPaymentBody, req_data, res_data,"no_three_ds", "automatic", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { @@ -119,27 +179,45 @@ describe("Card - Refund flow test", () => { }); it("refund-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.refundCallTest(refundBody, 3000, det, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PartialRefund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.refundCallTest(refundBody, req_data, res_data, 3000, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("refund-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.refundCallTest(refundBody, 3000, det, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PartialRefund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.refundCallTest(refundBody, req_data, res_data, 3000, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("sync-refund-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.syncRefundCallTest(det, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["SyncRefund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.syncRefundCallTest(req_data, res_data, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); - }); - + context("Card - Full Refund for fully captured No-3DS payment", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); it("create-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.createPaymentIntentTest(createPaymentBody, det, "no_three_ds", "manual", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PaymentIntent"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createPaymentIntentTest(createPaymentBody, req_data, res_data, "no_three_ds", "manual", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("payment_methods-call-test", () => { @@ -148,9 +226,12 @@ describe("Card - Refund flow test", () => { it("confirm-call-test", () => { console.log("confirm -> " + globalState.get("connectorId")); - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - console.log("det -> " + det.card); - cy.confirmCallTest(confirmBody, det, true, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DSManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.confirmCallTest(confirmBody, req_data, res_data, true, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { @@ -158,9 +239,12 @@ describe("Card - Refund flow test", () => { }); it("capture-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - console.log("det -> " + det.card); - cy.captureCallTest(captureBody, 6500, det.paymentSuccessfulStatus, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Capture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.captureCallTest(captureBody, req_data, res_data, 6500, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { @@ -168,21 +252,37 @@ describe("Card - Refund flow test", () => { }); it("refund-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.refundCallTest(refundBody, 6500, det, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Refund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.refundCallTest(refundBody, req_data, res_data, 6500, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("sync-refund-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.syncRefundCallTest(det, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["SyncRefund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.syncRefundCallTest(req_data, res_data, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); }); context("Card - Partial Refund for fully captured No-3DS payment", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); it("create-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.createPaymentIntentTest(createPaymentBody, det, "no_three_ds", "manual", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PaymentIntent"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createPaymentIntentTest(createPaymentBody, req_data, res_data, "no_three_ds", "manual", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("payment_methods-call-test", () => { @@ -191,9 +291,12 @@ describe("Card - Refund flow test", () => { it("confirm-call-test", () => { console.log("confirm -> " + globalState.get("connectorId")); - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - console.log("det -> " + det.card); - cy.confirmCallTest(confirmBody, det, true, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DSManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.confirmCallTest(confirmBody, req_data, res_data, true, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { @@ -201,9 +304,12 @@ describe("Card - Refund flow test", () => { }); it("capture-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - console.log("det -> " + det.card); - cy.captureCallTest(captureBody, 6500, det.paymentSuccessfulStatus, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Capture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.captureCallTest(captureBody, req_data, res_data, 6500, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { @@ -211,17 +317,26 @@ describe("Card - Refund flow test", () => { }); it("refund-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.refundCallTest(refundBody, 5000, det, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PartialRefund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.refundCallTest(refundBody, req_data, res_data, 3000, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("refund-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.refundCallTest(refundBody, 500, det, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PartialRefund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.refundCallTest(refundBody, req_data, res_data, 3000, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("sync-refund-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.syncRefundCallTest(det, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["SyncRefund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.syncRefundCallTest(req_data, res_data, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("list-refund-call-test", () => { cy.listRefundCallTest(listRefundCall, globalState); @@ -229,10 +344,20 @@ describe("Card - Refund flow test", () => { }); context("Card - Full Refund for partially captured No-3DS payment", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); it("create-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.createPaymentIntentTest(createPaymentBody, det, "no_three_ds", "manual", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PaymentIntent"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createPaymentIntentTest(createPaymentBody, req_data, res_data, "no_three_ds", "manual", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("payment_methods-call-test", () => { @@ -241,9 +366,12 @@ describe("Card - Refund flow test", () => { it("confirm-call-test", () => { console.log("confirm -> " + globalState.get("connectorId")); - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - console.log("det -> " + det.card); - cy.confirmCallTest(confirmBody, det, true, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DSManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.confirmCallTest(confirmBody, req_data, res_data, true, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { @@ -251,9 +379,12 @@ describe("Card - Refund flow test", () => { }); it("capture-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - console.log("det -> " + det.card); - cy.captureCallTest(captureBody, 4000, det.paymentSuccessfulStatus, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PartialCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.captureCallTest(captureBody, req_data, res_data, 100, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { @@ -261,21 +392,38 @@ describe("Card - Refund flow test", () => { }); it("refund-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.refundCallTest(refundBody, 4000, det, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Refund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.refundCallTest(refundBody, req_data, res_data, 100, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); it("sync-refund-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.syncRefundCallTest(det, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["SyncRefund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.syncRefundCallTest(req_data, res_data, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); }); context("Card - partial Refund for partially captured No-3DS payment", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); it("create-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.createPaymentIntentTest(createPaymentBody, det, "no_three_ds", "manual", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PaymentIntent"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createPaymentIntentTest(createPaymentBody, req_data, res_data, "no_three_ds", "manual", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("payment_methods-call-test", () => { @@ -284,9 +432,12 @@ describe("Card - Refund flow test", () => { it("confirm-call-test", () => { console.log("confirm -> " + globalState.get("connectorId")); - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - console.log("det -> " + det.card); - cy.confirmCallTest(confirmBody, det, true, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DSManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.confirmCallTest(confirmBody, req_data, res_data, true, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { @@ -294,9 +445,12 @@ describe("Card - Refund flow test", () => { }); it("capture-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - console.log("det -> " + det.card); - cy.captureCallTest(captureBody, 4000, det.paymentSuccessfulStatus, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PartialCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.captureCallTest(captureBody, req_data, res_data, 100, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { @@ -304,22 +458,38 @@ describe("Card - Refund flow test", () => { }); it("refund-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.refundCallTest(refundBody, 3000, det, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PartialRefund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.refundCallTest(refundBody, req_data, res_data, 100, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("sync-refund-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.syncRefundCallTest(det, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["SyncRefund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.syncRefundCallTest(req_data, res_data, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); }); context("Card - Full Refund for Create + Confirm Automatic CIT and MIT payment flow test", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); it("Confirm No 3DS CIT", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["MandateMultiUseNo3DS"]; - console.log("det -> " + det.card); - cy.citForMandatesCallTest(citConfirmBody, 7000, det, true, "automatic", "new_mandate", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["MandateMultiUseNo3DSAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + req_data.card); + cy.citForMandatesCallTest(citConfirmBody, req_data, res_data, 7000, true, "automatic", "new_mandate", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("Confirm No 3DS MIT", () => { @@ -331,14 +501,481 @@ describe("Card - Refund flow test", () => { }); it("refund-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["MandateMultiUseNo3DS"]; - cy.refundCallTest(refundBody, 7000, det, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Refund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.refundCallTest(refundBody, req_data, res_data, 7000, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("sync-refund-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["MandateMultiUseNo3DS"]; - cy.syncRefundCallTest(det, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["SyncRefund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.syncRefundCallTest(req_data, res_data, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); }); }); + + context("Card - Full Refund flow test for 3DS", () => { + + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PaymentIntent"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createPaymentIntentTest(createPaymentBody, req_data, res_data, "three_ds", "automatic", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("Confirm 3DS", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["3DSAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.confirmCallTest(confirmBody, req_data, res_data, true, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + + it("Handle redirection", () => { + let expected_redirection = confirmBody["return_url"]; + cy.handleRedirection(globalState, expected_redirection); + }) + + it("retrieve-payment-call-test", () => { + cy.retrievePaymentCallTest(globalState); + }); + + it("refund-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Refund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.refundCallTest(refundBody, req_data, res_data, 6500, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + }); + + context("Card - Partial Refund flow test for 3DS", () => { + + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PaymentIntent"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createPaymentIntentTest(createPaymentBody, req_data, res_data, "three_ds", "automatic", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("Confirm 3DS", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["3DSAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.confirmCallTest(confirmBody, req_data, res_data, true, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + + it("Handle redirection", () => { + let expected_redirection = confirmBody["return_url"]; + cy.handleRedirection(globalState, expected_redirection); + }) + + it("retrieve-payment-call-test", () => { + cy.retrievePaymentCallTest(globalState); + }); + + it("refund-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PartialRefund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.refundCallTest(refundBody, req_data, res_data, 1200, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + + it("refund-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PartialRefund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.refundCallTest(refundBody, req_data, res_data, 1200, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + }); + + context("Fully Refund Card-ThreeDS payment flow test Create+Confirm", () => { + + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); + + it("create+confirm-payment-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["3DSAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createConfirmPaymentTest(createConfirmPaymentBody, req_data, res_data, "three_ds", "automatic", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + + it("Handle redirection", () => { + let expected_redirection = confirmBody["return_url"]; + cy.handleRedirection(globalState, expected_redirection); + }) + + it("retrieve-payment-call-test", () => { + cy.retrievePaymentCallTest(globalState); + }); + + it("refund-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Refund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.refundCallTest(refundBody, req_data, res_data, 6500, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + + }); + + context("Partially Refund Card-ThreeDS payment flow test Create+Confirm", () => { + + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); + + it("create+confirm-payment-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["3DSAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createConfirmPaymentTest(createConfirmPaymentBody, req_data, res_data, "three_ds", "automatic", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + + it("Handle redirection", () => { + let expected_redirection = confirmBody["return_url"]; + cy.handleRedirection(globalState, expected_redirection); + }) + + it("retrieve-payment-call-test", () => { + cy.retrievePaymentCallTest(globalState); + }); + + it("refund-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PartialRefund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.refundCallTest(refundBody, req_data, res_data, 3000, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + + it("refund-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PartialRefund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.refundCallTest(refundBody, req_data, res_data, 3000, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + + it("sync-refund-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["SyncRefund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.syncRefundCallTest(req_data, res_data, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + + }); + + context("Card - Full Refund for fully captured 3DS payment", () => { + + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PaymentIntent"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createPaymentIntentTest(createPaymentBody, req_data, res_data, "three_ds", "manual", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("Confirm 3DS", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["3DSManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.confirmCallTest(confirmBody, req_data, res_data, true, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + + it("Handle redirection", () => { + let expected_redirection = confirmBody["return_url"]; + cy.handleRedirection(globalState, expected_redirection); + }) + + + it("retrieve-payment-call-test", () => { + cy.retrievePaymentCallTest(globalState); + }); + + it("capture-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Capture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.captureCallTest(captureBody, req_data, res_data, 6500, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + + }); + + it("retrieve-payment-call-test", () => { + cy.retrievePaymentCallTest(globalState); + }); + + it("refund-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Refund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.refundCallTest(refundBody, req_data, res_data, 6500, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + + }); + + context("Card - Partial Refund for fully captured 3DS payment", () => { + + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PaymentIntent"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createPaymentIntentTest(createPaymentBody, req_data, res_data, "three_ds", "manual", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("Confirm 3DS", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["3DSManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.confirmCallTest(confirmBody, req_data, res_data, true, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + + it("Handle redirection", () => { + let expected_redirection = confirmBody["return_url"]; + cy.handleRedirection(globalState, expected_redirection); + }) + + + it("retrieve-payment-call-test", () => { + cy.retrievePaymentCallTest(globalState); + }); + + it("capture-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Capture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.captureCallTest(captureBody, req_data, res_data, 6500, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + + }); + + it("retrieve-payment-call-test", () => { + cy.retrievePaymentCallTest(globalState); + }); + + it("refund-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Refund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.refundCallTest(refundBody, req_data, res_data, 5000, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + it("refund-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Refund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.refundCallTest(refundBody, req_data, res_data, 1500, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + + + }); + + context("Card - Full Refund for partially captured 3DS payment", () => { + + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PaymentIntent"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createPaymentIntentTest(createPaymentBody, req_data, res_data, "three_ds", "manual", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("Confirm 3DS", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["3DSManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.confirmCallTest(confirmBody, req_data, res_data, true, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + + it("Handle redirection", () => { + let expected_redirection = confirmBody["return_url"]; + cy.handleRedirection(globalState, expected_redirection); + }) + + + it("retrieve-payment-call-test", () => { + cy.retrievePaymentCallTest(globalState); + }); + + it("capture-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PartialCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.captureCallTest(captureBody, req_data, res_data, 100, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + + }); + + it("retrieve-payment-call-test", () => { + cy.retrievePaymentCallTest(globalState); + }); + + it("refund-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Refund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.refundCallTest(refundBody, req_data, res_data, 100, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + + + }); + + context("Card - partial Refund for partially captured 3DS payment", () => { + + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PaymentIntent"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createPaymentIntentTest(createPaymentBody, req_data, res_data, "three_ds", "manual", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("Confirm 3DS", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["3DSManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.confirmCallTest(confirmBody, req_data, res_data, true, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + + it("Handle redirection", () => { + let expected_redirection = confirmBody["return_url"]; + cy.handleRedirection(globalState, expected_redirection); + }) + + + it("retrieve-payment-call-test", () => { + cy.retrievePaymentCallTest(globalState); + }); + + it("capture-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PartialCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.captureCallTest(captureBody, req_data, res_data, 100, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + + }); + + it("retrieve-payment-call-test", () => { + cy.retrievePaymentCallTest(globalState); + }); + + it("refund-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Refund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.refundCallTest(refundBody, req_data, res_data, 50 , globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + }); diff --git a/cypress-tests/cypress/e2e/ConnectorTest/00009-SyncRefund.cy.js b/cypress-tests/cypress/e2e/ConnectorTest/00009-SyncRefund.cy.js index 6bf9639da263..31e5e6855d6a 100644 --- a/cypress-tests/cypress/e2e/ConnectorTest/00009-SyncRefund.cy.js +++ b/cypress-tests/cypress/e2e/ConnectorTest/00009-SyncRefund.cy.js @@ -3,10 +3,18 @@ import createPaymentBody from "../../fixtures/create-payment-body.json"; import refundBody from "../../fixtures/refund-flow-body.json"; import State from "../../utils/State"; import getConnectorDetails from "../ConnectorUtils/utils"; +import * as utils from "../ConnectorUtils/utils"; let globalState; describe("Card - Sync Refund flow test", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); before("seed global state", () => { @@ -22,8 +30,11 @@ describe("Card - Sync Refund flow test", () => { }) it("create-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.createPaymentIntentTest(createPaymentBody, det, "no_three_ds", "automatic", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PaymentIntent"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createPaymentIntentTest(createPaymentBody, req_data, res_data, "no_three_ds", "automatic", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("payment_methods-call-test", () => { @@ -32,9 +43,12 @@ describe("Card - Sync Refund flow test", () => { it("confirm-call-test", () => { console.log("confirm -> " + globalState.get("connectorId")); - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - console.log("det -> " + det.card); - cy.confirmCallTest(confirmBody, det, true, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DSAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.confirmCallTest(confirmBody, req_data, res_data, true, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { @@ -42,13 +56,19 @@ describe("Card - Sync Refund flow test", () => { }); it("refund-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.refundCallTest(refundBody, 6500, det, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Refund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.refundCallTest(refundBody, req_data, res_data, 6500, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("sync-refund-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["No3DS"]; - cy.syncRefundCallTest(det, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["SyncRefund"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.syncRefundCallTest(req_data, res_data, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); -}); \ No newline at end of file +}); diff --git a/cypress-tests/cypress/e2e/ConnectorTest/00010-CreateSingleuseMandate.cy.js b/cypress-tests/cypress/e2e/ConnectorTest/00010-CreateSingleuseMandate.cy.js index bd795d8a38b3..4ece0319324e 100644 --- a/cypress-tests/cypress/e2e/ConnectorTest/00010-CreateSingleuseMandate.cy.js +++ b/cypress-tests/cypress/e2e/ConnectorTest/00010-CreateSingleuseMandate.cy.js @@ -3,6 +3,7 @@ import citConfirmBody from "../../fixtures/create-mandate-cit.json"; import mitConfirmBody from "../../fixtures/create-mandate-mit.json"; import State from "../../utils/State"; import getConnectorDetails from "../ConnectorUtils/utils"; +import * as utils from "../ConnectorUtils/utils"; let globalState; @@ -22,12 +23,22 @@ describe("Card - SingleUse Mandates flow test", () => { }) context("Card - NoThreeDS Create + Confirm Automatic CIT and MIT payment flow test", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); it("Confirm No 3DS CIT", () => { console.log("confirm -> " + globalState.get("connectorId")); - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["MandateSingleUseNo3DS"]; - console.log("det -> " + det.card); - cy.citForMandatesCallTest(citConfirmBody, 7000, det, true, "automatic", "new_mandate", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["MandateSingleUseNo3DSAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.citForMandatesCallTest(citConfirmBody, req_data, res_data, 7000, true, "automatic", "new_mandate", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("Confirm No 3DS MIT", () => { @@ -36,28 +47,44 @@ describe("Card - SingleUse Mandates flow test", () => { }); context("Card - NoThreeDS Create + Confirm Manual CIT and MIT payment flow test", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); it("Confirm No 3DS CIT", () => { console.log("confirm -> " + globalState.get("connectorId")); - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["MandateSingleUseNo3DS"]; - console.log("det -> " + det.card); - cy.citForMandatesCallTest(citConfirmBody, 7000, det, true, "manual", "new_mandate", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["MandateSingleUseNo3DSManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.citForMandatesCallTest(citConfirmBody, req_data, res_data, 6500, true, "manual", "new_mandate", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("cit-capture-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["MandateSingleUseNo3DS"]; - console.log("det -> " + det.card); - cy.captureCallTest(captureBody, 7000, det.paymentSuccessfulStatus, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Capture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.captureCallTest(captureBody, req_data, res_data, 6500, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("Confirm No 3DS MIT", () => { - cy.mitForMandatesCallTest(mitConfirmBody, 7000, true, "manual", globalState); + cy.mitForMandatesCallTest(mitConfirmBody, 6500, true, "manual", globalState); }); it("mit-capture-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["MandateSingleUseNo3DS"]; - console.log("det -> " + det.card); - cy.captureCallTest(captureBody, 7000, det.paymentSuccessfulStatus, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Capture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.captureCallTest(captureBody, req_data, res_data, 6500, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("list-mandate-call-test", () => { @@ -65,19 +92,32 @@ describe("Card - SingleUse Mandates flow test", () => { }); }); - context.skip("Card - ThreeDS Create + Confirm Manual CIT and MIT payment flow test", () => { + context("Card - ThreeDS Create + Confirm Manual CIT and MIT payment flow test", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails - it("Confirm No 3DS CIT", () => { + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); + + it("Create No 3DS CIT", () => { console.log("confirm -> " + globalState.get("connectorId")); - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["MandateSingleUse3DS"]; - console.log("det -> " + det.card); - cy.citForMandatesCallTest(citConfirmBody, 6500, det, true, "automatic", "new_mandate", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["MandateSingleUseNo3DSManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.citForMandatesCallTest(citConfirmBody, req_data, res_data, 6500, true, "manual", "new_mandate", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("cit-capture-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["MandateSingleUse3DS"]; - console.log("det -> " + det.card); - cy.captureCallTest(captureBody, 6500, det.paymentSuccessfulStatus, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Capture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.captureCallTest(captureBody, req_data, res_data, 6500, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("Confirm No 3DS MIT", () => { @@ -88,4 +128,4 @@ describe("Card - SingleUse Mandates flow test", () => { cy.listMandateCallTest(globalState); }); }); -}); \ No newline at end of file +}); diff --git a/cypress-tests/cypress/e2e/ConnectorTest/00011-CreateMultiuseMandate.cy.js b/cypress-tests/cypress/e2e/ConnectorTest/00011-CreateMultiuseMandate.cy.js index 474024a1bcb7..898258c8d6a3 100644 --- a/cypress-tests/cypress/e2e/ConnectorTest/00011-CreateMultiuseMandate.cy.js +++ b/cypress-tests/cypress/e2e/ConnectorTest/00011-CreateMultiuseMandate.cy.js @@ -3,6 +3,7 @@ import citConfirmBody from "../../fixtures/create-mandate-cit.json"; import mitConfirmBody from "../../fixtures/create-mandate-mit.json"; import State from "../../utils/State"; import getConnectorDetails from "../ConnectorUtils/utils"; +import * as utils from "../ConnectorUtils/utils"; let globalState; @@ -23,12 +24,22 @@ describe("Card - MultiUse Mandates flow test", () => { context("Card - NoThreeDS Create + Confirm Automatic CIT and MIT payment flow test", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); it("Confirm No 3DS CIT", () => { console.log("confirm -> " + globalState.get("connectorId")); - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["MandateMultiUseNo3DS"]; - console.log("det -> " + det.card); - cy.citForMandatesCallTest(citConfirmBody, 7000, det, true, "automatic", "new_mandate", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["MandateMultiUseNo3DSAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.citForMandatesCallTest(citConfirmBody, req_data, res_data, 7000, true, "automatic", "new_mandate", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("Confirm No 3DS MIT", () => { @@ -40,58 +51,90 @@ describe("Card - MultiUse Mandates flow test", () => { }); context("Card - NoThreeDS Create + Confirm Manual CIT and MIT payment flow test", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); it("Confirm No 3DS CIT", () => { console.log("confirm -> " + globalState.get("connectorId")); - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["MandateMultiUseNo3DS"]; - console.log("det -> " + det.card); - cy.citForMandatesCallTest(citConfirmBody, 7000, det, true, "manual", "new_mandate", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["MandateMultiUseNo3DSManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.citForMandatesCallTest(citConfirmBody, req_data, res_data, 6500, true, "manual", "new_mandate", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("cit-capture-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["MandateMultiUseNo3DS"]; - console.log("det -> " + det.card); - cy.captureCallTest(captureBody, 7000, det.paymentSuccessfulStatus, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Capture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.captureCallTest(captureBody, req_data, res_data, 6500, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("Confirm No 3DS MIT 1", () => { - cy.mitForMandatesCallTest(mitConfirmBody, 7000, true, "manual", globalState); + cy.mitForMandatesCallTest(mitConfirmBody, 6500, true, "manual", globalState); }); it("mit-capture-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["MandateMultiUseNo3DS"]; - console.log("det -> " + det.card); - cy.captureCallTest(captureBody, 7000, det.paymentSuccessfulStatus, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Capture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.captureCallTest(captureBody, req_data, res_data, 6500, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("Confirm No 3DS MIT 2", () => { - cy.mitForMandatesCallTest(mitConfirmBody, 7000, true, "manual", globalState); + cy.mitForMandatesCallTest(mitConfirmBody, 6500, true, "manual", globalState); }); it("mit-capture-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["MandateMultiUseNo3DS"]; - console.log("det -> " + det.card); - cy.captureCallTest(captureBody, 7000, det.paymentSuccessfulStatus, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Capture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.captureCallTest(captureBody, req_data, res_data, 6500, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); }); - context.skip("Card - ThreeDS Create + Confirm Manual CIT and MIT payment flow test", () => { + context("Card - ThreeDS Create + Confirm Manual CIT and MIT payment flow test", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); it("Confirm No 3DS CIT", () => { console.log("confirm -> " + globalState.get("connectorId")); - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["MandateMultiUse3DS"]; - console.log("det -> " + det.card); - cy.citForMandatesCallTest(citConfirmBody, 6500, det, true, "automatic", "new_mandate", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["MandateMultiUseNo3DSManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.citForMandatesCallTest(citConfirmBody, req_data, res_data, 6500, true, "manual", "new_mandate", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("cit-capture-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["MandateMultiUse3DS"]; - console.log("det -> " + det.card); - cy.captureCallTest(captureBody, 6500, det.paymentSuccessfulStatus, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Capture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.captureCallTest(captureBody, req_data, res_data, 6500, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("Confirm No 3DS MIT", () => { - cy.mitForMandatesCallTest(mitConfirmBody, 7000, true, "automatic", globalState); + cy.mitForMandatesCallTest(mitConfirmBody, 6500, true, "automatic", globalState); }); }); -}); \ No newline at end of file +}); diff --git a/cypress-tests/cypress/e2e/ConnectorTest/00012-ListAndRevokeMandate.cy.js b/cypress-tests/cypress/e2e/ConnectorTest/00012-ListAndRevokeMandate.cy.js index 8504f602cc5d..8de48fbf95a4 100644 --- a/cypress-tests/cypress/e2e/ConnectorTest/00012-ListAndRevokeMandate.cy.js +++ b/cypress-tests/cypress/e2e/ConnectorTest/00012-ListAndRevokeMandate.cy.js @@ -1,6 +1,7 @@ import citConfirmBody from "../../fixtures/create-mandate-cit.json"; import mitConfirmBody from "../../fixtures/create-mandate-mit.json"; import getConnectorDetails from "../ConnectorUtils/utils"; +import * as utils from "../ConnectorUtils/utils"; import State from "../../utils/State"; @@ -22,12 +23,22 @@ describe("Card - SingleUse Mandates flow test", () => { }) context("Card - NoThreeDS Create + Confirm Automatic CIT and MIT payment flow test", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); it("Confirm No 3DS CIT", () => { console.log("confirm -> " + globalState.get("connectorId")); - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["MandateSingleUseNo3DS"]; - console.log("det -> " + det.card); - cy.citForMandatesCallTest(citConfirmBody, 7000, det, true, "automatic", "new_mandate", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["MandateSingleUseNo3DSAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + console.log("det -> " + data.card); + cy.citForMandatesCallTest(citConfirmBody, req_data, res_data, 7000, true, "automatic", "new_mandate", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("Confirm No 3DS MIT", () => { @@ -47,4 +58,4 @@ describe("Card - SingleUse Mandates flow test", () => { }); }); -}); \ No newline at end of file +}); diff --git a/cypress-tests/cypress/e2e/ConnectorTest/00013-SaveCardFlow.cy.js b/cypress-tests/cypress/e2e/ConnectorTest/00013-SaveCardFlow.cy.js index eda63cc02f55..1168a5690893 100644 --- a/cypress-tests/cypress/e2e/ConnectorTest/00013-SaveCardFlow.cy.js +++ b/cypress-tests/cypress/e2e/ConnectorTest/00013-SaveCardFlow.cy.js @@ -3,8 +3,9 @@ import confirmBody from "../../fixtures/confirm-body.json"; import createPaymentBody from "../../fixtures/create-payment-body.json"; import createConfirmPaymentBody from "../../fixtures/create-confirm-body.json"; import customerCreateBody from "../../fixtures/create-customer-body.json"; -import saveCardConfirmBody from "../../fixtures/save-card-confirm-body.json"; +import SaveCardConfirmBody from "../../fixtures/save-card-confirm-body.json"; import getConnectorDetails from "../ConnectorUtils/utils"; +import * as utils from "../ConnectorUtils/utils"; import State from "../../utils/State"; let globalState; @@ -19,105 +20,169 @@ describe("Card - SaveCard payment flow test", () => { }) - context("Save card for NoThreeDS automatic capture payment- Create+Confirm", () => { + context("Save card for NoThreeDS automatic capture payment- Create+Confirm", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); + + it("customer-create-call-test", () => { + cy.createCustomerCallTest(customerCreateBody, globalState); + }); + + it("create+confirm-payment-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["SaveCardUseNo3DSAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createConfirmPaymentTest( createConfirmPaymentBody, req_data, res_data,"no_three_ds", "automatic", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + + it("retrieve-payment-call-test", () => { + cy.retrievePaymentCallTest(globalState); + }); + + it("retrieve-customerPM-call-test", () => { + cy.listCustomerPMCallTest(globalState); + }); + + it("create-payment-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PaymentIntent"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createPaymentIntentTest(createPaymentBody, req_data, res_data, "no_three_ds", "automatic", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + + it ("confirm-save-card-payment-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["SaveCardUseNo3DSAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.saveCardConfirmCallTest(SaveCardConfirmBody, req_data, res_data, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + + }); + + context("Save card for NoThreeDS manual full capture payment- Create+Confirm", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); + it("customer-create-call-test", () => { cy.createCustomerCallTest(customerCreateBody, globalState); }); it("create+confirm-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["SaveCardUseNo3DS"]; - cy.createConfirmPaymentTest( createConfirmPaymentBody, det,"no_three_ds", "automatic", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["SaveCardUseNo3DSAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createConfirmPaymentTest( createConfirmPaymentBody, req_data, res_data,"no_three_ds", "automatic", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { cy.retrievePaymentCallTest(globalState); }); - + it("retrieve-customerPM-call-test", () => { cy.listCustomerPMCallTest(globalState); }); it("create-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["SaveCardUseNo3DS"]; - cy.createPaymentIntentTest( createPaymentBody, det, "no_three_ds", "automatic", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PaymentIntent"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createPaymentIntentTest( createPaymentBody, req_data, res_data, "no_three_ds", "manual", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); + it ("confirm-save-card-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["SaveCardUseNo3DS"]; - cy.saveCardConfirmCallTest(saveCardConfirmBody,det,globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["SaveCardUseNo3DSManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.saveCardConfirmCallTest(SaveCardConfirmBody, req_data, res_data, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); + + it("retrieve-payment-call-test", () => { + cy.retrievePaymentCallTest(globalState); }); + it("capture-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Capture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.captureCallTest(captureBody, req_data, res_data, 6500, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); }); - context("Save card for NoThreeDS manual full capture payment- Create+Confirm", () => { - it("customer-create-call-test", () => { - cy.createCustomerCallTest(customerCreateBody, globalState); - }); - - it("create+confirm-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["SaveCardUseNo3DS"]; - cy.createConfirmPaymentTest( createConfirmPaymentBody, det,"no_three_ds", "automatic", globalState); - }); - - it("retrieve-payment-call-test", () => { - cy.retrievePaymentCallTest(globalState); - }); - - it("retrieve-customerPM-call-test", () => { - cy.listCustomerPMCallTest(globalState); - }); - - it("create-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["SaveCardUseNo3DS"]; - cy.createPaymentIntentTest( createPaymentBody, det, "no_three_ds", "manual", globalState); - }); + context("Save card for NoThreeDS manual partial capture payment- Create + Confirm", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); + it("customer-create-call-test", () => { + cy.createCustomerCallTest(customerCreateBody, globalState); + }); + + it("create+confirm-payment-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["SaveCardUseNo3DSAutoCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createConfirmPaymentTest( createConfirmPaymentBody, req_data, res_data,"no_three_ds", "automatic", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); - it ("confirm-save-card-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["SaveCardUseNo3DS"]; - cy.saveCardConfirmCallTest(saveCardConfirmBody,det,globalState); - }); - - it("capture-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["SaveCardUseNo3DS"]; - cy.captureCallTest(captureBody, 6500, det.paymentSuccessfulStatus, globalState); - }); + it("retrieve-payment-call-test", () => { + cy.retrievePaymentCallTest(globalState); + }); + + it("retrieve-customerPM-call-test", () => { + cy.listCustomerPMCallTest(globalState); + }); + it("create-payment-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PaymentIntent"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createPaymentIntentTest( createPaymentBody, req_data, res_data, "no_three_ds", "manual", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); - context("Save card for NoThreeDS manual partial capture payment- Create + Confirm", () => { - it("customer-create-call-test", () => { - cy.createCustomerCallTest(customerCreateBody, globalState); - }); - - it("create+confirm-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["SaveCardUseNo3DS"]; - cy.createConfirmPaymentTest( createConfirmPaymentBody, det,"no_three_ds", "automatic", globalState); - }); - - it("retrieve-payment-call-test", () => { - cy.retrievePaymentCallTest(globalState); - }); - - it("retrieve-customerPM-call-test", () => { - cy.listCustomerPMCallTest(globalState); - }); - - it("create-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["SaveCardUseNo3DS"]; - cy.createPaymentIntentTest( createPaymentBody, det, "no_three_ds", "manual", globalState); - }); - - it ("confirm-save-card-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["SaveCardUseNo3DS"]; - cy.saveCardConfirmCallTest(saveCardConfirmBody,det,globalState); - }); - - it("capture-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["SaveCardUseNo3DS"]; - cy.captureCallTest(captureBody, 5500, det.paymentSuccessfulStatus, globalState); - }); + it ("confirm-save-card-payment-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["SaveCardUseNo3DSManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.saveCardConfirmCallTest(SaveCardConfirmBody,req_data, res_data,globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); + it("retrieve-payment-call-test", () => { + cy.retrievePaymentCallTest(globalState); + }); + + it("capture-call-test", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PartialCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.captureCallTest(captureBody, req_data, res_data, 100, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); + }); }); -}) \ No newline at end of file + +}); diff --git a/cypress-tests/cypress/e2e/ConnectorTest/00014-ZeroAuthMandate.cy.js b/cypress-tests/cypress/e2e/ConnectorTest/00014-ZeroAuthMandate.cy.js index 23b9526f58b4..ee0edcb94ac2 100644 --- a/cypress-tests/cypress/e2e/ConnectorTest/00014-ZeroAuthMandate.cy.js +++ b/cypress-tests/cypress/e2e/ConnectorTest/00014-ZeroAuthMandate.cy.js @@ -2,6 +2,7 @@ import citConfirmBody from "../../fixtures/create-mandate-cit.json"; import mitConfirmBody from "../../fixtures/create-mandate-mit.json"; import State from "../../utils/State"; import getConnectorDetails from "../ConnectorUtils/utils"; +import * as utils from "../ConnectorUtils/utils"; let globalState; @@ -21,10 +22,20 @@ describe("Card - SingleUse Mandates flow test", () => { }) context("Card - NoThreeDS Create + Confirm Automatic CIT and Single use MIT payment flow test", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); it("Confirm No 3DS CIT", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["MandateSingleUseNo3DS"]; - cy.citForMandatesCallTest(citConfirmBody, 0, det, true, "automatic", "setup_mandate", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["ZeroAuthMandate"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.citForMandatesCallTest(citConfirmBody, req_data, res_data, 0, true, "automatic", "setup_mandate", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("Confirm No 3DS MIT", () => { @@ -32,10 +43,20 @@ describe("Card - SingleUse Mandates flow test", () => { }); }); context("Card - NoThreeDS Create + Confirm Automatic CIT and Multi use MIT payment flow test", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); it("Confirm No 3DS CIT", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["MandateSingleUseNo3DS"]; - cy.citForMandatesCallTest(citConfirmBody, 0, det, true, "automatic", "setup_mandate", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["ZeroAuthMandate"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.citForMandatesCallTest(citConfirmBody, req_data, res_data, 0, true, "automatic", "setup_mandate", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("Confirm No 3DS MIT", () => { @@ -46,4 +67,4 @@ describe("Card - SingleUse Mandates flow test", () => { }); }); -}); \ No newline at end of file +}); diff --git a/cypress-tests/cypress/e2e/ConnectorTest/00015-ThreeDSManualCapture.cy.js b/cypress-tests/cypress/e2e/ConnectorTest/00015-ThreeDSManualCapture.cy.js index fb308c7fbec2..fa6de0092dc8 100644 --- a/cypress-tests/cypress/e2e/ConnectorTest/00015-ThreeDSManualCapture.cy.js +++ b/cypress-tests/cypress/e2e/ConnectorTest/00015-ThreeDSManualCapture.cy.js @@ -4,6 +4,7 @@ import confirmBody from "../../fixtures/confirm-body.json"; import getConnectorDetails from "../ConnectorUtils/utils"; import State from "../../utils/State"; import captureBody from "../../fixtures/capture-flow-body.json"; +import * as utils from "../ConnectorUtils/utils"; let globalState; @@ -23,9 +24,20 @@ describe("Card - ThreeDS Manual payment flow test", () => { context("Card - ThreeDS Manual Full Capture payment flow test", () => { context("payment Create and Confirm", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); + it("create-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["3DS"]; - cy.createPaymentIntentTest(createPaymentBody, det, "three_ds", "manual", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PaymentIntent"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createPaymentIntentTest(createPaymentBody, req_data, res_data, "three_ds", "manual", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("payment_methods-call-test", () => { @@ -33,8 +45,11 @@ describe("Card - ThreeDS Manual payment flow test", () => { }); it("confirm-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["3DS"]; - cy.confirmCallTest(confirmBody, det, true, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["3DSManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.confirmCallTest(confirmBody, req_data, res_data, true, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("Handle redirection", () => { @@ -47,8 +62,11 @@ describe("Card - ThreeDS Manual payment flow test", () => { }); it("capture-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["3DS"]; - cy.captureCallTest(captureBody, 6500, det.paymentSuccessfulStatus, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Capture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.captureCallTest(captureBody, req_data, res_data, 6500, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { @@ -58,9 +76,20 @@ describe("Card - ThreeDS Manual payment flow test", () => { }); context("Payment Create+Confirm", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); + it("create+confirm-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["3DS"]; - cy.createConfirmPaymentTest(createConfirmPaymentBody, det, "three_ds", "manual", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["3DSManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createConfirmPaymentTest(createConfirmPaymentBody, req_data, res_data, "three_ds", "manual", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("Handle redirection", () => { @@ -74,8 +103,11 @@ describe("Card - ThreeDS Manual payment flow test", () => { }); it("capture-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["3DS"]; - cy.captureCallTest(captureBody, 6540, det.paymentSuccessfulStatus, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["Capture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.captureCallTest(captureBody, req_data, res_data, 6500, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { @@ -88,9 +120,20 @@ describe("Card - ThreeDS Manual payment flow test", () => { context("Card - ThreeDS Manual Partial Capture payment flow test - Create and Confirm", () => { context("payment Create and Payment Confirm", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); + it("create-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["3DS"]; - cy.createPaymentIntentTest(createPaymentBody, det, "three_ds", "manual", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PaymentIntent"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createPaymentIntentTest(createPaymentBody, req_data, res_data, "three_ds", "manual", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("payment_methods-call-test", () => { @@ -98,8 +141,11 @@ describe("Card - ThreeDS Manual payment flow test", () => { }); it("confirm-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["3DS"]; - cy.confirmCallTest(confirmBody, det, true, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["3DSManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.confirmCallTest(confirmBody, req_data, res_data, true, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("Handle redirection", () => { @@ -112,8 +158,11 @@ describe("Card - ThreeDS Manual payment flow test", () => { }); it("capture-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["3DS"]; - cy.captureCallTest(captureBody, 100, det.paymentSuccessfulStatus, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PartialCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.captureCallTest(captureBody, req_data, res_data, 100, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { @@ -122,9 +171,20 @@ describe("Card - ThreeDS Manual payment flow test", () => { }); context("payment + Confirm", () => { + let should_continue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if(!should_continue) { + this.skip(); + } + }); + it("create+confirm-payment-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["3DS"]; - cy.createConfirmPaymentTest(createConfirmPaymentBody, det, "three_ds", "manual", globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["3DSManualCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.createConfirmPaymentTest(createConfirmPaymentBody, req_data, res_data, "three_ds", "manual", globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("Handle redirection", () => { @@ -137,8 +197,11 @@ describe("Card - ThreeDS Manual payment flow test", () => { }); it("capture-call-test", () => { - let det = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["3DS"]; - cy.captureCallTest(captureBody, 5000, det.paymentSuccessfulStatus, globalState); + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"]["PartialCapture"]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.captureCallTest(captureBody, req_data, res_data, 100, globalState); + if(should_continue) should_continue = utils.should_continue_further(res_data); }); it("retrieve-payment-call-test", () => { @@ -149,4 +212,4 @@ describe("Card - ThreeDS Manual payment flow test", () => { }); -}); \ No newline at end of file +}); diff --git a/cypress-tests/cypress/e2e/ConnectorUtils/Adyen.js b/cypress-tests/cypress/e2e/ConnectorUtils/Adyen.js index 94ffe3009f68..1bbbbbd4b5b5 100644 --- a/cypress-tests/cypress/e2e/ConnectorUtils/Adyen.js +++ b/cypress-tests/cypress/e2e/ConnectorUtils/Adyen.js @@ -17,98 +17,366 @@ const successfulThreeDSTestCardDetails = { export const connectorDetails = { card_pm:{ - "3DS": { - "card": successfulThreeDSTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "pending", - "refundSyncStatus": "pending", - "customer_acceptance": null, - "setup_future_usage": "on_session", - }, - "No3DS": { - "card": successfulNo3DSCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "pending", - "refundSyncStatus": "pending", - "customer_acceptance": null, - "setup_future_usage": "on_session", - }, - "MandateSingleUse3DS": { - "card": successfulThreeDSTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "requires_customer_action", - "paymentSyncStatus": "processing", - "refundStatus": "pending", - "refundSyncStatus": "pending", - "mandate_type": { - "single_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "MandateSingleUseNo3DS": { - "card": successfulNo3DSCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "pending", - "refundSyncStatus": "pending", - "mandate_type": { - "single_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "MandateMultiUseNo3DS": { - "card": successfulNo3DSCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "pending", - "refundSyncStatus": "pending", - "mandate_type": { - "multi_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "MandateMultiUse3DS": { - "card": successfulThreeDSTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "requires_customer_action", - "paymentSyncStatus": "processing", - "refundStatus": "pending", - "refundSyncStatus": "pending", - "mandate_type": { - "multi_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "SaveCardUseNo3DS": { - "card": successfulNo3DSCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "succeeded", - "refundSyncStatus": "succeeded", - "setup_future_usage": "on_session", - "customer_acceptance": { - "acceptance_type": "offline", - "accepted_at": "1963-05-03T04:07:52.723Z", - "online": { - "ip_address": "127.0.0.1", - "user_agent": "amet irure esse" + "PaymentIntent": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "requires_payment_method" + } + } + }, + "3DSManualCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "processing" + } + } + }, + "3DSAutoCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "No3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + "No3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "Capture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + }, + "Response": { + "status": 200, + "body":{ + "status": "processing", + "amount": 6500, + "amount_capturable": 6500, + "amount_received": 0, + + } + } + }, + + "PartialCapture": { + "Request": { + }, + "Response": { + "status": 200, + "body": { + "status": "processing", + "amount": 6500, + "amount_capturable": 6500, + "amount_received": 0, + } + + } + }, + "Void":{ + "Request": { + }, + "Response": { + "status": 200, + "body":{ + status: "processing" + + } + } + }, + "Refund": { + "Request": { + + "currency": "USD", + + }, + "Response": { + "status": 400, + "body": { + "status": "pending", } + + } + }, + "PartialRefund": { + "Request": { + + "currency": "USD", + + }, + "Response": { + "status": 200, + "body": { + "status": "pending", + } + + } + }, + "SyncRefund": { + "Request": { + + "currency": "USD", + }, + "Response": { + "status": 200, + "body": { + "status": "pending", + } + + } + }, + "MandateSingleUse3DSAutoCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + + }, + "MandateSingleUse3DSManualCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "requires_customer_action" + } + } + + }, + "MandateSingleUseNo3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "MandateSingleUseNo3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "processing" + } + } + }, + "MandateMultiUseNo3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "MandateMultiUseNo3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + "MandateMultiUse3DSAutoCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + }, + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + "MandateMultiUse3DSManualCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + }, + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + "ZeroAuthMandate": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "SaveCardUseNo3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "setup_future_usage": "on_session", + "customer_acceptance": { + "acceptance_type": "offline", + "accepted_at": "1963-05-03T04:07:52.723Z", + "online": { + "ip_address": "127.0.0.1", + "user_agent": "amet irure esse" + } + }, + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "SaveCardUseNo3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "setup_future_usage": "on_session", + "customer_acceptance": { + "acceptance_type": "offline", + "accepted_at": "1963-05-03T04:07:52.723Z", + "online": { + "ip_address": "127.0.0.1", + "user_agent": "amet irure esse" + } + }, + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } }, } }; \ No newline at end of file diff --git a/cypress-tests/cypress/e2e/ConnectorUtils/BankOfAmerica.js b/cypress-tests/cypress/e2e/ConnectorUtils/BankOfAmerica.js index d92b66e965fc..5b3df0efc5d8 100644 --- a/cypress-tests/cypress/e2e/ConnectorUtils/BankOfAmerica.js +++ b/cypress-tests/cypress/e2e/ConnectorUtils/BankOfAmerica.js @@ -14,101 +14,368 @@ const successfulThreeDSTestCardDetails = { "card_holder_name": "joseph Doe", "card_cvc": "123" }; - export const connectorDetails = { -card_pm:{ - "3DS": { - "card": successfulThreeDSTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "pending", - "refundSyncStatus": "pending", - "customer_acceptance": null, - "setup_future_usage": "on_session", - }, - "No3DS": { - "card": successfulNo3DSCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "pending", - "refundSyncStatus": "pending", - "customer_acceptance": null, - "setup_future_usage": "on_session", - }, - "MandateSingleUse3DS": { - "card": successfulThreeDSTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "requires_customer_action", - "paymentSyncStatus": "succeeded", - "refundStatus": "pending", - "refundSyncStatus": "pending", - "mandate_type": { - "single_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "MandateSingleUseNo3DS": { - "card": successfulNo3DSCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "pending", - "refundSyncStatus": "pending", - "mandate_type": { - "single_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "MandateMultiUseNo3DS": { - "card": successfulNo3DSCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "pending", - "refundSyncStatus": "pending", - "mandate_type": { - "multi_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "MandateMultiUse3DS": { - "card": successfulThreeDSTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "requires_customer_action", - "paymentSyncStatus": "succeeded", - "refundStatus": "pending", - "refundSyncStatus": "pending", - "mandate_type": { - "multi_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "SaveCardUseNo3DS": { - "card": successfulNo3DSCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "succeeded", - "refundSyncStatus": "succeeded", - "setup_future_usage": "on_session", - "customer_acceptance": { - "acceptance_type": "offline", - "accepted_at": "1963-05-03T04:07:52.723Z", - "online": { - "ip_address": "127.0.0.1", - "user_agent": "amet irure esse" - } - }, - }, -} -}; \ No newline at end of file + card_pm:{ + "PaymentIntent": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "requires_payment_method" + } + } + }, + "3DSManualCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + "3DSAutoCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "No3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + "No3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "Capture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "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": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + }, + "Response": { + "status": 200, + "body": { + "status": "pending", + } + + } + }, + "PartialRefund": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + }, + "Response": { + "status": 200, + "body": { + "status": "pending", + } + + } + }, + "SyncRefund": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + }, + "Response": { + "status": 200, + "body": { + "status": "pending", + } + + } + }, + "MandateSingleUse3DSAutoCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + + }, + "MandateSingleUse3DSManualCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "requires_customer_action" + } + } + + }, + "MandateSingleUseNo3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "MandateSingleUseNo3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + "MandateMultiUseNo3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "MandateMultiUseNo3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + "MandateMultiUse3DSAutoCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + }, + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + "MandateMultiUse3DSManualCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + }, + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + "ZeroAuthMandate": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "SaveCardUseNo3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "setup_future_usage": "on_session", + "customer_acceptance": { + "acceptance_type": "offline", + "accepted_at": "1963-05-03T04:07:52.723Z", + "online": { + "ip_address": "127.0.0.1", + "user_agent": "amet irure esse" + } + }, + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "SaveCardUseNo3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "setup_future_usage": "on_session", + "customer_acceptance": { + "acceptance_type": "offline", + "accepted_at": "1963-05-03T04:07:52.723Z", + "online": { + "ip_address": "127.0.0.1", + "user_agent": "amet irure esse" + } + }, + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + } + }; diff --git a/cypress-tests/cypress/e2e/ConnectorUtils/Bluesnap.js b/cypress-tests/cypress/e2e/ConnectorUtils/Bluesnap.js index 540152eb6c3c..17e040668adc 100644 --- a/cypress-tests/cypress/e2e/ConnectorUtils/Bluesnap.js +++ b/cypress-tests/cypress/e2e/ConnectorUtils/Bluesnap.js @@ -15,99 +15,411 @@ const successfulThreeDSTestCardDetails = { }; export const connectorDetails = { -card_pm:{ - "3DS": { - "card": successfulThreeDSTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "succeeded", - "refundSyncStatus": "succeeded", - "customer_acceptance": null, - "setup_future_usage": "on_session", - }, - "No3DS": { - "card": successfulNo3DSCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "succeeded", - "refundSyncStatus": "succeeded", - "customer_acceptance": null, - "setup_future_usage": "on_session", - }, - "MandateSingleUse3DS": { - "card": successfulThreeDSTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "requires_customer_action", - "paymentSyncStatus": "succeeded", - "refundStatus": "succeeded", - "refundSyncStatus": "succeeded", - "mandate_type": { - "single_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "MandateSingleUseNo3DS": { - "card": successfulNo3DSCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "succeeded", - "refundSyncStatus": "succeeded", - "mandate_type": { - "single_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "MandateMultiUseNo3DS": { - "card": successfulNo3DSCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "succeeded", - "refundSyncStatus": "succeeded", - "mandate_type": { - "multi_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "MandateMultiUse3DS": { - "card": successfulThreeDSTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "requires_customer_action", - "paymentSyncStatus": "succeeded", - "refundStatus": "succeeded", - "refundSyncStatus": "succeeded", - "mandate_type": { - "multi_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "SaveCardUseNo3DS": { - "card": successfulNo3DSCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "succeeded", - "refundSyncStatus": "succeeded", - "setup_future_usage": "on_session", - "customer_acceptance": { - "acceptance_type": "offline", - "accepted_at": "1963-05-03T04:07:52.723Z", - "online": { - "ip_address": "127.0.0.1", - "user_agent": "amet irure esse" - } - }, - }, - } -}; \ No newline at end of file + card_pm:{ + "PaymentIntent": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "requires_payment_method" + } + } + }, + "3DSManualCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + "3DSAutoCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "No3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + "No3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "Capture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "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": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded", + } + + } + }, + "PartialRefund": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded", + } + + } + }, + "SyncRefund": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded", + } + + } + }, + "MandateSingleUse3DSAutoCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 400, + "body":{ + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + "reason": "debit mandate payment is not supported by bluesnap" + } + } + } + + }, + "MandateSingleUse3DSManualCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 400, + "body":{ + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + "reason": "debit mandate payment is not supported by bluesnap" + } + } + } + + }, + "MandateSingleUseNo3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 400, + "body":{ + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + "reason": "debit mandate payment is not supported by bluesnap" + } + } + } + }, + "MandateSingleUseNo3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 400, + "body":{ + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + "reason": "debit mandate payment is not supported by bluesnap" + } + } + } + }, + "MandateMultiUseNo3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 400, + "body":{ + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + "reason": "debit mandate payment is not supported by bluesnap" + } + } + } + }, + "MandateMultiUseNo3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 400, + "body":{ + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + "reason": "debit mandate payment is not supported by bluesnap" + } + } + } + }, + "MandateMultiUse3DSAutoCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + }, + }, + "Response": { + "status": 400, + "body":{ + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + "reason": "debit mandate payment is not supported by bluesnap" + } + } + } + }, + "MandateMultiUse3DSManualCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + }, + }, + "Response": { + "status": 400, + "body":{ + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + "reason": "debit mandate payment is not supported by bluesnap" + } + } + } + }, + "ZeroAuthMandate": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 501, + "body": { + "error": { + "type": "invalid_request", + "message": "Setup Mandate flow for Bluesnap is not implemented", + "code": "IR_00", + } + } + } + }, + "SaveCardUseNo3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "setup_future_usage": "on_session", + "customer_acceptance": { + "acceptance_type": "offline", + "accepted_at": "1963-05-03T04:07:52.723Z", + "online": { + "ip_address": "127.0.0.1", + "user_agent": "amet irure esse" + } + }, + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "SaveCardUseNo3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "setup_future_usage": "on_session", + "customer_acceptance": { + "acceptance_type": "offline", + "accepted_at": "1963-05-03T04:07:52.723Z", + "online": { + "ip_address": "127.0.0.1", + "user_agent": "amet irure esse" + } + }, + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + } + }; \ No newline at end of file diff --git a/cypress-tests/cypress/e2e/ConnectorUtils/Cybersource.js b/cypress-tests/cypress/e2e/ConnectorUtils/Cybersource.js index ec81ddc20c79..67d2875df5c8 100644 --- a/cypress-tests/cypress/e2e/ConnectorUtils/Cybersource.js +++ b/cypress-tests/cypress/e2e/ConnectorUtils/Cybersource.js @@ -15,99 +15,367 @@ const successfulThreeDSTestCardDetails = { }; export const connectorDetails = { -card_pm:{ - "3DS": { - "card": successfulThreeDSTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "pending", - "refundSyncStatus": "pending", - "customer_acceptance": null, - "setup_future_usage": "on_session", - }, - "No3DS": { - "card": successfulNo3DSCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "pending", - "refundSyncStatus": "pending", - "customer_acceptance": null, - "setup_future_usage": "on_session", - }, - "MandateSingleUse3DS": { - "card": successfulThreeDSTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "requires_customer_action", - "paymentSyncStatus": "succeeded", - "refundStatus": "pending", - "refundSyncStatus": "pending", - "mandate_type": { - "single_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "MandateSingleUseNo3DS": { - "card": successfulNo3DSCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "pending", - "refundSyncStatus": "pending", - "mandate_type": { - "single_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "MandateMultiUseNo3DS": { - "card": successfulNo3DSCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "pending", - "refundSyncStatus": "pending", - "mandate_type": { - "multi_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "MandateMultiUse3DS": { - "card": successfulThreeDSTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "requires_customer_action", - "paymentSyncStatus": "succeeded", - "refundStatus": "pending", - "refundSyncStatus": "pending", - "mandate_type": { - "multi_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "SaveCardUseNo3DS": { - "card": successfulNo3DSCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "succeeded", - "refundSyncStatus": "succeeded", - "setup_future_usage": "on_session", - "customer_acceptance": { - "acceptance_type": "offline", - "accepted_at": "1963-05-03T04:07:52.723Z", - "online": { - "ip_address": "127.0.0.1", - "user_agent": "amet irure esse" - } - }, - }, - } -}; \ No newline at end of file + card_pm:{ + "PaymentIntent": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "requires_payment_method" + } + } + }, + "3DSManualCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + "3DSAutoCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "No3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + "No3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "Capture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "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": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + }, + "Response": { + "status": 200, + "body": { + "status": "pending", + } + + } + }, + "PartialRefund": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + }, + "Response": { + "status": 200, + "body": { + "status": "pending", + } + + } + }, + "SyncRefund": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + }, + "Response": { + "status": 200, + "body": { + "status": "pending", + } + + } + }, + "MandateSingleUse3DSAutoCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + + }, + "MandateSingleUse3DSManualCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "requires_customer_action" + } + } + + }, + "MandateSingleUseNo3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "MandateSingleUseNo3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + "MandateMultiUseNo3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "MandateMultiUseNo3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + "MandateMultiUse3DSAutoCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + }, + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + "MandateMultiUse3DSManualCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + }, + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + "ZeroAuthMandate": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "SaveCardUseNo3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "setup_future_usage": "on_session", + "customer_acceptance": { + "acceptance_type": "offline", + "accepted_at": "1963-05-03T04:07:52.723Z", + "online": { + "ip_address": "127.0.0.1", + "user_agent": "amet irure esse" + } + }, + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "SaveCardUseNo3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "setup_future_usage": "on_session", + "customer_acceptance": { + "acceptance_type": "offline", + "accepted_at": "1963-05-03T04:07:52.723Z", + "online": { + "ip_address": "127.0.0.1", + "user_agent": "amet irure esse" + } + }, + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + } + }; \ No newline at end of file diff --git a/cypress-tests/cypress/e2e/ConnectorUtils/Nmi.js b/cypress-tests/cypress/e2e/ConnectorUtils/Nmi.js index 55d25a8ecf9d..27caaa82dc83 100644 --- a/cypress-tests/cypress/e2e/ConnectorUtils/Nmi.js +++ b/cypress-tests/cypress/e2e/ConnectorUtils/Nmi.js @@ -15,100 +15,413 @@ const successfulThreeDSTestCardDetails = { }; export const connectorDetails = { -card_pm:{ - "3DS": { - "card": successfulThreeDSTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "pending", - "refundSyncStatus": "succeeded", - "customer_acceptance": null, - "setup_future_usage": "on_session", - - }, - "No3DS": { - "card": successfulNo3DSCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "processing", - "paymentSyncStatus": "succeeded", - "refundStatus": "pending", - "refundSyncStatus": "succeeded", - "customer_acceptance": null, - "setup_future_usage": "on_session", - }, - "MandateSingleUse3DS": { - "card": successfulThreeDSTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "requires_customer_action", - "paymentSyncStatus": "processing", - "refundStatus": "pending", - "refundSyncStatus": "succeeded", - "mandate_type": { - "single_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "MandateSingleUseNo3DS": { - "card": successfulNo3DSCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "processing", - "paymentSyncStatus": "succeeded", - "refundStatus": "pending", - "refundSyncStatus": "succeeded", - "mandate_type": { - "single_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "MandateMultiUseNo3DS": { - "card": successfulNo3DSCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "processing", - "paymentSyncStatus": "succeeded", - "refundStatus": "pending", - "refundSyncStatus": "succeeded", - "mandate_type": { - "multi_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "MandateMultiUse3DS": { - "card": successfulThreeDSTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "requires_customer_action", - "paymentSyncStatus": "succeeded", - "refundStatus": "pending", - "refundSyncStatus": "succeeded", - "mandate_type": { - "multi_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "SaveCardUseNo3DS": { - "card": successfulNo3DSCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "processing", - "paymentSyncStatus": "succeeded", - "refundStatus": "pending", - "refundSyncStatus": "succeeded", - "setup_future_usage": "on_session", - "customer_acceptance": { - "acceptance_type": "offline", - "accepted_at": "1963-05-03T04:07:52.723Z", - "online": { - "ip_address": "127.0.0.1", - "user_agent": "amet irure esse" - } - }, - }, - } -}; \ No newline at end of file + card_pm:{ + "PaymentIntent": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "requires_payment_method" + } + } + }, + "3DSManualCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "processing" + } + } + }, + "3DSAutoCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "processing" + } + } + }, + "No3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "processing" + } + } + }, + "No3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "processing" + } + } + }, + "Capture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + }, + "Response": { + "status": 200, + "body":{ + "status": "processing", + "amount": 6500, + "amount_capturable": 6500, + + } + } + }, + "PartialCapture": { + "Request": { + + }, + "Response": { + "status": 200, + "body": { + "status": "processing", + "amount": 6500, + "amount_capturable": 6500, + } + + } + }, + "Void":{ + "Request": { + }, + "Response": { + "status": 400, + "body":{ + "error":{ + "code":"IR_16", + "message":"You cannot cancel this payment because it has status processing", + "type":"invalid_request", + } + + } + } + }, + "Refund": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + }, + "Response": { + "status": 200, + "body": { + "status": "pending", + } + + } + }, + "PartialRefund": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + }, + "Response": { + "status": 200, + "body": { + "status": "pending", + } + + } + }, + "SyncRefund": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded", + } + + } + }, + "MandateSingleUse3DSAutoCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 400, + "body":{ + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + "reason": "debit mandate payment is not supported by nmi" + } + } + } + + }, + "MandateSingleUse3DSManualCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 400, + "body":{ + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + "reason": "debit mandate payment is not supported by nmi" + } + } + } + + }, + "MandateSingleUseNo3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 400, + "body":{ + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + "reason": "debit mandate payment is not supported by nmi" + } + } + } + }, + "MandateSingleUseNo3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 400, + "body":{ + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + "reason": "debit mandate payment is not supported by nmi" + } + } + } + }, + "MandateMultiUseNo3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 400, + "body":{ + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + "reason": "debit mandate payment is not supported by nmi" + } + } + } + }, + "MandateMultiUseNo3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 400, + "body":{ + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + "reason": "debit mandate payment is not supported by nmi" + } + } + } + }, + "MandateMultiUse3DSAutoCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + }, + }, + "Response": { + "status": 400, + "body":{ + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + "reason": "debit mandate payment is not supported by nmi" + } + } + } + }, + "MandateMultiUse3DSManualCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + }, + }, + "Response": { + "status": 400, + "body":{ + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + "reason": "debit mandate payment is not supported by nmi" + } + } + } + }, + "ZeroAuthMandate": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 501, + "body": { + "error": { + "type": "invalid_request", + "message": "Setup Mandate flow for Nmi is not implemented", + "code": "IR_00", + } + } + } + }, + "SaveCardUseNo3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "setup_future_usage": "on_session", + "customer_acceptance": { + "acceptance_type": "offline", + "accepted_at": "1963-05-03T04:07:52.723Z", + "online": { + "ip_address": "127.0.0.1", + "user_agent": "amet irure esse" + } + }, + }, + "Response": { + "status": 200, + "body": { + "status": "processing" + } + } + }, + "SaveCardUseNo3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "setup_future_usage": "on_session", + "customer_acceptance": { + "acceptance_type": "offline", + "accepted_at": "1963-05-03T04:07:52.723Z", + "online": { + "ip_address": "127.0.0.1", + "user_agent": "amet irure esse" + } + }, + }, + "Response": { + "status": 200, + "body": { + "status": "processing" + } + } + }, + } + }; \ No newline at end of file diff --git a/cypress-tests/cypress/e2e/ConnectorUtils/Paypal.js b/cypress-tests/cypress/e2e/ConnectorUtils/Paypal.js index b0e876a71278..056b4b5e8ac3 100644 --- a/cypress-tests/cypress/e2e/ConnectorUtils/Paypal.js +++ b/cypress-tests/cypress/e2e/ConnectorUtils/Paypal.js @@ -17,90 +17,424 @@ const successfulThreeDSTestCardDetails = { export const connectorDetails = { card_pm:{ - "3DS": { - "card": successfulThreeDSTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "requires_customer_action", - "paymentSyncStatus": "processing", - "customer_acceptance":null, - "setup_future_usage": "on_session", + "PaymentIntent": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "requires_payment_method" + } + } + }, + "3DSManualCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + "3DSAutoCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "processing" + } + } + }, + "No3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + "No3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "processing" + } + } + }, + "Capture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + }, + "Response": { + "status": 200, + "body":{ + "status": "processing", + "amount": 6500, + "amount_capturable": 6500, + "amount_received": 0, + } + } }, - "No3DS": { - "card": successfulNo3DSCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "processing", - "paymentSyncStatus": "processing", - "customer_acceptance":null, - "setup_future_usage": "on_session", + "PartialCapture": { + "Request": { + + }, + "Response": { + "status": 200, + "body": { + "status": "processing", + "amount": 6500, + "amount_capturable": 6500, + "amount_received": 0, + } + } }, - "MandateSingleUse3DS": { - "card": successfulThreeDSTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "requires_customer_action", - "paymentSyncStatus": "processing", - "mandate_type": { - "single_use": { - "amount": 8000, - "currency": "USD" + "Void":{ + "Request": { + }, + "Response": { + "status": 200, + "body":{ + status: "cancelled" + + } + } + }, + "Refund": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + }, + "Response": { + "status": 400, + "body": { + "error": { + "type": "invalid_request", + "message": "This Payment could not be refund because it has a status of processing. The expected state is succeeded, partially_captured", + "code" : "IR_14" + }, } + } }, - "MandateSingleUseNo3DS": { - "card": successfulNo3DSCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "mandate_type": { - "single_use": { - "amount": 8000, - "currency": "USD" + "PartialRefund": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + }, + "Response": { + "status": 400, + "body": { + "error": { + "type": "invalid_request", + "message": "This Payment could not be refund because it has a status of processing. The expected state is succeeded, partially_captured", + "code" : "IR_14" + }, } + } }, - "MandateMultiUseNo3DS": { - "card": successfulNo3DSCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "mandate_type": { - "multi_use": { - "amount": 8000, - "currency": "USD" + "SyncRefund": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + }, + "Response": { + "status": 400, + "body": { + "error": { + "type": "invalid_request", + "message": "This Payment could not be refund because it has a status of processing. The expected state is succeeded, partially_captured", + "code" : "IR_14" + }, } + } }, - "MandateMultiUse3DS": { - "card": successfulThreeDSTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "requires_customer_action", - "paymentSyncStatus": "processing", - "mandate_type": { - "multi_use": { - "amount": 8000, - "currency": "USD" + "MandateSingleUse3DSAutoCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 400, + "body":{ + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + "reason": "debit mandate payment is not supported by paypal" + } } } + }, - "SaveCardUseNo3DS": { - "card": successfulNo3DSCardDetails, - "currency":"USD", - "paymentSuccessfulStatus": "processing", - "paymentSyncStatus": "succeeded", - "refundStatus": "succeeded", - "refundSyncStatus": "succeeded", - "setup_future_usage": "on_session", - "customer_acceptance": { - "acceptance_type": "offline", - "accepted_at": "1963-05-03T04:07:52.723Z", - "online": { - "ip_address": "127.0.0.1", - "user_agent": "amet irure esse" + "MandateSingleUse3DSManualCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } } }, + "Response": { + "status": 400, + "body":{ + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + "reason": "debit mandate payment is not supported by paypal" + } + } + } + }, - }, -}; + "MandateSingleUseNo3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 400, + "body":{ + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + "reason": "debit mandate payment is not supported by paypal" + } + } + } + }, + "MandateSingleUseNo3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 400, + "body":{ + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + "reason": "debit mandate payment is not supported by paypal" + } + } + } + }, + "MandateMultiUseNo3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 400, + "body":{ + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + "reason": "debit mandate payment is not supported by paypal" + } + } + } + }, + "MandateMultiUseNo3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 400, + "body":{ + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + "reason": "debit mandate payment is not supported by paypal" + } + } + } + }, + "MandateMultiUse3DSAutoCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + }, + }, + "Response": { + "status": 400, + "body":{ + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + "reason": "debit mandate payment is not supported by paypal" + } + } + } + }, + "MandateMultiUse3DSManualCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + }, + }, + "Response": { + "status": 400, + "body":{ + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + "reason": "debit mandate payment is not supported by paypal" + } + } + } + }, + "ZeroAuthMandate": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 501, + "body": { + "error": { + "type": "invalid_request", + "message": "Setup Mandate flow for Paypal is not implemented", + "code": "IR_00", + } + } + } + }, + "SaveCardUseNo3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "setup_future_usage": "on_session", + "customer_acceptance": { + "acceptance_type": "offline", + "accepted_at": "1963-05-03T04:07:52.723Z", + "online": { + "ip_address": "127.0.0.1", + "user_agent": "amet irure esse" + } + }, + }, + "Response": { + "status": 200, + "body": { + "status": "processing" + } + } + }, + "SaveCardUseNo3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "setup_future_usage": "on_session", + "customer_acceptance": { + "acceptance_type": "offline", + "accepted_at": "1963-05-03T04:07:52.723Z", + "online": { + "ip_address": "127.0.0.1", + "user_agent": "amet irure esse" + } + }, + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + } + }; diff --git a/cypress-tests/cypress/e2e/ConnectorUtils/Stripe.js b/cypress-tests/cypress/e2e/ConnectorUtils/Stripe.js index fb0683db2ad8..1003946dd736 100644 --- a/cypress-tests/cypress/e2e/ConnectorUtils/Stripe.js +++ b/cypress-tests/cypress/e2e/ConnectorUtils/Stripe.js @@ -1,7 +1,7 @@ const successfulTestCard = "4242424242424242"; const successful3DSCard = "4000002760003184"; -const successfulTestCardDetails = { +const successfulNo3DSCardDetails = { "card_number": "4242424242424242", "card_exp_month": "10", "card_exp_year": "25", @@ -19,98 +19,366 @@ const successfulThreeDSTestCardDetails = { export const connectorDetails = { card_pm:{ - "3DS": { - "card": successfulThreeDSTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "succeeded", - "refundSyncStatus": "succeeded", - "customer_acceptance": null, - "setup_future_usage": "on_session" - }, - "No3DS": { - "card": successfulTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "succeeded", - "refundSyncStatus": "succeeded", - "customer_acceptance": null, - "setup_future_usage": "on_session" - }, - "MandateSingleUse3DS": { - "card": successfulThreeDSTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "requires_customer_action", - "paymentSyncStatus": "succeeded", - "refundStatus": "succeeded", - "refundSyncStatus": "succeeded", - "mandate_type": { - "single_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "MandateSingleUseNo3DS": { - "card": successfulTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "succeeded", - "refundSyncStatus": "succeeded", - "mandate_type": { - "single_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "MandateMultiUseNo3DS": { - "card": successfulTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "succeeded", - "refundSyncStatus": "succeeded", - "mandate_type": { - "multi_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "MandateMultiUse3DS": { - "card": successfulThreeDSTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "requires_customer_action", - "paymentSyncStatus": "succeeded", - "refundStatus": "succeeded", - "refundSyncStatus": "succeeded", - "mandate_type": { - "multi_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "SaveCardUseNo3DS": { - "card": successfulTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "succeeded", - "refundSyncStatus": "succeeded", - "setup_future_usage": "on_session", - "customer_acceptance": { - "acceptance_type": "offline", - "accepted_at": "1963-05-03T04:07:52.723Z", - "online": { - "ip_address": "127.0.0.1", - "user_agent": "amet irure esse" + "PaymentIntent": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "requires_payment_method" + } + } + }, + "3DSManualCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + "3DSAutoCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "No3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + "No3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "Capture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "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": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded", + } + + } + }, + "PartialRefund": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded", + } + + } + }, + "SyncRefund": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded", + } + + } + }, + "MandateSingleUse3DSAutoCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + + }, + "MandateSingleUse3DSManualCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "requires_customer_action" + } + } + + }, + "MandateSingleUseNo3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } } }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "MandateSingleUseNo3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + "MandateMultiUseNo3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "MandateMultiUseNo3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + "MandateMultiUse3DSAutoCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + }, + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + "MandateMultiUse3DSManualCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + }, + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } + }, + "ZeroAuthMandate": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "SaveCardUseNo3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "setup_future_usage": "on_session", + "customer_acceptance": { + "acceptance_type": "offline", + "accepted_at": "1963-05-03T04:07:52.723Z", + "online": { + "ip_address": "127.0.0.1", + "user_agent": "amet irure esse" + } + }, + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "SaveCardUseNo3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "setup_future_usage": "on_session", + "customer_acceptance": { + "acceptance_type": "offline", + "accepted_at": "1963-05-03T04:07:52.723Z", + "online": { + "ip_address": "127.0.0.1", + "user_agent": "amet irure esse" + } + }, + }, + "Response": { + "status": 200, + "body": { + "status": "requires_capture" + } + } }, } }; \ No newline at end of file diff --git a/cypress-tests/cypress/e2e/ConnectorUtils/Trustpay.js b/cypress-tests/cypress/e2e/ConnectorUtils/Trustpay.js index 71d0348c5eec..182d10857ece 100644 --- a/cypress-tests/cypress/e2e/ConnectorUtils/Trustpay.js +++ b/cypress-tests/cypress/e2e/ConnectorUtils/Trustpay.js @@ -16,98 +16,419 @@ const successfulThreeDSTestCardDetails = { export const connectorDetails = { card_pm:{ - "3DS": { - "card": successfulThreeDSTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "succeeded", - "refundSyncStatus": "succeeded", - "customer_acceptance": null, - "setup_future_usage": "on_session", - }, - "No3DS": { - "card": successfulNo3DSCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "succeeded", - "refundSyncStatus": "succeeded", - "customer_acceptance": null, - "setup_future_usage": "on_session", - }, - "MandateSingleUse3DS": { - "card": successfulThreeDSTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "requires_customer_action", - "paymentSyncStatus": "succeeded", - "refundStatus": "succeeded", - "refundSyncStatus": "succeeded", - "mandate_type": { - "single_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "MandateSingleUseNo3DS": { - "card": successfulNo3DSCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "succeeded", - "refundSyncStatus": "succeeded", - "mandate_type": { - "single_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "MandateMultiUseNo3DS": { - "card": successfulNo3DSCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "succeeded", - "refundSyncStatus": "succeeded", - "mandate_type": { - "multi_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "MandateMultiUse3DS": { - "card": successfulThreeDSTestCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "requires_customer_action", - "paymentSyncStatus": "succeeded", - "refundStatus": "succeeded", - "refundSyncStatus": "succeeded", - "mandate_type": { - "multi_use": { - "amount": 8000, - "currency": "USD" - } - } - }, - "SaveCardUseNo3DS": { - "card": successfulNo3DSCardDetails, - "currency": "USD", - "paymentSuccessfulStatus": "succeeded", - "paymentSyncStatus": "succeeded", - "refundStatus": "succeeded", - "refundSyncStatus": "succeeded", - "setup_future_usage": "on_session", - "customer_acceptance": { - "acceptance_type": "offline", - "accepted_at": "1963-05-03T04:07:52.723Z", - "online": { - "ip_address": "127.0.0.1", - "user_agent": "amet irure esse" + "PaymentIntent": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "requires_payment_method" } + } + }, + "3DSAutoCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "3DSManualCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" }, + "Response": { + "status": 400, + "body": { + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + "reason": "manual is not supported by trustpay" + } + } + } + }, + "No3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "No3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + "setup_future_usage": "on_session" + }, + "Response": { + "status": 400, + "body": { + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + "reason": "manual is not supported by trustpay" + } + } + } + }, + "Capture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + }, + "Response": { + "status": 400, + "body": { + "error": { + "type": "invalid_request", + "message": "This Payment could not be captured because it has a payment.status of requires_payment_method. The expected state is requires_capture, partially_captured_and_capturable, processing", + "code": "IR_14", + } + } + } + }, + "PartialCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "paymentSuccessfulStatus": "succeeded", + "paymentSyncStatus": "succeeded", + "refundStatus": "succeeded", + "refundSyncStatus": "succeeded", + "customer_acceptance": null, + }, + "Response": { + "status": 400, + "body": { + "error": { + "type": "invalid_request", + "message": "This Payment could not be captured because it has a payment.status of requires_payment_method. The expected state is requires_capture, partially_captured_and_capturable, processing", + "code": "IR_14", + } + } + } + }, + "Void":{ + "Request": { + }, + "Response": { + "status": 200, + "body":{ + status: "cancelled" + + } + } + }, + "Refund": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded", + } + + } + }, + "PartialRefund": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + }, + "Response": { + "status": 200, + "body": { + "error_code": "1", + "error_message": "transaction declined (invalid amount)", + } + + } + }, + "SyncRefund": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "customer_acceptance": null, + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded", + } + + } + }, + "MandateSingleUse3DSAutoCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 400, + "body": { + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + } + } + } + + }, + "MandateSingleUse3DSManualCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 400, + "body": { + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + } + } + } + + }, + "MandateSingleUseNo3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 400, + "body": { + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + } + } + } + }, + "MandateSingleUseNo3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 400, + "body": { + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + } + } + } + }, + "MandateMultiUseNo3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 400, + "body": { + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + } + } + } + }, + "MandateMultiUseNo3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 400, + "body": { + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + } + } + } + }, + "MandateMultiUse3DSAutoCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "MandateMultiUse3DSManualCapture": { + "Request": { + "card": successfulThreeDSTestCardDetails, + "currency": "USD", + "mandate_type": { + "multi_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 400, + "body": { + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + } + } + } + }, + "ZeroAuthMandate": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "mandate_type": { + "single_use": { + "amount": 8000, + "currency": "USD" + } + } + }, + "Response": { + "status": 501, + "body": { + "error": { + "type": "invalid_request", + "message": "Setup Mandate flow for Trustpay is not implemented", + "code": "IR_00", + } + } + } + }, + "SaveCardUseNo3DSAutoCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "setup_future_usage": "on_session", + "customer_acceptance": { + "acceptance_type": "offline", + "accepted_at": "1963-05-03T04:07:52.723Z", + "online": { + "ip_address": "127.0.0.1", + "user_agent": "amet irure esse" + } + }, + }, + "Response": { + "status": 200, + "body": { + "status": "succeeded" + } + } + }, + "SaveCardUseNo3DSManualCapture": { + "Request": { + "card": successfulNo3DSCardDetails, + "currency": "USD", + "setup_future_usage": "on_session", + "customer_acceptance": { + "acceptance_type": "offline", + "accepted_at": "1963-05-03T04:07:52.723Z", + "online": { + "ip_address": "127.0.0.1", + "user_agent": "amet irure esse" + } + }, + }, + "Response": { + "status": 400, + "body": { + "error": { + "type": "invalid_request", + "message": "Payment method type not supported", + "code": "HE_03", + } + } + } }, } } \ No newline at end of file diff --git a/cypress-tests/cypress/e2e/ConnectorUtils/utils.js b/cypress-tests/cypress/e2e/ConnectorUtils/utils.js index c667c402c564..960481a1252a 100644 --- a/cypress-tests/cypress/e2e/ConnectorUtils/utils.js +++ b/cypress-tests/cypress/e2e/ConnectorUtils/utils.js @@ -34,4 +34,13 @@ function getValueByKey(jsonObject, key) { } else { return null; } +} + +export const should_continue_further = (res_data) => { + if(res_data.body.error !== undefined || res_data.body.error_code !== undefined || res_data.body.error_message !== undefined){ + return false; + } + else { + return true; + } } \ No newline at end of file diff --git a/cypress-tests/cypress/fixtures/confirm-body.json b/cypress-tests/cypress/fixtures/confirm-body.json index 526d55e3fa84..32b9a7108be7 100644 --- a/cypress-tests/cypress/fixtures/confirm-body.json +++ b/cypress-tests/cypress/fixtures/confirm-body.json @@ -47,6 +47,7 @@ "screen_width": 1728, "time_zone": -330, "java_enabled": true, - "java_script_enabled": true + "java_script_enabled": true, + "ip_address": "127.0.0.1" } } diff --git a/cypress-tests/cypress/fixtures/create-confirm-body.json b/cypress-tests/cypress/fixtures/create-confirm-body.json index 1399f181342d..cf95e6f8c8fe 100644 --- a/cypress-tests/cypress/fixtures/create-confirm-body.json +++ b/cypress-tests/cypress/fixtures/create-confirm-body.json @@ -1,10 +1,10 @@ { - "amount": 6540, + "amount": 6500, "currency": "USD", "confirm": true, "capture_method": "automatic", "capture_on": "2022-09-10T10:11:12Z", - "amount_to_capture": 6540, + "amount_to_capture": 6500, "customer_id": "john123", "email": "guest@example.com", "name": "John Doe", diff --git a/cypress-tests/cypress/fixtures/create-mandate-cit.json b/cypress-tests/cypress/fixtures/create-mandate-cit.json index e75aab9d00fa..c96284ea99ba 100644 --- a/cypress-tests/cypress/fixtures/create-mandate-cit.json +++ b/cypress-tests/cypress/fixtures/create-mandate-cit.json @@ -11,7 +11,7 @@ "phone_country_code": "+65", "description": "Its my first payment request", "authentication_type": "no_three_ds", - "return_url": "https://duck.com", + "return_url": "https://hyperswitch.io", "payment_method": "card", "payment_method_type": "debit", "payment_method_data": { diff --git a/cypress-tests/cypress/fixtures/save-card-confirm-body.json b/cypress-tests/cypress/fixtures/save-card-confirm-body.json index 9a39a3f64972..6ef086825f66 100644 --- a/cypress-tests/cypress/fixtures/save-card-confirm-body.json +++ b/cypress-tests/cypress/fixtures/save-card-confirm-body.json @@ -28,5 +28,17 @@ "first_name": "john", "last_name": "doe" } + }, + "browser_info": { + "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", + "language": "en-US", + "color_depth": 30, + "screen_height": 1117, + "screen_width": 1728, + "time_zone": -330, + "java_enabled": true, + "java_script_enabled": true, + "ip_address": "127.0.0.1" } } diff --git a/cypress-tests/cypress/support/commands.js b/cypress-tests/cypress/support/commands.js index e6518f70257e..b28619d788a6 100644 --- a/cypress-tests/cypress/support/commands.js +++ b/cypress-tests/cypress/support/commands.js @@ -126,21 +126,19 @@ Cypress.Commands.add("createCustomerCallTest", (customerCreateBody, globalState) }).then((response) => { logRequestId(response.headers['x-request-id']); - // Handle the response as needed - console.log(response); - globalState.set("customerId", response.body.customer_id); }); }); -Cypress.Commands.add("createPaymentIntentTest", (request, det, authentication_type, capture_method, globalState) => { - if (!request || typeof request !== "object" || !det.currency || !authentication_type) { +Cypress.Commands.add("createPaymentIntentTest", (request, req_data, res_data, authentication_type, capture_method, globalState) => { + if (!request || typeof request !== "object" || !req_data.currency || !authentication_type) { throw new Error("Invalid parameters provided to createPaymentIntentTest command"); } - request.currency = det.currency; + request.currency = req_data.currency; request.authentication_type = authentication_type; request.capture_method = capture_method; - request.setup_future_usage = det.setup_future_usage; + request.setup_future_usage = req_data.setup_future_usage; + request.customer_acceptance = req_data.customer_acceptance; request.customer_id = globalState.get("customerId"); globalState.set("paymentAmount", request.amount); cy.request({ @@ -151,20 +149,33 @@ Cypress.Commands.add("createPaymentIntentTest", (request, det, authentication_ty Accept: "application/json", "api-key": globalState.get("apiKey"), }, + failOnStatusCode: false, body: request, }).then((response) => { logRequestId(response.headers['x-request-id']); - console.log(response); + + expect(res_data.status).to.equal(response.status); expect(response.headers["content-type"]).to.include("application/json"); - expect(response.body).to.have.property("client_secret"); - const clientSecret = response.body.client_secret; - globalState.set("clientSecret", clientSecret); - globalState.set("paymentID", response.body.payment_id); - cy.log(clientSecret); - expect("requires_payment_method").to.equal(response.body.status); - expect(request.amount).to.equal(response.body.amount); - expect(null).to.equal(response.body.amount_received); - expect(request.amount).to.equal(response.body.amount_capturable); + + if(response.status === 200){ + expect(response.body).to.have.property("client_secret"); + const clientSecret = response.body.client_secret; + globalState.set("clientSecret", clientSecret); + globalState.set("paymentID", response.body.payment_id); + cy.log(clientSecret); + for(const key in res_data.body) { + expect(res_data.body[key]).to.equal(response.body[key]); + } + expect(request.amount).to.equal(response.body.amount); + expect(null).to.equal(response.body.amount_received); + expect(request.amount).to.equal(response.body.amount_capturable); + } + else { + expect(response.body).to.have.property("error"); + for(const key in res_data.body.error) { + expect(res_data.body.error[key]).to.equal(response.body.error[key]); + } + } }); }); @@ -182,7 +193,6 @@ Cypress.Commands.add("paymentMethodsCallTest", (globalState) => { }).then((response) => { logRequestId(response.headers['x-request-id']); - console.log(response); expect(response.headers["content-type"]).to.include("application/json"); expect(response.body).to.have.property("redirect_url"); expect(response.body).to.have.property("payment_methods"); @@ -191,12 +201,12 @@ Cypress.Commands.add("paymentMethodsCallTest", (globalState) => { }); }); -Cypress.Commands.add("confirmCallTest", (confirmBody, details, confirm, globalState) => { +Cypress.Commands.add("confirmCallTest", (confirmBody, req_data, res_data, confirm, globalState) => { const paymentIntentID = globalState.get("paymentID"); - confirmBody.payment_method_data.card = details.card; + confirmBody.payment_method_data.card = req_data.card; confirmBody.confirm = confirm; confirmBody.client_secret = globalState.get("clientSecret"); - confirmBody.customer_acceptance = details.customer_acceptance; + confirmBody.customer_acceptance = req_data.customer_acceptance; cy.request({ method: "POST", @@ -205,49 +215,60 @@ Cypress.Commands.add("confirmCallTest", (confirmBody, details, confirm, globalSt "Content-Type": "application/json", "api-key": globalState.get("publishableKey"), }, + failOnStatusCode: false, body: confirmBody, }).then((response) => { logRequestId(response.headers['x-request-id']); - console.log(response); + + expect(res_data.status).to.equal(response.status); expect(response.headers["content-type"]).to.include("application/json"); - globalState.set("paymentID", paymentIntentID); - if (response.body.capture_method === "automatic") { - if (response.body.authentication_type === "three_ds") { - expect(response.body).to.have.property("next_action") - .to.have.property("redirect_to_url"); - globalState.set("nextActionUrl", response.body.next_action.redirect_to_url); - } else if (response.body.authentication_type === "no_three_ds") { - expect(details.paymentSuccessfulStatus).to.equal(response.body.status); - } else { - // Handle other authentication types as needed - throw new Error(`Unsupported authentication type: ${authentication_type}`); - } - } else if (response.body.capture_method === "manual") { - if (response.body.authentication_type === "three_ds") { - expect(response.body).to.have.property("next_action") - .to.have.property("redirect_to_url") - globalState.set("nextActionUrl", response.body.next_action.redirect_to_url); - } - else if (response.body.authentication_type === "no_three_ds") { - expect("requires_capture").to.equal(response.body.status); - } else { - // Handle other authentication types as needed - throw new Error(`Unsupported authentication type: ${authentication_type}`); + if(response.status === 200){ + globalState.set("paymentID", paymentIntentID); + if (response.body.capture_method === "automatic") { + if (response.body.authentication_type === "three_ds") { + expect(response.body).to.have.property("next_action") + .to.have.property("redirect_to_url"); + globalState.set("nextActionUrl", response.body.next_action.redirect_to_url); + } else if (response.body.authentication_type === "no_three_ds") { + for(const key in res_data.body) { + expect(res_data.body[key]).to.equal(response.body[key]); + } + } else { + // Handle other authentication types as needed + throw new Error(`Unsupported authentication type: ${authentication_type}`); + } + } else if (response.body.capture_method === "manual") { + if (response.body.authentication_type === "three_ds") { + expect(response.body).to.have.property("next_action") + .to.have.property("redirect_to_url") + globalState.set("nextActionUrl", response.body.next_action.redirect_to_url); + } + else if (response.body.authentication_type === "no_three_ds") { + for(const key in res_data.body) { + expect(res_data.body[key]).to.equal(response.body[key]); + } } else { + // Handle other authentication types as needed + throw new Error(`Unsupported authentication type: ${authentication_type}`); + } } } else { - throw new Error(`Unsupported capture method: ${capture_method}`); + expect(response.body).to.have.property("error"); + for(const key in res_data.body.error) { + expect(res_data.body.error[key]).to.equal(response.body.error[key]); + } } + }); }); -Cypress.Commands.add("createConfirmPaymentTest", (createConfirmPaymentBody, details, authentication_type, capture_method, globalState) => { - createConfirmPaymentBody.payment_method_data.card = details.card; +Cypress.Commands.add("createConfirmPaymentTest", (createConfirmPaymentBody, req_data, res_data, authentication_type, capture_method, globalState) => { + createConfirmPaymentBody.payment_method_data.card = req_data.card; createConfirmPaymentBody.authentication_type = authentication_type; - createConfirmPaymentBody.currency = details.currency; + createConfirmPaymentBody.currency = req_data.currency; createConfirmPaymentBody.capture_method = capture_method; - createConfirmPaymentBody.customer_acceptance = details.customer_acceptance; - createConfirmPaymentBody.setup_future_usage = details.setup_future_usage; + createConfirmPaymentBody.customer_acceptance = req_data.customer_acceptance; + createConfirmPaymentBody.setup_future_usage = req_data.setup_future_usage; createConfirmPaymentBody.customer_id = globalState.get("customerId"); cy.request({ @@ -257,49 +278,66 @@ Cypress.Commands.add("createConfirmPaymentTest", (createConfirmPaymentBody, deta "Content-Type": "application/json", "api-key": globalState.get("apiKey"), }, + failOnStatusCode: false, body: createConfirmPaymentBody, }).then((response) => { logRequestId(response.headers['x-request-id']); - console.log(response); + + expect(res_data.status).to.equal(response.status); expect(response.headers["content-type"]).to.include("application/json"); - expect(response.body).to.have.property("status"); - globalState.set("paymentAmount", createConfirmPaymentBody.amount); - globalState.set("paymentID", response.body.payment_id); - if (response.body.capture_method === "automatic") { - if (response.body.authentication_type === "three_ds") { - expect(response.body).to.have.property("next_action") - .to.have.property("redirect_to_url") - globalState.set("nextActionUrl", response.body.next_action.redirect_to_url); + + if(response.status === 200){ + if (response.body.capture_method === "automatic") { + expect(response.body).to.have.property("status"); + globalState.set("paymentAmount", createConfirmPaymentBody.amount); + globalState.set("paymentID", response.body.payment_id); + if (response.body.authentication_type === "three_ds") { + expect(response.body).to.have.property("next_action") + .to.have.property("redirect_to_url") + globalState.set("nextActionUrl", response.body.next_action.redirect_to_url); + } + else if (response.body.authentication_type === "no_three_ds") { + for(const key in res_data.body) { + expect(res_data.body[key]).to.equal(response.body[key]); + } + } else { + // Handle other authentication types as needed + throw new Error(`Unsupported authentication type: ${authentication_type}`); + } } - else if (response.body.authentication_type === "no_three_ds") { - expect(details.paymentSuccessfulStatus).to.equal(response.body.status); - } else { - // Handle other authentication types as needed - throw new Error(`Unsupported authentication type: ${authentication_type}`); + else if (response.body.capture_method === "manual") { + expect(response.body).to.have.property("status"); + globalState.set("paymentAmount", createConfirmPaymentBody.amount); + globalState.set("paymentID", response.body.payment_id); + if (response.body.authentication_type === "three_ds") { + expect(response.body).to.have.property("next_action") + .to.have.property("redirect_to_url") + globalState.set("nextActionUrl", response.body.next_action.redirect_to_url); + } + else if (response.body.authentication_type === "no_three_ds") { + for(const key in res_data.body) { + expect(res_data.body[key]).to.equal(response.body[key]); + } } else { + // Handle other authentication types as needed + throw new Error(`Unsupported authentication type: ${authentication_type}`); + } } } - else if (response.body.capture_method === "manual") { - if (response.body.authentication_type === "three_ds") { - expect(response.body).to.have.property("next_action") - .to.have.property("redirect_to_url") - globalState.set("nextActionUrl", response.body.next_action.redirect_to_url); - } - else if (response.body.authentication_type === "no_three_ds") { - expect("requires_capture").to.equal(response.body.status); - } else { - // Handle other authentication types as needed - throw new Error(`Unsupported authentication type: ${authentication_type}`); + else{ + expect(response.body).to.have.property("error"); + for(const key in res_data.body.error) { + expect(res_data.body.error[key]).to.equal(response.body.error[key]); } } }); }); // This is consequent saved card payment confirm call test(Using payment token) -Cypress.Commands.add("saveCardConfirmCallTest", (saveCardConfirmBody,det,globalState) => { +Cypress.Commands.add("saveCardConfirmCallTest", (SaveCardConfirmBody, req_data, res_data,globalState) => { const paymentIntentID = globalState.get("paymentID"); - saveCardConfirmBody.card_cvc = det.card.card_cvc; - saveCardConfirmBody.payment_token = globalState.get("paymentToken"); - saveCardConfirmBody.client_secret = globalState.get("clientSecret"); + SaveCardConfirmBody.card_cvc = req_data.card.card_cvc; + SaveCardConfirmBody.payment_token = globalState.get("paymentToken"); + SaveCardConfirmBody.client_secret = globalState.get("clientSecret"); console.log("conf conn ->" + globalState.get("connectorId")); cy.request({ method: "POST", @@ -308,46 +346,55 @@ Cypress.Commands.add("saveCardConfirmCallTest", (saveCardConfirmBody,det,globalS "Content-Type": "application/json", "api-key": globalState.get("publishableKey"), }, - body: saveCardConfirmBody, - + failOnStatusCode: false, + body: SaveCardConfirmBody, }) .then((response) => { logRequestId(response.headers['x-request-id']); - console.log(response); + + expect(res_data.status).to.equal(response.status); expect(response.headers["content-type"]).to.include("application/json"); globalState.set("paymentID", paymentIntentID); - if (response.body.capture_method === "automatic") { - if (response.body.authentication_type === "three_ds") { - expect(response.body).to.have.property("next_action") - .to.have.property("redirect_to_url"); - const nextActionUrl = response.body.next_action.redirect_to_url; - } else if (response.body.authentication_type === "no_three_ds") { - expect(response.body.status).to.equal(det.paymentSuccessfulStatus); - expect(response.body.customer_id).to.equal(globalState.get("customerId")); - } else { - // Handle other authentication types as needed - throw new Error(`Unsupported authentication type: ${authentication_type}`); - } - } else if (response.body.capture_method === "manual") { - if (response.body.authentication_type === "three_ds") { - expect(response.body).to.have.property("next_action") - .to.have.property("redirect_to_url") - } - else if (response.body.authentication_type === "no_three_ds") { - expect(response.body.status).to.equal("requires_capture"); - expect(response.body.customer_id).to.equal(globalState.get("customerId")); - } else { - // Handle other authentication types as needed - throw new Error(`Unsupported authentication type: ${authentication_type}`); + if(response.status === 200){ + if (response.body.capture_method === "automatic") { + if (response.body.authentication_type === "three_ds") { + expect(response.body).to.have.property("next_action") + .to.have.property("redirect_to_url"); + const nextActionUrl = response.body.next_action.redirect_to_url; + } else if (response.body.authentication_type === "no_three_ds") { + for(const key in res_data.body) { + expect(res_data.body[key]).to.equal(response.body[key]); + } + expect(response.body.customer_id).to.equal(globalState.get("customerId")); + } else { + // Handle other authentication types as needed + throw new Error(`Unsupported authentication type: ${authentication_type}`); + } + } else if (response.body.capture_method === "manual") { + if (response.body.authentication_type === "three_ds") { + expect(response.body).to.have.property("next_action") + .to.have.property("redirect_to_url") + } + else if (response.body.authentication_type === "no_three_ds") { + for(const key in res_data.body) { + expect(res_data.body[key]).to.equal(response.body[key]); + } expect(response.body.customer_id).to.equal(globalState.get("customerId")); + } else { + // Handle other authentication types as needed + throw new Error(`Unsupported authentication type: ${authentication_type}`); + } } } else { - throw new Error(`Unsupported capture method: ${capture_method}`); + expect(response.body).to.have.property("error"); + for(const key in res_data.body.error) { + expect(res_data.body.error[key]).to.equal(response.body.error[key]); + } } }); }); -Cypress.Commands.add("captureCallTest", (requestBody, amount_to_capture, paymentSuccessfulStatus, globalState) => { +Cypress.Commands.add("captureCallTest", (requestBody, req_data, res_data, amount_to_capture, globalState) => { const payment_id = globalState.get("paymentID"); requestBody.amount_to_capture = amount_to_capture; let amount = globalState.get("paymentAmount"); @@ -358,33 +405,30 @@ Cypress.Commands.add("captureCallTest", (requestBody, amount_to_capture, payment "Content-Type": "application/json", "api-key": globalState.get("apiKey"), }, + failOnStatusCode: false, body: requestBody, }).then((response) => { logRequestId(response.headers['x-request-id']); + expect(res_data.status).to.equal(response.status); expect(response.headers["content-type"]).to.include("application/json"); - expect(response.body.payment_id).to.equal(payment_id); - if (amount_to_capture == amount && response.body.status == "succeeded") { - expect(response.body.amount).to.equal(amount_to_capture); - expect(response.body.amount_capturable).to.equal(0); - expect(response.body.amount_received).to.equal(amount); - expect(response.body.status).to.equal(paymentSuccessfulStatus); - } else if (response.body.status == "processing") { - expect(response.body.amount).to.equal(amount); - expect(response.body.amount_capturable).to.equal(amount); - expect(response.body.amount_received).to.equal(0); - expect(response.body.status).to.equal(paymentSuccessfulStatus); + if(response.body.capture_method !== undefined) { + expect(response.body.payment_id).to.equal(payment_id); + for(const key in res_data.body) { + expect(res_data.body[key]).to.equal(response.body[key]); + } } - else { - expect(response.body.amount).to.equal(amount); - expect(response.body.amount_capturable).to.equal(0); - expect(response.body.amount_received).to.equal(amount_to_capture); - expect(response.body.status).to.equal("partially_captured"); + else{ + expect(response.body).to.have.property("error"); + for(const key in res_data.body.error) { + expect(res_data.body.error[key]).to.equal(response.body.error[key]); + } } + }); }); -Cypress.Commands.add("voidCallTest", (requestBody, globalState) => { +Cypress.Commands.add("voidCallTest", (requestBody, req_data, res_data, globalState) => { const payment_id = globalState.get("paymentID"); cy.request({ method: "POST", @@ -393,16 +437,24 @@ Cypress.Commands.add("voidCallTest", (requestBody, globalState) => { "Content-Type": "application/json", "api-key": globalState.get("apiKey"), }, + failOnStatusCode: false, body: requestBody, }).then((response) => { logRequestId(response.headers['x-request-id']); + expect(res_data.status).to.equal(response.status); expect(response.headers["content-type"]).to.include("application/json"); - expect(response.body.payment_id).to.equal(payment_id); - expect(response.body.amount).to.equal(globalState.get("paymentAmount")); - // expect(response.body.amount_capturable).to.equal(0); - expect(response.body.amount_received).to.be.oneOf([0, null]); - expect(response.body.status).to.equal("cancelled"); + if(response.status === 200) { + for(const key in res_data.body) { + expect(res_data.body[key]).to.equal(response.body[key]); + } + } + else{ + expect(response.body).to.have.property("error"); + for(const key in res_data.body.error) { + expect(res_data.body.error[key]).to.equal(response.body.error[key]); + } + } }); }); @@ -416,6 +468,7 @@ Cypress.Commands.add("retrievePaymentCallTest", (globalState) => { "Content-Type": "application/json", "api-key": globalState.get("apiKey"), }, + failOnStatusCode: false, }).then((response) => { logRequestId(response.headers['x-request-id']); @@ -427,7 +480,7 @@ Cypress.Commands.add("retrievePaymentCallTest", (globalState) => { }); }); -Cypress.Commands.add("refundCallTest", (requestBody, refund_amount, det, globalState) => { +Cypress.Commands.add("refundCallTest", (requestBody, req_data, res_data, refund_amount, globalState) => { const payment_id = globalState.get("paymentID"); requestBody.payment_id = payment_id; requestBody.amount = refund_amount; @@ -438,19 +491,31 @@ Cypress.Commands.add("refundCallTest", (requestBody, refund_amount, det, globalS "Content-Type": "application/json", "api-key": globalState.get("apiKey"), }, + failOnStatusCode: false, body: requestBody }).then((response) => { logRequestId(response.headers['x-request-id']); - + expect(res_data.status).to.equal(response.status); expect(response.headers["content-type"]).to.include("application/json"); - globalState.set("refundId", response.body.refund_id); - expect(response.body.status).to.equal(det.refundStatus); - expect(response.body.amount).to.equal(refund_amount); - expect(response.body.payment_id).to.equal(payment_id); + + if(response.status === 200) { + globalState.set("refundId", response.body.refund_id); + for(const key in res_data.body) { + expect(res_data.body[key]).to.equal(response.body[key]); + } + expect(response.body.payment_id).to.equal(payment_id); + } + else{ + expect(response.body).to.have.property("error"); + for(const key in res_data.body.error) { + expect(res_data.body.error[key]).to.equal(response.body.error[key]); + } + } + }); }); -Cypress.Commands.add("syncRefundCallTest", (det, globalState) => { +Cypress.Commands.add("syncRefundCallTest", (req_data, res_data, globalState) => { const refundId = globalState.get("refundId"); cy.request({ method: "GET", @@ -459,22 +524,25 @@ Cypress.Commands.add("syncRefundCallTest", (det, globalState) => { "Content-Type": "application/json", "api-key": globalState.get("apiKey"), }, + failOnStatusCode: false, }).then((response) => { logRequestId(response.headers['x-request-id']); - + expect(res_data.status).to.equal(response.status); expect(response.headers["content-type"]).to.include("application/json"); - expect(response.body.status).to.equal(det.refundSyncStatus); + for(const key in res_data.body) { + expect(res_data.body[key]).to.equal(response.body[key]); + } }); }); -Cypress.Commands.add("citForMandatesCallTest", (requestBody, amount, details, confirm, capture_method, payment_type, globalState) => { - requestBody.payment_method_data.card = details.card; +Cypress.Commands.add("citForMandatesCallTest", (requestBody, req_data, res_data, amount, confirm, capture_method, payment_type, globalState) => { + requestBody.payment_method_data.card = req_data.card; requestBody.payment_type = payment_type; requestBody.confirm = confirm; requestBody.amount = amount; - requestBody.currency = details.currency; + requestBody.currency = req_data.currency; requestBody.capture_method = capture_method; - requestBody.mandate_data.mandate_type = details.mandate_type; + requestBody.mandate_data.mandate_type = req_data.mandate_type; requestBody.customer_id = globalState.get("customerId"); globalState.set("paymentAmount", requestBody.amount); cy.request({ @@ -484,40 +552,50 @@ Cypress.Commands.add("citForMandatesCallTest", (requestBody, amount, details, co "Content-Type": "application/json", "api-key": globalState.get("apiKey"), }, + failOnStatusCode: false, body: requestBody, }).then((response) => { logRequestId(response.headers['x-request-id']); - + expect(res_data.status).to.equal(response.status); expect(response.headers["content-type"]).to.include("application/json"); - expect(response.body).to.have.property("mandate_id"); globalState.set("mandateId", response.body.mandate_id); globalState.set("paymentID", response.body.payment_id); - if (response.body.capture_method === "automatic") { - if (response.body.authentication_type === "three_ds") { - expect(response.body).to.have.property("next_action") - .to.have.property("redirect_to_url"); - const nextActionUrl = response.body.next_action.redirect_to_url; - cy.log(response.body); - cy.log(nextActionUrl); - } else if (response.body.authentication_type === "no_three_ds") { - expect(response.body.status).to.equal(details.paymentSuccessfulStatus); - } else { - // Handle other authentication types as needed - throw new Error(`Unsupported authentication type: ${authentication_type}`); + if(response.status === 200) { + if (response.body.capture_method === "automatic") { + expect(response.body).to.have.property("mandate_id"); + if (response.body.authentication_type === "three_ds") { + expect(response.body).to.have.property("next_action") + .to.have.property("redirect_to_url"); + const nextActionUrl = response.body.next_action.redirect_to_url; + globalState.set("nextActionUrl", response.body.next_action.redirect_to_url); + cy.log(response.body); + cy.log(nextActionUrl); + } else if (response.body.authentication_type === "no_three_ds") { + for(const key in res_data.body) { + expect(res_data.body[key]).to.equal(response.body[key]); + } + } + for(const key in res_data.body) { + expect(res_data.body[key]).to.equal(response.body[key]); + } } - } - else if (response.body.capture_method === "manual") { - if (response.body.authentication_type === "three_ds") { - expect(response.body).to.have.property("next_action") + else if (response.body.capture_method === "manual") { + expect(response.body).to.have.property("mandate_id"); + if (response.body.authentication_type === "three_ds") { + expect(response.body).to.have.property("next_action") + } + for(const key in res_data.body) { + expect(res_data.body[key]).to.equal(response.body[key]); + } } - else if (response.body.authentication_type === "no_three_ds") { - expect(response.body.status).to.equal("requires_capture"); - } else { - throw new Error(`Unsupported authentication type: ${authentication_type}`); + } + else{ + expect(response.body).to.have.property("error"); + for(const key in res_data.body.error) { + expect(res_data.body.error[key]).to.equal(response.body.error[key]); } } - }); }); @@ -536,10 +614,10 @@ Cypress.Commands.add("mitForMandatesCallTest", (requestBody, amount, confirm, ca "Content-Type": "application/json", "api-key": globalState.get("apiKey"), }, + failOnStatusCode: false, body: requestBody, }).then((response) => { logRequestId(response.headers['x-request-id']); - expect(response.headers["content-type"]).to.include("application/json"); globalState.set("paymentID", response.body.payment_id); console.log("mit statusss-> " + response.body.status); @@ -643,13 +721,13 @@ Cypress.Commands.add("handleRedirection", (globalState, expected_redirection) => }) } else if (globalState.get("connectorId") === "nmi" || globalState.get("connectorId") === "noon") { - cy.get('iframe', { timeout: 100000 }) + cy.get('iframe', { timeout: 150000 }) .its('0.contentDocument.body') .within((body) => { - cy.get('iframe', { timeout: 10000 }) + cy.get('iframe', { timeout: 20000 }) .its('0.contentDocument.body') .within((body) => { - cy.get('form[name="cardholderInput"]', { timeout: 10000 }).should('exist').then(form => { + cy.get('form[name="cardholderInput"]', { timeout: 20000 }).should('exist').then(form => { cy.get('input[name="challengeDataEntry"]').click().type("1234"); cy.get('input[value="SUBMIT"]').click(); }) diff --git a/cypress-tests/readme.md b/cypress-tests/readme.md index 9285066bfe60..511625cdbfb9 100644 --- a/cypress-tests/readme.md +++ b/cypress-tests/readme.md @@ -37,7 +37,7 @@ To run test cases, follow these steps: export CYPRESS_BASEURL="base_url" export DEBUG=cypress:cli export CYPRESS_ADMINAPIKEY="admin_api_key" - export CYPRESS_CONNECToR_AUtH_FILE_PATH="path/to/creds.json" + export CYPRESS_CONNECTOR_AUTH_FILE_PATH="path/to/creds.json" ``` 4. Run Cypress test cases diff --git a/docker-compose-development.yml b/docker-compose-development.yml index f2ba6d3eda47..09750ba84487 100644 --- a/docker-compose-development.yml +++ b/docker-compose-development.yml @@ -298,6 +298,7 @@ services: hyperswitch-control-center: image: juspaydotin/hyperswitch-control-center:latest + pull_policy: always networks: - router_net ports: diff --git a/docker-compose.yml b/docker-compose.yml index e6b9c4cec4bd..64d36caf2487 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -48,6 +48,7 @@ services: ### Application services hyperswitch-server: image: juspaydotin/hyperswitch-router:standalone + pull_policy: always command: /local/bin/router -f /local/config/docker_compose.toml ports: - "8080:8080" @@ -67,6 +68,7 @@ services: hyperswitch-producer: image: juspaydotin/hyperswitch-producer:standalone + pull_policy: always command: /local/bin/scheduler -f /local/config/docker_compose.toml networks: - router_net @@ -84,6 +86,7 @@ services: hyperswitch-consumer: image: juspaydotin/hyperswitch-consumer:standalone + pull_policy: always command: /local/bin/scheduler -f /local/config/docker_compose.toml networks: - router_net @@ -107,6 +110,7 @@ services: hyperswitch-drainer: image: juspaydotin/hyperswitch-drainer:standalone + pull_policy: always command: /local/bin/drainer -f /local/config/docker_compose.toml deploy: replicas: ${DRAINER_INSTANCE_COUNT:-1} @@ -148,6 +152,7 @@ services: ### Control Center hyperswitch-control-center: image: juspaydotin/hyperswitch-control-center:latest + pull_policy: always ports: - "9000:9000" environment: @@ -333,7 +338,7 @@ services: JMX_PORT: 9997 KAFKA_JMX_OPTS: -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=kafka0 -Dcom.sun.management.jmxremote.rmi.port=9997 profiles: - - analytics + - olap volumes: - ./monitoring/kafka-script.sh:/tmp/update_run.sh command: "bash -c 'if [ ! -f /tmp/update_run.sh ]; then echo \"ERROR: Did you forget the update_run.sh file that came with this docker-compose.yml file?\" && exit 1 ; else /tmp/update_run.sh && /etc/confluent/docker/run ; fi'" @@ -348,7 +353,7 @@ services: depends_on: - kafka0 profiles: - - analytics + - olap environment: KAFKA_CLUSTERS_0_NAME: local KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka0:29092 @@ -364,7 +369,7 @@ services: volumes: - ./crates/analytics/docs/clickhouse/scripts:/docker-entrypoint-initdb.d profiles: - - analytics + - olap ulimits: nofile: soft: 262144 @@ -376,6 +381,8 @@ services: - ./docker/fluentd/conf:/fluentd/etc networks: - router_net + profiles: + - olap opensearch: image: public.ecr.aws/opensearchproject/opensearch:1.3.14 @@ -389,6 +396,8 @@ services: - "9200:9200" networks: - router_net + profiles: + - olap opensearch-dashboards: image: opensearchproject/opensearch-dashboards:1.3.14 @@ -400,3 +409,5 @@ services: OPENSEARCH_HOSTS: '["https://opensearch:9200"]' networks: - router_net + profiles: + - olap diff --git a/docs/try_local_system.md b/docs/try_local_system.md index a13d04ade834..a05e9b9f44ea 100644 --- a/docs/try_local_system.md +++ b/docs/try_local_system.md @@ -36,7 +36,7 @@ Check the Table Of Contents to jump to the relevant section. 2. Clone the repository and switch to the project directory: ```shell - git clone https://github.com/juspay/hyperswitch + git clone --depth 1 --branch latest https://github.com/juspay/hyperswitch cd hyperswitch ``` @@ -51,13 +51,13 @@ Check the Table Of Contents to jump to the relevant section. docker compose up -d ``` - This should run the hyperswitch payments router, the primary component within - hyperswitch. + This should run the hyperswitch app server, web client and control center. Wait for the `migration_runner` container to finish installing `diesel_cli` - and running migrations (approximately 2 minutes) before proceeding further. + and running migrations (approximately 2 minutes), and for the + `hyperswitch-web` container to finish compiling before proceeding further. You can also choose to [run the scheduler and monitoring services](#run-the-scheduler-and-monitoring-services) - in addition to the payments router. + in addition to the app server, web client and control center. 5. Verify that the server is up and running by hitting the health endpoint: diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index a104f5760b40..2c3e83ad2ba9 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -30,6 +30,7 @@ jwt_secret = "secret" [user] password_validity_in_days = 90 +two_factor_auth_expiry_in_secs = 300 [locker] host = "" diff --git a/migrations/2024-05-06-165401_add_charges_in_payment_intent/down.sql b/migrations/2024-05-06-165401_add_charges_in_payment_intent/down.sql new file mode 100644 index 000000000000..53c2368276e7 --- /dev/null +++ b/migrations/2024-05-06-165401_add_charges_in_payment_intent/down.sql @@ -0,0 +1,5 @@ +ALTER TABLE payment_intent DROP COLUMN IF EXISTS charges; + +ALTER TABLE payment_attempt DROP COLUMN IF EXISTS charge_id; + +ALTER TABLE refund DROP COLUMN IF EXISTS charges; diff --git a/migrations/2024-05-06-165401_add_charges_in_payment_intent/up.sql b/migrations/2024-05-06-165401_add_charges_in_payment_intent/up.sql new file mode 100644 index 000000000000..c2f1ce14d8b0 --- /dev/null +++ b/migrations/2024-05-06-165401_add_charges_in_payment_intent/up.sql @@ -0,0 +1,6 @@ +ALTER TABLE payment_intent ADD COLUMN IF NOT EXISTS charges jsonb; + +ALTER TABLE payment_attempt +ADD COLUMN IF NOT EXISTS charge_id VARCHAR(64); + +ALTER TABLE refund ADD COLUMN IF NOT EXISTS charges jsonb; diff --git a/migrations/2024-05-10-074332_add_frm_metadata_to_payment_intent/down.sql b/migrations/2024-05-10-074332_add_frm_metadata_to_payment_intent/down.sql new file mode 100644 index 000000000000..17c9260fe30e --- /dev/null +++ b/migrations/2024-05-10-074332_add_frm_metadata_to_payment_intent/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE payment_intent DROP COLUMN IF EXISTS frm_metadata; \ No newline at end of file diff --git a/migrations/2024-05-10-074332_add_frm_metadata_to_payment_intent/up.sql b/migrations/2024-05-10-074332_add_frm_metadata_to_payment_intent/up.sql new file mode 100644 index 000000000000..6918adb82a8c --- /dev/null +++ b/migrations/2024-05-10-074332_add_frm_metadata_to_payment_intent/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE payment_intent ADD COLUMN IF NOT EXISTS frm_metadata JSONB DEFAULT NULL; \ No newline at end of file diff --git a/migrations/2024-05-14-092623_add_updated_by_column/down.sql b/migrations/2024-05-14-092623_add_updated_by_column/down.sql new file mode 100644 index 000000000000..436a57dade3f --- /dev/null +++ b/migrations/2024-05-14-092623_add_updated_by_column/down.sql @@ -0,0 +1,6 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE payment_methods DROP COLUMN IF EXISTS updated_by; + +ALTER TABLE mandate DROP COLUMN IF EXISTS updated_by; + +ALTER TABLE customers DROP COLUMN IF EXISTS updated_by; \ No newline at end of file diff --git a/migrations/2024-05-14-092623_add_updated_by_column/up.sql b/migrations/2024-05-14-092623_add_updated_by_column/up.sql new file mode 100644 index 000000000000..869f52cbfe9a --- /dev/null +++ b/migrations/2024-05-14-092623_add_updated_by_column/up.sql @@ -0,0 +1,6 @@ +-- Your SQL goes here +ALTER TABLE payment_methods ADD COLUMN IF NOT EXISTS updated_by VARCHAR(64); + +ALTER TABLE mandate ADD COLUMN IF NOT EXISTS updated_by VARCHAR(64); + +ALTER TABLE customers ADD COLUMN IF NOT EXISTS updated_by VARCHAR(64); \ No newline at end of file diff --git a/migrations/2024-05-15-133715_add_client_info_columns_in_payment_attempts/down.sql b/migrations/2024-05-15-133715_add_client_info_columns_in_payment_attempts/down.sql new file mode 100644 index 000000000000..5e91f5197d85 --- /dev/null +++ b/migrations/2024-05-15-133715_add_client_info_columns_in_payment_attempts/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE payment_attempt DROP COLUMN IF EXISTS client_version; +ALTER TABLE payment_attempt DROP COLUMN IF EXISTS client_source; \ No newline at end of file diff --git a/migrations/2024-05-15-133715_add_client_info_columns_in_payment_attempts/up.sql b/migrations/2024-05-15-133715_add_client_info_columns_in_payment_attempts/up.sql new file mode 100644 index 000000000000..81584637c813 --- /dev/null +++ b/migrations/2024-05-15-133715_add_client_info_columns_in_payment_attempts/up.sql @@ -0,0 +1,3 @@ +-- Your SQL goes here +ALTER TABLE payment_attempt ADD COLUMN IF NOT EXISTS client_source VARCHAR(64) DEFAULT NULL; +ALTER TABLE payment_attempt ADD COLUMN IF NOT EXISTS client_version VARCHAR(64) DEFAULT NULL; \ No newline at end of file diff --git a/migrations/2024-05-21-075556_add_directory_server_id_in_authentication/down.sql b/migrations/2024-05-21-075556_add_directory_server_id_in_authentication/down.sql new file mode 100644 index 000000000000..7025236fe882 --- /dev/null +++ b/migrations/2024-05-21-075556_add_directory_server_id_in_authentication/down.sql @@ -0,0 +1 @@ +ALTER TABLE authentication DROP COLUMN IF EXISTS directory_server_id; \ No newline at end of file diff --git a/migrations/2024-05-21-075556_add_directory_server_id_in_authentication/up.sql b/migrations/2024-05-21-075556_add_directory_server_id_in_authentication/up.sql new file mode 100644 index 000000000000..ecad9d16f6ee --- /dev/null +++ b/migrations/2024-05-21-075556_add_directory_server_id_in_authentication/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE authentication ADD COLUMN IF NOT EXISTS directory_server_id VARCHAR(128); \ No newline at end of file diff --git a/migrations/2024-05-24-093318_add_more_currencies/down.sql b/migrations/2024-05-24-093318_add_more_currencies/down.sql new file mode 100644 index 000000000000..e0ac49d1ecfb --- /dev/null +++ b/migrations/2024-05-24-093318_add_more_currencies/down.sql @@ -0,0 +1 @@ +SELECT 1; diff --git a/migrations/2024-05-24-093318_add_more_currencies/up.sql b/migrations/2024-05-24-093318_add_more_currencies/up.sql new file mode 100644 index 000000000000..bc93f04788a8 --- /dev/null +++ b/migrations/2024-05-24-093318_add_more_currencies/up.sql @@ -0,0 +1,25 @@ +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'AOA'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'BAM'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'BGN'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'BYN'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'CVE'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'FKP'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'GEL'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'IQD'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'LYD'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'MRU'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'MZN'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'PAB'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'RSD'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'SBD'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'SHP'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'SLE'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'SRD'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'STN'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'TND'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'TOP'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'UAH'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'VES'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'WST'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'XCD'; +ALTER TYPE "Currency" ADD VALUE IF NOT EXISTS 'ZMW'; diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index 31b44551aa1a..c3bbd9fa05a1 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -853,6 +853,57 @@ ] } }, + "/{payment_id}/complete_authorize": { + "post": { + "tags": [ + "Payments" + ], + "summary": "Payments - Complete Authorize", + "description": "Payments - Complete Authorize\n\n", + "operationId": "Complete Authorize a Payment", + "parameters": [ + { + "name": "payment_id", + "in": "path", + "description": "The identifier for payment", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentsCompleteAuthorizeRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Payments Complete Authorize Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentsResponse" + } + } + } + }, + "400": { + "description": "Missing mandatory fields" + } + }, + "security": [ + { + "publishable_key": [] + } + ] + } + }, "/refunds": { "post": { "tags": [ @@ -5888,17 +5939,19 @@ "properties": { "interac": { "type": "object", - "required": [ - "country", - "email" - ], "properties": { "country": { - "$ref": "#/components/schemas/CountryAlpha2" + "allOf": [ + { + "$ref": "#/components/schemas/CountryAlpha2" + } + ], + "nullable": true }, "email": { "type": "string", - "example": "john.doe@example.com" + "example": "john.doe@example.com", + "nullable": true } } } @@ -6929,7 +6982,8 @@ "amount": { "type": "integer", "format": "int64", - "description": "The capture amount. Amount for the payment in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc.," + "description": "The capture amount. Amount for the payment in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc.,", + "example": 6540 }, "currency": { "allOf": [ @@ -7263,6 +7317,29 @@ "CashappQr": { "type": "object" }, + "ChargeRefunds": { + "type": "object", + "description": "Charge object for refunds", + "required": [ + "charge_id" + ], + "properties": { + "charge_id": { + "type": "string", + "description": "Identifier for charge created for the payment" + }, + "revert_platform_fee": { + "type": "boolean", + "description": "Toggle for reverting the application fee that was collected for the payment.\nIf set to false, the funds are pulled from the destination account.", + "nullable": true + }, + "revert_transfer": { + "type": "boolean", + "description": "Toggle for reverting the transfer that was made during the charge.\nIf set to false, the funds are pulled from the main platform's account.", + "nullable": true + } + } + }, "Comparison": { "type": "object", "description": "Represents a single comparison condition.", @@ -9307,8 +9384,7 @@ "type": "object", "description": "Details of FrmPaymentMethod are mentioned here... it should be passed in payment connector create api call, and stored in merchant_connector_table", "required": [ - "payment_method", - "payment_method_types" + "payment_method" ], "properties": { "payment_method": { @@ -9319,7 +9395,16 @@ "items": { "$ref": "#/components/schemas/FrmPaymentMethodType" }, - "description": "payment method types(credit, debit) that can be used in the payment" + "description": "payment method types(credit, debit) that can be used in the payment. This field is deprecated. It has not been removed to provide backward compatibility.", + "nullable": true + }, + "flow": { + "allOf": [ + { + "$ref": "#/components/schemas/FrmPreferredFlowTypes" + } + ], + "nullable": true } }, "additionalProperties": false @@ -10068,7 +10153,8 @@ "amount": { "type": "integer", "format": "int64", - "description": "Amount the authorization has been made for" + "description": "Amount the authorization has been made for", + "example": 6540 }, "status": { "$ref": "#/components/schemas/AuthorizationStatus" @@ -10084,9 +10170,7 @@ "nullable": true }, "previously_authorized_amount": { - "type": "integer", - "format": "int64", - "description": "Previously authorized amount for the payment" + "$ref": "#/components/schemas/MinorUnit" } } }, @@ -11539,6 +11623,11 @@ } } }, + "MinorUnit": { + "type": "integer", + "format": "int64", + "description": "This Unit struct represents MinorUnit in which core amount works" + }, "MobilePayRedirection": { "type": "object" }, @@ -12378,7 +12467,8 @@ "amount": { "type": "integer", "format": "int64", - "description": "The payment attempt amount. Amount for the payment in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc.," + "description": "The payment attempt amount. Amount for the payment in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc.,", + "example": 6540 }, "currency": { "allOf": [ @@ -12486,6 +12576,70 @@ } } }, + "PaymentChargeRequest": { + "type": "object", + "required": [ + "charge_type", + "fees", + "transfer_account_id" + ], + "properties": { + "charge_type": { + "$ref": "#/components/schemas/PaymentChargeType" + }, + "fees": { + "type": "integer", + "format": "int64", + "description": "Platform fees to be collected on the payment" + }, + "transfer_account_id": { + "type": "string", + "description": "Identifier for the reseller's account to send the funds to" + } + } + }, + "PaymentChargeResponse": { + "type": "object", + "required": [ + "charge_type", + "application_fees", + "transfer_account_id" + ], + "properties": { + "charge_id": { + "type": "string", + "description": "Identifier for charge created for the payment", + "nullable": true + }, + "charge_type": { + "$ref": "#/components/schemas/PaymentChargeType" + }, + "application_fees": { + "type": "integer", + "format": "int64", + "description": "Platform fees collected on the payment" + }, + "transfer_account_id": { + "type": "string", + "description": "Identifier for the reseller's account where the funds were transferred" + } + } + }, + "PaymentChargeType": { + "oneOf": [ + { + "type": "object", + "required": [ + "Stripe" + ], + "properties": { + "Stripe": { + "$ref": "#/components/schemas/StripeChargeType" + } + } + } + ] + }, "PaymentCreatePaymentLinkConfig": { "allOf": [ { @@ -13499,6 +13653,9 @@ }, "PaymentsCaptureRequest": { "type": "object", + "required": [ + "amount_to_capture" + ], "properties": { "merchant_id": { "type": "string", @@ -13509,7 +13666,7 @@ "type": "integer", "format": "int64", "description": "The Amount to be captured/ debited from the user's payment method.", - "nullable": true + "example": 6540 }, "refund_uncaptured_amount": { "type": "boolean", @@ -13536,6 +13693,26 @@ } } }, + "PaymentsCompleteAuthorizeRequest": { + "type": "object", + "required": [ + "client_secret" + ], + "properties": { + "shipping": { + "allOf": [ + { + "$ref": "#/components/schemas/Address" + } + ], + "nullable": true + }, + "client_secret": { + "type": "string", + "description": "Client Secret" + } + } + }, "PaymentsConfirmRequest": { "type": "object", "properties": { @@ -13646,39 +13823,11 @@ }, "customer_id": { "type": "string", - "description": "The identifier for the customer object. This field will be deprecated soon, use the customer object instead", + "description": "The identifier for the customer object.", "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", "nullable": true, "maxLength": 255 }, - "email": { - "type": "string", - "description": "The customer's email address This field will be deprecated soon, use the customer object instead", - "example": "johntest@test.com", - "nullable": true, - "maxLength": 255 - }, - "name": { - "type": "string", - "description": "The customer's name.\nThis field will be deprecated soon, use the customer object instead.", - "example": "John Test", - "nullable": true, - "maxLength": 255 - }, - "phone": { - "type": "string", - "description": "The customer's phone number\nThis field will be deprecated soon, use the customer object instead", - "example": "3141592653", - "nullable": true, - "maxLength": 255 - }, - "phone_country_code": { - "type": "string", - "description": "The country code for the customer phone number\nThis field will be deprecated soon, use the customer object instead", - "example": "+1", - "nullable": true, - "maxLength": 255 - }, "off_session": { "type": "boolean", "description": "Set to true to indicate that the customer is not in your checkout flow during this payment, and therefore is unable to authenticate. This parameter is intended for scenarios where you collect card details and charge them later. When making a recurring payment by passing a mandate_id, this parameter is mandatory", @@ -13899,6 +14048,7 @@ "minimum": 0 }, "frm_metadata": { + "type": "object", "description": "additional data related to some frm connectors", "nullable": true }, @@ -13915,6 +14065,14 @@ } ], "nullable": true + }, + "charges": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentChargeRequest" + } + ], + "nullable": true } } }, @@ -14025,39 +14183,11 @@ }, "customer_id": { "type": "string", - "description": "The identifier for the customer object. This field will be deprecated soon, use the customer object instead", + "description": "The identifier for the customer object.", "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", "nullable": true, "maxLength": 255 }, - "email": { - "type": "string", - "description": "The customer's email address This field will be deprecated soon, use the customer object instead", - "example": "johntest@test.com", - "nullable": true, - "maxLength": 255 - }, - "name": { - "type": "string", - "description": "The customer's name.\nThis field will be deprecated soon, use the customer object instead.", - "example": "John Test", - "nullable": true, - "maxLength": 255 - }, - "phone": { - "type": "string", - "description": "The customer's phone number\nThis field will be deprecated soon, use the customer object instead", - "example": "3141592653", - "nullable": true, - "maxLength": 255 - }, - "phone_country_code": { - "type": "string", - "description": "The country code for the customer phone number\nThis field will be deprecated soon, use the customer object instead", - "example": "+1", - "nullable": true, - "maxLength": 255 - }, "off_session": { "type": "boolean", "description": "Set to true to indicate that the customer is not in your checkout flow during this payment, and therefore is unable to authenticate. This parameter is intended for scenarios where you collect card details and charge them later. When making a recurring payment by passing a mandate_id, this parameter is mandatory", @@ -14299,6 +14429,7 @@ "minimum": 0 }, "frm_metadata": { + "type": "object", "description": "additional data related to some frm connectors", "nullable": true }, @@ -14315,6 +14446,14 @@ } ], "nullable": true + }, + "charges": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentChargeRequest" + } + ], + "nullable": true } } }, @@ -14516,14 +14655,15 @@ }, "customer_id": { "type": "string", - "description": "The identifier for the customer object. This field will be deprecated soon, use the customer object instead", + "description": "The identifier for the customer object.", "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", "nullable": true, "maxLength": 255 }, "email": { "type": "string", - "description": "The customer's email address This field will be deprecated soon, use the customer object instead", + "description": "The customer's email address.\nThis field will be deprecated soon, use the customer object instead", + "deprecated": true, "example": "johntest@test.com", "nullable": true, "maxLength": 255 @@ -14531,6 +14671,7 @@ "name": { "type": "string", "description": "The customer's name.\nThis field will be deprecated soon, use the customer object instead.", + "deprecated": true, "example": "John Test", "nullable": true, "maxLength": 255 @@ -14538,6 +14679,7 @@ "phone": { "type": "string", "description": "The customer's phone number\nThis field will be deprecated soon, use the customer object instead", + "deprecated": true, "example": "3141592653", "nullable": true, "maxLength": 255 @@ -14545,6 +14687,7 @@ "phone_country_code": { "type": "string", "description": "The country code for the customer phone number\nThis field will be deprecated soon, use the customer object instead", + "deprecated": true, "example": "+1", "nullable": true, "maxLength": 255 @@ -14801,6 +14944,7 @@ "minimum": 0 }, "frm_metadata": { + "type": "object", "description": "additional data related to some frm connectors", "nullable": true }, @@ -14817,6 +14961,14 @@ } ], "nullable": true + }, + "charges": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentChargeRequest" + } + ], + "nullable": true } }, "additionalProperties": false @@ -14827,6 +14979,8 @@ "status", "amount", "net_amount", + "amount_capturable", + "amount_received", "currency", "payment_method", "attempt_count" @@ -14859,29 +15013,26 @@ "type": "integer", "format": "int64", "description": "The payment amount. Amount for the payment in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc.,", - "example": 100 + "example": 6540 }, "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", - "example": 110 + "example": 6540 }, "amount_capturable": { "type": "integer", "format": "int64", "description": "The maximum amount that could be captured from the payment", "example": 6540, - "nullable": true, "minimum": 100 }, "amount_received": { "type": "integer", "format": "int64", "description": "The amount which is already captured from the payment", - "example": 6540, - "nullable": true, - "minimum": 100 + "example": 6540 }, "connector": { "type": "string", @@ -14907,7 +15058,8 @@ }, "customer_id": { "type": "string", - "description": "The identifier for the customer object. If not provided the customer ID will be autogenerated.", + "description": "The identifier for the customer object. If not provided the customer ID will be autogenerated.\nThis field will be deprecated soon. Please refer to `customer.id`", + "deprecated": true, "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", "nullable": true, "maxLength": 255 @@ -15046,21 +15198,24 @@ }, "email": { "type": "string", - "description": "description: The customer's email address", + "description": "description: The customer's email address\nThis field will be deprecated soon. Please refer to `customer.email` object", + "deprecated": true, "example": "johntest@test.com", "nullable": true, "maxLength": 255 }, "name": { "type": "string", - "description": "description: The customer's name", + "description": "description: The customer's name\nThis field will be deprecated soon. Please refer to `customer.name` object", + "deprecated": true, "example": "John Test", "nullable": true, "maxLength": 255 }, "phone": { "type": "string", - "description": "The customer's phone number", + "description": "The customer's phone number\nThis field will be deprecated soon. Please refer to `customer.phone` object", + "deprecated": true, "example": "3141592653", "nullable": true, "maxLength": 255 @@ -15338,6 +15493,19 @@ "description": "Date time at which payment was updated", "example": "2022-09-10T10:11:12Z", "nullable": true + }, + "charges": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentChargeResponse" + } + ], + "nullable": true + }, + "frm_metadata": { + "type": "object", + "description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. FRM Metadata is useful for storing additional, structured information on an object related to FRM.", + "nullable": true } } }, @@ -15585,39 +15753,11 @@ }, "customer_id": { "type": "string", - "description": "The identifier for the customer object. This field will be deprecated soon, use the customer object instead", + "description": "The identifier for the customer object.", "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", "nullable": true, "maxLength": 255 }, - "email": { - "type": "string", - "description": "The customer's email address This field will be deprecated soon, use the customer object instead", - "example": "johntest@test.com", - "nullable": true, - "maxLength": 255 - }, - "name": { - "type": "string", - "description": "The customer's name.\nThis field will be deprecated soon, use the customer object instead.", - "example": "John Test", - "nullable": true, - "maxLength": 255 - }, - "phone": { - "type": "string", - "description": "The customer's phone number\nThis field will be deprecated soon, use the customer object instead", - "example": "3141592653", - "nullable": true, - "maxLength": 255 - }, - "phone_country_code": { - "type": "string", - "description": "The country code for the customer phone number\nThis field will be deprecated soon, use the customer object instead", - "example": "+1", - "nullable": true, - "maxLength": 255 - }, "off_session": { "type": "boolean", "description": "Set to true to indicate that the customer is not in your checkout flow during this payment, and therefore is unable to authenticate. This parameter is intended for scenarios where you collect card details and charge them later. When making a recurring payment by passing a mandate_id, this parameter is mandatory", @@ -15833,6 +15973,7 @@ "minimum": 0 }, "frm_metadata": { + "type": "object", "description": "additional data related to some frm connectors", "nullable": true }, @@ -15849,6 +15990,14 @@ } ], "nullable": true + }, + "charges": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentChargeRequest" + } + ], + "nullable": true } } }, @@ -17098,6 +17247,14 @@ } ], "nullable": true + }, + "charges": { + "allOf": [ + { + "$ref": "#/components/schemas/ChargeRefunds" + } + ], + "nullable": true } }, "additionalProperties": false @@ -17179,6 +17336,14 @@ "type": "string", "description": "The merchant_connector_id of the processor through which this payment went through", "nullable": true + }, + "charges": { + "allOf": [ + { + "$ref": "#/components/schemas/ChargeRefunds" + } + ], + "nullable": true } } }, @@ -17296,11 +17461,15 @@ "properties": { "surcharge_amount": { "type": "integer", - "format": "int64" + "format": "int64", + "example": 6540 }, "tax_amount": { - "type": "integer", - "format": "int64", + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], "nullable": true } } @@ -17415,7 +17584,8 @@ }, "amount": { "type": "integer", - "format": "int64" + "format": "int64", + "example": 6540 }, "created_at": { "type": "string", @@ -18195,6 +18365,13 @@ "propertyName": "type" } }, + "StripeChargeType": { + "type": "string", + "enum": [ + "direct", + "destination" + ] + }, "SurchargeDetailsResponse": { "type": "object", "required": [ @@ -18266,9 +18443,7 @@ ] }, "value": { - "type": "integer", - "format": "int64", - "description": "Fixed Surcharge value" + "$ref": "#/components/schemas/MinorUnit" } } }, @@ -18339,6 +18514,16 @@ }, "poll_config": { "$ref": "#/components/schemas/PollConfigResponse" + }, + "message_version": { + "type": "string", + "description": "Message Version", + "nullable": true + }, + "directory_server_id": { + "type": "string", + "description": "Directory Server ID", + "nullable": true } } }, diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/.meta.json index b7ca4ab1f555..e62ea65cc06a 100644 --- a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/.meta.json +++ b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/.meta.json @@ -9,12 +9,11 @@ "Scenario8-Refund for unsuccessful payment", "Scenario9-Create a recurring payment with greater mandate amount", "Scenario10-Manual multiple capture", - "Scenario11-Failure card", - "Scenario12-Refund exceeds amount captured", - "Scenario13-Revoke for revoked mandates", - "Scenario14-Recurring payment for revoked mandates", - "Scenario15-Setup_future_usage is off_session for normal payments", - "Scenario16-Setup_future_usage is on_session for mandates payments -confirm false", - "Scenario17-Setup_future_usage is null for normal payments" + "Scenario11-Refund exceeds amount captured", + "Scenario12-Revoke for revoked mandates", + "Scenario13-Recurring payment for revoked mandates", + "Scenario14-Setup_future_usage is off_session for normal payments", + "Scenario15-Setup_future_usage is on_session for mandates payments -confirm false", + "Scenario16-Setup_future_usage is null for normal payments" ] } diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Failure card/.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Failure card/.meta.json deleted file mode 100644 index 60051ecca220..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Failure card/.meta.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "childrenOrder": [ - "Payments - Create", - "Payments - Retrieve" - ] -} diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Failure card/Payments - Create/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Failure card/Payments - Create/event.test.js deleted file mode 100644 index 1d169aade2d9..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Failure card/Payments - Create/event.test.js +++ /dev/null @@ -1,71 +0,0 @@ -// Validate status 2xx -pm.test("[POST]::/payments - Status code is 2xx", function () { - pm.response.to.be.success; -}); - -// Validate if response header has matching content-type -pm.test("[POST]::/payments - Content-Type is application/json", function () { - pm.expect(pm.response.headers.get("Content-Type")).to.include( - "application/json", - ); -}); - -// Validate if response has JSON Body -pm.test("[POST]::/payments - Response has JSON Body", function () { - pm.response.to.have.jsonBody(); -}); - -// Set response object as internal variable -let jsonData = {}; -try { - jsonData = pm.response.json(); -} catch (e) {} - -// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id -if (jsonData?.payment_id) { - pm.collectionVariables.set("payment_id", jsonData.payment_id); - console.log( - "- use {{payment_id}} as collection variable for value", - jsonData.payment_id, - ); -} else { - console.log( - "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", - ); -} - -// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret -if (jsonData?.client_secret) { - pm.collectionVariables.set("client_secret", jsonData.client_secret); - console.log( - "- use {{client_secret}} as collection variable for value", - jsonData.client_secret, - ); -} else { - console.log( - "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", - ); -} - -// Response body should have value "failed" for "status" -if (jsonData?.status) { - pm.test( - "[POST]::/payments - Content check if value for 'status' matches 'failed'", - function () { - pm.expect(jsonData.status).to.eql("failed"); - }, - ); -} - -// Check if error code exists -pm.test("[POST]::/payments - Content check if error code exists", function () { - pm.expect(jsonData.error_code).to.not.equal(null);; -}); - -// Check if error message exists -pm.test("[POST]::/payments - Content check if error message exists", function () { - pm.expect(jsonData.error_message).to.not.equal(null);; -}); - - - diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Failure card/Payments - Create/request.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Failure card/Payments - Create/request.json deleted file mode 100644 index 93441083df2e..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Failure card/Payments - Create/request.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw_json_formatted": { - "amount": 6540, - "currency": "USD", - "confirm": true, - "business_country": "US", - "business_label": "default", - "capture_method": "automatic", - "capture_on": "2022-09-10T10:11:12Z", - "amount_to_capture": 6540, - "customer_id": "{{customer_id}}", - "email": "guest@example.com", - "setup_future_usage": "on_session", - "customer_acceptance": { - "acceptance_type": "offline", - "accepted_at": "1963-05-03T04:07:52.723Z", - "online": { - "ip_address": "127.0.0.1", - "user_agent": "amet irure esse" - } - }, - "name": "John Doe", - "phone": "999999999", - "phone_country_code": "+65", - "description": "Its my first payment request", - "authentication_type": "no_three_ds", - "return_url": "https://duck.com", - "payment_method": "card", - "payment_method_type": "debit", - "payment_method_data": { - "card": { - "card_number": "412345678912345678914", - "card_exp_month": "01", - "card_exp_year": "25", - "card_holder_name": "joseph Doe", - "card_cvc": "123" - } - }, - "billing": { - "address": { - "line1": "1467", - "line2": "Harrison Street", - "line3": "Harrison Street", - "city": "San Fransico", - "state": "California", - "zip": "94122", - "country": "US", - "first_name": "sundari", - "last_name": "sundari" - } - }, - "shipping": { - "address": { - "line1": "1467", - "line2": "Harrison Street", - "line3": "Harrison Street", - "city": "San Fransico", - "state": "California", - "zip": "94122", - "country": "US", - "first_name": "sundari", - "last_name": "sundari" - } - }, - "statement_descriptor_name": "joseph", - "statement_descriptor_suffix": "JS", - "metadata": { - "count_tickets": 1, - "transaction_number": "5590045" - } - } - }, - "url": { - "raw": "{{baseUrl}}/payments", - "host": ["{{baseUrl}}"], - "path": ["payments"] - }, - "description": "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" -} diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Failure card/Payments - Retrieve/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Failure card/Payments - Retrieve/event.test.js deleted file mode 100644 index 0ef16bb95662..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Failure card/Payments - Retrieve/event.test.js +++ /dev/null @@ -1,69 +0,0 @@ -// Validate status 2xx -pm.test("[GET]::/payments/:id - Status code is 2xx", function () { - pm.response.to.be.success; -}); - -// Validate if response header has matching content-type -pm.test("[GET]::/payments/:id - Content-Type is application/json", function () { - pm.expect(pm.response.headers.get("Content-Type")).to.include( - "application/json", - ); -}); - -// Validate if response has JSON Body -pm.test("[GET]::/payments/:id - Response has JSON Body", function () { - pm.response.to.have.jsonBody(); -}); - -// Set response object as internal variable -let jsonData = {}; -try { - jsonData = pm.response.json(); -} catch (e) {} - -// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id -if (jsonData?.payment_id) { - pm.collectionVariables.set("payment_id", jsonData.payment_id); - console.log( - "- use {{payment_id}} as collection variable for value", - jsonData.payment_id, - ); -} else { - console.log( - "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", - ); -} - -// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret -if (jsonData?.client_secret) { - pm.collectionVariables.set("client_secret", jsonData.client_secret); - console.log( - "- use {{client_secret}} as collection variable for value", - jsonData.client_secret, - ); -} else { - console.log( - "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", - ); -} - -// Response body should have value "failed" for "status" -if (jsonData?.status) { - pm.test( - "[POST]::/payments - Content check if value for 'status' matches 'failed'", - function () { - pm.expect(jsonData.status).to.eql("failed"); - }, - ); -} - -// Check if error code exists -pm.test("[POST]::/payments - Content check if error code exists", function () { - pm.expect(jsonData.error_code).to.not.equal(null);; -}); - -// Check if error message exists -pm.test("[POST]::/payments - Content check if error message exists", function () { - pm.expect(jsonData.error_message).to.not.equal(null);; -}); - diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Failure card/Payments - Create/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Payments - Capture/.event.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Failure card/Payments - Create/.event.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Payments - Capture/.event.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Payments - Capture/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Payments - Capture/event.test.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Payments - Capture/event.test.js rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Payments - Capture/event.test.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Payments - Capture/request.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Payments - Capture/request.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Payments - Capture/request.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Payments - Capture/request.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Failure card/Payments - Create/response.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Payments - Capture/response.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Failure card/Payments - Create/response.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Payments - Capture/response.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Failure card/Payments - Retrieve/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Failure card/Payments - Retrieve/.event.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Payments - Create/.event.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Payments - Create/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Payments - Create/event.test.js rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Payments - Create/event.test.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Payments - Create/request.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Payments - Create/request.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Payments - Create/request.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Payments - Create/request.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Failure card/Payments - Retrieve/response.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Payments - Create/response.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Failure card/Payments - Retrieve/response.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Payments - Create/response.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Payments - Capture/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Payments - Retrieve/.event.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Payments - Capture/.event.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Payments - Retrieve/.event.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Payments - Retrieve/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Payments - Retrieve/event.test.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Payments - Retrieve/event.test.js rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Payments - Retrieve/event.test.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Failure card/Payments - Retrieve/request.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Payments - Retrieve/request.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Failure card/Payments - Retrieve/request.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Payments - Retrieve/request.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Payments - Capture/response.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Payments - Retrieve/response.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Payments - Capture/response.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Payments - Retrieve/response.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Payments - Create/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Refunds - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Payments - Create/.event.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Refunds - Create/.event.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Refunds - Create/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Refunds - Create/event.test.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Refunds - Create/event.test.js rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Refunds - Create/event.test.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Refunds - Create/request.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Refunds - Create/request.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Refunds - Create/request.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Refunds - Create/request.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Payments - Create/response.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Refunds - Create/response.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Payments - Create/response.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario11-Refund exceeds amount captured/Refunds - Create/response.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Payments - Retrieve/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Payments - Retrieve/.event.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Payments - Create/.event.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Payments - Create/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Payments - Create/event.test.js rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Payments - Create/event.test.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Payments - Create/request.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Payments - Create/request.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Payments - Create/request.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Payments - Create/request.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Payments - Retrieve/response.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Payments - Create/response.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Payments - Retrieve/response.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Payments - Create/response.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Refunds - Create/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Payments - Retrieve/.event.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Refunds - Create/.event.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Payments - Retrieve/.event.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Payments - Retrieve/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Payments - Retrieve/event.test.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Payments - Retrieve/event.test.js rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Payments - Retrieve/event.test.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Payments - Retrieve/request.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Payments - Retrieve/request.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Payments - Retrieve/request.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Payments - Retrieve/request.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Refunds - Create/response.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Payments - Retrieve/response.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Refund exceeds amount captured/Refunds - Create/response.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Payments - Retrieve/response.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Payments - Create/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Revoke - Mandates-copy/.event.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Payments - Create/.event.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Revoke - Mandates-copy/.event.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Revoke - Mandates-copy/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Revoke - Mandates-copy/event.test.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Revoke - Mandates-copy/event.test.js rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Revoke - Mandates-copy/event.test.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Revoke - Mandates-copy/request.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Revoke - Mandates-copy/request.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Revoke - Mandates-copy/request.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Revoke - Mandates-copy/request.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Payments - Create/response.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Revoke - Mandates-copy/response.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Payments - Create/response.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Revoke - Mandates-copy/response.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Payments - Retrieve/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Revoke - Mandates/.event.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Payments - Retrieve/.event.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Revoke - Mandates/.event.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Revoke - Mandates/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Revoke - Mandates/event.test.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Revoke - Mandates/event.test.js rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Revoke - Mandates/event.test.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Revoke - Mandates/request.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Revoke - Mandates/request.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Revoke - Mandates/request.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Revoke - Mandates/request.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Payments - Retrieve/response.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Revoke - Mandates/response.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Payments - Retrieve/response.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario12-Revoke for revoked mandates/Revoke - Mandates/response.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Revoke - Mandates-copy/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Revoke - Mandates-copy/.event.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Payments - Create/.event.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Payments - Create/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Payments - Create/event.test.js rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Payments - Create/event.test.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Payments - Create/request.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Payments - Create/request.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Payments - Create/request.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Payments - Create/request.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Revoke - Mandates-copy/response.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Payments - Create/response.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Revoke - Mandates-copy/response.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Payments - Create/response.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Revoke - Mandates/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Payments - Retrieve/.event.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Revoke - Mandates/.event.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Payments - Retrieve/.event.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Payments - Retrieve/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Payments - Retrieve/event.test.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Payments - Retrieve/event.test.js rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Payments - Retrieve/event.test.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Payments - Retrieve/request.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Payments - Retrieve/request.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Payments - Retrieve/request.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Payments - Retrieve/request.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Revoke - Mandates/response.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Payments - Retrieve/response.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Revoke for revoked mandates/Revoke - Mandates/response.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Payments - Retrieve/response.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Payments - Create/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Recurring Payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Payments - Create/.event.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Recurring Payments - Create/.event.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Recurring Payments - Create/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Recurring Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Recurring Payments - Create/event.test.js rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Recurring Payments - Create/event.test.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Recurring Payments - Create/request.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Recurring Payments - Create/request.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Recurring Payments - Create/request.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Recurring Payments - Create/request.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Payments - Create/response.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Recurring Payments - Create/response.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Payments - Create/response.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Recurring Payments - Create/response.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Payments - Retrieve/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Revoke - Mandates/.event.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Payments - Retrieve/.event.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Revoke - Mandates/.event.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Revoke - Mandates/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Revoke - Mandates/event.test.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Revoke - Mandates/event.test.js rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Revoke - Mandates/event.test.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Revoke - Mandates/request.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Revoke - Mandates/request.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Revoke - Mandates/request.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Revoke - Mandates/request.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Payments - Retrieve/response.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Revoke - Mandates/response.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Payments - Retrieve/response.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario13-Recurring payment for revoked mandates/Revoke - Mandates/response.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Payments - Retrieve/request.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Payments - Retrieve/request.json deleted file mode 100644 index b9ebc1be4aa3..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Payments - Retrieve/request.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "method": "GET", - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "payments", - ":id" - ], - "query": [ - { - "key": "force_sync", - "value": "true" - } - ], - "variable": [ - { - "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" - } - ] - }, - "description": "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" -} diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Revoke - Mandates/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Revoke - Mandates/.event.meta.json deleted file mode 100644 index 688c85746ef1..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Revoke - Mandates/.event.meta.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "eventOrder": [ - "event.test.js" - ] -} diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is off_session for normal payments/.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Setup_future_usage is off_session for normal payments/.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is off_session for normal payments/.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Setup_future_usage is off_session for normal payments/.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is off_session for normal payments/Payments - Create/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Setup_future_usage is off_session for normal payments/Payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is off_session for normal payments/Payments - Create/.event.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Setup_future_usage is off_session for normal payments/Payments - Create/.event.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is off_session for normal payments/Payments - Create/event.prerequest.js b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Setup_future_usage is off_session for normal payments/Payments - Create/event.prerequest.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is off_session for normal payments/Payments - Create/event.prerequest.js rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Setup_future_usage is off_session for normal payments/Payments - Create/event.prerequest.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is off_session for normal payments/Payments - Create/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Setup_future_usage is off_session for normal payments/Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is off_session for normal payments/Payments - Create/event.test.js rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Setup_future_usage is off_session for normal payments/Payments - Create/event.test.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is off_session for normal payments/Payments - Create/request.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Setup_future_usage is off_session for normal payments/Payments - Create/request.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is off_session for normal payments/Payments - Create/request.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Setup_future_usage is off_session for normal payments/Payments - Create/request.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Recurring Payments - Create/response.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Setup_future_usage is off_session for normal payments/Payments - Create/response.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Recurring Payments - Create/response.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Setup_future_usage is off_session for normal payments/Payments - Create/response.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is on_session for mandates payments -confirm false/.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is on_session for mandates payments -confirm false/.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is on_session for mandates payments -confirm false/.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is on_session for mandates payments -confirm false/.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Confirm/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Confirm/.event.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Confirm/.event.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Confirm/.event.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Confirm/event.prerequest.js b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Confirm/event.prerequest.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Confirm/event.prerequest.js rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Confirm/event.prerequest.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Confirm/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Confirm/event.test.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Confirm/event.test.js rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Confirm/event.test.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Confirm/request.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Confirm/request.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Confirm/request.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Confirm/request.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Revoke - Mandates/response.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Confirm/response.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Revoke - Mandates/response.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Confirm/response.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Create/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Create/.event.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Create/.event.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Create/event.prerequest.js b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Create/event.prerequest.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Create/event.prerequest.js rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Create/event.prerequest.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Create/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Create/event.test.js rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Create/event.test.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Create/request.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Create/request.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Create/request.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Create/request.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is off_session for normal payments/Payments - Create/response.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Create/response.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is off_session for normal payments/Payments - Create/response.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario15-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Create/response.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Recurring Payments - Create/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/List payment methods for a Customer/.event.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario14-Recurring payment for revoked mandates/Recurring Payments - Create/.event.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/List payment methods for a Customer/.event.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/List payment methods for a Customer/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/List payment methods for a Customer/event.test.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/List payment methods for a Customer/event.test.js rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/List payment methods for a Customer/event.test.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/List payment methods for a Customer/request.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/List payment methods for a Customer/request.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/List payment methods for a Customer/request.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/List payment methods for a Customer/request.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Confirm/response.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/List payment methods for a Customer/response.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Confirm/response.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/List payment methods for a Customer/response.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/Payments - Confirm/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/Payments - Confirm/.event.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/Payments - Confirm/.event.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/Payments - Confirm/.event.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/Payments - Confirm/event.prerequest.js b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/Payments - Confirm/event.prerequest.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/Payments - Confirm/event.prerequest.js rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/Payments - Confirm/event.prerequest.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/Payments - Confirm/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/Payments - Confirm/event.test.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/Payments - Confirm/event.test.js rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/Payments - Confirm/event.test.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/Payments - Confirm/request.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/Payments - Confirm/request.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/Payments - Confirm/request.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/Payments - Confirm/request.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Create/response.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/Payments - Confirm/response.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is on_session for mandates payments -confirm false/Payments - Create/response.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/Payments - Confirm/response.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/Payments - Create/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/Payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/Payments - Create/.event.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/Payments - Create/.event.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/Payments - Create/event.prerequest.js b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/Payments - Create/event.prerequest.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/Payments - Create/event.prerequest.js rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/Payments - Create/event.prerequest.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/Payments - Create/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/Payments - Create/event.test.js rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/Payments - Create/event.test.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/Payments - Create/request.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/Payments - Create/request.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/Payments - Create/request.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/Payments - Create/request.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/List payment methods for a Customer/response.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/Payments - Create/response.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/List payment methods for a Customer/response.json rename to postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario16-Setup_future_usage is null for normal payments/Payments - Create/response.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/List payment methods for a Customer/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/List payment methods for a Customer/.event.meta.json deleted file mode 100644 index 688c85746ef1..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/List payment methods for a Customer/.event.meta.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "eventOrder": [ - "event.test.js" - ] -} diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/Payments - Confirm/response.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/Payments - Confirm/response.json deleted file mode 100644 index fe51488c7066..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/Payments - Confirm/response.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/Payments - Create/response.json b/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/Payments - Create/response.json deleted file mode 100644 index fe51488c7066..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Variation Cases/Scenario17-Setup_future_usage is null for normal payments/Payments - Create/response.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario13-BNPL-afterpay/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario13-BNPL-afterpay/Payments - Create/request.json index 273ed5ee3c68..d108e1198a7d 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario13-BNPL-afterpay/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario13-BNPL-afterpay/Payments - Create/request.json @@ -41,7 +41,7 @@ "zip": "94122", "first_name": "John", "last_name": "Doe", - "country": "SE" + "country": "ES" }, "email": "narayan@example.com" }, diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Ideal/Payments - Confirm/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Ideal/Payments - Confirm/request.json index 529dab2b10db..863e03c525f6 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Ideal/Payments - Confirm/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Ideal/Payments - Confirm/request.json @@ -43,12 +43,8 @@ "payment_method_data": { "bank_redirect": { "ideal": { - "billing_details": { - "billing_name": "John Doe" - }, "bank_name": "ing", - "preferred_language": "en", - "country": "DE" + "preferred_language": "en" } } }, diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Ideal/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Ideal/Payments - Create/request.json index 21e71ad037a9..048893f35111 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Ideal/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Ideal/Payments - Create/request.json @@ -42,7 +42,7 @@ "city": "San Fransico", "state": "California", "zip": "94122", - "country": "DE" + "country": "NL" } }, "browser_info": { diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-sofort/Payments - Confirm/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-sofort/Payments - Confirm/request.json index fd2297e984d7..686ac64b6075 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-sofort/Payments - Confirm/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-sofort/Payments - Confirm/request.json @@ -43,12 +43,8 @@ "payment_method_data": { "bank_redirect": { "sofort": { - "billing_details": { - "billing_name": "John Doe" - }, "bank_name": "hypo_noe_lb_fur_niederosterreich_u_wien", - "preferred_language": "en", - "country": "DE" + "preferred_language": "en" } } }, diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario17-Bank Redirect-eps/Payments - Confirm/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario17-Bank Redirect-eps/Payments - Confirm/request.json index 61add73d4116..c0e2a2a3d5ea 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario17-Bank Redirect-eps/Payments - Confirm/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario17-Bank Redirect-eps/Payments - Confirm/request.json @@ -48,7 +48,7 @@ }, "bank_name": "hypo_oberosterreich_salzburg_steiermark", "preferred_language": "en", - "country": "DE" + "country": "AT" } } }, diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario17-Bank Redirect-eps/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario17-Bank Redirect-eps/Payments - Create/request.json index 21e71ad037a9..026af8449ce8 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario17-Bank Redirect-eps/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario17-Bank Redirect-eps/Payments - Create/request.json @@ -42,7 +42,7 @@ "city": "San Fransico", "state": "California", "zip": "94122", - "country": "DE" + "country": "AT" } }, "browser_info": { diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22- Update address and List Payment method/Payments - Update/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22- Update address and List Payment method/Payments - Update/request.json index da347e0d0bb6..d75c1b016147 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22- Update address and List Payment method/Payments - Update/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22- Update address and List Payment method/Payments - Update/request.json @@ -51,7 +51,8 @@ "phone": { "number": "8796455689", "country_code": "+91" - } + }, + "email": "swangi@gmail.com" } } }, diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Confirm/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Confirm/request.json index c4c248b3bddc..c3497046a5cb 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Confirm/request.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Confirm/request.json @@ -49,7 +49,7 @@ }, "bank_name": "ing", "preferred_language": "en", - "country": "DE" + "country": "NL" } } } diff --git a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Create/request.json b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Create/request.json index 87d07a6016b3..5d44131e7fbe 100644 --- a/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Create/request.json +++ b/postman/collection-dir/trustpay/Flow Testcases/Happy Cases/Scenario7-Bank Redirect-Ideal/Payments - Create/request.json @@ -42,7 +42,7 @@ "city": "San Fransico", "state": "California", "zip": "94122", - "country": "DE" + "country": "NL" } }, "browser_info": { diff --git a/postman/collection-json/cybersource.postman_collection.json b/postman/collection-json/cybersource.postman_collection.json index 128789666aa5..1616eb0c445f 100644 --- a/postman/collection-json/cybersource.postman_collection.json +++ b/postman/collection-json/cybersource.postman_collection.json @@ -15757,247 +15757,7 @@ ] }, { - "name": "Scenario11-Failure card", - "item": [ - { - "name": "Payments - Create", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "// Validate status 2xx", - "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", - " pm.response.to.be.success;", - "});", - "", - "// Validate if response header has matching content-type", - "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - "});", - "", - "// Validate if response has JSON Body", - "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", - "});", - "", - "// Set response object as internal variable", - "let jsonData = {};", - "try {", - " jsonData = pm.response.json();", - "} catch (e) {}", - "", - "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", - "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(", - " \"- use {{payment_id}} as collection variable for value\",", - " jsonData.payment_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", - " );", - "}", - "", - "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", - "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(", - " \"- use {{client_secret}} as collection variable for value\",", - " jsonData.client_secret,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", - " );", - "}", - "", - "// Response body should have value \"failed\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'failed'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"failed\");", - " },", - " );", - "}", - "", - "// Check if error code exists", - "pm.test(\"[POST]::/payments - Content check if error code exists\", function () {", - " pm.expect(jsonData.error_code).to.not.equal(null);;", - "});", - "", - "// Check if error message exists", - "pm.test(\"[POST]::/payments - Content check if error message exists\", function () {", - " pm.expect(jsonData.error_message).to.not.equal(null);;", - "});", - "", - "", - "", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"business_country\":\"US\",\"business_label\":\"default\",\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"{{customer_id}}\",\"email\":\"guest@example.com\",\"setup_future_usage\":\"on_session\",\"customer_acceptance\":{\"acceptance_type\":\"offline\",\"accepted_at\":\"1963-05-03T04:07:52.723Z\",\"online\":{\"ip_address\":\"127.0.0.1\",\"user_agent\":\"amet irure esse\"}},\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"412345678912345678914\",\"card_exp_month\":\"01\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"count_tickets\":1,\"transaction_number\":\"5590045\"}}" - }, - "url": { - "raw": "{{baseUrl}}/payments", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "payments" - ] - }, - "description": "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" - }, - "response": [] - }, - { - "name": "Payments - Retrieve", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "// Validate status 2xx", - "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", - " pm.response.to.be.success;", - "});", - "", - "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - "});", - "", - "// Validate if response has JSON Body", - "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", - "});", - "", - "// Set response object as internal variable", - "let jsonData = {};", - "try {", - " jsonData = pm.response.json();", - "} catch (e) {}", - "", - "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", - "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(", - " \"- use {{payment_id}} as collection variable for value\",", - " jsonData.payment_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", - " );", - "}", - "", - "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", - "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(", - " \"- use {{client_secret}} as collection variable for value\",", - " jsonData.client_secret,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", - " );", - "}", - "", - "// Response body should have value \"failed\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'failed'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"failed\");", - " },", - " );", - "}", - "", - "// Check if error code exists", - "pm.test(\"[POST]::/payments - Content check if error code exists\", function () {", - " pm.expect(jsonData.error_code).to.not.equal(null);;", - "});", - "", - "// Check if error message exists", - "pm.test(\"[POST]::/payments - Content check if error message exists\", function () {", - " pm.expect(jsonData.error_message).to.not.equal(null);;", - "});", - "", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "payments", - ":id" - ], - "query": [ - { - "key": "force_sync", - "value": "true" - } - ], - "variable": [ - { - "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" - } - ] - }, - "description": "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" - }, - "response": [] - } - ] - }, - { - "name": "Scenario12-Refund exceeds amount captured", + "name": "Scenario11-Refund exceeds amount captured", "item": [ { "name": "Payments - Create", @@ -16455,7 +16215,7 @@ ] }, { - "name": "Scenario13-Revoke for revoked mandates", + "name": "Scenario12-Revoke for revoked mandates", "item": [ { "name": "Payments - Create", @@ -16879,7 +16639,7 @@ ] }, { - "name": "Scenario14-Recurring payment for revoked mandates", + "name": "Scenario13-Recurring payment for revoked mandates", "item": [ { "name": "Payments - Create", @@ -17327,7 +17087,7 @@ ] }, { - "name": "Scenario15-Setup_future_usage is off_session for normal payments", + "name": "Scenario14-Setup_future_usage is off_session for normal payments", "item": [ { "name": "Payments - Create", @@ -17446,7 +17206,7 @@ ] }, { - "name": "Scenario16-Setup_future_usage is on_session for mandates payments -confirm false", + "name": "Scenario15-Setup_future_usage is on_session for mandates payments -confirm false", "item": [ { "name": "Payments - Create", @@ -17732,7 +17492,7 @@ ] }, { - "name": "Scenario17-Setup_future_usage is null for normal payments", + "name": "Scenario16-Setup_future_usage is null for normal payments", "item": [ { "name": "Payments - Create", diff --git a/postman/collection-json/stripe.postman_collection.json b/postman/collection-json/stripe.postman_collection.json index a492d70f7c87..397864885cb6 100644 --- a/postman/collection-json/stripe.postman_collection.json +++ b/postman/collection-json/stripe.postman_collection.json @@ -13677,7 +13677,7 @@ "language": "json" } }, - "raw": "{\"amount\":7000,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"first_name\":\"John\",\"last_name\":\"Doe\",\"country\":\"SE\"},\"email\":\"narayan@example.com\"},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"SE\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"order_details\":{\"product_name\":\"Socks\",\"amount\":7000,\"quantity\":1}},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" + "raw": "{\"amount\":7000,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"first_name\":\"John\",\"last_name\":\"Doe\",\"country\":\"ES\"},\"email\":\"narayan@example.com\"},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"SE\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"order_details\":{\"product_name\":\"Socks\",\"amount\":7000,\"quantity\":1}},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -14537,7 +14537,7 @@ "language": "json" } }, - "raw": "{\"amount\":1000,\"currency\":\"EUR\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":1000,\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"first_name\":\"John\",\"last_name\":\"Doe\",\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"DE\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" + "raw": "{\"amount\":1000,\"currency\":\"EUR\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":1000,\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"first_name\":\"John\",\"last_name\":\"Doe\",\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"NL\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -14716,7 +14716,7 @@ "language": "json" } }, - "raw": "{\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"ideal\",\"payment_method_data\":{\"bank_redirect\":{\"ideal\":{\"billing_details\":{\"billing_name\":\"John Doe\"},\"bank_name\":\"ing\",\"preferred_language\":\"en\",\"country\":\"DE\"}}},\"client_secret\":\"{{client_secret}}\"}" + "raw": "{\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"ideal\",\"payment_method_data\":{\"bank_redirect\":{\"ideal\":{\"bank_name\":\"ing\",\"preferred_language\":\"en\"}}},\"client_secret\":\"{{client_secret}}\"}" }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", @@ -15146,7 +15146,7 @@ "language": "json" } }, - "raw": "{\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"sofort\",\"payment_method_data\":{\"bank_redirect\":{\"sofort\":{\"billing_details\":{\"billing_name\":\"John Doe\"},\"bank_name\":\"hypo_noe_lb_fur_niederosterreich_u_wien\",\"preferred_language\":\"en\",\"country\":\"DE\"}}},\"client_secret\":\"{{client_secret}}\"}" + "raw": "{\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"sofort\",\"payment_method_data\":{\"bank_redirect\":{\"sofort\":{\"bank_name\":\"hypo_noe_lb_fur_niederosterreich_u_wien\",\"preferred_language\":\"en\"}}},\"client_secret\":\"{{client_secret}}\"}" }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", @@ -15397,7 +15397,7 @@ "language": "json" } }, - "raw": "{\"amount\":1000,\"currency\":\"EUR\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":1000,\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"first_name\":\"John\",\"last_name\":\"Doe\",\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"DE\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" + "raw": "{\"amount\":1000,\"currency\":\"EUR\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":1000,\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"first_name\":\"John\",\"last_name\":\"Doe\",\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"AT\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":\"stripe\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -15576,7 +15576,7 @@ "language": "json" } }, - "raw": "{\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"eps\",\"payment_method_data\":{\"bank_redirect\":{\"eps\":{\"billing_details\":{\"billing_name\":\"John Doe\"},\"bank_name\":\"hypo_oberosterreich_salzburg_steiermark\",\"preferred_language\":\"en\",\"country\":\"DE\"}}},\"client_secret\":\"{{client_secret}}\"}" + "raw": "{\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"eps\",\"payment_method_data\":{\"bank_redirect\":{\"eps\":{\"billing_details\":{\"billing_name\":\"John Doe\"},\"bank_name\":\"hypo_oberosterreich_salzburg_steiermark\",\"preferred_language\":\"en\",\"country\":\"AT\"}}},\"client_secret\":\"{{client_secret}}\"}" }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", @@ -17569,7 +17569,7 @@ "language": "json" } }, - "raw": "{\"currency\":\"EUR\",\"shipping\":{\"address\":{\"line1\":\"1468\",\"line2\":\"Koramangala \",\"line3\":\"Koramangala \",\"city\":\"Bangalore\",\"state\":\"Karnataka\",\"zip\":\"560065\",\"country\":\"NL\",\"first_name\":\"Preeetam\",\"last_name\":\"Rev\"},\"phone\":{\"number\":\"8796455689\",\"country_code\":\"+91\"}},\"billing\":{\"address\":{\"line1\":\"1468\",\"line2\":\"Koramangala \",\"line3\":\"Koramangala \",\"city\":\"Bangalore\",\"state\":\"Karnataka\",\"zip\":\"560065\",\"country\":\"NL\",\"first_name\":\"Preeetam\",\"last_name\":\"Rev\"},\"phone\":{\"number\":\"8796455689\",\"country_code\":\"+91\"}}}" + "raw": "{\"currency\":\"EUR\",\"shipping\":{\"address\":{\"line1\":\"1468\",\"line2\":\"Koramangala \",\"line3\":\"Koramangala \",\"city\":\"Bangalore\",\"state\":\"Karnataka\",\"zip\":\"560065\",\"country\":\"NL\",\"first_name\":\"Preeetam\",\"last_name\":\"Rev\"},\"phone\":{\"number\":\"8796455689\",\"country_code\":\"+91\"}},\"billing\":{\"address\":{\"line1\":\"1468\",\"line2\":\"Koramangala \",\"line3\":\"Koramangala \",\"city\":\"Bangalore\",\"state\":\"Karnataka\",\"zip\":\"560065\",\"country\":\"NL\",\"first_name\":\"Preeetam\",\"last_name\":\"Rev\"},\"phone\":{\"number\":\"8796455689\",\"country_code\":\"+91\"},\"email\":\"swangi@gmail.com\"}}" }, "url": { "raw": "{{baseUrl}}/payments/:id", diff --git a/postman/collection-json/trustpay.postman_collection.json b/postman/collection-json/trustpay.postman_collection.json index a9051b994ca5..5c35168dca64 100644 --- a/postman/collection-json/trustpay.postman_collection.json +++ b/postman/collection-json/trustpay.postman_collection.json @@ -3177,7 +3177,7 @@ "language": "json" } }, - "raw": "{\"amount\":1000,\"currency\":\"EUR\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":1000,\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"first_name\":\"John\",\"last_name\":\"Doe\",\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"DE\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":1000,\"currency\":\"EUR\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":1000,\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"first_name\":\"John\",\"last_name\":\"Doe\",\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"NL\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -3347,7 +3347,7 @@ "language": "json" } }, - "raw": "{\"client_secret\":\"{{client_secret}}\",\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"ideal\",\"payment_method_data\":{\"bank_redirect\":{\"ideal\":{\"billing_details\":{\"billing_name\":\"John Doe\"},\"bank_name\":\"ing\",\"preferred_language\":\"en\",\"country\":\"DE\"}}}}" + "raw": "{\"client_secret\":\"{{client_secret}}\",\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"ideal\",\"payment_method_data\":{\"bank_redirect\":{\"ideal\":{\"billing_details\":{\"billing_name\":\"John Doe\"},\"bank_name\":\"ing\",\"preferred_language\":\"en\",\"country\":\"NL\"}}}}" }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm",